mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: Add orchestrator template and roles for executor and planner
- Created a new orchestrator template for Codex skill design, detailing structure and execution phases. - Introduced the executor role with responsibilities for task execution, including routing to backends and handling implementation. - Added the planner role for requirement breakdown, issue creation, and task dispatching, ensuring a structured planning process.
This commit is contained in:
353
.claude/skills/codex-skill-designer/SKILL.md
Normal file
353
.claude/skills/codex-skill-designer/SKILL.md
Normal file
@@ -0,0 +1,353 @@
|
|||||||
|
---
|
||||||
|
name: codex-skill-designer
|
||||||
|
description: Meta-skill for designing Codex-native skills with subagent orchestration (spawn_agent/wait/send_input/close_agent). Supports new skill creation and Claude→Codex conversion. Triggers on "design codex skill", "create codex skill", "codex skill designer", "convert to codex".
|
||||||
|
allowed-tools: Task, AskUserQuestion, TodoWrite, Read, Write, Edit, Bash, Glob, Grep
|
||||||
|
---
|
||||||
|
|
||||||
|
# Codex Skill Designer
|
||||||
|
|
||||||
|
Meta-skill for creating Codex-native skills that use the subagent API (`spawn_agent`/`wait`/`send_input`/`close_agent`). Generates complete skill packages with orchestrator coordination and agent role definitions.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
```
|
||||||
|
┌──────────────────────────────────────────────────────────────┐
|
||||||
|
│ Codex Skill Designer │
|
||||||
|
│ → Analyze requirements → Design orchestrator → Design agents│
|
||||||
|
└───────────────┬──────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────┼───────────┬───────────┐
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
|
||||||
|
│ Phase 1 │ │ Phase 2 │ │ Phase 3 │ │ Phase 4 │
|
||||||
|
│ Require │ │ Orch │ │ Agent │ │ Valid │
|
||||||
|
│ Analysis│ │ Design │ │ Design │ │ & Integ │
|
||||||
|
└─────────┘ └─────────┘ └─────────┘ └─────────┘
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
codexSkill orchestrator agents/ Complete
|
||||||
|
Config .md generated *.md skill pkg
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target Output Structure
|
||||||
|
|
||||||
|
The skill this meta-skill produces follows this structure:
|
||||||
|
|
||||||
|
### Mode A: Structured Skill Package (multi-agent orchestration)
|
||||||
|
|
||||||
|
```
|
||||||
|
.codex/skills/{skill-name}/
|
||||||
|
├── orchestrator.md # Main Codex orchestrator
|
||||||
|
│ ├── Frontmatter (name, description)
|
||||||
|
│ ├── Architecture (spawn/wait/close flow)
|
||||||
|
│ ├── Agent Registry (role → path mapping)
|
||||||
|
│ ├── Phase Execution (spawn_agent patterns)
|
||||||
|
│ ├── Result Aggregation (wait + merge)
|
||||||
|
│ └── Lifecycle Management (close_agent cleanup)
|
||||||
|
├── agents/ # Skill-specific agent definitions
|
||||||
|
│ ├── {agent-1}.md # → deploy to ~/.codex/agents/
|
||||||
|
│ └── {agent-2}.md # → deploy to ~/.codex/agents/
|
||||||
|
└── phases/ # [Optional] Phase execution detail
|
||||||
|
├── 01-{phase}.md
|
||||||
|
└── 02-{phase}.md
|
||||||
|
```
|
||||||
|
|
||||||
|
### Mode B: Single Prompt (simple or self-contained skills)
|
||||||
|
|
||||||
|
```
|
||||||
|
~/.codex/prompts/{skill-name}.md # Self-contained Codex prompt
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Design Principles — Codex-Native Patterns
|
||||||
|
|
||||||
|
### Pattern 1: Explicit Lifecycle Management
|
||||||
|
|
||||||
|
Every agent has a complete lifecycle: `spawn_agent` → `wait` → [`send_input`] → `close_agent`.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Standard lifecycle
|
||||||
|
const agentId = spawn_agent({ message: taskMessage })
|
||||||
|
const result = wait({ ids: [agentId], timeout_ms: 300000 })
|
||||||
|
// [Optional: send_input for multi-round]
|
||||||
|
close_agent({ id: agentId })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Rules**:
|
||||||
|
- Use `wait()` to get results, NEVER depend on `close_agent` return
|
||||||
|
- `close_agent` is irreversible — no further `wait`/`send_input` possible
|
||||||
|
- Delay `close_agent` until certain no more interaction is needed
|
||||||
|
|
||||||
|
### Pattern 2: Role Loading via Path Reference
|
||||||
|
|
||||||
|
Codex subagents cannot auto-load roles. Use MANDATORY FIRST STEPS pattern:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
## TASK CONTEXT
|
||||||
|
${taskContext}
|
||||||
|
|
||||||
|
## DELIVERABLES
|
||||||
|
${deliverables}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 3: Parallel Fan-out with Batch Wait
|
||||||
|
|
||||||
|
Multiple independent agents → batch `wait({ ids: [...] })`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agentIds = tasks.map(task =>
|
||||||
|
spawn_agent({ message: buildTaskMessage(task) })
|
||||||
|
)
|
||||||
|
const results = wait({ ids: agentIds, timeout_ms: 600000 })
|
||||||
|
agentIds.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 4: Deep Interaction (send_input Multi-round)
|
||||||
|
|
||||||
|
Single agent, multi-phase with context preservation:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agent = spawn_agent({ message: explorePrompt })
|
||||||
|
const round1 = wait({ ids: [agent] })
|
||||||
|
|
||||||
|
// Continue with clarification
|
||||||
|
send_input({ id: agent, message: clarificationAnswers })
|
||||||
|
const round2 = wait({ ids: [agent] })
|
||||||
|
|
||||||
|
close_agent({ id: agent }) // Only after all rounds complete
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 5: Two-Phase Workflow (Clarify → Execute)
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: spawn_agent → output Open Questions only
|
||||||
|
↓
|
||||||
|
Phase 2: send_input (answers) → output full solution
|
||||||
|
```
|
||||||
|
|
||||||
|
### Pattern 6: Structured Output Template
|
||||||
|
|
||||||
|
All agents produce uniform output:
|
||||||
|
|
||||||
|
```text
|
||||||
|
Summary:
|
||||||
|
- One-sentence completion status
|
||||||
|
|
||||||
|
Findings:
|
||||||
|
- Finding 1: specific description
|
||||||
|
- Finding 2: specific description
|
||||||
|
|
||||||
|
Proposed changes:
|
||||||
|
- File: path/to/file
|
||||||
|
- Change: specific modification
|
||||||
|
- Risk: potential impact
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- New/updated test cases needed
|
||||||
|
- Test commands to run
|
||||||
|
|
||||||
|
Open questions:
|
||||||
|
1. Question needing clarification
|
||||||
|
2. Question needing clarification
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Requirements Analysis
|
||||||
|
└─ Ref: phases/01-requirements-analysis.md
|
||||||
|
├─ Input: text description / Claude skill / requirements doc / existing codex prompt
|
||||||
|
└─ Output: codexSkillConfig (agents, phases, patterns, interaction model)
|
||||||
|
|
||||||
|
Phase 2: Orchestrator Design
|
||||||
|
└─ Ref: phases/02-orchestrator-design.md
|
||||||
|
├─ Input: codexSkillConfig
|
||||||
|
└─ Output: .codex/skills/{name}/orchestrator.md (or ~/.codex/prompts/{name}.md)
|
||||||
|
|
||||||
|
Phase 3: Agent Design
|
||||||
|
└─ Ref: phases/03-agent-design.md
|
||||||
|
├─ Input: codexSkillConfig + source content
|
||||||
|
└─ Output: .codex/skills/{name}/agents/*.md + optional phases/*.md
|
||||||
|
|
||||||
|
Phase 4: Validation & Delivery
|
||||||
|
└─ Ref: phases/04-validation.md
|
||||||
|
└─ Output: Validated skill package + deployment instructions
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase Reference Documents** (read on-demand when phase executes):
|
||||||
|
|
||||||
|
| Phase | Document | Purpose |
|
||||||
|
|-------|----------|---------|
|
||||||
|
| 1 | [phases/01-requirements-analysis.md](phases/01-requirements-analysis.md) | Analyze inputs, determine skill config |
|
||||||
|
| 2 | [phases/02-orchestrator-design.md](phases/02-orchestrator-design.md) | Generate Codex-native orchestrator |
|
||||||
|
| 3 | [phases/03-agent-design.md](phases/03-agent-design.md) | Generate agent roles & command patterns |
|
||||||
|
| 4 | [phases/04-validation.md](phases/04-validation.md) | Validate structure, patterns, quality |
|
||||||
|
|
||||||
|
## Input Sources
|
||||||
|
|
||||||
|
| Source | Description | Example |
|
||||||
|
|--------|-------------|---------|
|
||||||
|
| **Text description** | User describes desired Codex skill | "Create a 3-agent code review skill for Codex" |
|
||||||
|
| **Claude skill** | Convert existing Claude skill to Codex | `.claude/skills/workflow-plan/SKILL.md` |
|
||||||
|
| **Requirements doc** | Structured requirements file | `requirements.md` with agents/phases/outputs |
|
||||||
|
| **Existing Codex prompt** | Refactor/enhance a Codex prompt | `~/.codex/prompts/plan.md` |
|
||||||
|
|
||||||
|
## Conversion Mode (Claude → Codex)
|
||||||
|
|
||||||
|
When source is a Claude skill, apply conversion rules:
|
||||||
|
|
||||||
|
| Claude Pattern | Codex Equivalent |
|
||||||
|
|----------------|-----------------|
|
||||||
|
| `Task({ subagent_type, prompt })` | `spawn_agent({ message })` + `wait()` |
|
||||||
|
| `Task({ run_in_background: false })` | `spawn_agent()` + immediate `wait()` |
|
||||||
|
| `Task({ resume: agentId })` | `send_input({ id: agentId })` |
|
||||||
|
| `TaskOutput({ task_id, block })` | `wait({ ids: [id], timeout_ms })` |
|
||||||
|
| Automatic agent cleanup | Explicit `close_agent({ id })` |
|
||||||
|
| `subagent_type` auto-loads role | MANDATORY FIRST STEPS role path |
|
||||||
|
| Multiple parallel `Task()` calls | Multiple `spawn_agent()` + batch `wait({ ids })` |
|
||||||
|
|
||||||
|
**Full conversion spec**: Ref: specs/conversion-rules.md
|
||||||
|
|
||||||
|
## Data Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1 → codexSkillConfig:
|
||||||
|
{
|
||||||
|
name, description, outputMode (structured|single),
|
||||||
|
agents: [{ name, role_file, responsibility, patterns }],
|
||||||
|
phases: [{ name, agents_involved, interaction_model }],
|
||||||
|
parallelSplits: [{ strategy, agents }],
|
||||||
|
conversionSource: null | { type, path }
|
||||||
|
}
|
||||||
|
|
||||||
|
Phase 2 → orchestrator.md:
|
||||||
|
Generated Codex orchestrator with spawn/wait/close patterns
|
||||||
|
|
||||||
|
Phase 3 → agents/*.md:
|
||||||
|
Per-agent role definitions with Codex-native conventions
|
||||||
|
|
||||||
|
Phase 4 → validated package:
|
||||||
|
Structural completeness + pattern compliance + quality score
|
||||||
|
```
|
||||||
|
|
||||||
|
## TodoWrite Pattern
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase starts:
|
||||||
|
→ Sub-tasks ATTACHED to TodoWrite (in_progress + pending)
|
||||||
|
→ Designer executes sub-tasks sequentially
|
||||||
|
|
||||||
|
Phase ends:
|
||||||
|
→ Sub-tasks COLLAPSED back to high-level summary (completed)
|
||||||
|
→ Next phase begins
|
||||||
|
```
|
||||||
|
|
||||||
|
## Interactive Preference Collection
|
||||||
|
|
||||||
|
Collect preferences via AskUserQuestion before dispatching to phases:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const prefResponse = AskUserQuestion({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "What is the output mode for this Codex skill?",
|
||||||
|
header: "Output Mode",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Structured Package (Recommended)", description: "Multi-file: orchestrator.md + agents/*.md + phases/*.md" },
|
||||||
|
{ label: "Single Prompt", description: "Self-contained ~/.codex/prompts/{name}.md" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "What is the input source?",
|
||||||
|
header: "Input Source",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Text Description", description: "Describe the desired Codex skill in natural language" },
|
||||||
|
{ label: "Claude Skill (Convert)", description: "Convert existing .claude/skills/ to Codex-native" },
|
||||||
|
{ label: "Requirements Doc", description: "Structured requirements file" },
|
||||||
|
{ label: "Existing Codex Prompt", description: "Refactor/enhance existing ~/.codex/prompts/" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const workflowPreferences = {
|
||||||
|
outputMode: prefResponse["Output Mode"].includes("Structured") ? "structured" : "single",
|
||||||
|
inputSource: prefResponse["Input Source"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Specification Documents
|
||||||
|
|
||||||
|
Read specs on-demand for pattern guidance:
|
||||||
|
|
||||||
|
| Spec | Document | Purpose |
|
||||||
|
|------|----------|---------|
|
||||||
|
| Agent Patterns | [specs/codex-agent-patterns.md](specs/codex-agent-patterns.md) | Core Codex subagent API patterns |
|
||||||
|
| Conversion Rules | [specs/conversion-rules.md](specs/conversion-rules.md) | Claude → Codex mapping rules |
|
||||||
|
| Quality Standards | [specs/quality-standards.md](specs/quality-standards.md) | Quality gates & validation criteria |
|
||||||
|
|
||||||
|
## Generation Templates
|
||||||
|
|
||||||
|
Apply templates during generation:
|
||||||
|
|
||||||
|
| Template | Document | Purpose |
|
||||||
|
|----------|----------|---------|
|
||||||
|
| Orchestrator | [templates/orchestrator-template.md](templates/orchestrator-template.md) | Codex orchestrator output template |
|
||||||
|
| Agent Role | [templates/agent-role-template.md](templates/agent-role-template.md) | Agent role definition template |
|
||||||
|
| Command Patterns | [templates/command-pattern-template.md](templates/command-pattern-template.md) | Pre-built Codex command patterns |
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Scenario | Resolution |
|
||||||
|
|----------|------------|
|
||||||
|
| Source Claude skill has unsupported patterns | Log warning, provide manual conversion guidance |
|
||||||
|
| Agent role file path conflict | Append skill-name prefix to agent file |
|
||||||
|
| Output directory exists | Ask user: overwrite or new name |
|
||||||
|
| Validation score < 70% | Block delivery, report issues |
|
||||||
|
|
||||||
|
## Post-Phase Updates
|
||||||
|
|
||||||
|
After each phase, update accumulated state:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// After Phase 1
|
||||||
|
codexSkillConfig = { ...requirements analysis output }
|
||||||
|
|
||||||
|
// After Phase 2
|
||||||
|
generatedFiles.orchestrator = "path/to/orchestrator.md"
|
||||||
|
|
||||||
|
// After Phase 3
|
||||||
|
generatedFiles.agents = ["path/to/agent1.md", "path/to/agent2.md"]
|
||||||
|
generatedFiles.phases = ["path/to/phase1.md"] // optional
|
||||||
|
|
||||||
|
// After Phase 4
|
||||||
|
validationResult = { score, issues, passed }
|
||||||
|
```
|
||||||
|
|
||||||
|
## Coordinator Checklist
|
||||||
|
|
||||||
|
### Pre-Phase Actions
|
||||||
|
- [ ] Verify input source exists and is readable
|
||||||
|
- [ ] Collect preferences via AskUserQuestion
|
||||||
|
- [ ] Read relevant specs based on input source
|
||||||
|
|
||||||
|
### Post-Phase Actions
|
||||||
|
- [ ] Verify phase output completeness
|
||||||
|
- [ ] Update TodoWrite status
|
||||||
|
- [ ] Pass accumulated state to next phase
|
||||||
|
|
||||||
|
### Final Delivery
|
||||||
|
- [ ] All generated files written to target directory
|
||||||
|
- [ ] Deployment instructions provided
|
||||||
|
- [ ] Agent files include `~/.codex/agents/` deployment paths
|
||||||
@@ -0,0 +1,167 @@
|
|||||||
|
# Phase 1: Requirements Analysis
|
||||||
|
|
||||||
|
Analyze input source and extract Codex skill configuration.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
- Parse input source (text / Claude skill / requirements doc / Codex prompt)
|
||||||
|
- Identify agents, phases, interaction patterns
|
||||||
|
- Determine output mode (structured package vs single prompt)
|
||||||
|
- Produce codexSkillConfig for downstream phases
|
||||||
|
|
||||||
|
## Pre-Requisites
|
||||||
|
|
||||||
|
Read specification documents based on input source:
|
||||||
|
- **Always**: Read `specs/codex-agent-patterns.md` for available patterns
|
||||||
|
- **Claude conversion**: Also read `specs/conversion-rules.md`
|
||||||
|
- **Quality reference**: Read `specs/quality-standards.md` for target criteria
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Step 1.1: Input Source Detection
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Determine input type from workflowPreferences
|
||||||
|
const inputSource = workflowPreferences.inputSource
|
||||||
|
|
||||||
|
if (inputSource.includes("Claude Skill")) {
|
||||||
|
// Read source Claude skill
|
||||||
|
const sourceSkillPath = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: "Path to the Claude skill to convert?",
|
||||||
|
header: "Skill Path",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Browse", description: "I'll provide the path" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
// Read SKILL.md + phases/*.md from source
|
||||||
|
const skillContent = Read(sourceSkillPath)
|
||||||
|
const phaseFiles = Glob(`${sourceSkillDir}/phases/*.md`)
|
||||||
|
} else if (inputSource.includes("Text Description")) {
|
||||||
|
// Collect description via user interaction
|
||||||
|
} else if (inputSource.includes("Requirements Doc")) {
|
||||||
|
// Read requirements file
|
||||||
|
} else if (inputSource.includes("Existing Codex")) {
|
||||||
|
// Read existing Codex prompt for refactoring
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.2: Skill Structure Extraction
|
||||||
|
|
||||||
|
For each input type, extract:
|
||||||
|
|
||||||
|
**From Text Description**:
|
||||||
|
```javascript
|
||||||
|
const codexSkillConfig = {
|
||||||
|
name: extractSkillName(userDescription),
|
||||||
|
description: extractDescription(userDescription),
|
||||||
|
outputMode: workflowPreferences.outputMode,
|
||||||
|
agents: inferAgents(userDescription),
|
||||||
|
phases: inferPhases(userDescription),
|
||||||
|
parallelSplits: inferParallelism(userDescription),
|
||||||
|
interactionModel: inferInteractionModel(userDescription),
|
||||||
|
conversionSource: null
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**From Claude Skill** (conversion):
|
||||||
|
```javascript
|
||||||
|
// Parse Claude SKILL.md
|
||||||
|
const claudeConfig = {
|
||||||
|
phases: extractPhases(skillContent),
|
||||||
|
agents: extractTaskCalls(skillContent), // Find Task() invocations
|
||||||
|
dataFlow: extractDataFlow(skillContent),
|
||||||
|
todoPattern: extractTodoPattern(skillContent),
|
||||||
|
resumePatterns: findResumePatterns(skillContent) // For send_input mapping
|
||||||
|
}
|
||||||
|
|
||||||
|
const codexSkillConfig = {
|
||||||
|
name: claudeConfig.name,
|
||||||
|
description: claudeConfig.description,
|
||||||
|
outputMode: workflowPreferences.outputMode,
|
||||||
|
agents: claudeConfig.agents.map(a => ({
|
||||||
|
name: a.subagent_type,
|
||||||
|
role_file: mapToCodexRolePath(a.subagent_type),
|
||||||
|
responsibility: a.description,
|
||||||
|
patterns: determinePatterns(a)
|
||||||
|
})),
|
||||||
|
phases: claudeConfig.phases.map(p => ({
|
||||||
|
name: p.name,
|
||||||
|
agents_involved: p.agentCalls.map(a => a.subagent_type),
|
||||||
|
interaction_model: hasResume(p) ? "deep_interaction" : "standard"
|
||||||
|
})),
|
||||||
|
parallelSplits: detectParallelPatterns(claudeConfig),
|
||||||
|
conversionSource: { type: "claude_skill", path: sourceSkillPath }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.3: Agent Inventory Check
|
||||||
|
|
||||||
|
Verify agent roles exist in `~/.codex/agents/`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const existingAgents = Glob("~/.codex/agents/*.md")
|
||||||
|
const requiredAgents = codexSkillConfig.agents.map(a => a.role_file)
|
||||||
|
|
||||||
|
const missingAgents = requiredAgents.filter(r =>
|
||||||
|
!existingAgents.includes(r)
|
||||||
|
)
|
||||||
|
|
||||||
|
if (missingAgents.length > 0) {
|
||||||
|
// Mark as "needs new agent role definition"
|
||||||
|
codexSkillConfig.newAgentDefinitions = missingAgents
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.4: Interaction Model Selection
|
||||||
|
|
||||||
|
Based on agent relationships, select interaction patterns:
|
||||||
|
|
||||||
|
| Pattern | Condition | Result |
|
||||||
|
|---------|-----------|--------|
|
||||||
|
| **Standard** | Single agent, single task | `spawn → wait → close` |
|
||||||
|
| **Parallel Fan-out** | Multiple independent agents | `spawn[] → batch wait → close[]` |
|
||||||
|
| **Deep Interaction** | Multi-phase with context | `spawn → wait → send_input → wait → close` |
|
||||||
|
| **Two-Phase** | Needs clarification first | `spawn(clarify) → wait → send_input(answers) → wait → close` |
|
||||||
|
| **Pipeline** | Sequential agent chain | `spawn(A) → wait → spawn(B, with A result) → wait → close` |
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
codexSkillConfig.phases.forEach(phase => {
|
||||||
|
if (phase.agents_involved.length > 1) {
|
||||||
|
phase.interaction_model = "parallel_fanout"
|
||||||
|
} else if (phase.interaction_model === "deep_interaction") {
|
||||||
|
// Already set from resume pattern detection
|
||||||
|
} else {
|
||||||
|
phase.interaction_model = "standard"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.5: User Confirmation
|
||||||
|
|
||||||
|
Present extracted configuration for user review:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Skill "${codexSkillConfig.name}" will have ${codexSkillConfig.agents.length} agent(s) and ${codexSkillConfig.phases.length} phase(s). ${codexSkillConfig.newAgentDefinitions?.length || 0} new agent definitions needed. Proceed?`,
|
||||||
|
header: "Confirm",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Proceed", description: "Generate Codex skill package" },
|
||||||
|
{ label: "Adjust", description: "Modify configuration first" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **Variable**: `codexSkillConfig` — complete skill configuration
|
||||||
|
- **TodoWrite**: Mark Phase 1 completed, Phase 2 in_progress
|
||||||
|
|
||||||
|
## Next Phase
|
||||||
|
|
||||||
|
Return to orchestrator, then auto-continue to [Phase 2: Orchestrator Design](02-orchestrator-design.md).
|
||||||
@@ -0,0 +1,291 @@
|
|||||||
|
# Phase 2: Orchestrator Design
|
||||||
|
|
||||||
|
Generate the main Codex orchestrator document using codexSkillConfig.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
- Generate orchestrator.md (structured mode) or {skill-name}.md (single mode)
|
||||||
|
- Apply Codex-native patterns: spawn_agent, wait, send_input, close_agent
|
||||||
|
- Include agent registry, phase execution, lifecycle management
|
||||||
|
- Preserve source content faithfully when converting from Claude
|
||||||
|
|
||||||
|
## Pre-Requisites
|
||||||
|
|
||||||
|
- Read `templates/orchestrator-template.md` for output structure
|
||||||
|
- Read `specs/codex-agent-patterns.md` for pattern reference
|
||||||
|
- If converting: Read `specs/conversion-rules.md` for mapping rules
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Step 2.1: Determine Output Path
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const outputPath = codexSkillConfig.outputMode === "structured"
|
||||||
|
? `.codex/skills/${codexSkillConfig.name}/orchestrator.md`
|
||||||
|
: `~/.codex/prompts/${codexSkillConfig.name}.md`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.2: Generate Frontmatter
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {{skill_name}}
|
||||||
|
description: {{description}}
|
||||||
|
agents: {{agent_count}}
|
||||||
|
phases: {{phase_count}}
|
||||||
|
output_template: structured # or "open_questions" for clarification-first
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.3: Generate Architecture Diagram
|
||||||
|
|
||||||
|
Map phases and agents to ASCII flow:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For parallel fan-out:
|
||||||
|
const diagram = `
|
||||||
|
┌──────────────────────────────────────────┐
|
||||||
|
│ ${codexSkillConfig.name} Orchestrator │
|
||||||
|
└──────────────┬───────────────────────────┘
|
||||||
|
│
|
||||||
|
┌───────────┼───────────┬────────────┐
|
||||||
|
↓ ↓ ↓ ↓
|
||||||
|
┌──────┐ ┌──────┐ ┌──────┐ ┌──────┐
|
||||||
|
│Agent1│ │Agent2│ │Agent3│ │AgentN│
|
||||||
|
│spawn │ │spawn │ │spawn │ │spawn │
|
||||||
|
└──┬───┘ └──┬───┘ └──┬───┘ └──┬───┘
|
||||||
|
└──────────┼───────────┘ │
|
||||||
|
↓ │
|
||||||
|
batch wait({ids}) ←──────────┘
|
||||||
|
↓
|
||||||
|
Aggregate Results
|
||||||
|
↓
|
||||||
|
close_agent (all)
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.4: Generate Agent Registry
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agentRegistry = codexSkillConfig.agents.map(agent => ({
|
||||||
|
name: agent.name,
|
||||||
|
role_file: agent.role_file, // e.g., ~/.codex/agents/cli-explore-agent.md
|
||||||
|
responsibility: agent.responsibility,
|
||||||
|
is_new: agent.role_file.startsWith('.codex/skills/') // skill-specific new agent
|
||||||
|
}))
|
||||||
|
```
|
||||||
|
|
||||||
|
Output as registry table in orchestrator:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Agent Registry
|
||||||
|
|
||||||
|
| Agent | Role File | Responsibility | Status |
|
||||||
|
|-------|-----------|----------------|--------|
|
||||||
|
{{#each agents}}
|
||||||
|
| `{{name}}` | `{{role_file}}` | {{responsibility}} | {{#if is_new}}NEW{{else}}existing{{/if}} |
|
||||||
|
{{/each}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.5: Generate Phase Execution Blocks
|
||||||
|
|
||||||
|
For each phase in codexSkillConfig.phases, generate the appropriate pattern:
|
||||||
|
|
||||||
|
**Standard Pattern** (single agent, single task):
|
||||||
|
```javascript
|
||||||
|
// Phase N: {{phase.name}}
|
||||||
|
const agentId = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{agent.role_file}} (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: {{phase.goal}}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- 可做: {{phase.scope.include}}
|
||||||
|
- 不可做: {{phase.scope.exclude}}
|
||||||
|
|
||||||
|
Context:
|
||||||
|
{{phase.context}}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- {{phase.deliverables}}
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- {{phase.quality_criteria}}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = wait({ ids: [agentId], timeout_ms: {{phase.timeout_ms || 300000}} })
|
||||||
|
close_agent({ id: agentId })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Parallel Fan-out Pattern** (multiple independent agents):
|
||||||
|
```javascript
|
||||||
|
// Phase N: {{phase.name}} (Parallel)
|
||||||
|
const agentIds = {{phase.agents}}.map(agentConfig => {
|
||||||
|
return spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ${agentConfig.role_file} (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: ${agentConfig.specific_goal}
|
||||||
|
Scope: ${agentConfig.scope}
|
||||||
|
Deliverables: ${agentConfig.deliverables}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Batch wait for all agents
|
||||||
|
const results = wait({
|
||||||
|
ids: agentIds,
|
||||||
|
timeout_ms: {{phase.timeout_ms || 600000}}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle timeout
|
||||||
|
if (results.timed_out) {
|
||||||
|
const completed = agentIds.filter(id => results.status[id].completed)
|
||||||
|
const pending = agentIds.filter(id => !results.status[id].completed)
|
||||||
|
// Decision: continue waiting or use partial results
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate results
|
||||||
|
const aggregated = agentIds.map(id => results.status[id].completed)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
agentIds.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Deep Interaction Pattern** (multi-round with send_input):
|
||||||
|
```javascript
|
||||||
|
// Phase N: {{phase.name}} (Deep Interaction)
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{agent.role_file}} (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase A: {{phase.initial_goal}}
|
||||||
|
Goal: {{phase.initial_goal}}
|
||||||
|
Output: Findings + Open Questions (if any)
|
||||||
|
|
||||||
|
Output format for questions:
|
||||||
|
\`\`\`
|
||||||
|
CLARIFICATION_NEEDED:
|
||||||
|
Q1: [question] | Options: [A, B, C] | Recommended: [A]
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Phase B: {{phase.followup_goal}}
|
||||||
|
Trigger: Receive answers via send_input
|
||||||
|
Output: Complete deliverable
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Round 1: Initial exploration
|
||||||
|
const round1 = wait({ ids: [agent], timeout_ms: {{phase.timeout_ms || 600000}} })
|
||||||
|
|
||||||
|
// Check for clarification needs
|
||||||
|
const needsClarification = round1.status[agent].completed.includes('CLARIFICATION_NEEDED')
|
||||||
|
|
||||||
|
if (needsClarification) {
|
||||||
|
// Collect user answers (orchestrator responsibility)
|
||||||
|
const answers = collectUserAnswers(round1)
|
||||||
|
|
||||||
|
// Continue interaction
|
||||||
|
send_input({
|
||||||
|
id: agent,
|
||||||
|
message: `
|
||||||
|
## CLARIFICATION ANSWERS
|
||||||
|
${answers}
|
||||||
|
|
||||||
|
## NEXT STEP
|
||||||
|
Proceed with Phase B: {{phase.followup_goal}}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const round2 = wait({ ids: [agent], timeout_ms: {{phase.timeout_ms || 900000}} })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Pipeline Pattern** (sequential agent chain):
|
||||||
|
```javascript
|
||||||
|
// Phase N: {{phase.name}} (Pipeline)
|
||||||
|
|
||||||
|
// Stage 1
|
||||||
|
const agent1 = spawn_agent({ message: stage1Prompt })
|
||||||
|
const result1 = wait({ ids: [agent1] })
|
||||||
|
close_agent({ id: agent1 })
|
||||||
|
|
||||||
|
// Stage 2 (uses Stage 1 output)
|
||||||
|
const agent2 = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
...
|
||||||
|
## PREVIOUS STAGE OUTPUT
|
||||||
|
${result1.status[agent1].completed}
|
||||||
|
...
|
||||||
|
`
|
||||||
|
})
|
||||||
|
const result2 = wait({ ids: [agent2] })
|
||||||
|
close_agent({ id: agent2 })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.6: Generate Lifecycle Management Section
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Lifecycle Management
|
||||||
|
|
||||||
|
### Timeout Handling
|
||||||
|
|
||||||
|
| Timeout | Action |
|
||||||
|
|---------|--------|
|
||||||
|
| Agent completes within timeout | Process result, close_agent |
|
||||||
|
| Agent times out (partial) | Option 1: continue wait / Option 2: send_input to urge convergence / Option 3: close_agent and use partial |
|
||||||
|
| All agents timeout | Log warning, retry with extended timeout or abort |
|
||||||
|
|
||||||
|
### Cleanup Protocol
|
||||||
|
|
||||||
|
After ALL phases complete or on error:
|
||||||
|
1. Verify all agent IDs have been closed
|
||||||
|
2. Report any agents still running
|
||||||
|
3. Force close remaining agents
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
const allAgentIds = [] // accumulated during execution
|
||||||
|
allAgentIds.forEach(id => {
|
||||||
|
try { close_agent({ id }) } catch { /* already closed */ }
|
||||||
|
})
|
||||||
|
\`\`\`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 2.7: Write Orchestrator File
|
||||||
|
|
||||||
|
Apply template from `templates/orchestrator-template.md` with generated content.
|
||||||
|
|
||||||
|
Write the complete orchestrator to the output path.
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **File**: `{outputPath}` — generated Codex orchestrator
|
||||||
|
- **Variable**: `generatedFiles.orchestrator` = outputPath
|
||||||
|
- **TodoWrite**: Mark Phase 2 completed, Phase 3 in_progress
|
||||||
|
|
||||||
|
## Next Phase
|
||||||
|
|
||||||
|
Return to orchestrator, then auto-continue to [Phase 3: Agent Design](03-agent-design.md).
|
||||||
277
.claude/skills/codex-skill-designer/phases/03-agent-design.md
Normal file
277
.claude/skills/codex-skill-designer/phases/03-agent-design.md
Normal file
@@ -0,0 +1,277 @@
|
|||||||
|
# Phase 3: Agent Design
|
||||||
|
|
||||||
|
Generate agent role definitions and optional phase execution detail files.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
- Generate agent role files for `~/.codex/agents/` or `.codex/skills/{name}/agents/`
|
||||||
|
- Apply Codex-native conventions (MANDATORY FIRST STEPS, structured output)
|
||||||
|
- Preserve source content when converting from Claude
|
||||||
|
- Generate optional phase detail files for complex orchestrations
|
||||||
|
|
||||||
|
## Pre-Requisites
|
||||||
|
|
||||||
|
- Read `templates/agent-role-template.md` for role file structure
|
||||||
|
- Read `templates/command-pattern-template.md` for pre-built command patterns
|
||||||
|
- Read `specs/codex-agent-patterns.md` for API patterns
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Step 3.1: Identify Agents to Generate
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// From codexSkillConfig
|
||||||
|
const agentsToGenerate = codexSkillConfig.agents.filter(a =>
|
||||||
|
a.role_file.startsWith('.codex/skills/') // new skill-specific agents
|
||||||
|
|| codexSkillConfig.newAgentDefinitions?.includes(a.role_file)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Existing agents (already in ~/.codex/agents/) — skip generation
|
||||||
|
const existingAgents = codexSkillConfig.agents.filter(a =>
|
||||||
|
!agentsToGenerate.includes(a)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.2: Generate Agent Role Files
|
||||||
|
|
||||||
|
For each agent to generate, apply the agent-role-template:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
for (const agent of agentsToGenerate) {
|
||||||
|
const roleContent = applyTemplate('templates/agent-role-template.md', {
|
||||||
|
agent_name: agent.name,
|
||||||
|
description: agent.responsibility,
|
||||||
|
capabilities: agent.capabilities || inferCapabilities(agent),
|
||||||
|
execution_process: agent.workflow || inferWorkflow(agent),
|
||||||
|
output_format: codexSkillConfig.outputTemplate || "structured",
|
||||||
|
key_reminders: generateReminders(agent)
|
||||||
|
})
|
||||||
|
|
||||||
|
const outputPath = agent.role_file.startsWith('~/')
|
||||||
|
? agent.role_file
|
||||||
|
: `.codex/skills/${codexSkillConfig.name}/agents/${agent.name}.md`
|
||||||
|
|
||||||
|
Write(outputPath, roleContent)
|
||||||
|
generatedFiles.agents.push(outputPath)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.3: Agent Role File Content Structure
|
||||||
|
|
||||||
|
Each generated agent role file follows this structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {{agent_name}}
|
||||||
|
description: |
|
||||||
|
{{description}}
|
||||||
|
color: {{color}}
|
||||||
|
skill: {{parent_skill_name}}
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{agent_display_name}}
|
||||||
|
|
||||||
|
{{description_paragraph}}
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
1. **{{capability_1}}**: {{description}}
|
||||||
|
2. **{{capability_2}}**: {{description}}
|
||||||
|
3. **{{capability_3}}**: {{description}}
|
||||||
|
|
||||||
|
## Execution Process
|
||||||
|
|
||||||
|
### Step 1: Context Loading
|
||||||
|
- Read role-specific configuration files
|
||||||
|
- Load project context (.workflow/project-tech.json)
|
||||||
|
- Understand task scope from TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### Step 2: {{primary_action}}
|
||||||
|
{{primary_action_detail}}
|
||||||
|
|
||||||
|
### Step 3: {{secondary_action}}
|
||||||
|
{{secondary_action_detail}}
|
||||||
|
|
||||||
|
### Step 4: Output Delivery
|
||||||
|
Produce structured output following the template:
|
||||||
|
|
||||||
|
\`\`\`text
|
||||||
|
Summary:
|
||||||
|
- {{summary_format}}
|
||||||
|
|
||||||
|
Findings:
|
||||||
|
- {{findings_format}}
|
||||||
|
|
||||||
|
Proposed changes:
|
||||||
|
- {{changes_format}}
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- {{tests_format}}
|
||||||
|
|
||||||
|
Open questions:
|
||||||
|
- {{questions_format}}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Key Reminders
|
||||||
|
|
||||||
|
**ALWAYS**:
|
||||||
|
- Read role definition file as FIRST action
|
||||||
|
- Follow structured output template
|
||||||
|
- Stay within assigned scope
|
||||||
|
- Report open questions instead of guessing
|
||||||
|
|
||||||
|
**NEVER**:
|
||||||
|
- Modify files outside assigned scope
|
||||||
|
- Skip role definition loading
|
||||||
|
- Produce unstructured output
|
||||||
|
- Make assumptions about unclear requirements
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.4: Conversion from Claude Agent Definitions
|
||||||
|
|
||||||
|
When converting from Claude skill, extract agent behavior from:
|
||||||
|
|
||||||
|
1. **Task() prompts**: The `prompt` parameter contains the agent's task instructions
|
||||||
|
2. **Phase files**: Phase execution detail contains the full agent interaction
|
||||||
|
3. **subagent_type**: Maps to existing `~/.codex/agents/` roles
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For each Task() call found in Claude source
|
||||||
|
for (const taskCall of claudeConfig.agents) {
|
||||||
|
const existingRole = roleMapping[taskCall.subagent_type]
|
||||||
|
|
||||||
|
if (existingRole) {
|
||||||
|
// Map to existing Codex agent — no new file needed
|
||||||
|
// Just reference in orchestrator's MANDATORY FIRST STEPS
|
||||||
|
codexSkillConfig.agents.push({
|
||||||
|
name: taskCall.subagent_type,
|
||||||
|
role_file: `~/.codex/agents/${taskCall.subagent_type}.md`,
|
||||||
|
responsibility: taskCall.description,
|
||||||
|
is_new: false
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Extract agent behavior from Claude prompt and create new role
|
||||||
|
const newRole = extractRoleFromPrompt(taskCall.prompt)
|
||||||
|
// Generate new role file
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.5: Command Pattern Selection
|
||||||
|
|
||||||
|
For agents that need specific command patterns, select from pre-built templates:
|
||||||
|
|
||||||
|
| Pattern | Use When | Template |
|
||||||
|
|---------|----------|----------|
|
||||||
|
| **Explore** | Agent needs codebase exploration | Parallel fan-out spawn_agent |
|
||||||
|
| **Analyze** | Agent performs multi-perspective analysis | Parallel spawn + merge |
|
||||||
|
| **Implement** | Agent writes code | Sequential spawn + validate |
|
||||||
|
| **Validate** | Agent runs tests | Iterative spawn + send_input fix cycle |
|
||||||
|
| **Review** | Agent reviews code/artifacts | Parallel spawn + aggregate |
|
||||||
|
| **Deep Interact** | Agent needs multi-round conversation | spawn + wait + send_input loop |
|
||||||
|
| **Two-Phase** | Agent needs clarification first | spawn(clarify) + send_input(execute) |
|
||||||
|
|
||||||
|
Read `templates/command-pattern-template.md` for full pattern implementations.
|
||||||
|
|
||||||
|
### Step 3.6: Generate Phase Detail Files (Optional)
|
||||||
|
|
||||||
|
For structured mode with complex phases, generate phase detail files:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (codexSkillConfig.outputMode === "structured") {
|
||||||
|
for (const phase of codexSkillConfig.phases) {
|
||||||
|
if (phase.complexity === "high" || phase.agents_involved.length > 2) {
|
||||||
|
const phaseContent = generatePhaseDetail(phase, codexSkillConfig)
|
||||||
|
const phasePath = `.codex/skills/${codexSkillConfig.name}/phases/${phase.index}-${phase.slug}.md`
|
||||||
|
Write(phasePath, phaseContent)
|
||||||
|
generatedFiles.phases.push(phasePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Phase detail structure:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Phase {{N}}: {{Phase Name}}
|
||||||
|
|
||||||
|
{{One-sentence description}}
|
||||||
|
|
||||||
|
## Agents Involved
|
||||||
|
|
||||||
|
| Agent | Role | Interaction Model |
|
||||||
|
|-------|------|-------------------|
|
||||||
|
{{#each phase.agents}}
|
||||||
|
| {{name}} | {{role_file}} | {{interaction_model}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### spawn_agent Configuration
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{role_file}} (MUST read first)
|
||||||
|
...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: {{goal}}
|
||||||
|
Scope: {{scope}}
|
||||||
|
Context: {{context}}
|
||||||
|
Deliverables: {{deliverables}}
|
||||||
|
Quality bar: {{quality}}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Wait & Result Processing
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
const result = wait({ ids: [agent], timeout_ms: {{timeout}} })
|
||||||
|
// Process: {{result_processing}}
|
||||||
|
close_agent({ id: agent })
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **Result**: {{output_description}}
|
||||||
|
- **Passed to**: Phase {{N+1}}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 3.7: Deployment Mapping
|
||||||
|
|
||||||
|
Generate deployment instructions:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const deploymentMap = {
|
||||||
|
// Existing agents — no action needed
|
||||||
|
existing: existingAgents.map(a => ({
|
||||||
|
name: a.name,
|
||||||
|
path: a.role_file,
|
||||||
|
action: "already deployed"
|
||||||
|
})),
|
||||||
|
// New agents — need deployment
|
||||||
|
new: agentsToGenerate.map(a => ({
|
||||||
|
name: a.name,
|
||||||
|
sourcePath: `.codex/skills/${codexSkillConfig.name}/agents/${a.name}.md`,
|
||||||
|
targetPath: `~/.codex/agents/${a.name}.md`,
|
||||||
|
action: "copy to ~/.codex/agents/"
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **Files**: `generatedFiles.agents[]` — agent role files
|
||||||
|
- **Files**: `generatedFiles.phases[]` — optional phase detail files
|
||||||
|
- **Variable**: `deploymentMap` — deployment instructions
|
||||||
|
- **TodoWrite**: Mark Phase 3 completed, Phase 4 in_progress
|
||||||
|
|
||||||
|
## Next Phase
|
||||||
|
|
||||||
|
Return to orchestrator, then auto-continue to [Phase 4: Validation & Delivery](04-validation.md).
|
||||||
254
.claude/skills/codex-skill-designer/phases/04-validation.md
Normal file
254
.claude/skills/codex-skill-designer/phases/04-validation.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# Phase 4: Validation & Delivery
|
||||||
|
|
||||||
|
Validate the generated Codex skill package and deliver to target location.
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
- Verify structural completeness of all generated files
|
||||||
|
- Validate Codex pattern compliance (lifecycle, role loading, output format)
|
||||||
|
- Score quality against standards
|
||||||
|
- Deploy to target location with instructions
|
||||||
|
|
||||||
|
## Pre-Requisites
|
||||||
|
|
||||||
|
- Read `specs/quality-standards.md` for validation criteria
|
||||||
|
- Access `generatedFiles` from previous phases
|
||||||
|
- Access `codexSkillConfig` for expected structure
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Step 4.1: Structural Completeness Check
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const structuralChecks = {
|
||||||
|
// Orchestrator exists
|
||||||
|
orchestrator: {
|
||||||
|
exists: fileExists(generatedFiles.orchestrator),
|
||||||
|
hasFrontmatter: checkFrontmatter(generatedFiles.orchestrator),
|
||||||
|
hasArchitecture: checkSection(generatedFiles.orchestrator, "Architecture"),
|
||||||
|
hasAgentRegistry: checkSection(generatedFiles.orchestrator, "Agent Registry"),
|
||||||
|
hasPhaseExecution: checkSection(generatedFiles.orchestrator, "Phase"),
|
||||||
|
hasLifecycleManagement: checkSection(generatedFiles.orchestrator, "Lifecycle"),
|
||||||
|
hasTimeoutHandling: checkSection(generatedFiles.orchestrator, "Timeout"),
|
||||||
|
passed: 0, total: 7
|
||||||
|
},
|
||||||
|
|
||||||
|
// Agent files exist and are well-formed
|
||||||
|
agents: codexSkillConfig.agents.map(agent => ({
|
||||||
|
name: agent.name,
|
||||||
|
exists: fileExists(agent.role_file) || fileExists(generatedFiles.agents.find(f => f.includes(agent.name))),
|
||||||
|
hasFrontmatter: checkFrontmatter(agentFile),
|
||||||
|
hasCapabilities: checkSection(agentFile, "Core Capabilities"),
|
||||||
|
hasExecution: checkSection(agentFile, "Execution Process"),
|
||||||
|
hasReminders: checkSection(agentFile, "Key Reminders"),
|
||||||
|
passed: 0, total: 5
|
||||||
|
})),
|
||||||
|
|
||||||
|
// Phase files (if structured mode)
|
||||||
|
phases: generatedFiles.phases?.map(phasePath => ({
|
||||||
|
path: phasePath,
|
||||||
|
exists: fileExists(phasePath),
|
||||||
|
hasAgentTable: checkSection(phasePath, "Agents Involved"),
|
||||||
|
hasSpawnConfig: checkSection(phasePath, "spawn_agent"),
|
||||||
|
hasWaitProcessing: checkSection(phasePath, "Wait"),
|
||||||
|
passed: 0, total: 4
|
||||||
|
})) || []
|
||||||
|
}
|
||||||
|
|
||||||
|
// Count passes
|
||||||
|
let totalPassed = 0, totalChecks = 0
|
||||||
|
// ... count logic
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4.2: Codex Pattern Compliance
|
||||||
|
|
||||||
|
Verify all Codex-native patterns are correctly applied:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const patternChecks = {
|
||||||
|
// Lifecycle: every spawn has a close
|
||||||
|
lifecycle: {
|
||||||
|
spawnCount: countPattern(orchestratorContent, /spawn_agent/g),
|
||||||
|
closeCount: countPattern(orchestratorContent, /close_agent/g),
|
||||||
|
balanced: spawnCount <= closeCount, // close >= spawn (batch close is OK)
|
||||||
|
description: "Every spawn_agent must have matching close_agent"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Role loading: MANDATORY FIRST STEPS present
|
||||||
|
roleLoading: {
|
||||||
|
hasPattern: orchestratorContent.includes("MANDATORY FIRST STEPS"),
|
||||||
|
allAgentsReferenced: codexSkillConfig.agents.every(a =>
|
||||||
|
orchestratorContent.includes(a.role_file)
|
||||||
|
),
|
||||||
|
usesPathNotInline: !orchestratorContent.includes("## ROLE DEFINITION"),
|
||||||
|
description: "Role files loaded via path reference, not inline content"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Wait pattern: uses wait() not close_agent for results
|
||||||
|
waitPattern: {
|
||||||
|
usesWaitForResults: countPattern(orchestratorContent, /wait\(\s*\{/) > 0,
|
||||||
|
noCloseForResults: !hasPatternSequence(orchestratorContent, "close_agent", "result"),
|
||||||
|
description: "Results obtained via wait(), not close_agent"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Batch wait: parallel agents use batch wait
|
||||||
|
batchWait: {
|
||||||
|
applicable: codexSkillConfig.parallelSplits?.length > 0,
|
||||||
|
usesBatchIds: orchestratorContent.includes("ids: [") ||
|
||||||
|
orchestratorContent.includes("ids: agentIds"),
|
||||||
|
description: "Parallel agents use batch wait({ ids: [...] })"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Timeout handling: timeout_ms specified
|
||||||
|
timeout: {
|
||||||
|
hasTimeout: orchestratorContent.includes("timeout_ms"),
|
||||||
|
hasTimeoutHandling: orchestratorContent.includes("timed_out"),
|
||||||
|
description: "Timeout specified and timeout scenarios handled"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Structured output: agents produce uniform output
|
||||||
|
structuredOutput: {
|
||||||
|
hasSummary: agentContents.every(c => c.includes("Summary:")),
|
||||||
|
hasDeliverables: agentContents.every(c => c.includes("Deliverables") || c.includes("Findings")),
|
||||||
|
description: "All agents produce structured output template"
|
||||||
|
},
|
||||||
|
|
||||||
|
// No Claude patterns: no Task(), no TaskOutput(), no resume
|
||||||
|
noClaudePatterns: {
|
||||||
|
noTask: !orchestratorContent.includes("Task("),
|
||||||
|
noTaskOutput: !orchestratorContent.includes("TaskOutput("),
|
||||||
|
noResume: !orchestratorContent.includes("resume:") && !orchestratorContent.includes("resume ="),
|
||||||
|
description: "No Claude-specific patterns remain"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const patternScore = calculatePatternScore(patternChecks)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4.3: Content Quality Check
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const qualityChecks = {
|
||||||
|
// Orchestrator quality
|
||||||
|
orchestratorQuality: {
|
||||||
|
hasDescription: orchestratorContent.length > 500,
|
||||||
|
hasCodeBlocks: countPattern(orchestratorContent, /```/g) >= 4,
|
||||||
|
hasErrorHandling: orchestratorContent.includes("Error") || orchestratorContent.includes("error"),
|
||||||
|
noPlaceholders: !orchestratorContent.includes("{{") || !orchestratorContent.includes("TODO"),
|
||||||
|
description: "Orchestrator is complete and production-ready"
|
||||||
|
},
|
||||||
|
|
||||||
|
// Agent quality
|
||||||
|
agentQuality: agentContents.map(content => ({
|
||||||
|
hasSubstantiveContent: content.length > 300,
|
||||||
|
hasActionableSteps: countPattern(content, /Step \d/g) >= 2,
|
||||||
|
hasOutputFormat: content.includes("Output") || content.includes("Deliverables"),
|
||||||
|
noPlaceholders: !content.includes("{{") || !content.includes("TODO")
|
||||||
|
})),
|
||||||
|
|
||||||
|
// Conversion quality (if applicable)
|
||||||
|
conversionQuality: codexSkillConfig.conversionSource ? {
|
||||||
|
allTasksConverted: true, // verify all Claude Task() calls are mapped
|
||||||
|
noLostFunctionality: true, // verify no features dropped
|
||||||
|
interactionPreserved: true // verify resume → send_input mapping
|
||||||
|
} : null
|
||||||
|
}
|
||||||
|
|
||||||
|
const qualityScore = calculateQualityScore(qualityChecks)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4.4: Quality Gate
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const overallScore = (
|
||||||
|
structuralScore * 0.30 +
|
||||||
|
patternScore * 0.40 +
|
||||||
|
qualityScore * 0.30
|
||||||
|
)
|
||||||
|
|
||||||
|
const verdict = overallScore >= 80 ? "PASS" :
|
||||||
|
overallScore >= 60 ? "REVIEW" : "FAIL"
|
||||||
|
```
|
||||||
|
|
||||||
|
| Verdict | Score | Action |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| **PASS** | >= 80% | Deliver to target location |
|
||||||
|
| **REVIEW** | 60-79% | Report issues, ask user to proceed or fix |
|
||||||
|
| **FAIL** | < 60% | Block delivery, list critical issues |
|
||||||
|
|
||||||
|
### Step 4.5: Validation Report
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const validationReport = {
|
||||||
|
skill: codexSkillConfig.name,
|
||||||
|
outputMode: codexSkillConfig.outputMode,
|
||||||
|
scores: {
|
||||||
|
structural: structuralScore,
|
||||||
|
pattern: patternScore,
|
||||||
|
quality: qualityScore,
|
||||||
|
overall: overallScore
|
||||||
|
},
|
||||||
|
verdict: verdict,
|
||||||
|
issues: collectIssues(structuralChecks, patternChecks, qualityChecks),
|
||||||
|
generatedFiles: generatedFiles,
|
||||||
|
deploymentMap: deploymentMap
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4.6: Delivery
|
||||||
|
|
||||||
|
If verdict is PASS or user approves REVIEW:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// For structured mode — files already in .codex/skills/{name}/
|
||||||
|
// Report deployment instructions for agent files
|
||||||
|
|
||||||
|
const deploymentInstructions = `
|
||||||
|
## Deployment Instructions
|
||||||
|
|
||||||
|
### Generated Files
|
||||||
|
${generatedFiles.orchestrator}
|
||||||
|
${generatedFiles.agents.join('\n')}
|
||||||
|
${generatedFiles.phases?.join('\n') || '(no phase files)'}
|
||||||
|
|
||||||
|
### Agent Deployment
|
||||||
|
${deploymentMap.new.map(a =>
|
||||||
|
`Copy: ${a.sourcePath} → ${a.targetPath}`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
### Existing Agents (no action needed)
|
||||||
|
${deploymentMap.existing.map(a =>
|
||||||
|
`✓ ${a.name}: ${a.path}`
|
||||||
|
).join('\n')}
|
||||||
|
|
||||||
|
### Usage
|
||||||
|
Invoke the generated orchestrator via Codex:
|
||||||
|
- Read the orchestrator.md and follow its phase execution
|
||||||
|
- Or register as a Codex prompt in ~/.codex/prompts/
|
||||||
|
|
||||||
|
### Validation Score
|
||||||
|
Overall: ${overallScore}% (${verdict})
|
||||||
|
- Structural: ${structuralScore}%
|
||||||
|
- Pattern Compliance: ${patternScore}%
|
||||||
|
- Content Quality: ${qualityScore}%
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 4.7: Final Summary to User
|
||||||
|
|
||||||
|
Present:
|
||||||
|
1. Generated file list with paths
|
||||||
|
2. Validation scores
|
||||||
|
3. Deployment instructions
|
||||||
|
4. Any issues or warnings
|
||||||
|
5. Next steps (e.g., "test the skill by running the orchestrator")
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
- **Report**: Validation report with scores
|
||||||
|
- **Deployment**: Instructions for agent file deployment
|
||||||
|
- **TodoWrite**: Mark Phase 4 completed
|
||||||
|
|
||||||
|
## Completion
|
||||||
|
|
||||||
|
Skill package generation complete. All files written and validated.
|
||||||
@@ -0,0 +1,406 @@
|
|||||||
|
# Codex Agent Patterns
|
||||||
|
|
||||||
|
Core Codex subagent API patterns reference for skill generation.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 0 | Read to understand available Codex patterns |
|
||||||
|
| Phase 2 | Reference when generating orchestrator patterns |
|
||||||
|
| Phase 3 | Reference when designing agent interactions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. API Reference
|
||||||
|
|
||||||
|
### 1.1 spawn_agent
|
||||||
|
|
||||||
|
Creates a new subagent with independent context.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agentId = spawn_agent({
|
||||||
|
message: "task message", // Required: task assignment
|
||||||
|
agent_type: "type" // Optional: preset baseline
|
||||||
|
})
|
||||||
|
// Returns: agent_id (string)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Facts**:
|
||||||
|
- Each agent has isolated context (no shared state)
|
||||||
|
- `agent_type` selects preset behavior baseline
|
||||||
|
- Role definition must be loaded via MANDATORY FIRST STEPS
|
||||||
|
- Returns immediately — use `wait()` for results
|
||||||
|
|
||||||
|
### 1.2 wait
|
||||||
|
|
||||||
|
Retrieves results from one or more agents.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const result = wait({
|
||||||
|
ids: [agentId1, agentId2], // Required: agent IDs to wait for
|
||||||
|
timeout_ms: 300000 // Optional: max wait time (ms)
|
||||||
|
})
|
||||||
|
// Returns: { timed_out: boolean, status: { [id]: { completed: string } } }
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Facts**:
|
||||||
|
- Primary result retrieval method (NOT close_agent)
|
||||||
|
- Supports batch wait for multiple agents
|
||||||
|
- `timed_out: true` means some agents haven't finished — can re-wait
|
||||||
|
- Can be called multiple times on same agent
|
||||||
|
|
||||||
|
### 1.3 send_input
|
||||||
|
|
||||||
|
Continues interaction with an active agent.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
send_input({
|
||||||
|
id: agentId, // Required: target agent
|
||||||
|
message: "follow-up", // Required: continuation message
|
||||||
|
interrupt: false // Optional: interrupt current processing
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Facts**:
|
||||||
|
- Agent must NOT be closed
|
||||||
|
- Preserves full conversation context
|
||||||
|
- Use for: clarification answers, phase transitions, iterative refinement
|
||||||
|
- `interrupt: true` — use with caution (stops current processing)
|
||||||
|
|
||||||
|
### 1.4 close_agent
|
||||||
|
|
||||||
|
Permanently terminates an agent.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
close_agent({ id: agentId })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key Facts**:
|
||||||
|
- Irreversible — no further wait/send_input possible
|
||||||
|
- Do NOT use to retrieve results (use wait instead)
|
||||||
|
- Delay until certain no more interaction needed
|
||||||
|
- Call for ALL agents at end of workflow (cleanup)
|
||||||
|
|
||||||
|
## 2. Interaction Patterns
|
||||||
|
|
||||||
|
### 2.1 Standard (Single Agent, Single Task)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn_agent → wait → close_agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Simple, one-shot tasks with clear deliverables.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agent = spawn_agent({ message: taskPrompt })
|
||||||
|
const result = wait({ ids: [agent], timeout_ms: 300000 })
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Parallel Fan-out (Multiple Independent Agents)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn_agent × N → batch wait({ ids: [...] }) → close_agent × N
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Multiple independent tasks that can run concurrently.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agents = tasks.map(t => spawn_agent({ message: buildPrompt(t) }))
|
||||||
|
const results = wait({ ids: agents, timeout_ms: 600000 })
|
||||||
|
// Aggregate results
|
||||||
|
const merged = agents.map(id => results.status[id].completed)
|
||||||
|
// Cleanup all
|
||||||
|
agents.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
**Split Strategies**:
|
||||||
|
|
||||||
|
| Strategy | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| By responsibility | Each agent has different role | Research / Plan / Test |
|
||||||
|
| By module | Each agent handles different code area | auth / api / database |
|
||||||
|
| By perspective | Each agent analyzes from different angle | security / performance / maintainability |
|
||||||
|
|
||||||
|
### 2.3 Deep Interaction (Multi-round with send_input)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn_agent → wait (round 1) → send_input → wait (round 2) → ... → close_agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Tasks needing iterative refinement or multi-phase execution within single agent context.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agent = spawn_agent({ message: initialPrompt })
|
||||||
|
|
||||||
|
// Round 1
|
||||||
|
const r1 = wait({ ids: [agent], timeout_ms: 300000 })
|
||||||
|
|
||||||
|
// Round 2 (refine based on r1)
|
||||||
|
send_input({ id: agent, message: refinementPrompt })
|
||||||
|
const r2 = wait({ ids: [agent], timeout_ms: 300000 })
|
||||||
|
|
||||||
|
// Round 3 (finalize)
|
||||||
|
send_input({ id: agent, message: finalizationPrompt })
|
||||||
|
const r3 = wait({ ids: [agent], timeout_ms: 300000 })
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.4 Two-Phase (Clarify → Execute)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn_agent → wait (questions) → send_input (answers) → wait (solution) → close_agent
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Complex tasks where requirements need clarification before execution.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
...
|
||||||
|
### Phase A: Exploration & Clarification
|
||||||
|
Output findings + Open Questions (CLARIFICATION_NEEDED format)
|
||||||
|
|
||||||
|
### Phase B: Full Solution (after receiving answers)
|
||||||
|
Output complete deliverable
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Phase A
|
||||||
|
const exploration = wait({ ids: [agent], timeout_ms: 600000 })
|
||||||
|
|
||||||
|
if (exploration.status[agent].completed.includes('CLARIFICATION_NEEDED')) {
|
||||||
|
// Collect answers
|
||||||
|
const answers = getUserAnswers(exploration)
|
||||||
|
|
||||||
|
// Phase B
|
||||||
|
send_input({
|
||||||
|
id: agent,
|
||||||
|
message: `## CLARIFICATION ANSWERS\n${answers}\n\n## PROCEED\nGenerate full solution.`
|
||||||
|
})
|
||||||
|
const solution = wait({ ids: [agent], timeout_ms: 900000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.5 Pipeline (Sequential Agent Chain)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn(A) → wait(A) → close(A) → spawn(B, with A's output) → wait(B) → close(B)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Tasks where each stage depends on the previous stage's output.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Stage 1: Research
|
||||||
|
const researcher = spawn_agent({ message: researchPrompt })
|
||||||
|
const research = wait({ ids: [researcher] })
|
||||||
|
close_agent({ id: researcher })
|
||||||
|
|
||||||
|
// Stage 2: Plan (uses research output)
|
||||||
|
const planner = spawn_agent({
|
||||||
|
message: `${planPrompt}\n\n## RESEARCH CONTEXT\n${research.status[researcher].completed}`
|
||||||
|
})
|
||||||
|
const plan = wait({ ids: [planner] })
|
||||||
|
close_agent({ id: planner })
|
||||||
|
|
||||||
|
// Stage 3: Execute (uses plan output)
|
||||||
|
const executor = spawn_agent({
|
||||||
|
message: `${executePrompt}\n\n## PLAN\n${plan.status[planner].completed}`
|
||||||
|
})
|
||||||
|
const execution = wait({ ids: [executor] })
|
||||||
|
close_agent({ id: executor })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.6 Merged Exploration (Explore + Clarify + Plan in Single Agent)
|
||||||
|
|
||||||
|
```
|
||||||
|
spawn(dual-role) → wait(explore) → send_input(clarify) → wait(plan) → close
|
||||||
|
```
|
||||||
|
|
||||||
|
**Use When**: Exploration and planning are tightly coupled and benefit from shared context.
|
||||||
|
|
||||||
|
**Advantages over Pipeline**:
|
||||||
|
- 60-80% fewer agent creations
|
||||||
|
- No context loss between phases
|
||||||
|
- Higher result consistency
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## DUAL ROLE ASSIGNMENT
|
||||||
|
|
||||||
|
### Role A: Explorer
|
||||||
|
Explore codebase, identify patterns, generate questions
|
||||||
|
|
||||||
|
### Role B: Planner (activated after clarification)
|
||||||
|
Generate implementation plan based on exploration + answers
|
||||||
|
|
||||||
|
### Phase 1: Explore
|
||||||
|
Output: Findings + CLARIFICATION_NEEDED questions
|
||||||
|
|
||||||
|
### Phase 2: Plan (triggered by send_input)
|
||||||
|
Output: plan.json
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const explore = wait({ ids: [agent] })
|
||||||
|
// ... handle clarification ...
|
||||||
|
send_input({ id: agent, message: answers })
|
||||||
|
const plan = wait({ ids: [agent] })
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. Message Design
|
||||||
|
|
||||||
|
### 3.1 TASK ASSIGNMENT Structure
|
||||||
|
|
||||||
|
```text
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: One-sentence objective
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: allowed operations
|
||||||
|
- Exclude: forbidden operations
|
||||||
|
- Directory: target paths
|
||||||
|
- Dependencies: dependency constraints
|
||||||
|
|
||||||
|
Context:
|
||||||
|
- Key paths: relevant file paths
|
||||||
|
- Current state: system status
|
||||||
|
- Constraints: must-follow rules
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- Output structured following template
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- Criterion 1
|
||||||
|
- Criterion 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Structured Output Template
|
||||||
|
|
||||||
|
```text
|
||||||
|
Summary:
|
||||||
|
- One-sentence completion status
|
||||||
|
|
||||||
|
Findings:
|
||||||
|
- Finding 1: description
|
||||||
|
- Finding 2: description
|
||||||
|
|
||||||
|
Proposed changes:
|
||||||
|
- File: path/to/file
|
||||||
|
- Change: modification detail
|
||||||
|
- Risk: impact assessment
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- Test cases needed
|
||||||
|
- Commands to run
|
||||||
|
|
||||||
|
Open questions:
|
||||||
|
1. Unresolved question 1
|
||||||
|
2. Unresolved question 2
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.3 Clarification Format
|
||||||
|
|
||||||
|
```text
|
||||||
|
CLARIFICATION_NEEDED:
|
||||||
|
Q1: [question] | Options: [A, B, C] | Recommended: [A]
|
||||||
|
Q2: [question] | Options: [A, B] | Recommended: [B]
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Error Handling
|
||||||
|
|
||||||
|
### 4.1 Timeout
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const result = wait({ ids: [agent], timeout_ms: 30000 })
|
||||||
|
if (result.timed_out) {
|
||||||
|
// Option 1: Continue waiting
|
||||||
|
const retry = wait({ ids: [agent], timeout_ms: 60000 })
|
||||||
|
|
||||||
|
// Option 2: Urge convergence
|
||||||
|
send_input({ id: agent, message: "Please wrap up and output current findings." })
|
||||||
|
const urged = wait({ ids: [agent], timeout_ms: 30000 })
|
||||||
|
|
||||||
|
// Option 3: Abort
|
||||||
|
close_agent({ id: agent })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.2 Agent Recovery (post close_agent)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Cannot recover closed agent — must recreate
|
||||||
|
const newAgent = spawn_agent({
|
||||||
|
message: `${originalPrompt}\n\n## PREVIOUS ATTEMPT OUTPUT\n${previousOutput}`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4.3 Partial Results (parallel fan-out)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const results = wait({ ids: agents, timeout_ms: 300000 })
|
||||||
|
const completed = agents.filter(id => results.status[id]?.completed)
|
||||||
|
const pending = agents.filter(id => !results.status[id]?.completed)
|
||||||
|
|
||||||
|
if (completed.length >= Math.ceil(agents.length * 0.7)) {
|
||||||
|
// 70%+ complete — proceed with partial results
|
||||||
|
pending.forEach(id => close_agent({ id }))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Role Loading
|
||||||
|
|
||||||
|
### 5.1 Path Reference Pattern (Recommended)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/${agentType}.md (MUST read first)
|
||||||
|
...
|
||||||
|
`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Why**: Keeps message lean, agent loads its own role context.
|
||||||
|
|
||||||
|
### 5.2 Role Mapping
|
||||||
|
|
||||||
|
| Agent Type | Role File |
|
||||||
|
|------------|-----------|
|
||||||
|
| cli-explore-agent | ~/.codex/agents/cli-explore-agent.md |
|
||||||
|
| cli-lite-planning-agent | ~/.codex/agents/cli-lite-planning-agent.md |
|
||||||
|
| code-developer | ~/.codex/agents/code-developer.md |
|
||||||
|
| context-search-agent | ~/.codex/agents/context-search-agent.md |
|
||||||
|
| debug-explore-agent | ~/.codex/agents/debug-explore-agent.md |
|
||||||
|
| doc-generator | ~/.codex/agents/doc-generator.md |
|
||||||
|
| action-planning-agent | ~/.codex/agents/action-planning-agent.md |
|
||||||
|
| test-fix-agent | ~/.codex/agents/test-fix-agent.md |
|
||||||
|
| universal-executor | ~/.codex/agents/universal-executor.md |
|
||||||
|
| tdd-developer | ~/.codex/agents/tdd-developer.md |
|
||||||
|
| ui-design-agent | ~/.codex/agents/ui-design-agent.md |
|
||||||
|
|
||||||
|
## 6. Design Principles
|
||||||
|
|
||||||
|
1. **Delay close_agent**: Only close when certain no more interaction needed
|
||||||
|
2. **Batch wait over sequential**: Use `wait({ ids: [...] })` for parallel agents
|
||||||
|
3. **Merge phases when context-dependent**: Use send_input over new agents
|
||||||
|
4. **Structured output always**: Enforce uniform output template
|
||||||
|
5. **Minimal message size**: Pass role file paths, not inline content
|
||||||
|
6. **Explicit lifecycle**: Every spawn must have a close (balanced)
|
||||||
|
7. **Timeout handling**: Always specify timeout_ms, always handle timed_out
|
||||||
228
.claude/skills/codex-skill-designer/specs/conversion-rules.md
Normal file
228
.claude/skills/codex-skill-designer/specs/conversion-rules.md
Normal file
@@ -0,0 +1,228 @@
|
|||||||
|
# Claude → Codex Conversion Rules
|
||||||
|
|
||||||
|
Comprehensive mapping rules for converting Claude Code skills to Codex-native skills.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 1 | Reference when analyzing Claude source skill |
|
||||||
|
| Phase 2 | Apply when generating Codex orchestrator |
|
||||||
|
| Phase 3 | Apply when converting agent definitions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. API Mapping
|
||||||
|
|
||||||
|
### 1.1 Core API Conversion
|
||||||
|
|
||||||
|
| Claude Pattern | Codex Equivalent | Notes |
|
||||||
|
|----------------|-----------------|-------|
|
||||||
|
| `Task({ subagent_type, prompt })` | `spawn_agent({ message })` + `wait()` | Split create and result retrieval |
|
||||||
|
| `Task({ run_in_background: false })` | `spawn_agent()` + immediate `wait()` | Synchronous equivalent |
|
||||||
|
| `Task({ run_in_background: true })` | `spawn_agent()` (wait later) | Deferred wait |
|
||||||
|
| `Task({ resume: agentId })` | `send_input({ id: agentId })` | Agent must not be closed |
|
||||||
|
| `TaskOutput({ task_id, block: true })` | `wait({ ids: [id] })` | Blocking wait |
|
||||||
|
| `TaskOutput({ task_id, block: false })` | `wait({ ids: [id], timeout_ms: 1000 })` | Polling with short timeout |
|
||||||
|
| Agent auto-cleanup | `close_agent({ id })` | Must be explicit |
|
||||||
|
|
||||||
|
### 1.2 Parallel Task Conversion
|
||||||
|
|
||||||
|
**Claude**:
|
||||||
|
```javascript
|
||||||
|
// Multiple Task() calls in single message (parallel)
|
||||||
|
const result1 = Task({ subagent_type: "agent-a", prompt: promptA })
|
||||||
|
const result2 = Task({ subagent_type: "agent-b", prompt: promptB })
|
||||||
|
const result3 = Task({ subagent_type: "agent-c", prompt: promptC })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Codex**:
|
||||||
|
```javascript
|
||||||
|
// Explicit parallel: spawn all, then batch wait
|
||||||
|
const idA = spawn_agent({ message: promptA_with_role })
|
||||||
|
const idB = spawn_agent({ message: promptB_with_role })
|
||||||
|
const idC = spawn_agent({ message: promptC_with_role })
|
||||||
|
|
||||||
|
const results = wait({ ids: [idA, idB, idC], timeout_ms: 600000 })
|
||||||
|
|
||||||
|
// Process results
|
||||||
|
const resultA = results.status[idA].completed
|
||||||
|
const resultB = results.status[idB].completed
|
||||||
|
const resultC = results.status[idC].completed
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
;[idA, idB, idC].forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.3 Resume/Continue Conversion
|
||||||
|
|
||||||
|
**Claude**:
|
||||||
|
```javascript
|
||||||
|
// Resume a previous agent
|
||||||
|
Task({ subagent_type: "agent-a", resume: previousAgentId, prompt: "Continue..." })
|
||||||
|
```
|
||||||
|
|
||||||
|
**Codex**:
|
||||||
|
```javascript
|
||||||
|
// send_input to continue (agent must still be alive)
|
||||||
|
send_input({
|
||||||
|
id: previousAgentId,
|
||||||
|
message: "Continue..."
|
||||||
|
})
|
||||||
|
const continued = wait({ ids: [previousAgentId] })
|
||||||
|
```
|
||||||
|
|
||||||
|
### 1.4 TaskOutput Polling Conversion
|
||||||
|
|
||||||
|
**Claude**:
|
||||||
|
```javascript
|
||||||
|
while (!done) {
|
||||||
|
const output = TaskOutput({ task_id: id, block: false })
|
||||||
|
if (output.status === 'completed') done = true
|
||||||
|
sleep(1000)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Codex**:
|
||||||
|
```javascript
|
||||||
|
let result = wait({ ids: [id], timeout_ms: 30000 })
|
||||||
|
while (result.timed_out) {
|
||||||
|
result = wait({ ids: [id], timeout_ms: 30000 })
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Role Loading Conversion
|
||||||
|
|
||||||
|
### 2.1 subagent_type → MANDATORY FIRST STEPS
|
||||||
|
|
||||||
|
**Claude**: Role automatically loaded via `subagent_type` parameter.
|
||||||
|
|
||||||
|
**Codex**: Role must be explicitly loaded by agent as first action.
|
||||||
|
|
||||||
|
**Conversion**:
|
||||||
|
```javascript
|
||||||
|
// Claude
|
||||||
|
Task({
|
||||||
|
subagent_type: "cli-explore-agent",
|
||||||
|
prompt: "Explore the codebase for authentication patterns"
|
||||||
|
})
|
||||||
|
|
||||||
|
// Codex
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Explore the codebase for authentication patterns
|
||||||
|
Deliverables: Structured findings following output template
|
||||||
|
`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 Role Mapping Table
|
||||||
|
|
||||||
|
| Claude subagent_type | Codex Role Path |
|
||||||
|
|----------------------|-----------------|
|
||||||
|
| `Explore` | `~/.codex/agents/cli-explore-agent.md` |
|
||||||
|
| `Plan` | `~/.codex/agents/cli-lite-planning-agent.md` |
|
||||||
|
| `code-developer` | `~/.codex/agents/code-developer.md` |
|
||||||
|
| `context-search-agent` | `~/.codex/agents/context-search-agent.md` |
|
||||||
|
| `debug-explore-agent` | `~/.codex/agents/debug-explore-agent.md` |
|
||||||
|
| `doc-generator` | `~/.codex/agents/doc-generator.md` |
|
||||||
|
| `action-planning-agent` | `~/.codex/agents/action-planning-agent.md` |
|
||||||
|
| `test-fix-agent` | `~/.codex/agents/test-fix-agent.md` |
|
||||||
|
| `universal-executor` | `~/.codex/agents/universal-executor.md` |
|
||||||
|
| `tdd-developer` | `~/.codex/agents/tdd-developer.md` |
|
||||||
|
| `general-purpose` | `~/.codex/agents/universal-executor.md` |
|
||||||
|
| `Bash` | Direct shell execution (no agent needed) |
|
||||||
|
| `haiku` / `sonnet` / `opus` | Model selection via agent_type parameter |
|
||||||
|
|
||||||
|
## 3. Structural Conversion
|
||||||
|
|
||||||
|
### 3.1 SKILL.md → orchestrator.md
|
||||||
|
|
||||||
|
| Claude SKILL.md Section | Codex orchestrator.md Section |
|
||||||
|
|--------------------------|-------------------------------|
|
||||||
|
| Frontmatter (name, description, allowed-tools) | Frontmatter (name, description, agents, phases) |
|
||||||
|
| Architecture Overview | Architecture Overview (spawn/wait/close flow) |
|
||||||
|
| Execution Flow (Ref: markers) | Phase Execution (spawn_agent code blocks) |
|
||||||
|
| Data Flow (variables, files) | Data Flow (wait results, context passing) |
|
||||||
|
| TodoWrite Pattern | update_plan tracking (Codex convention) |
|
||||||
|
| Interactive Preference Collection | User interaction via orchestrator prompts |
|
||||||
|
| Error Handling | Timeout + Lifecycle error handling |
|
||||||
|
| Phase Reference Documents table | Agent Registry + Phase detail files |
|
||||||
|
|
||||||
|
### 3.2 Phase Files → Phase Detail or Inline
|
||||||
|
|
||||||
|
**Simple phases** (single agent, no branching): Inline in orchestrator.md
|
||||||
|
|
||||||
|
**Complex phases** (multi-agent, conditional): Separate `phases/0N-{name}.md`
|
||||||
|
|
||||||
|
### 3.3 Pattern-Level Conversion
|
||||||
|
|
||||||
|
| Claude Pattern | Codex Pattern |
|
||||||
|
|----------------|---------------|
|
||||||
|
| Orchestrator + Progressive Loading | Orchestrator + Agent Registry + on-demand phase loading |
|
||||||
|
| TodoWrite Attachment/Collapse | update_plan pending → in_progress → completed |
|
||||||
|
| Inter-Phase Data Flow (variables) | wait() result passing between phases |
|
||||||
|
| Conditional Phase Execution | if/else on wait() results |
|
||||||
|
| Direct Phase Handoff (Read phase doc) | Inline execution or separate phase files |
|
||||||
|
| AskUserQuestion | Direct user interaction in orchestrator |
|
||||||
|
|
||||||
|
## 4. Content Preservation Rules
|
||||||
|
|
||||||
|
When converting Claude skills:
|
||||||
|
|
||||||
|
1. **Agent prompts**: Preserve task descriptions, goals, scope, deliverables VERBATIM
|
||||||
|
2. **Bash commands**: Preserve all shell commands unchanged
|
||||||
|
3. **Code blocks**: Preserve implementation code unchanged
|
||||||
|
4. **Validation logic**: Preserve quality checks and success criteria
|
||||||
|
5. **Error handling**: Convert to Codex timeout/lifecycle patterns, preserve intent
|
||||||
|
|
||||||
|
**Transform** (structure changes):
|
||||||
|
- `Task()` calls → `spawn_agent()` + `wait()` + `close_agent()`
|
||||||
|
- `subagent_type` → MANDATORY FIRST STEPS role path
|
||||||
|
- Synchronous returns → Explicit `wait()` calls
|
||||||
|
- Auto-cleanup → Explicit `close_agent()` calls
|
||||||
|
|
||||||
|
**Preserve** (content unchanged):
|
||||||
|
- Task descriptions and goals
|
||||||
|
- Scope definitions
|
||||||
|
- Quality criteria
|
||||||
|
- File paths and patterns
|
||||||
|
- Shell commands
|
||||||
|
- Business logic
|
||||||
|
|
||||||
|
## 5. Anti-Patterns to Avoid
|
||||||
|
|
||||||
|
| Anti-Pattern | Why | Correct Pattern |
|
||||||
|
|-------------|-----|-----------------|
|
||||||
|
| Using close_agent for results | Returns are unreliable | Use wait() for results |
|
||||||
|
| Inline role content in message | Bloats message, wastes tokens | Pass role file path in MANDATORY FIRST STEPS |
|
||||||
|
| Early close_agent before potential follow-up | Cannot resume closed agent | Delay close until certain no more interaction |
|
||||||
|
| Sequential wait for parallel agents | Wasted time | Batch wait({ ids: [...] }) |
|
||||||
|
| No timeout_ms | Indefinite hang risk | Always specify timeout_ms |
|
||||||
|
| No timed_out handling | Silent failures | Always check result.timed_out |
|
||||||
|
| Claude Task() remaining in output | Runtime incompatibility | Convert all Task() to spawn_agent |
|
||||||
|
| Claude resume: in output | Runtime incompatibility | Convert to send_input() |
|
||||||
|
|
||||||
|
## 6. Conversion Checklist
|
||||||
|
|
||||||
|
Before delivering converted skill:
|
||||||
|
|
||||||
|
- [ ] All `Task()` calls converted to `spawn_agent()` + `wait()` + `close_agent()`
|
||||||
|
- [ ] All `subagent_type` mapped to MANDATORY FIRST STEPS role paths
|
||||||
|
- [ ] All `resume` converted to `send_input()`
|
||||||
|
- [ ] All `TaskOutput` polling converted to `wait()` with timeout
|
||||||
|
- [ ] No Claude-specific patterns remain (Task, TaskOutput, resume, subagent_type)
|
||||||
|
- [ ] Timeout handling added for all `wait()` calls
|
||||||
|
- [ ] Lifecycle balanced (spawn count ≤ close count)
|
||||||
|
- [ ] Structured output template enforced for all agents
|
||||||
|
- [ ] Agent prompts/goals/scope preserved verbatim
|
||||||
|
- [ ] Error handling converted to Codex patterns
|
||||||
163
.claude/skills/codex-skill-designer/specs/quality-standards.md
Normal file
163
.claude/skills/codex-skill-designer/specs/quality-standards.md
Normal file
@@ -0,0 +1,163 @@
|
|||||||
|
# Quality Standards
|
||||||
|
|
||||||
|
Quality criteria and validation gates for generated Codex skills.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 3 | Reference during generation |
|
||||||
|
| Phase 4 | Apply during validation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Quality Dimensions
|
||||||
|
|
||||||
|
### 1.1 Structural Completeness (30%)
|
||||||
|
|
||||||
|
| Check | Weight | Criteria |
|
||||||
|
|-------|--------|----------|
|
||||||
|
| Orchestrator exists | 5 | File present at expected path |
|
||||||
|
| Frontmatter valid | 3 | Contains name, description |
|
||||||
|
| Architecture diagram | 3 | ASCII flow showing spawn/wait/close |
|
||||||
|
| Agent Registry | 4 | Table with all agents, role paths, responsibilities |
|
||||||
|
| Phase Execution blocks | 5 | Code blocks for each phase with spawn/wait/close |
|
||||||
|
| Lifecycle Management | 5 | Timeout handling + cleanup protocol |
|
||||||
|
| Agent files complete | 5 | All new agent roles have complete role files |
|
||||||
|
|
||||||
|
**Scoring**: Each check passes (full weight) or fails (0). Total = sum / max.
|
||||||
|
|
||||||
|
### 1.2 Pattern Compliance (40%)
|
||||||
|
|
||||||
|
| Check | Weight | Criteria |
|
||||||
|
|-------|--------|----------|
|
||||||
|
| Lifecycle balanced | 6 | Every spawn_agent has matching close_agent |
|
||||||
|
| Role loading correct | 6 | MANDATORY FIRST STEPS pattern used (not inline content) |
|
||||||
|
| Wait for results | 5 | wait() used for results (not close_agent) |
|
||||||
|
| Batch wait for parallel | 5 | Parallel agents use wait({ ids: [...] }) |
|
||||||
|
| Timeout specified | 4 | All wait() calls have timeout_ms |
|
||||||
|
| Timeout handled | 4 | timed_out checked after every wait() |
|
||||||
|
| Structured output | 5 | Agents produce Summary/Findings/Changes/Tests/Questions |
|
||||||
|
| No Claude patterns | 5 | No Task(), TaskOutput(), resume: remaining |
|
||||||
|
|
||||||
|
**Scoring**: Each check passes (full weight) or fails (0). Total = sum / max.
|
||||||
|
|
||||||
|
### 1.3 Content Quality (30%)
|
||||||
|
|
||||||
|
| Check | Weight | Criteria |
|
||||||
|
|-------|--------|----------|
|
||||||
|
| Orchestrator substantive | 4 | Content > 500 chars, not boilerplate |
|
||||||
|
| Code blocks present | 3 | >= 4 code blocks with executable patterns |
|
||||||
|
| Error handling | 3 | Timeout + recovery + partial results handling |
|
||||||
|
| No placeholders | 4 | No `{{...}}` or `TODO` remaining in output |
|
||||||
|
| Agent roles substantive | 4 | Each agent role > 300 chars with actionable steps |
|
||||||
|
| Output format defined | 3 | Structured output template in each agent |
|
||||||
|
| Goals/scope clear | 4 | Every spawn_agent has Goal + Scope + Deliverables |
|
||||||
|
| Conversion faithful | 5 | Source content preserved (if converting) |
|
||||||
|
|
||||||
|
**Scoring**: Each check passes (full weight) or fails (0). Total = sum / max.
|
||||||
|
|
||||||
|
## 2. Quality Gates
|
||||||
|
|
||||||
|
| Verdict | Score | Action |
|
||||||
|
|---------|-------|--------|
|
||||||
|
| **PASS** | >= 80% | Deliver to target location |
|
||||||
|
| **REVIEW** | 60-79% | Report issues, user decides |
|
||||||
|
| **FAIL** | < 60% | Block delivery, list critical issues |
|
||||||
|
|
||||||
|
### 2.1 Critical Failures (Auto-FAIL)
|
||||||
|
|
||||||
|
These issues force FAIL regardless of overall score:
|
||||||
|
|
||||||
|
1. **No orchestrator file** — skill has no entry point
|
||||||
|
2. **Task() calls in output** — runtime incompatible with Codex
|
||||||
|
3. **No agent registry** — agents cannot be identified
|
||||||
|
4. **Missing close_agent** — resource leak risk
|
||||||
|
5. **Inline role content** — violates Codex pattern (message bloat)
|
||||||
|
|
||||||
|
### 2.2 Warnings (Non-blocking)
|
||||||
|
|
||||||
|
1. **Missing timeout handling** — degraded reliability
|
||||||
|
2. **No error handling section** — reduced robustness
|
||||||
|
3. **Placeholder text remaining** — needs manual completion
|
||||||
|
4. **Phase files missing** — acceptable for simple skills
|
||||||
|
|
||||||
|
## 3. Validation Process
|
||||||
|
|
||||||
|
### 3.1 Automated Checks
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function validateSkill(generatedFiles, codexSkillConfig) {
|
||||||
|
const checks = []
|
||||||
|
|
||||||
|
// Structural
|
||||||
|
checks.push(checkFileExists(generatedFiles.orchestrator))
|
||||||
|
checks.push(checkFrontmatter(generatedFiles.orchestrator))
|
||||||
|
checks.push(checkSection(generatedFiles.orchestrator, "Architecture"))
|
||||||
|
checks.push(checkSection(generatedFiles.orchestrator, "Agent Registry"))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Pattern compliance
|
||||||
|
const content = Read(generatedFiles.orchestrator)
|
||||||
|
checks.push(checkBalancedLifecycle(content))
|
||||||
|
checks.push(checkRoleLoading(content))
|
||||||
|
checks.push(checkWaitPattern(content))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Content quality
|
||||||
|
checks.push(checkNoPlaceholders(content))
|
||||||
|
checks.push(checkSubstantiveContent(content))
|
||||||
|
// ...
|
||||||
|
|
||||||
|
// Critical failures
|
||||||
|
const criticals = checkCriticalFailures(content, generatedFiles)
|
||||||
|
if (criticals.length > 0) return { verdict: "FAIL", criticals }
|
||||||
|
|
||||||
|
// Score
|
||||||
|
const score = calculateWeightedScore(checks)
|
||||||
|
const verdict = score >= 80 ? "PASS" : score >= 60 ? "REVIEW" : "FAIL"
|
||||||
|
|
||||||
|
return { score, verdict, checks, issues: checks.filter(c => !c.passed) }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 Manual Review Points
|
||||||
|
|
||||||
|
For REVIEW verdict, highlight these for user attention:
|
||||||
|
|
||||||
|
1. Agent role completeness — are all capabilities covered?
|
||||||
|
2. Interaction model appropriateness — right pattern for use case?
|
||||||
|
3. Timeout values — appropriate for expected task duration?
|
||||||
|
4. Scope definitions — clear boundaries for each agent?
|
||||||
|
5. Output format — suitable for downstream consumers?
|
||||||
|
|
||||||
|
## 4. Scoring Formula
|
||||||
|
|
||||||
|
```
|
||||||
|
Overall = Structural × 0.30 + PatternCompliance × 0.40 + ContentQuality × 0.30
|
||||||
|
```
|
||||||
|
|
||||||
|
Pattern compliance weighted highest because Codex runtime correctness is critical.
|
||||||
|
|
||||||
|
## 5. Quality Improvement Guidance
|
||||||
|
|
||||||
|
### Low Structural Score
|
||||||
|
|
||||||
|
- Add missing sections to orchestrator
|
||||||
|
- Create missing agent role files
|
||||||
|
- Add frontmatter to all files
|
||||||
|
|
||||||
|
### Low Pattern Score
|
||||||
|
|
||||||
|
- Add MANDATORY FIRST STEPS to all spawn_agent messages
|
||||||
|
- Replace inline role content with path references
|
||||||
|
- Add close_agent for every spawn_agent
|
||||||
|
- Add timeout_ms and timed_out handling to all wait calls
|
||||||
|
- Remove any remaining Claude patterns
|
||||||
|
|
||||||
|
### Low Content Score
|
||||||
|
|
||||||
|
- Expand agent role definitions with more specific steps
|
||||||
|
- Add concrete Goal/Scope/Deliverables to spawn messages
|
||||||
|
- Replace placeholders with actual content
|
||||||
|
- Add error handling for each phase
|
||||||
@@ -0,0 +1,215 @@
|
|||||||
|
# Agent Role Template
|
||||||
|
|
||||||
|
Template for generating per-agent role definition files.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 0 | Read to understand agent role file structure |
|
||||||
|
| Phase 3 | Apply with agent-specific content |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {{agent_name}}
|
||||||
|
description: |
|
||||||
|
{{description}}
|
||||||
|
color: {{color}}
|
||||||
|
skill: {{parent_skill_name}}
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{agent_display_name}}
|
||||||
|
|
||||||
|
{{description_paragraph}}
|
||||||
|
|
||||||
|
## Core Capabilities
|
||||||
|
|
||||||
|
{{#each capabilities}}
|
||||||
|
{{@index}}. **{{this.name}}**: {{this.description}}
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
## Execution Process
|
||||||
|
|
||||||
|
### Step 1: Context Loading
|
||||||
|
|
||||||
|
**MANDATORY**: Execute these steps FIRST before any other action.
|
||||||
|
|
||||||
|
1. Read this role definition file (already done if you're reading this)
|
||||||
|
2. Read: `.workflow/project-tech.json` — understand project technology stack
|
||||||
|
3. Read: `.workflow/project-guidelines.json` — understand project conventions
|
||||||
|
4. Parse the TASK ASSIGNMENT from the spawn message for:
|
||||||
|
- **Goal**: What to achieve
|
||||||
|
- **Scope**: What's allowed and forbidden
|
||||||
|
- **Context**: Relevant background information
|
||||||
|
- **Deliverables**: Expected output format
|
||||||
|
- **Quality bar**: Success criteria
|
||||||
|
|
||||||
|
### Step 2: {{primary_action_name}}
|
||||||
|
|
||||||
|
{{primary_action_detail}}
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// {{primary_action_description}}
|
||||||
|
{{primary_action_code}}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Step 3: {{secondary_action_name}}
|
||||||
|
|
||||||
|
{{secondary_action_detail}}
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// {{secondary_action_description}}
|
||||||
|
{{secondary_action_code}}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
### Step 4: Output Delivery
|
||||||
|
|
||||||
|
Produce structured output following this EXACT template:
|
||||||
|
|
||||||
|
\`\`\`text
|
||||||
|
Summary:
|
||||||
|
- One-sentence completion summary
|
||||||
|
|
||||||
|
Findings:
|
||||||
|
- Finding 1: [specific description with file:line references]
|
||||||
|
- Finding 2: [specific description]
|
||||||
|
|
||||||
|
Proposed changes:
|
||||||
|
- File: [path/to/file]
|
||||||
|
- Change: [specific modification description]
|
||||||
|
- Risk: [low/medium/high] - [impact description]
|
||||||
|
|
||||||
|
Tests:
|
||||||
|
- Test cases: [list of needed test cases]
|
||||||
|
- Commands: [test commands to verify]
|
||||||
|
|
||||||
|
Open questions:
|
||||||
|
1. [Question needing clarification, if any]
|
||||||
|
2. [Question needing clarification, if any]
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Important**: If there are open questions that block progress, prepend output with:
|
||||||
|
\`\`\`
|
||||||
|
CLARIFICATION_NEEDED:
|
||||||
|
Q1: [question] | Options: [A, B, C] | Recommended: [A]
|
||||||
|
Q2: [question] | Options: [A, B] | Recommended: [B]
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Key Reminders
|
||||||
|
|
||||||
|
**ALWAYS**:
|
||||||
|
- Read role definition file as FIRST action (Step 1)
|
||||||
|
- Follow structured output template EXACTLY
|
||||||
|
- Stay within the assigned Scope boundaries
|
||||||
|
- Include file:line references in Findings
|
||||||
|
- Report open questions via CLARIFICATION_NEEDED format
|
||||||
|
- Provide actionable, specific deliverables
|
||||||
|
|
||||||
|
**NEVER**:
|
||||||
|
- Modify files outside the assigned Scope
|
||||||
|
- Skip context loading (Step 1)
|
||||||
|
- Produce unstructured or free-form output
|
||||||
|
- Make assumptions about unclear requirements (ask instead)
|
||||||
|
- Exceed the defined Quality bar without explicit approval
|
||||||
|
- Ignore the Goal/Scope/Deliverables from TASK ASSIGNMENT
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Scenario | Action |
|
||||||
|
|----------|--------|
|
||||||
|
| Cannot access required file | Report in Open questions, continue with available data |
|
||||||
|
| Task scope unclear | Output CLARIFICATION_NEEDED, provide best-effort findings |
|
||||||
|
| Unexpected error | Report error details in Summary, include partial results |
|
||||||
|
| Quality bar not achievable | Report gap in Summary, explain constraints |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template Variants by Responsibility Type
|
||||||
|
|
||||||
|
### Exploration Agent
|
||||||
|
|
||||||
|
**Step 2**: Codebase Discovery
|
||||||
|
```javascript
|
||||||
|
// Search for relevant code patterns
|
||||||
|
const files = Glob("src/**/*.{ts,js,tsx,jsx}")
|
||||||
|
const matches = Grep(targetPattern, files)
|
||||||
|
// Trace call chains, identify entry points
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3**: Pattern Analysis
|
||||||
|
```javascript
|
||||||
|
// Analyze discovered patterns
|
||||||
|
// Cross-reference with project conventions
|
||||||
|
// Identify similar implementations
|
||||||
|
```
|
||||||
|
|
||||||
|
### Implementation Agent
|
||||||
|
|
||||||
|
**Step 2**: Code Implementation
|
||||||
|
```javascript
|
||||||
|
// Implement changes according to plan
|
||||||
|
// Follow existing code patterns
|
||||||
|
// Maintain backward compatibility
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3**: Self-Validation
|
||||||
|
```javascript
|
||||||
|
// Run relevant tests
|
||||||
|
// Check for syntax/type errors
|
||||||
|
// Verify changes match acceptance criteria
|
||||||
|
```
|
||||||
|
|
||||||
|
### Analysis Agent
|
||||||
|
|
||||||
|
**Step 2**: Multi-Dimensional Analysis
|
||||||
|
```javascript
|
||||||
|
// Analyze from assigned perspective (security/perf/quality/etc.)
|
||||||
|
// Collect evidence with file:line references
|
||||||
|
// Classify findings by severity
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3**: Recommendation Generation
|
||||||
|
```javascript
|
||||||
|
// Propose fixes for each finding
|
||||||
|
// Assess risk and effort
|
||||||
|
// Prioritize by impact
|
||||||
|
```
|
||||||
|
|
||||||
|
### Testing Agent
|
||||||
|
|
||||||
|
**Step 2**: Test Design
|
||||||
|
```javascript
|
||||||
|
// Identify test scenarios from requirements
|
||||||
|
// Design test cases with expected results
|
||||||
|
// Map to test frameworks
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 3**: Test Execution & Validation
|
||||||
|
```javascript
|
||||||
|
// Run tests
|
||||||
|
// Collect pass/fail results
|
||||||
|
// Iterate on failures
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Variable Reference
|
||||||
|
|
||||||
|
| Variable | Source | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `{{agent_name}}` | config.name | Agent identifier (lowercase, hyphenated) |
|
||||||
|
| `{{agent_display_name}}` | Derived from name | Human-readable title |
|
||||||
|
| `{{description}}` | config.description | Short description (1-3 lines) |
|
||||||
|
| `{{description_paragraph}}` | config.description | Full paragraph description |
|
||||||
|
| `{{color}}` | Auto-assigned | Terminal color for output |
|
||||||
|
| `{{parent_skill_name}}` | codexSkillConfig.name | Parent skill identifier |
|
||||||
|
| `{{capabilities}}` | Inferred from responsibility | Array of capability objects |
|
||||||
|
| `{{primary_action_name}}` | Derived from responsibility | Step 2 title |
|
||||||
|
| `{{primary_action_detail}}` | Generated or from source | Step 2 content |
|
||||||
|
| `{{secondary_action_name}}` | Derived from responsibility | Step 3 title |
|
||||||
|
| `{{secondary_action_detail}}` | Generated or from source | Step 3 content |
|
||||||
@@ -0,0 +1,414 @@
|
|||||||
|
# Command Pattern Template
|
||||||
|
|
||||||
|
Pre-built Codex command patterns for common agent interaction scenarios.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 0 | Read to understand available command patterns |
|
||||||
|
| Phase 2 | Select appropriate patterns for orchestrator |
|
||||||
|
| Phase 3 | Apply patterns to agent definitions |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern 1: Explore (Parallel Fan-out)
|
||||||
|
|
||||||
|
**Use When**: Multi-angle codebase exploration needed.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Explore Pattern ====================
|
||||||
|
|
||||||
|
// Step 1: Define exploration angles
|
||||||
|
const angles = ["architecture", "dependencies", "patterns", "testing"]
|
||||||
|
|
||||||
|
// Step 2: Create parallel exploration agents
|
||||||
|
const agents = angles.map(angle =>
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Execute ${angle} exploration for ${task_description}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: All source files relevant to ${angle}
|
||||||
|
- Exclude: node_modules, dist, build artifacts
|
||||||
|
|
||||||
|
Context:
|
||||||
|
- Task: ${task_description}
|
||||||
|
- Angle: ${angle}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- Structured findings following output template
|
||||||
|
- File:line references for key discoveries
|
||||||
|
- Open questions for unclear areas
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- At least 3 relevant files identified
|
||||||
|
- Findings backed by concrete evidence
|
||||||
|
`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
// Step 3: Batch wait
|
||||||
|
const results = wait({ ids: agents, timeout_ms: 600000 })
|
||||||
|
|
||||||
|
// Step 4: Aggregate
|
||||||
|
const findings = agents.map((id, i) => ({
|
||||||
|
angle: angles[i],
|
||||||
|
result: results.status[id].completed
|
||||||
|
}))
|
||||||
|
|
||||||
|
// Step 5: Cleanup
|
||||||
|
agents.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 2: Analyze (Multi-Perspective)
|
||||||
|
|
||||||
|
**Use When**: Code analysis from multiple dimensions needed.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Analyze Pattern ====================
|
||||||
|
|
||||||
|
const perspectives = [
|
||||||
|
{ name: "security", focus: "OWASP Top 10, injection, auth bypass" },
|
||||||
|
{ name: "performance", focus: "O(n²), memory leaks, blocking I/O" },
|
||||||
|
{ name: "maintainability", focus: "complexity, coupling, duplication" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const agents = perspectives.map(p =>
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Analyze ${targetModule} from ${p.name} perspective
|
||||||
|
Focus: ${p.focus}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: ${targetPaths}
|
||||||
|
- Exclude: Test files, generated code
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- Severity-classified findings (Critical/High/Medium/Low)
|
||||||
|
- File:line references for each finding
|
||||||
|
- Remediation recommendations
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- Every finding must have evidence (code reference)
|
||||||
|
- Remediation must be actionable
|
||||||
|
`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const results = wait({ ids: agents, timeout_ms: 600000 })
|
||||||
|
|
||||||
|
// Merge findings by severity
|
||||||
|
const merged = {
|
||||||
|
critical: [], high: [], medium: [], low: []
|
||||||
|
}
|
||||||
|
agents.forEach((id, i) => {
|
||||||
|
const parsed = parseFindings(results.status[id].completed)
|
||||||
|
Object.keys(merged).forEach(sev => merged[sev].push(...(parsed[sev] || [])))
|
||||||
|
})
|
||||||
|
|
||||||
|
agents.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 3: Implement (Sequential Delegation)
|
||||||
|
|
||||||
|
**Use When**: Code implementation following a plan.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Implement Pattern ====================
|
||||||
|
|
||||||
|
const implementAgent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/code-developer.md (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Implement ${featureDescription}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: ${targetPaths}
|
||||||
|
- Exclude: Unrelated modules
|
||||||
|
- Constraints: No breaking changes, follow existing patterns
|
||||||
|
|
||||||
|
Context:
|
||||||
|
- Plan: ${planContent}
|
||||||
|
- Dependencies: ${dependencies}
|
||||||
|
- Existing patterns: ${patterns}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- Working implementation following plan
|
||||||
|
- Updated/new test files
|
||||||
|
- Summary of changes with file:line references
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- All existing tests pass
|
||||||
|
- New code follows project conventions
|
||||||
|
- No TypeScript errors
|
||||||
|
- Backward compatible
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = wait({ ids: [implementAgent], timeout_ms: 900000 })
|
||||||
|
|
||||||
|
// Check for open questions (might need clarification)
|
||||||
|
if (result.status[implementAgent].completed.includes('CLARIFICATION_NEEDED')) {
|
||||||
|
// Handle clarification via send_input
|
||||||
|
const answers = getUserAnswers(result)
|
||||||
|
send_input({ id: implementAgent, message: `## ANSWERS\n${answers}\n\n## CONTINUE\nProceed with implementation.` })
|
||||||
|
const final = wait({ ids: [implementAgent], timeout_ms: 900000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: implementAgent })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 4: Validate (Test-Fix Cycle)
|
||||||
|
|
||||||
|
**Use When**: Running tests and fixing failures iteratively.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Validate Pattern ====================
|
||||||
|
|
||||||
|
const validateAgent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/test-fix-agent.md (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Validate ${component} — run tests, fix failures, iterate
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: ${testPaths}
|
||||||
|
- Exclude: Unrelated test suites
|
||||||
|
|
||||||
|
Context:
|
||||||
|
- Recent changes: ${changedFiles}
|
||||||
|
- Test framework: ${testFramework}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- All tests passing (or documented blocked tests)
|
||||||
|
- Fix summary with file:line references
|
||||||
|
- Coverage report
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
- Pass rate >= 95%
|
||||||
|
- No new test regressions
|
||||||
|
- Max 5 fix iterations
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const round1 = wait({ ids: [validateAgent], timeout_ms: 600000 })
|
||||||
|
|
||||||
|
// Check if more iterations needed
|
||||||
|
let iteration = 1
|
||||||
|
while (
|
||||||
|
iteration < 5 &&
|
||||||
|
round1.status[validateAgent].completed.includes('TESTS_FAILING')
|
||||||
|
) {
|
||||||
|
send_input({
|
||||||
|
id: validateAgent,
|
||||||
|
message: `## ITERATION ${iteration + 1}\nContinue fixing remaining failures. Focus on:\n${remainingFailures}`
|
||||||
|
})
|
||||||
|
const roundN = wait({ ids: [validateAgent], timeout_ms: 300000 })
|
||||||
|
iteration++
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: validateAgent })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 5: Review (Multi-Dimensional)
|
||||||
|
|
||||||
|
**Use When**: Code review from multiple dimensions.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Review Pattern ====================
|
||||||
|
|
||||||
|
const dimensions = [
|
||||||
|
{ name: "correctness", agent: "cli-explore-agent" },
|
||||||
|
{ name: "security", agent: "cli-explore-agent" },
|
||||||
|
{ name: "performance", agent: "cli-explore-agent" },
|
||||||
|
{ name: "style", agent: "cli-explore-agent" }
|
||||||
|
]
|
||||||
|
|
||||||
|
const agents = dimensions.map(d =>
|
||||||
|
spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/${d.agent}.md (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: Review ${targetCode} for ${d.name}
|
||||||
|
Scope: ${changedFiles}
|
||||||
|
Deliverables: Findings with severity, file:line, remediation
|
||||||
|
`
|
||||||
|
})
|
||||||
|
)
|
||||||
|
|
||||||
|
const results = wait({ ids: agents, timeout_ms: 600000 })
|
||||||
|
|
||||||
|
// Aggregate review findings
|
||||||
|
const review = {
|
||||||
|
approved: true,
|
||||||
|
findings: [],
|
||||||
|
blockers: []
|
||||||
|
}
|
||||||
|
|
||||||
|
agents.forEach((id, i) => {
|
||||||
|
const parsed = parseReview(results.status[id].completed)
|
||||||
|
review.findings.push(...parsed.findings)
|
||||||
|
if (parsed.blockers.length > 0) {
|
||||||
|
review.approved = false
|
||||||
|
review.blockers.push(...parsed.blockers)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
agents.forEach(id => close_agent({ id }))
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 6: Deep Interact (Merged Explore + Plan)
|
||||||
|
|
||||||
|
**Use When**: Exploration and planning are tightly coupled.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Deep Interact Pattern ====================
|
||||||
|
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
|
2. **Also read**: ~/.codex/agents/cli-lite-planning-agent.md (dual role)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase A: Exploration
|
||||||
|
Goal: Explore codebase for ${task_description}
|
||||||
|
Output: Structured findings + CLARIFICATION_NEEDED questions (if any)
|
||||||
|
|
||||||
|
### Phase B: Planning (activated after clarification)
|
||||||
|
Goal: Generate implementation plan based on exploration + answers
|
||||||
|
Output: Structured plan following plan schema
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
- Phase A: exploration findings (Summary/Findings/Open questions)
|
||||||
|
- Phase B: implementation plan (after receiving clarification answers)
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Phase A: Exploration
|
||||||
|
const exploration = wait({ ids: [agent], timeout_ms: 600000 })
|
||||||
|
|
||||||
|
if (exploration.status[agent].completed.includes('CLARIFICATION_NEEDED')) {
|
||||||
|
const answers = getUserAnswers(exploration)
|
||||||
|
|
||||||
|
// Phase B: Planning (same agent, preserved context)
|
||||||
|
send_input({
|
||||||
|
id: agent,
|
||||||
|
message: `
|
||||||
|
## CLARIFICATION ANSWERS
|
||||||
|
${answers}
|
||||||
|
|
||||||
|
## PROCEED TO PHASE B
|
||||||
|
Generate implementation plan based on your exploration findings and these answers.
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const plan = wait({ ids: [agent], timeout_ms: 900000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
## Pattern 7: Two-Phase (Clarify → Execute)
|
||||||
|
|
||||||
|
**Use When**: Task requires explicit clarification before execution.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ==================== Two-Phase Pattern ====================
|
||||||
|
|
||||||
|
// Phase 1: Clarification
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: `
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: ~/.codex/agents/${agentType}.md (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### PHASE: CLARIFICATION ONLY
|
||||||
|
|
||||||
|
Goal: Understand ${task_description} and identify unclear points
|
||||||
|
|
||||||
|
Output ONLY:
|
||||||
|
1. Your understanding of the task (2-3 sentences)
|
||||||
|
2. CLARIFICATION_NEEDED questions (if any)
|
||||||
|
3. Recommended approach (1-2 sentences)
|
||||||
|
|
||||||
|
DO NOT execute any changes yet.
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const clarification = wait({ ids: [agent], timeout_ms: 300000 })
|
||||||
|
|
||||||
|
// Collect user confirmation/answers
|
||||||
|
const userResponse = processUserInput(clarification)
|
||||||
|
|
||||||
|
// Phase 2: Execution
|
||||||
|
send_input({
|
||||||
|
id: agent,
|
||||||
|
message: `
|
||||||
|
## USER CONFIRMATION
|
||||||
|
${userResponse}
|
||||||
|
|
||||||
|
## PROCEED TO EXECUTION
|
||||||
|
Now execute the task with full implementation.
|
||||||
|
Output: Complete deliverable following structured output template.
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
const execution = wait({ ids: [agent], timeout_ms: 900000 })
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Pattern Selection Guide
|
||||||
|
|
||||||
|
| Scenario | Recommended Pattern | Reason |
|
||||||
|
|----------|-------------------|--------|
|
||||||
|
| Explore codebase from N angles | Pattern 1: Explore | Parallel fan-out, independent angles |
|
||||||
|
| Analyze code quality | Pattern 2: Analyze | Multi-perspective, severity classification |
|
||||||
|
| Implement from plan | Pattern 3: Implement | Sequential, plan-driven |
|
||||||
|
| Run tests + fix | Pattern 4: Validate | Iterative send_input loop |
|
||||||
|
| Code review | Pattern 5: Review | Multi-dimensional, aggregated verdict |
|
||||||
|
| Explore then plan | Pattern 6: Deep Interact | Context preservation, merged phases |
|
||||||
|
| Complex/unclear task | Pattern 7: Two-Phase | Clarify first, reduce rework |
|
||||||
|
| Simple one-shot task | Standard (no pattern) | spawn → wait → close |
|
||||||
@@ -0,0 +1,306 @@
|
|||||||
|
# Orchestrator Template
|
||||||
|
|
||||||
|
Template for the generated Codex orchestrator document.
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
| Phase | Usage |
|
||||||
|
|-------|-------|
|
||||||
|
| Phase 0 | Read to understand orchestrator output structure |
|
||||||
|
| Phase 2 | Apply with skill-specific content |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
---
|
||||||
|
name: {{skill_name}}
|
||||||
|
description: |
|
||||||
|
{{description}}
|
||||||
|
agents: {{agent_count}}
|
||||||
|
phases: {{phase_count}}
|
||||||
|
---
|
||||||
|
|
||||||
|
# {{skill_display_name}}
|
||||||
|
|
||||||
|
{{one_paragraph_description}}
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
\`\`\`
|
||||||
|
{{architecture_diagram}}
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Agent Registry
|
||||||
|
|
||||||
|
| Agent | Role File | Responsibility | New/Existing |
|
||||||
|
|-------|-----------|----------------|--------------|
|
||||||
|
{{#each agents}}
|
||||||
|
| `{{this.name}}` | `{{this.role_file}}` | {{this.responsibility}} | {{this.status}} |
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
## Phase Execution
|
||||||
|
|
||||||
|
{{#each phases}}
|
||||||
|
### Phase {{this.index}}: {{this.name}}
|
||||||
|
|
||||||
|
{{this.description}}
|
||||||
|
|
||||||
|
{{#if this.is_parallel}}
|
||||||
|
#### Parallel Fan-out
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// Create parallel agents
|
||||||
|
const agentIds = [
|
||||||
|
{{#each this.agents}}
|
||||||
|
spawn_agent({
|
||||||
|
message: \`
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{this.role_file}} (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: {{this.goal}}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: {{this.scope_include}}
|
||||||
|
- Exclude: {{this.scope_exclude}}
|
||||||
|
|
||||||
|
Context:
|
||||||
|
{{this.context}}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
{{this.deliverables}}
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
{{this.quality_bar}}
|
||||||
|
\`
|
||||||
|
}),
|
||||||
|
{{/each}}
|
||||||
|
]
|
||||||
|
|
||||||
|
// Batch wait
|
||||||
|
const results = wait({
|
||||||
|
ids: agentIds,
|
||||||
|
timeout_ms: {{this.timeout_ms}}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle timeout
|
||||||
|
if (results.timed_out) {
|
||||||
|
const completed = agentIds.filter(id => results.status[id]?.completed)
|
||||||
|
const pending = agentIds.filter(id => !results.status[id]?.completed)
|
||||||
|
// Use completed results, log pending
|
||||||
|
}
|
||||||
|
|
||||||
|
// Aggregate results
|
||||||
|
const phaseResults = agentIds.map(id => results.status[id].completed)
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
agentIds.forEach(id => close_agent({ id }))
|
||||||
|
\`\`\`
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.is_standard}}
|
||||||
|
#### Standard Execution
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
const agentId = spawn_agent({
|
||||||
|
message: \`
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{this.agent.role_file}} (MUST read first)
|
||||||
|
2. Read: .workflow/project-tech.json
|
||||||
|
3. Read: .workflow/project-guidelines.json
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: {{this.goal}}
|
||||||
|
|
||||||
|
Scope:
|
||||||
|
- Include: {{this.scope_include}}
|
||||||
|
- Exclude: {{this.scope_exclude}}
|
||||||
|
|
||||||
|
Context:
|
||||||
|
{{this.context}}
|
||||||
|
|
||||||
|
Deliverables:
|
||||||
|
{{this.deliverables}}
|
||||||
|
|
||||||
|
Quality bar:
|
||||||
|
{{this.quality_bar}}
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = wait({ ids: [agentId], timeout_ms: {{this.timeout_ms}} })
|
||||||
|
|
||||||
|
if (result.timed_out) {
|
||||||
|
// Timeout handling: continue wait or urge convergence
|
||||||
|
send_input({ id: agentId, message: "Please finalize and output current findings." })
|
||||||
|
const retry = wait({ ids: [agentId], timeout_ms: 60000 })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: agentId })
|
||||||
|
\`\`\`
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.is_deep_interaction}}
|
||||||
|
#### Deep Interaction (Multi-round)
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
const agent = spawn_agent({
|
||||||
|
message: \`
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{this.agent.role_file}} (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Phase A: {{this.initial_goal}}
|
||||||
|
Output: Findings + Open Questions (CLARIFICATION_NEEDED format)
|
||||||
|
|
||||||
|
### Phase B: {{this.followup_goal}} (after clarification)
|
||||||
|
Output: Complete deliverable
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Round 1: Initial exploration
|
||||||
|
const round1 = wait({ ids: [agent], timeout_ms: {{this.timeout_ms}} })
|
||||||
|
|
||||||
|
// Check for clarification needs
|
||||||
|
if (round1.status[agent].completed.includes('CLARIFICATION_NEEDED')) {
|
||||||
|
// Parse questions, collect user answers
|
||||||
|
const answers = collectUserAnswers(round1.status[agent].completed)
|
||||||
|
|
||||||
|
// Round 2: Continue with answers
|
||||||
|
send_input({
|
||||||
|
id: agent,
|
||||||
|
message: \`
|
||||||
|
## CLARIFICATION ANSWERS
|
||||||
|
\${answers}
|
||||||
|
|
||||||
|
## NEXT STEP
|
||||||
|
Proceed with Phase B.
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
|
||||||
|
const round2 = wait({ ids: [agent], timeout_ms: {{this.followup_timeout_ms}} })
|
||||||
|
}
|
||||||
|
|
||||||
|
close_agent({ id: agent })
|
||||||
|
\`\`\`
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{#if this.is_pipeline}}
|
||||||
|
#### Pipeline (Sequential Chain)
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
{{#each this.stages}}
|
||||||
|
// Stage {{this.index}}: {{this.name}}
|
||||||
|
const stage{{this.index}}Agent = spawn_agent({
|
||||||
|
message: \`
|
||||||
|
## TASK ASSIGNMENT
|
||||||
|
|
||||||
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
|
1. **Read role definition**: {{this.role_file}} (MUST read first)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
Goal: {{this.goal}}
|
||||||
|
{{#if this.previous_output}}
|
||||||
|
## PREVIOUS STAGE OUTPUT
|
||||||
|
\${stage{{this.previous_index}}Result}
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
Deliverables: {{this.deliverables}}
|
||||||
|
\`
|
||||||
|
})
|
||||||
|
|
||||||
|
const stage{{this.index}}Result = wait({ ids: [stage{{this.index}}Agent], timeout_ms: {{this.timeout_ms}} })
|
||||||
|
close_agent({ id: stage{{this.index}}Agent })
|
||||||
|
{{/each}}
|
||||||
|
\`\`\`
|
||||||
|
{{/if}}
|
||||||
|
|
||||||
|
{{/each}}
|
||||||
|
|
||||||
|
## Result Aggregation
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// Merge results from all phases
|
||||||
|
const finalResult = {
|
||||||
|
{{#each phases}}
|
||||||
|
phase{{this.index}}: phase{{this.index}}Results,
|
||||||
|
{{/each}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Output summary
|
||||||
|
console.log(\`
|
||||||
|
## Skill Execution Complete
|
||||||
|
|
||||||
|
{{#each phases}}
|
||||||
|
### Phase {{this.index}}: {{this.name}}
|
||||||
|
Status: \${phase{{this.index}}Results.status}
|
||||||
|
{{/each}}
|
||||||
|
\`)
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Lifecycle Management
|
||||||
|
|
||||||
|
### Timeout Handling
|
||||||
|
|
||||||
|
| Timeout Scenario | Action |
|
||||||
|
|-----------------|--------|
|
||||||
|
| Single agent timeout | send_input to urge convergence, retry wait |
|
||||||
|
| Parallel partial timeout | Use completed results if >= 70%, close pending |
|
||||||
|
| All agents timeout | Log error, abort with partial state |
|
||||||
|
|
||||||
|
### Cleanup Protocol
|
||||||
|
|
||||||
|
\`\`\`javascript
|
||||||
|
// Track all agents created during execution
|
||||||
|
const allAgentIds = []
|
||||||
|
|
||||||
|
// ... (agents added during phase execution) ...
|
||||||
|
|
||||||
|
// Final cleanup (end of orchestrator or on error)
|
||||||
|
allAgentIds.forEach(id => {
|
||||||
|
try { close_agent({ id }) } catch { /* already closed */ }
|
||||||
|
})
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Scenario | Resolution |
|
||||||
|
|----------|------------|
|
||||||
|
| Agent produces invalid output | Retry with clarified instructions via send_input |
|
||||||
|
| Agent timeout | Urge convergence, retry, or abort |
|
||||||
|
| Missing role file | Log error, skip agent or use fallback |
|
||||||
|
| Partial results | Proceed with available data, log gaps |
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Variable Reference
|
||||||
|
|
||||||
|
| Variable | Source | Description |
|
||||||
|
|----------|--------|-------------|
|
||||||
|
| `{{skill_name}}` | codexSkillConfig.name | Skill identifier |
|
||||||
|
| `{{skill_display_name}}` | Derived from name | Human-readable title |
|
||||||
|
| `{{description}}` | codexSkillConfig.description | Skill description |
|
||||||
|
| `{{agent_count}}` | codexSkillConfig.agents.length | Number of agents |
|
||||||
|
| `{{phase_count}}` | codexSkillConfig.phases.length | Number of phases |
|
||||||
|
| `{{architecture_diagram}}` | Generated from phase/agent topology | ASCII flow diagram |
|
||||||
|
| `{{agents}}` | codexSkillConfig.agents | Array of agent configs |
|
||||||
|
| `{{phases}}` | codexSkillConfig.phases | Array of phase configs |
|
||||||
|
| `{{phases[].is_parallel}}` | phase.interaction_model === "parallel_fanout" | Boolean |
|
||||||
|
| `{{phases[].is_standard}}` | phase.interaction_model === "standard" | Boolean |
|
||||||
|
| `{{phases[].is_deep_interaction}}` | phase.interaction_model === "deep_interaction" | Boolean |
|
||||||
|
| `{{phases[].is_pipeline}}` | phase.interaction_model === "pipeline" | Boolean |
|
||||||
|
| `{{phases[].timeout_ms}}` | Phase-specific timeout | Default: 300000 |
|
||||||
@@ -89,6 +89,36 @@ if (issueIds.length === 0) {
|
|||||||
|
|
||||||
// Auto-detect mode
|
// Auto-detect mode
|
||||||
const mode = detectMode(issueIds, explicitMode)
|
const mode = detectMode(issueIds, explicitMode)
|
||||||
|
|
||||||
|
// Execution method selection (for BUILD phase)
|
||||||
|
const execSelection = AskUserQuestion({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "选择代码执行方式:",
|
||||||
|
header: "Execution",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Agent", description: "code-developer agent(同步,适合简单任务)" },
|
||||||
|
{ label: "Codex", description: "Codex CLI(后台,适合复杂任务)" },
|
||||||
|
{ label: "Gemini", description: "Gemini CLI(后台,适合分析类任务)" },
|
||||||
|
{ label: "Auto", description: "根据 solution task_count 自动选择(默认)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "实现后是否进行代码审查?",
|
||||||
|
header: "Code Review",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Skip", description: "不审查" },
|
||||||
|
{ label: "Gemini Review", description: "Gemini CLI 审查" },
|
||||||
|
{ label: "Codex Review", description: "Git-aware review(--uncommitted)" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
const executionMethod = execSelection.Execution || 'Auto'
|
||||||
|
const codeReviewTool = execSelection['Code Review'] || 'Skip'
|
||||||
```
|
```
|
||||||
|
|
||||||
**Mode Auto-Detection**:
|
**Mode Auto-Detection**:
|
||||||
@@ -171,7 +201,7 @@ for (const issueId of issueIds) {
|
|||||||
|
|
||||||
TaskCreate({
|
TaskCreate({
|
||||||
subject: `BUILD-001: Implement solution for ${issueId}`,
|
subject: `BUILD-001: Implement solution for ${issueId}`,
|
||||||
description: `Implement solution for issue ${issueId}. Load via ccw issue detail <item-id>, execute tasks, report via ccw issue done.`,
|
description: `Implement solution for issue ${issueId}. Load via ccw issue detail <item-id>, execute tasks, report via ccw issue done.\nexecution_method: ${executionMethod}\ncode_review: ${codeReviewTool}`,
|
||||||
activeForm: `Implementing ${issueId}`,
|
activeForm: `Implementing ${issueId}`,
|
||||||
owner: "implementer",
|
owner: "implementer",
|
||||||
addBlockedBy: [marshalId]
|
addBlockedBy: [marshalId]
|
||||||
@@ -215,7 +245,7 @@ for (const issueId of issueIds) {
|
|||||||
|
|
||||||
TaskCreate({
|
TaskCreate({
|
||||||
subject: `BUILD-001: Implement solution for ${issueId}`,
|
subject: `BUILD-001: Implement solution for ${issueId}`,
|
||||||
description: `Implement approved solution for issue ${issueId}.`,
|
description: `Implement approved solution for issue ${issueId}.\nexecution_method: ${executionMethod}\ncode_review: ${codeReviewTool}`,
|
||||||
activeForm: `Implementing ${issueId}`,
|
activeForm: `Implementing ${issueId}`,
|
||||||
owner: "implementer",
|
owner: "implementer",
|
||||||
addBlockedBy: [marshalId]
|
addBlockedBy: [marshalId]
|
||||||
@@ -282,6 +312,9 @@ const marshalId = TaskCreate({
|
|||||||
})
|
})
|
||||||
|
|
||||||
// BUILD tasks created dynamically after MARSHAL completes (based on DAG)
|
// BUILD tasks created dynamically after MARSHAL completes (based on DAG)
|
||||||
|
// Each BUILD-* task description MUST include:
|
||||||
|
// execution_method: ${executionMethod}
|
||||||
|
// code_review: ${codeReviewTool}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 4: Coordination Loop
|
### Phase 4: Coordination Loop
|
||||||
|
|||||||
@@ -160,6 +160,43 @@ if (mode === 'spec-only' || mode === 'full-lifecycle') {
|
|||||||
|
|
||||||
Simple tasks can skip clarification.
|
Simple tasks can skip clarification.
|
||||||
|
|
||||||
|
#### Execution Method Selection (impl/full-lifecycle modes)
|
||||||
|
|
||||||
|
When mode includes implementation, select execution backend before team creation:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (mode === 'impl-only' || mode === 'full-lifecycle') {
|
||||||
|
const execSelection = AskUserQuestion({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "选择代码执行方式:",
|
||||||
|
header: "Execution",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Agent", description: "code-developer agent(同步,适合简单任务)" },
|
||||||
|
{ label: "Codex", description: "Codex CLI(后台,适合复杂任务)" },
|
||||||
|
{ label: "Gemini", description: "Gemini CLI(后台,适合分析类任务)" },
|
||||||
|
{ label: "Auto", description: "根据任务复杂度自动选择(默认)" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "实现后是否进行代码审查?",
|
||||||
|
header: "Code Review",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Skip", description: "不审查(Reviewer 角色独立负责)" },
|
||||||
|
{ label: "Gemini Review", description: "Gemini CLI 审查" },
|
||||||
|
{ label: "Codex Review", description: "Git-aware review(--uncommitted)" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
|
||||||
|
var executionMethod = execSelection.Execution || 'Auto'
|
||||||
|
var codeReviewTool = execSelection['Code Review'] || 'Skip'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
### Phase 2: Create Team + Spawn Workers
|
### Phase 2: Create Team + Spawn Workers
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
@@ -277,7 +314,7 @@ TaskCreate({ subject: "PLAN-001: 探索和规划实现", description: `${taskDes
|
|||||||
TaskUpdate({ taskId: planId, owner: "planner" })
|
TaskUpdate({ taskId: planId, owner: "planner" })
|
||||||
|
|
||||||
// IMPL-001 (blockedBy PLAN-001)
|
// IMPL-001 (blockedBy PLAN-001)
|
||||||
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json`, activeForm: "实现中" })
|
TaskCreate({ subject: "IMPL-001: 实现已批准的计划", description: `${taskDescription}\n\nSession: ${sessionFolder}\nPlan: ${sessionFolder}/plan/plan.json\nexecution_method: ${executionMethod || 'Auto'}\ncode_review: ${codeReviewTool || 'Skip'}`, activeForm: "实现中" })
|
||||||
TaskUpdate({ taskId: implId, owner: "executor", addBlockedBy: [planId] })
|
TaskUpdate({ taskId: implId, owner: "executor", addBlockedBy: [planId] })
|
||||||
|
|
||||||
// TEST-001 (blockedBy IMPL-001)
|
// TEST-001 (blockedBy IMPL-001)
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
# Role: executor
|
# Role: executor
|
||||||
|
|
||||||
Code implementation following approved plans. Reads plan files, implements changes, self-validates, and reports completion.
|
Code implementation following approved plans. Reads plan files, routes to selected execution backend (Agent/Codex/Gemini), self-validates, and reports completion.
|
||||||
|
|
||||||
## Role Identity
|
## Role Identity
|
||||||
|
|
||||||
- **Name**: `executor`
|
- **Name**: `executor`
|
||||||
- **Task Prefix**: `IMPL-*`
|
- **Task Prefix**: `IMPL-*`
|
||||||
- **Responsibility**: Load plan → Implement code → Self-validate → Report completion
|
- **Responsibility**: Load plan → Route to backend → Implement code → Self-validate → Report completion
|
||||||
- **Communication**: SendMessage to coordinator only
|
- **Communication**: SendMessage to coordinator only
|
||||||
|
|
||||||
## Message Types
|
## Message Types
|
||||||
@@ -40,6 +40,35 @@ When `mcp__ccw-tools__team_msg` MCP is unavailable:
|
|||||||
Bash(`ccw team log --team "${teamName}" --from "executor" --to "coordinator" --type "impl_complete" --summary "IMPL-001 complete: 5 files changed" --json`)
|
Bash(`ccw team log --team "${teamName}" --from "executor" --to "coordinator" --type "impl_complete" --summary "IMPL-001 complete: 5 files changed" --json`)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Execution Backends
|
||||||
|
|
||||||
|
| Backend | Tool | Invocation | Mode |
|
||||||
|
|---------|------|------------|------|
|
||||||
|
| `agent` | code-developer subagent | `Task({ subagent_type: "code-developer" })` | 同步 |
|
||||||
|
| `codex` | Codex CLI | `ccw cli --tool codex --mode write` | 后台 |
|
||||||
|
| `gemini` | Gemini CLI | `ccw cli --tool gemini --mode write` | 后台 |
|
||||||
|
|
||||||
|
## Execution Method Resolution
|
||||||
|
|
||||||
|
从 IMPL-* 任务 description 中解析执行方式(coordinator 在创建任务时已写入):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function resolveExecutor(taskDesc, taskCount) {
|
||||||
|
const methodMatch = taskDesc.match(/execution_method:\s*(Agent|Codex|Gemini|Auto)/i)
|
||||||
|
const method = methodMatch ? methodMatch[1] : 'Auto'
|
||||||
|
|
||||||
|
if (method.toLowerCase() === 'auto') {
|
||||||
|
return taskCount <= 3 ? 'agent' : 'codex'
|
||||||
|
}
|
||||||
|
return method.toLowerCase() // 'agent' | 'codex' | 'gemini'
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveCodeReview(taskDesc) {
|
||||||
|
const reviewMatch = taskDesc.match(/code_review:\s*(\S+)/i)
|
||||||
|
return reviewMatch ? reviewMatch[1] : 'Skip'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Execution (5-Phase)
|
## Execution (5-Phase)
|
||||||
|
|
||||||
### Phase 1: Task & Plan Loading
|
### Phase 1: Task & Plan Loading
|
||||||
@@ -69,6 +98,10 @@ if (!planPath) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const plan = JSON.parse(Read(planPath))
|
const plan = JSON.parse(Read(planPath))
|
||||||
|
|
||||||
|
// Resolve execution method
|
||||||
|
const executor = resolveExecutor(task.description, plan.task_count || plan.task_ids?.length || 0)
|
||||||
|
const codeReview = resolveCodeReview(task.description)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 2: Task Grouping
|
### Phase 2: Task Grouping
|
||||||
@@ -103,7 +136,7 @@ const planTasks = plan.task_ids.map(id => JSON.parse(Read(`${planPath.replace('p
|
|||||||
const batches = createBatches(planTasks)
|
const batches = createBatches(planTasks)
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 3: Code Implementation
|
### Phase 3: Code Implementation (Multi-Backend Routing)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Unified Task Prompt Builder
|
// Unified Task Prompt Builder
|
||||||
@@ -130,33 +163,55 @@ ${(planTask.convergence?.criteria || []).map(c => `- [ ] ${c}`).join('\n')}
|
|||||||
`
|
`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function buildBatchPrompt(batch) {
|
||||||
|
const taskPrompts = batch.tasks.map(buildExecutionPrompt).join('\n\n---\n')
|
||||||
|
return `## Goal\n${plan.summary}\n\n## Tasks\n${taskPrompts}\n\n## Context\n### Project Guidelines\n@.workflow/project-guidelines.json\n\nComplete each task according to its "Done when" checklist.`
|
||||||
|
}
|
||||||
|
|
||||||
const changedFiles = []
|
const changedFiles = []
|
||||||
|
const sessionId = task.description.match(/TLS-[\w-]+/)?.[0] || 'lifecycle'
|
||||||
|
|
||||||
for (const batch of batches) {
|
for (const batch of batches) {
|
||||||
if (batch.tasks.length === 1 && isSimpleTask(batch.tasks[0])) {
|
const batchPrompt = buildBatchPrompt(batch)
|
||||||
// Simple task: direct file editing
|
const batchId = `${sessionId}-B${batches.indexOf(batch) + 1}`
|
||||||
|
|
||||||
|
if (batch.tasks.length === 1 && isSimpleTask(batch.tasks[0]) && executor === 'agent') {
|
||||||
|
// Simple task + Agent mode: direct file editing
|
||||||
const t = batch.tasks[0]
|
const t = batch.tasks[0]
|
||||||
for (const f of (t.files || [])) {
|
for (const f of (t.files || [])) {
|
||||||
const content = Read(f.path)
|
const content = Read(f.path)
|
||||||
Edit({ file_path: f.path, old_string: "...", new_string: "..." })
|
Edit({ file_path: f.path, old_string: "...", new_string: "..." })
|
||||||
changedFiles.push(f.path)
|
changedFiles.push(f.path)
|
||||||
}
|
}
|
||||||
} else {
|
} else if (executor === 'agent') {
|
||||||
// Complex task(s): delegate to code-developer sub-agent
|
// Agent execution (synchronous)
|
||||||
const prompt = batch.tasks.map(buildExecutionPrompt).join('\n\n---\n')
|
|
||||||
|
|
||||||
Task({
|
Task({
|
||||||
subagent_type: "code-developer",
|
subagent_type: "code-developer",
|
||||||
run_in_background: false,
|
run_in_background: false,
|
||||||
description: batch.tasks.map(t => t.title).join(' | '),
|
description: batch.tasks.map(t => t.title).join(' | '),
|
||||||
prompt: `## Goal\n${plan.summary}\n\n## Tasks\n${prompt}\n\n## Context\n### Project Guidelines\n@.workflow/project-guidelines.json\n\nComplete each task according to its "Done when" checklist.`
|
prompt: batchPrompt
|
||||||
})
|
})
|
||||||
|
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||||
|
} else if (executor === 'codex') {
|
||||||
|
// Codex CLI execution (background)
|
||||||
|
Bash(
|
||||||
|
`ccw cli -p "${batchPrompt}" --tool codex --mode write --id ${batchId}`,
|
||||||
|
{ run_in_background: true }
|
||||||
|
)
|
||||||
|
// STOP — CLI 后台执行,等待 task hook callback
|
||||||
|
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||||
|
} else if (executor === 'gemini') {
|
||||||
|
// Gemini CLI execution (background)
|
||||||
|
Bash(
|
||||||
|
`ccw cli -p "${batchPrompt}" --tool gemini --mode write --id ${batchId}`,
|
||||||
|
{ run_in_background: true }
|
||||||
|
)
|
||||||
|
// STOP — CLI 后台执行,等待 task hook callback
|
||||||
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
batch.tasks.forEach(t => (t.files || []).forEach(f => changedFiles.push(f.path)))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Progress update
|
// Progress update
|
||||||
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_progress", summary: `Batch完成: ${changedFiles.length}个文件已变更` })
|
mcp__ccw-tools__team_msg({ operation: "log", team: teamName, from: "executor", to: "coordinator", type: "impl_progress", summary: `Batch完成 (${executor}): ${changedFiles.length}个文件已变更` })
|
||||||
}
|
}
|
||||||
|
|
||||||
function isSimpleTask(task) {
|
function isSimpleTask(task) {
|
||||||
@@ -183,6 +238,22 @@ const testFiles = changedFiles
|
|||||||
.map(f => f.replace(/\/src\//, '/tests/').replace(/\.(ts|js)$/, '.test.$1'))
|
.map(f => f.replace(/\/src\//, '/tests/').replace(/\.(ts|js)$/, '.test.$1'))
|
||||||
.filter(f => Bash(`test -f ${f} && echo exists || true`).includes('exists'))
|
.filter(f => Bash(`test -f ${f} && echo exists || true`).includes('exists'))
|
||||||
if (testFiles.length > 0) Bash(`npx jest ${testFiles.join(' ')} --passWithNoTests 2>&1 || true`)
|
if (testFiles.length > 0) Bash(`npx jest ${testFiles.join(' ')} --passWithNoTests 2>&1 || true`)
|
||||||
|
|
||||||
|
// Optional: Code review (if configured by coordinator)
|
||||||
|
if (codeReview !== 'Skip') {
|
||||||
|
if (codeReview === 'Gemini Review' || codeReview === 'Gemini') {
|
||||||
|
Bash(`ccw cli -p "PURPOSE: Code review for IMPL changes against plan convergence criteria
|
||||||
|
TASK: • Verify convergence criteria • Check test coverage • Analyze code quality
|
||||||
|
MODE: analysis
|
||||||
|
CONTEXT: @**/* | Memory: Review lifecycle IMPL execution
|
||||||
|
EXPECTED: Quality assessment with issue identification
|
||||||
|
CONSTRAINTS: analysis=READ-ONLY" --tool gemini --mode analysis --id ${sessionId}-review`,
|
||||||
|
{ run_in_background: true })
|
||||||
|
} else if (codeReview === 'Codex Review' || codeReview === 'Codex') {
|
||||||
|
Bash(`ccw cli --tool codex --mode review --uncommitted`,
|
||||||
|
{ run_in_background: true })
|
||||||
|
}
|
||||||
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Phase 5: Report to Coordinator
|
### Phase 5: Report to Coordinator
|
||||||
@@ -192,7 +263,7 @@ mcp__ccw-tools__team_msg({
|
|||||||
operation: "log", team: teamName,
|
operation: "log", team: teamName,
|
||||||
from: "executor", to: "coordinator",
|
from: "executor", to: "coordinator",
|
||||||
type: "impl_complete",
|
type: "impl_complete",
|
||||||
summary: `IMPL完成: ${[...new Set(changedFiles)].length}个文件变更, syntax=${hasSyntaxErrors ? 'errors' : 'clean'}`
|
summary: `IMPL完成 (${executor}): ${[...new Set(changedFiles)].length}个文件变更, syntax=${hasSyntaxErrors ? 'errors' : 'clean'}`
|
||||||
})
|
})
|
||||||
|
|
||||||
SendMessage({
|
SendMessage({
|
||||||
@@ -201,6 +272,8 @@ SendMessage({
|
|||||||
content: `## Implementation Complete
|
content: `## Implementation Complete
|
||||||
|
|
||||||
**Task**: ${task.subject}
|
**Task**: ${task.subject}
|
||||||
|
**Executor**: ${executor}
|
||||||
|
**Code Review**: ${codeReview}
|
||||||
|
|
||||||
### Changed Files
|
### Changed Files
|
||||||
${[...new Set(changedFiles)].map(f => '- ' + f).join('\n')}
|
${[...new Set(changedFiles)].map(f => '- ' + f).join('\n')}
|
||||||
@@ -211,9 +284,10 @@ ${acceptanceStatus.map(t => '**' + t.title + '**: ' + (t.criteria.every(c => c.m
|
|||||||
### Validation
|
### Validation
|
||||||
- Syntax: ${hasSyntaxErrors ? 'Has errors (attempted fix)' : 'Clean'}
|
- Syntax: ${hasSyntaxErrors ? 'Has errors (attempted fix)' : 'Clean'}
|
||||||
- Tests: ${testFiles.length > 0 ? 'Ran' : 'N/A'}
|
- Tests: ${testFiles.length > 0 ? 'Ran' : 'N/A'}
|
||||||
|
${executor !== 'agent' ? `- CLI Resume ID: ${sessionId}-B*` : ''}
|
||||||
|
|
||||||
Implementation is ready for testing and review.`,
|
Implementation is ready for testing and review.`,
|
||||||
summary: `IMPL complete: ${[...new Set(changedFiles)].length} files changed`
|
summary: `IMPL complete (${executor}): ${[...new Set(changedFiles)].length} files changed`
|
||||||
})
|
})
|
||||||
|
|
||||||
TaskUpdate({ taskId: task.id, status: 'completed' })
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
@@ -227,8 +301,11 @@ TaskUpdate({ taskId: task.id, status: 'completed' })
|
|||||||
|----------|------------|
|
|----------|------------|
|
||||||
| No IMPL-* tasks available | Idle, wait for coordinator assignment |
|
| No IMPL-* tasks available | Idle, wait for coordinator assignment |
|
||||||
| Plan file not found | Notify coordinator, request plan location |
|
| Plan file not found | Notify coordinator, request plan location |
|
||||||
|
| Unknown execution_method | Fallback to `agent` with warning |
|
||||||
| Syntax errors after implementation | Attempt auto-fix, report remaining errors |
|
| Syntax errors after implementation | Attempt auto-fix, report remaining errors |
|
||||||
| Sub-agent failure | Retry once, then attempt direct implementation |
|
| Agent (code-developer) failure | Retry once, then attempt direct implementation |
|
||||||
|
| CLI (Codex/Gemini) failure | Provide resume command with fixed ID, report error |
|
||||||
|
| CLI timeout | Use fixed ID `${sessionId}-B*` for resume |
|
||||||
| File conflict / merge issue | Notify coordinator, request guidance |
|
| File conflict / merge issue | Notify coordinator, request guidance |
|
||||||
| Test failures in self-validation | Report in completion message, let tester handle |
|
| Test failures in self-validation | Report in completion message, let tester handle |
|
||||||
| Circular dependencies in plan | Execute in plan order, ignore dependency chain |
|
| Circular dependencies in plan | Execute in plan order, ignore dependency chain |
|
||||||
|
|||||||
@@ -180,6 +180,83 @@ Final: planner 发送 all_planned → executor 完成剩余 EXEC-* → 结束
|
|||||||
- executor 持续轮询并消费可用的 EXEC-* 任务
|
- executor 持续轮询并消费可用的 EXEC-* 任务
|
||||||
- 当 planner 发送 `all_planned` 消息后,executor 完成所有剩余任务即可结束
|
- 当 planner 发送 `all_planned` 消息后,executor 完成所有剩余任务即可结束
|
||||||
|
|
||||||
|
## Execution Method Selection
|
||||||
|
|
||||||
|
在编排模式或直接调用 executor 前,**必须先确定执行方式**。支持 3 种执行后端:
|
||||||
|
|
||||||
|
| Executor | 后端 | 适用场景 |
|
||||||
|
|----------|------|----------|
|
||||||
|
| `agent` | code-developer subagent | 简单任务、同步执行 |
|
||||||
|
| `codex` | `ccw cli --tool codex --mode write` | 复杂任务、后台执行 |
|
||||||
|
| `gemini` | `ccw cli --tool gemini --mode write` | 分析类任务、后台执行 |
|
||||||
|
|
||||||
|
### 选择逻辑
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const autoYes = args.includes('--auto') || args.includes('-y')
|
||||||
|
const explicitExec = args.match(/--exec[=\s]+(agent|codex|gemini|auto)/i)?.[1]
|
||||||
|
|
||||||
|
let executionConfig
|
||||||
|
|
||||||
|
if (explicitExec) {
|
||||||
|
// 显式指定
|
||||||
|
executionConfig = {
|
||||||
|
executionMethod: explicitExec.charAt(0).toUpperCase() + explicitExec.slice(1),
|
||||||
|
codeReviewTool: "Skip"
|
||||||
|
}
|
||||||
|
} else if (autoYes) {
|
||||||
|
// Auto 模式:默认 Agent + Skip review
|
||||||
|
executionConfig = { executionMethod: "Auto", codeReviewTool: "Skip" }
|
||||||
|
} else {
|
||||||
|
// 交互选择
|
||||||
|
executionConfig = AskUserQuestion({
|
||||||
|
questions: [
|
||||||
|
{
|
||||||
|
question: "选择执行方式:",
|
||||||
|
header: "Execution",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Agent", description: "code-developer agent(同步,适合简单任务)" },
|
||||||
|
{ label: "Codex", description: "Codex CLI(后台,适合复杂任务)" },
|
||||||
|
{ label: "Gemini", description: "Gemini CLI(后台,适合分析类任务)" },
|
||||||
|
{ label: "Auto", description: "根据任务复杂度自动选择" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "执行后是否进行代码审查?",
|
||||||
|
header: "Code Review",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Skip", description: "不审查" },
|
||||||
|
{ label: "Gemini Review", description: "Gemini CLI 审查" },
|
||||||
|
{ label: "Codex Review", description: "Git-aware review(--uncommitted)" },
|
||||||
|
{ label: "Agent Review", description: "当前 agent 审查" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// Auto 解析:根据 solution task_count 决定
|
||||||
|
function resolveExecutor(taskCount) {
|
||||||
|
if (executionConfig.executionMethod === 'Auto') {
|
||||||
|
return taskCount <= 3 ? 'agent' : 'codex'
|
||||||
|
}
|
||||||
|
return executionConfig.executionMethod.toLowerCase()
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 通过 args 指定
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 显式指定
|
||||||
|
Skill(skill="team-planex", args="--exec=codex ISS-xxx")
|
||||||
|
Skill(skill="team-planex", args="--exec=agent --text '简单功能'")
|
||||||
|
|
||||||
|
# Auto 模式(跳过交互)
|
||||||
|
Skill(skill="team-planex", args="-y --text '添加日志'")
|
||||||
|
```
|
||||||
|
|
||||||
## Orchestration Mode
|
## Orchestration Mode
|
||||||
|
|
||||||
当不带 `--role` 调用时,SKILL.md 进入轻量编排模式:
|
当不带 `--role` 调用时,SKILL.md 进入轻量编排模式:
|
||||||
@@ -195,7 +272,10 @@ const planMatch = args.match(/--plan\s+(\S+)/)
|
|||||||
|
|
||||||
let plannerInput = args // 透传给 planner
|
let plannerInput = args // 透传给 planner
|
||||||
|
|
||||||
// 3. 创建初始 PLAN-* 任务
|
// 3. 执行方式选择(见上方 Execution Method Selection)
|
||||||
|
// executionConfig 已确定: { executionMethod, codeReviewTool }
|
||||||
|
|
||||||
|
// 4. 创建初始 PLAN-* 任务
|
||||||
TaskCreate({
|
TaskCreate({
|
||||||
subject: "PLAN-001: 初始规划",
|
subject: "PLAN-001: 初始规划",
|
||||||
description: `规划任务。输入: ${plannerInput}`,
|
description: `规划任务。输入: ${plannerInput}`,
|
||||||
@@ -203,7 +283,7 @@ TaskCreate({
|
|||||||
owner: "planner"
|
owner: "planner"
|
||||||
})
|
})
|
||||||
|
|
||||||
// 4. Spawn planner agent
|
// 5. Spawn planner agent
|
||||||
Task({
|
Task({
|
||||||
subagent_type: "general-purpose",
|
subagent_type: "general-purpose",
|
||||||
team_name: teamName,
|
team_name: teamName,
|
||||||
@@ -212,10 +292,17 @@ Task({
|
|||||||
当你收到 PLAN-* 任务时,调用 Skill(skill="team-planex", args="--role=planner") 执行。
|
当你收到 PLAN-* 任务时,调用 Skill(skill="team-planex", args="--role=planner") 执行。
|
||||||
当前输入: ${plannerInput}
|
当前输入: ${plannerInput}
|
||||||
|
|
||||||
|
## 执行配置
|
||||||
|
executor 的执行方式已确定: ${executionConfig.executionMethod}
|
||||||
|
创建 EXEC-* 任务时,在 description 中包含:
|
||||||
|
execution_method: ${executionConfig.executionMethod}
|
||||||
|
code_review: ${executionConfig.codeReviewTool}
|
||||||
|
|
||||||
## 角色准则(强制)
|
## 角色准则(强制)
|
||||||
- 你只能处理 PLAN-* 前缀的任务
|
- 你只能处理 PLAN-* 前缀的任务
|
||||||
- 所有输出必须带 [planner] 标识前缀
|
- 所有输出必须带 [planner] 标识前缀
|
||||||
- 完成每个 wave 后立即创建 EXEC-* 任务供 executor 消费
|
- 完成每个 wave 后立即创建 EXEC-* 任务供 executor 消费
|
||||||
|
- EXEC-* 任务 description 中必须包含 execution_method 字段
|
||||||
|
|
||||||
## 消息总线(必须)
|
## 消息总线(必须)
|
||||||
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
每次 SendMessage 前,先调用 mcp__ccw-tools__team_msg 记录。
|
||||||
@@ -227,7 +314,7 @@ Task({
|
|||||||
4. TaskUpdate completed → 检查下一个任务`
|
4. TaskUpdate completed → 检查下一个任务`
|
||||||
})
|
})
|
||||||
|
|
||||||
// 5. Spawn executor agent
|
// 6. Spawn executor agent
|
||||||
Task({
|
Task({
|
||||||
subagent_type: "general-purpose",
|
subagent_type: "general-purpose",
|
||||||
team_name: teamName,
|
team_name: teamName,
|
||||||
@@ -235,9 +322,15 @@ Task({
|
|||||||
prompt: `你是 team "${teamName}" 的 EXECUTOR。
|
prompt: `你是 team "${teamName}" 的 EXECUTOR。
|
||||||
当你收到 EXEC-* 任务时,调用 Skill(skill="team-planex", args="--role=executor") 执行。
|
当你收到 EXEC-* 任务时,调用 Skill(skill="team-planex", args="--role=executor") 执行。
|
||||||
|
|
||||||
|
## 执行配置
|
||||||
|
默认执行方式: ${executionConfig.executionMethod}
|
||||||
|
代码审查: ${executionConfig.codeReviewTool}
|
||||||
|
(每个 EXEC-* 任务 description 中可能包含 execution_method 覆盖)
|
||||||
|
|
||||||
## 角色准则(强制)
|
## 角色准则(强制)
|
||||||
- 你只能处理 EXEC-* 前缀的任务
|
- 你只能处理 EXEC-* 前缀的任务
|
||||||
- 所有输出必须带 [executor] 标识前缀
|
- 所有输出必须带 [executor] 标识前缀
|
||||||
|
- 根据 execution_method 选择执行后端(Agent/Codex/Gemini)
|
||||||
- 每个 solution 完成后通知 planner
|
- 每个 solution 完成后通知 planner
|
||||||
|
|
||||||
## 消息总线(必须)
|
## 消息总线(必须)
|
||||||
|
|||||||
402
.claude/skills/team-planex/roles/executor.md
Normal file
402
.claude/skills/team-planex/roles/executor.md
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
# Role: executor
|
||||||
|
|
||||||
|
加载 solution → 根据 execution_method 路由到对应后端(Agent/Codex/Gemini)→ 测试验证 → 提交。支持多种 CLI 执行后端,执行方式在 skill 启动前已确定(见 SKILL.md Execution Method Selection)。
|
||||||
|
|
||||||
|
## Role Identity
|
||||||
|
|
||||||
|
- **Name**: `executor`
|
||||||
|
- **Task Prefix**: `EXEC-*`
|
||||||
|
- **Responsibility**: Code implementation (solution → route to backend → test → commit)
|
||||||
|
- **Communication**: SendMessage to planner only
|
||||||
|
- **Output Tag**: `[executor]`
|
||||||
|
|
||||||
|
## Role Boundaries
|
||||||
|
|
||||||
|
### MUST
|
||||||
|
|
||||||
|
- 仅处理 `EXEC-*` 前缀的任务
|
||||||
|
- 所有输出必须带 `[executor]` 标识
|
||||||
|
- 按照 EXEC-* 任务中的 `execution_method` 字段选择执行后端
|
||||||
|
- 每个 issue 完成后通知 planner
|
||||||
|
- 持续轮询新的 EXEC-* 任务(planner 可能随时创建新 wave)
|
||||||
|
|
||||||
|
### MUST NOT
|
||||||
|
|
||||||
|
- ❌ 创建 issue(planner 职责)
|
||||||
|
- ❌ 修改 solution 或 queue(planner 职责)
|
||||||
|
- ❌ 调用 issue-plan-agent 或 issue-queue-agent
|
||||||
|
- ❌ 直接与用户交互(AskUserQuestion)
|
||||||
|
- ❌ 为 planner 创建 PLAN-* 任务
|
||||||
|
|
||||||
|
## Message Types
|
||||||
|
|
||||||
|
| Type | Direction | Trigger | Description |
|
||||||
|
|------|-----------|---------|-------------|
|
||||||
|
| `impl_complete` | executor → planner | Implementation and tests pass | 单个 issue 实现完成 |
|
||||||
|
| `impl_failed` | executor → planner | Implementation failed after retries | 实现失败 |
|
||||||
|
| `wave_done` | executor → planner | All EXEC tasks in a wave completed | 整个 wave 完成 |
|
||||||
|
| `error` | executor → planner | Blocking error | 执行错误 |
|
||||||
|
|
||||||
|
## Toolbox
|
||||||
|
|
||||||
|
### Execution Backends
|
||||||
|
|
||||||
|
| Backend | Tool | Invocation | Mode |
|
||||||
|
|---------|------|------------|------|
|
||||||
|
| `agent` | code-developer subagent | `Task({ subagent_type: "code-developer" })` | 同步 |
|
||||||
|
| `codex` | Codex CLI | `ccw cli --tool codex --mode write` | 后台 |
|
||||||
|
| `gemini` | Gemini CLI | `ccw cli --tool gemini --mode write` | 后台 |
|
||||||
|
|
||||||
|
### Direct Capabilities
|
||||||
|
|
||||||
|
| Tool | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `Read` | 读取 solution plan 和队列文件 |
|
||||||
|
| `Write` | 写入实现产物 |
|
||||||
|
| `Edit` | 编辑源代码 |
|
||||||
|
| `Bash` | 运行测试、git 操作、CLI 调用 |
|
||||||
|
|
||||||
|
### CLI Capabilities
|
||||||
|
|
||||||
|
| CLI Command | Purpose |
|
||||||
|
|-------------|---------|
|
||||||
|
| `ccw issue status <id> --json` | 查看 issue 状态 |
|
||||||
|
| `ccw issue solutions <id> --json` | 加载 bound solution |
|
||||||
|
| `ccw issue update <id> --status in-progress` | 更新 issue 状态为进行中 |
|
||||||
|
| `ccw issue update <id> --status resolved` | 标记 issue 已解决 |
|
||||||
|
|
||||||
|
## Execution Method Resolution
|
||||||
|
|
||||||
|
从 EXEC-* 任务的 description 中解析执行方式:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 从任务描述中解析 execution_method
|
||||||
|
function resolveExecutor(taskDesc, solutionTaskCount) {
|
||||||
|
const methodMatch = taskDesc.match(/execution_method:\s*(Agent|Codex|Gemini|Auto)/i)
|
||||||
|
const method = methodMatch ? methodMatch[1] : 'Auto'
|
||||||
|
|
||||||
|
if (method.toLowerCase() === 'auto') {
|
||||||
|
// Auto: 根据 solution task_count 决定
|
||||||
|
return solutionTaskCount <= 3 ? 'agent' : 'codex'
|
||||||
|
}
|
||||||
|
return method.toLowerCase() // 'agent' | 'codex' | 'gemini'
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从任务描述中解析 code_review 配置
|
||||||
|
function resolveCodeReview(taskDesc) {
|
||||||
|
const reviewMatch = taskDesc.match(/code_review:\s*(\S+)/i)
|
||||||
|
return reviewMatch ? reviewMatch[1] : 'Skip'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution Prompt Builder
|
||||||
|
|
||||||
|
统一的 prompt 构建,所有后端共用:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function buildExecutionPrompt(issueId, solution) {
|
||||||
|
return `
|
||||||
|
## Issue
|
||||||
|
ID: ${issueId}
|
||||||
|
Title: ${solution.bound.title || 'N/A'}
|
||||||
|
|
||||||
|
## Solution Plan
|
||||||
|
${JSON.stringify(solution.bound, null, 2)}
|
||||||
|
|
||||||
|
## Implementation Requirements
|
||||||
|
|
||||||
|
1. Follow the solution plan tasks in order
|
||||||
|
2. Write clean, minimal code following existing patterns
|
||||||
|
3. Run tests after each significant change
|
||||||
|
4. Ensure all existing tests still pass
|
||||||
|
5. Do NOT over-engineer — implement exactly what the solution specifies
|
||||||
|
|
||||||
|
## Quality Checklist
|
||||||
|
- [ ] All solution tasks implemented
|
||||||
|
- [ ] No TypeScript/linting errors
|
||||||
|
- [ ] Existing tests pass
|
||||||
|
- [ ] New tests added where appropriate
|
||||||
|
- [ ] No security vulnerabilities introduced
|
||||||
|
|
||||||
|
## Project Guidelines
|
||||||
|
@.workflow/project-guidelines.json
|
||||||
|
`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Execution (5-Phase)
|
||||||
|
|
||||||
|
### Phase 1: Task Discovery
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tasks = TaskList()
|
||||||
|
const myTasks = tasks.filter(t =>
|
||||||
|
t.subject.startsWith('EXEC-') &&
|
||||||
|
t.owner === 'executor' &&
|
||||||
|
t.status === 'pending' &&
|
||||||
|
t.blockedBy.length === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (myTasks.length === 0) return // idle — wait for planner to create EXEC tasks
|
||||||
|
|
||||||
|
const task = TaskGet({ taskId: myTasks[0].id })
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Load Solution & Resolve Executor
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Extract issue ID from task description
|
||||||
|
const issueIdMatch = task.description.match(/ISS-\d{8}-\d{6}/)
|
||||||
|
const issueId = issueIdMatch ? issueIdMatch[0] : null
|
||||||
|
|
||||||
|
if (!issueId) {
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log", team: "planex", from: "executor", to: "planner",
|
||||||
|
type: "error",
|
||||||
|
summary: "[executor] No issue ID found in task"
|
||||||
|
})
|
||||||
|
SendMessage({
|
||||||
|
type: "message", recipient: "planner",
|
||||||
|
content: "## [executor] Error\nNo issue ID in task description",
|
||||||
|
summary: "[executor] error: no issue ID"
|
||||||
|
})
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load solution plan
|
||||||
|
const solJson = Bash(`ccw issue solutions ${issueId} --json`)
|
||||||
|
const solution = JSON.parse(solJson)
|
||||||
|
|
||||||
|
if (!solution.bound) {
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log", team: "planex", from: "executor", to: "planner",
|
||||||
|
type: "error",
|
||||||
|
summary: `[executor] No bound solution for ${issueId}`
|
||||||
|
})
|
||||||
|
SendMessage({
|
||||||
|
type: "message", recipient: "planner",
|
||||||
|
content: `## [executor] Error\nNo bound solution for ${issueId}`,
|
||||||
|
summary: `[executor] error: no solution for ${issueId}`
|
||||||
|
})
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve execution method from task description
|
||||||
|
const taskCount = solution.bound.task_count || solution.bound.tasks?.length || 0
|
||||||
|
const executor = resolveExecutor(task.description, taskCount)
|
||||||
|
const codeReview = resolveCodeReview(task.description)
|
||||||
|
|
||||||
|
// Update issue status
|
||||||
|
Bash(`ccw issue update ${issueId} --status in-progress`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Implementation (Multi-Backend Routing)
|
||||||
|
|
||||||
|
根据 `executor` 变量路由到对应后端:
|
||||||
|
|
||||||
|
#### Option A: Agent Execution (`executor === 'agent'`)
|
||||||
|
|
||||||
|
同步调用 code-developer subagent,适合简单任务(task_count ≤ 3)。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (executor === 'agent') {
|
||||||
|
const implResult = Task({
|
||||||
|
subagent_type: "code-developer",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Implement solution for ${issueId}`,
|
||||||
|
prompt: buildExecutionPrompt(issueId, solution)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option B: Codex CLI Execution (`executor === 'codex'`)
|
||||||
|
|
||||||
|
后台调用 Codex CLI,适合复杂任务。使用固定 ID 支持 resume。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (executor === 'codex') {
|
||||||
|
const fixedId = `planex-${issueId}`
|
||||||
|
|
||||||
|
Bash(
|
||||||
|
`ccw cli -p "${buildExecutionPrompt(issueId, solution)}" --tool codex --mode write --id ${fixedId}`,
|
||||||
|
{ run_in_background: true }
|
||||||
|
)
|
||||||
|
// STOP — CLI 后台执行,等待 task hook callback 通知完成
|
||||||
|
|
||||||
|
// 失败时 resume:
|
||||||
|
// ccw cli -p "Continue implementation" --resume ${fixedId} --tool codex --mode write --id ${fixedId}-retry
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Option C: Gemini CLI Execution (`executor === 'gemini'`)
|
||||||
|
|
||||||
|
后台调用 Gemini CLI,适合需要分析的复合任务。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (executor === 'gemini') {
|
||||||
|
const fixedId = `planex-${issueId}`
|
||||||
|
|
||||||
|
Bash(
|
||||||
|
`ccw cli -p "${buildExecutionPrompt(issueId, solution)}" --tool gemini --mode write --id ${fixedId}`,
|
||||||
|
{ run_in_background: true }
|
||||||
|
)
|
||||||
|
// STOP — CLI 后台执行,等待 task hook callback 通知完成
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Verify & Commit
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Detect test command from package.json or project config
|
||||||
|
let testCmd = 'npm test'
|
||||||
|
try {
|
||||||
|
const pkgJson = JSON.parse(Read('package.json'))
|
||||||
|
if (pkgJson.scripts?.test) testCmd = 'npm test'
|
||||||
|
else if (pkgJson.scripts?.['test:unit']) testCmd = 'npm run test:unit'
|
||||||
|
} catch {
|
||||||
|
// Fallback: try common test runners
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify implementation
|
||||||
|
const testResult = Bash(`${testCmd} 2>&1 || echo "TEST_FAILED"`)
|
||||||
|
const testPassed = !testResult.includes('TEST_FAILED') && !testResult.includes('FAIL')
|
||||||
|
|
||||||
|
if (!testPassed) {
|
||||||
|
// Implementation failed — report to planner
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log", team: "planex", from: "executor", to: "planner",
|
||||||
|
type: "impl_failed",
|
||||||
|
summary: `[executor] Tests failing for ${issueId} after implementation (via ${executor})`
|
||||||
|
})
|
||||||
|
|
||||||
|
SendMessage({
|
||||||
|
type: "message", recipient: "planner",
|
||||||
|
content: `## [executor] Implementation Failed
|
||||||
|
|
||||||
|
**Issue**: ${issueId}
|
||||||
|
**Executor**: ${executor}
|
||||||
|
**Status**: Tests failing after implementation
|
||||||
|
**Test Output** (truncated):
|
||||||
|
${testResult.slice(0, 500)}
|
||||||
|
|
||||||
|
**Action**: May need solution revision or manual intervention.
|
||||||
|
${executor !== 'agent' ? `**Resume**: \`ccw cli -p "Fix failing tests" --resume planex-${issueId} --tool ${executor} --mode write --id planex-${issueId}-fix\`` : ''}`,
|
||||||
|
summary: `[executor] impl_failed: ${issueId} (${executor})`
|
||||||
|
})
|
||||||
|
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Optional: Code review (if configured)
|
||||||
|
if (codeReview !== 'Skip') {
|
||||||
|
executeCodeReview(codeReview, issueId)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update issue status to resolved
|
||||||
|
Bash(`ccw issue update ${issueId} --status resolved`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Code Review (Optional)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function executeCodeReview(reviewTool, issueId) {
|
||||||
|
const reviewPrompt = `PURPOSE: Code review for ${issueId} implementation against solution plan
|
||||||
|
TASK: • Verify solution convergence criteria • Check test coverage • Analyze code quality • Identify issues
|
||||||
|
MODE: analysis
|
||||||
|
CONTEXT: @**/* | Memory: Review planex execution for ${issueId}
|
||||||
|
EXPECTED: Quality assessment with issue identification and recommendations
|
||||||
|
CONSTRAINTS: Focus on solution adherence and code quality | analysis=READ-ONLY`
|
||||||
|
|
||||||
|
if (reviewTool === 'Gemini Review') {
|
||||||
|
Bash(`ccw cli -p "${reviewPrompt}" --tool gemini --mode analysis --id planex-review-${issueId}`,
|
||||||
|
{ run_in_background: true })
|
||||||
|
} else if (reviewTool === 'Codex Review') {
|
||||||
|
// Codex review: --uncommitted flag only (no prompt with target flags)
|
||||||
|
Bash(`ccw cli --tool codex --mode review --uncommitted`,
|
||||||
|
{ run_in_background: true })
|
||||||
|
} else if (reviewTool === 'Agent Review') {
|
||||||
|
// Current agent performs review inline
|
||||||
|
// Read solution convergence criteria and verify against implementation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Report + Loop
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log",
|
||||||
|
team: "planex",
|
||||||
|
from: "executor",
|
||||||
|
to: "planner",
|
||||||
|
type: "impl_complete",
|
||||||
|
summary: `[executor] Implementation complete for ${issueId} via ${executor}, tests passing`
|
||||||
|
})
|
||||||
|
|
||||||
|
SendMessage({
|
||||||
|
type: "message",
|
||||||
|
recipient: "planner",
|
||||||
|
content: `## [executor] Implementation Complete
|
||||||
|
|
||||||
|
**Issue**: ${issueId}
|
||||||
|
**Executor**: ${executor}
|
||||||
|
**Solution**: ${solution.bound.id}
|
||||||
|
**Code Review**: ${codeReview}
|
||||||
|
**Status**: All tests passing
|
||||||
|
**Issue Status**: Updated to resolved`,
|
||||||
|
summary: `[executor] EXEC complete: ${issueId} (${executor})`
|
||||||
|
})
|
||||||
|
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
|
|
||||||
|
// Check for next EXEC-* task (may include new wave tasks from planner)
|
||||||
|
const nextTasks = TaskList().filter(t =>
|
||||||
|
t.subject.startsWith('EXEC-') &&
|
||||||
|
t.owner === 'executor' &&
|
||||||
|
t.status === 'pending' &&
|
||||||
|
t.blockedBy.length === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextTasks.length > 0) {
|
||||||
|
// Continue with next task → back to Phase 1
|
||||||
|
} else {
|
||||||
|
// Check if planner has sent all_planned signal
|
||||||
|
// If yes and no more tasks → send wave_done and exit
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log",
|
||||||
|
team: "planex",
|
||||||
|
from: "executor",
|
||||||
|
to: "planner",
|
||||||
|
type: "wave_done",
|
||||||
|
summary: "[executor] All EXEC tasks completed"
|
||||||
|
})
|
||||||
|
|
||||||
|
SendMessage({
|
||||||
|
type: "message",
|
||||||
|
recipient: "planner",
|
||||||
|
content: `## [executor] All Tasks Done
|
||||||
|
|
||||||
|
All EXEC-* tasks have been completed. Pipeline finished.`,
|
||||||
|
summary: "[executor] wave_done: all complete"
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Scenario | Resolution |
|
||||||
|
|----------|------------|
|
||||||
|
| No EXEC-* tasks available | Idle, wait for planner to create tasks |
|
||||||
|
| Solution plan not found | Report error to planner |
|
||||||
|
| Unknown execution_method | Fallback to `agent` with warning |
|
||||||
|
| Agent (code-developer) failure | Retry once, then report impl_failed |
|
||||||
|
| CLI (Codex/Gemini) failure | Provide resume command with fixed ID, report impl_failed |
|
||||||
|
| CLI timeout | Use fixed ID `planex-{issueId}` for resume |
|
||||||
|
| Tests failing after implementation | Report impl_failed with test output + resume info |
|
||||||
|
| Issue status update failure | Log warning, continue with report |
|
||||||
|
| Dependency not yet complete | Wait — task is blocked by blockedBy |
|
||||||
|
| All tasks done but planner still planning | Send wave_done, then idle for more |
|
||||||
373
.claude/skills/team-planex/roles/planner.md
Normal file
373
.claude/skills/team-planex/roles/planner.md
Normal file
@@ -0,0 +1,373 @@
|
|||||||
|
# Role: planner
|
||||||
|
|
||||||
|
需求拆解 → issue 创建 → 方案设计 → 队列编排 → EXEC 任务派发。内部调用 issue-plan-agent 和 issue-queue-agent,并通过 Wave Pipeline 持续推进。planner 同时承担 lead 角色(无独立 coordinator)。
|
||||||
|
|
||||||
|
## Role Identity
|
||||||
|
|
||||||
|
- **Name**: `planner`
|
||||||
|
- **Task Prefix**: `PLAN-*`
|
||||||
|
- **Responsibility**: Planning lead (requirement → issues → solutions → queue → dispatch)
|
||||||
|
- **Communication**: SendMessage to executor; 需要时 AskUserQuestion
|
||||||
|
- **Output Tag**: `[planner]`
|
||||||
|
|
||||||
|
## Role Boundaries
|
||||||
|
|
||||||
|
### MUST
|
||||||
|
|
||||||
|
- 仅处理 `PLAN-*` 前缀的任务
|
||||||
|
- 所有输出必须带 `[planner]` 标识
|
||||||
|
- 完成每个 wave 的 queue 后**立即创建 EXEC-\* 任务**
|
||||||
|
- 不等待 executor 完成当前 wave,直接进入下一 wave 规划
|
||||||
|
|
||||||
|
### MUST NOT
|
||||||
|
|
||||||
|
- ❌ 直接编写/修改业务代码(executor 职责)
|
||||||
|
- ❌ 调用 code-developer agent
|
||||||
|
- ❌ 运行项目测试
|
||||||
|
- ❌ git commit 代码变更
|
||||||
|
|
||||||
|
## Message Types
|
||||||
|
|
||||||
|
| Type | Direction | Trigger | Description |
|
||||||
|
|------|-----------|---------|-------------|
|
||||||
|
| `wave_ready` | planner → executor | Wave queue 完成 + EXEC 任务已创建 | 新 wave 可执行 |
|
||||||
|
| `queue_ready` | planner → executor | 单个 issue 的 queue 就绪 | 增量通知 |
|
||||||
|
| `all_planned` | planner → executor | 所有 wave 规划完毕 | 最终信号 |
|
||||||
|
| `error` | planner → executor | 阻塞性错误 | 规划失败 |
|
||||||
|
|
||||||
|
## Toolbox
|
||||||
|
|
||||||
|
### Subagent Capabilities
|
||||||
|
|
||||||
|
| Agent Type | Purpose |
|
||||||
|
|------------|---------|
|
||||||
|
| `issue-plan-agent` | Closed-loop planning: ACE exploration + solution generation + binding |
|
||||||
|
| `issue-queue-agent` | Solution ordering + conflict detection → execution queue |
|
||||||
|
|
||||||
|
### CLI Capabilities
|
||||||
|
|
||||||
|
| CLI Command | Purpose |
|
||||||
|
|-------------|---------|
|
||||||
|
| `ccw issue new --text '...' --json` | 从文本创建 issue |
|
||||||
|
| `ccw issue status <id> --json` | 查看 issue 状态 |
|
||||||
|
| `ccw issue solutions <id> --json` | 查看已绑定 solution |
|
||||||
|
| `ccw issue bind <id> <sol-id>` | 绑定 solution 到 issue |
|
||||||
|
|
||||||
|
### Skill Capabilities
|
||||||
|
|
||||||
|
| Skill | Purpose |
|
||||||
|
|-------|---------|
|
||||||
|
| `Skill(skill="issue:new", args="--text '...'")` | 从文本创建 issue |
|
||||||
|
|
||||||
|
## Execution (5-Phase)
|
||||||
|
|
||||||
|
### Phase 1: Task Discovery
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tasks = TaskList()
|
||||||
|
const myTasks = tasks.filter(t =>
|
||||||
|
t.subject.startsWith('PLAN-') &&
|
||||||
|
t.owner === 'planner' &&
|
||||||
|
t.status === 'pending' &&
|
||||||
|
t.blockedBy.length === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (myTasks.length === 0) return // idle
|
||||||
|
|
||||||
|
const task = TaskGet({ taskId: myTasks[0].id })
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'in_progress' })
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Input Parsing
|
||||||
|
|
||||||
|
解析任务描述中的输入类型,确定处理方式。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const desc = task.description
|
||||||
|
const args = "$ARGUMENTS"
|
||||||
|
|
||||||
|
// 1) 已有 Issue IDs
|
||||||
|
const issueIds = (desc + ' ' + args).match(/ISS-\d{8}-\d{6}/g) || []
|
||||||
|
|
||||||
|
// 2) 文本输入
|
||||||
|
const textMatch = (desc + ' ' + args).match(/--text\s+['"]([^'"]+)['"]/)
|
||||||
|
const inputText = textMatch ? textMatch[1] : null
|
||||||
|
|
||||||
|
// 3) Plan 文件输入
|
||||||
|
const planMatch = (desc + ' ' + args).match(/--plan\s+(\S+)/)
|
||||||
|
const planFile = planMatch ? planMatch[1] : null
|
||||||
|
|
||||||
|
// Determine input type
|
||||||
|
let inputType = 'unknown'
|
||||||
|
if (issueIds.length > 0) inputType = 'issue_ids'
|
||||||
|
else if (inputText) inputType = 'text'
|
||||||
|
else if (planFile) inputType = 'plan_file'
|
||||||
|
else {
|
||||||
|
// 任务描述本身可能就是需求文本
|
||||||
|
inputType = 'text_from_description'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Issue Processing Pipeline
|
||||||
|
|
||||||
|
根据输入类型执行不同的处理路径:
|
||||||
|
|
||||||
|
#### Path A: 文本输入 → 创建 Issue
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (inputType === 'text' || inputType === 'text_from_description') {
|
||||||
|
const text = inputText || desc
|
||||||
|
|
||||||
|
// 使用 issue:new skill 创建 issue
|
||||||
|
Skill(skill="issue:new", args=`--text '${text}'`)
|
||||||
|
|
||||||
|
// 获取新创建的 issue ID
|
||||||
|
// issue:new 会输出创建的 issue ID
|
||||||
|
// 将其加入 issueIds 列表
|
||||||
|
issueIds.push(newIssueId)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path B: Plan 文件 → 批量创建 Issues
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (inputType === 'plan_file') {
|
||||||
|
const planContent = Read(planFile)
|
||||||
|
|
||||||
|
// 解析 Plan 文件中的 Phase/步骤
|
||||||
|
// 每个 Phase 或独立步骤创建一个 issue
|
||||||
|
const phases = parsePlanPhases(planContent)
|
||||||
|
|
||||||
|
for (const phase of phases) {
|
||||||
|
Skill(skill="issue:new", args=`--text '${phase.title}: ${phase.description}'`)
|
||||||
|
issueIds.push(newIssueId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Path C: Issue IDs → 直接进入规划
|
||||||
|
|
||||||
|
Issue IDs 已就绪,直接进入 solution 规划。
|
||||||
|
|
||||||
|
#### Wave 规划(所有路径汇聚)
|
||||||
|
|
||||||
|
将 issueIds 按波次分组规划:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const projectRoot = Bash('cd . && pwd').trim()
|
||||||
|
|
||||||
|
// 按批次分组(每 wave 最多 5 个 issues)
|
||||||
|
const WAVE_SIZE = 5
|
||||||
|
const waves = []
|
||||||
|
for (let i = 0; i < issueIds.length; i += WAVE_SIZE) {
|
||||||
|
waves.push(issueIds.slice(i, i + WAVE_SIZE))
|
||||||
|
}
|
||||||
|
|
||||||
|
let waveNum = 0
|
||||||
|
for (const waveIssues of waves) {
|
||||||
|
waveNum++
|
||||||
|
|
||||||
|
// Step 1: 调用 issue-plan-agent 生成 solutions
|
||||||
|
const planResult = Task({
|
||||||
|
subagent_type: "issue-plan-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Plan solutions for wave ${waveNum}`,
|
||||||
|
prompt: `
|
||||||
|
issue_ids: ${JSON.stringify(waveIssues)}
|
||||||
|
project_root: "${projectRoot}"
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- Generate solutions for each issue
|
||||||
|
- Auto-bind single solutions
|
||||||
|
- For multiple solutions, select the most pragmatic one
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Step 2: 调用 issue-queue-agent 形成 queue
|
||||||
|
const queueResult = Task({
|
||||||
|
subagent_type: "issue-queue-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Form queue for wave ${waveNum}`,
|
||||||
|
prompt: `
|
||||||
|
issue_ids: ${JSON.stringify(waveIssues)}
|
||||||
|
project_root: "${projectRoot}"
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- Order solutions by dependency (DAG)
|
||||||
|
- Detect conflicts between solutions
|
||||||
|
- Output execution queue
|
||||||
|
`
|
||||||
|
})
|
||||||
|
|
||||||
|
// Step 3: → Phase 4 (Wave Dispatch)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Wave Dispatch
|
||||||
|
|
||||||
|
每个 wave 的 queue 完成后,**立即创建 EXEC-\* 任务**供 executor 消费。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Read the generated queue
|
||||||
|
const queuePath = `.workflow/issues/queue/execution-queue.json`
|
||||||
|
const queue = JSON.parse(Read(queuePath))
|
||||||
|
|
||||||
|
// Create EXEC-* tasks from queue entries
|
||||||
|
const execTasks = []
|
||||||
|
for (const entry of queue.queue) {
|
||||||
|
const execTask = TaskCreate({
|
||||||
|
subject: `EXEC-W${waveNum}-${entry.issue_id}: 实现 ${entry.title || entry.issue_id}`,
|
||||||
|
description: `## 执行任务
|
||||||
|
|
||||||
|
**Wave**: ${waveNum}
|
||||||
|
**Issue**: ${entry.issue_id}
|
||||||
|
**Solution**: ${entry.solution_id}
|
||||||
|
**Priority**: ${entry.priority || 'normal'}
|
||||||
|
**Dependencies**: ${entry.depends_on?.join(', ') || 'none'}
|
||||||
|
|
||||||
|
加载 solution plan 并实现代码。完成后运行测试、提交。`,
|
||||||
|
activeForm: `实现 ${entry.issue_id}`,
|
||||||
|
owner: "executor"
|
||||||
|
})
|
||||||
|
execTasks.push(execTask)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set up dependency chains between EXEC tasks (based on queue DAG)
|
||||||
|
for (const entry of queue.queue) {
|
||||||
|
if (entry.depends_on?.length > 0) {
|
||||||
|
const thisTask = execTasks.find(t => t.subject.includes(entry.issue_id))
|
||||||
|
const depTasks = entry.depends_on.map(depId =>
|
||||||
|
execTasks.find(t => t.subject.includes(depId))
|
||||||
|
).filter(Boolean)
|
||||||
|
|
||||||
|
if (thisTask && depTasks.length > 0) {
|
||||||
|
TaskUpdate({
|
||||||
|
taskId: thisTask.id,
|
||||||
|
addBlockedBy: depTasks.map(t => t.id)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify executor: wave ready
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log",
|
||||||
|
team: "planex",
|
||||||
|
from: "planner",
|
||||||
|
to: "executor",
|
||||||
|
type: "wave_ready",
|
||||||
|
summary: `[planner] Wave ${waveNum} ready: ${execTasks.length} EXEC tasks created`
|
||||||
|
})
|
||||||
|
|
||||||
|
SendMessage({
|
||||||
|
type: "message",
|
||||||
|
recipient: "executor",
|
||||||
|
content: `## [planner] Wave ${waveNum} Ready
|
||||||
|
|
||||||
|
**Issues**: ${waveIssues.join(', ')}
|
||||||
|
**EXEC Tasks Created**: ${execTasks.length}
|
||||||
|
**Queue**: ${queuePath}
|
||||||
|
|
||||||
|
Executor 可以开始实现。`,
|
||||||
|
summary: `[planner] wave_ready: wave ${waveNum}`
|
||||||
|
})
|
||||||
|
|
||||||
|
// 不等待 executor 完成,继续下一 wave → back to Phase 3 loop
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 5: Report + Finalize
|
||||||
|
|
||||||
|
所有 wave 规划完毕后,发送最终信号。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// All waves planned
|
||||||
|
mcp__ccw-tools__team_msg({
|
||||||
|
operation: "log",
|
||||||
|
team: "planex",
|
||||||
|
from: "planner",
|
||||||
|
to: "executor",
|
||||||
|
type: "all_planned",
|
||||||
|
summary: `[planner] All ${waveNum} waves planned, ${issueIds.length} issues total`
|
||||||
|
})
|
||||||
|
|
||||||
|
SendMessage({
|
||||||
|
type: "message",
|
||||||
|
recipient: "executor",
|
||||||
|
content: `## [planner] All Waves Planned
|
||||||
|
|
||||||
|
**Total Waves**: ${waveNum}
|
||||||
|
**Total Issues**: ${issueIds.length}
|
||||||
|
**Status**: 所有规划完毕,等待 executor 完成剩余 EXEC-* 任务
|
||||||
|
|
||||||
|
Pipeline 完成后请 executor 发送 wave_done 确认。`,
|
||||||
|
summary: `[planner] all_planned: ${waveNum} waves, ${issueIds.length} issues`
|
||||||
|
})
|
||||||
|
|
||||||
|
TaskUpdate({ taskId: task.id, status: 'completed' })
|
||||||
|
|
||||||
|
// Check for next PLAN-* task (e.g., user added more requirements)
|
||||||
|
const nextTasks = TaskList().filter(t =>
|
||||||
|
t.subject.startsWith('PLAN-') &&
|
||||||
|
t.owner === 'planner' &&
|
||||||
|
t.status === 'pending' &&
|
||||||
|
t.blockedBy.length === 0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (nextTasks.length > 0) {
|
||||||
|
// Continue with next task → back to Phase 1
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Plan File Parsing
|
||||||
|
|
||||||
|
解析 Plan 文件为 phases 列表:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function parsePlanPhases(planContent) {
|
||||||
|
const phases = []
|
||||||
|
|
||||||
|
// 匹配 ## Phase N: Title 或 ## Step N: Title 或 ### N. Title
|
||||||
|
const phaseRegex = /^#{2,3}\s+(?:Phase|Step|阶段)\s*\d*[:.:]\s*(.+?)$/gm
|
||||||
|
let match
|
||||||
|
let lastIndex = 0
|
||||||
|
let lastTitle = null
|
||||||
|
|
||||||
|
while ((match = phaseRegex.exec(planContent)) !== null) {
|
||||||
|
if (lastTitle !== null) {
|
||||||
|
const description = planContent.slice(lastIndex, match.index).trim()
|
||||||
|
phases.push({ title: lastTitle, description })
|
||||||
|
}
|
||||||
|
lastTitle = match[1].trim()
|
||||||
|
lastIndex = match.index + match[0].length
|
||||||
|
}
|
||||||
|
|
||||||
|
// Last phase
|
||||||
|
if (lastTitle !== null) {
|
||||||
|
const description = planContent.slice(lastIndex).trim()
|
||||||
|
phases.push({ title: lastTitle, description })
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: 如果没有匹配到 Phase 结构,将整个内容作为单个 issue
|
||||||
|
if (phases.length === 0) {
|
||||||
|
const titleMatch = planContent.match(/^#\s+(.+)$/m)
|
||||||
|
phases.push({
|
||||||
|
title: titleMatch ? titleMatch[1] : 'Plan Implementation',
|
||||||
|
description: planContent.slice(0, 500)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return phases
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Scenario | Resolution |
|
||||||
|
|----------|------------|
|
||||||
|
| No PLAN-* tasks available | Idle, wait for orchestrator |
|
||||||
|
| Issue creation failure | Retry once with simplified text, then report error |
|
||||||
|
| issue-plan-agent failure | Retry once, then report error and skip to next issue |
|
||||||
|
| issue-queue-agent failure | Retry once, then create EXEC tasks without DAG ordering |
|
||||||
|
| Plan file not found | Report error with expected path |
|
||||||
|
| Empty input (no issues, no text, no plan) | AskUserQuestion for clarification |
|
||||||
|
| Wave partially failed | Report partial success, continue with successful issues |
|
||||||
@@ -1,465 +0,0 @@
|
|||||||
---
|
|
||||||
name: ccw-loop
|
|
||||||
description: Stateless iterative development loop with single-agent deep interaction. Supports develop, debug, validate phases with file-based state tracking. Triggers on "ccw-loop", "dev loop", "development loop", "开发循环", "迭代开发".
|
|
||||||
allowed-tools: spawn_agent, wait, send_input, close_agent, AskUserQuestion, Read, Write, Edit, Bash, Glob, Grep
|
|
||||||
---
|
|
||||||
|
|
||||||
# CCW Loop - Stateless Iterative Development Workflow
|
|
||||||
|
|
||||||
Stateless iterative development loop using Codex single-agent deep interaction pattern. One agent handles all phases (develop → debug → validate → complete) via `send_input`, with file-based state tracking and Dashboard integration.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
| Dashboard (UI) |
|
|
||||||
| [Create] [Start] [Pause] [Resume] [Stop] [View Progress] |
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
| loop-v2-routes.ts (Control Plane) |
|
|
||||||
| |
|
|
||||||
| State: {projectRoot}/.workflow/.loop/{loopId}.json (MASTER) |
|
|
||||||
| Tasks: {projectRoot}/.workflow/.loop/{loopId}/.task/*.json |
|
|
||||||
| |
|
|
||||||
| /start -> Trigger ccw-loop skill with --loop-id |
|
|
||||||
| /pause -> Set status='paused' (skill checks before action) |
|
|
||||||
| /stop -> Set status='failed' (skill terminates) |
|
|
||||||
| /resume -> Set status='running' (skill continues) |
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
| ccw-loop Skill (Execution Plane) |
|
|
||||||
| |
|
|
||||||
| Single Agent Deep Interaction: |
|
|
||||||
| spawn_agent -> wait -> send_input -> ... -> close_agent |
|
|
||||||
| |
|
|
||||||
| Actions: INIT -> DEVELOP -> DEBUG -> VALIDATE -> COMPLETE |
|
|
||||||
+-------------------------------------------------------------+
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Design Principles
|
|
||||||
|
|
||||||
1. **Single Agent Deep Interaction**: One agent handles entire loop lifecycle via `send_input` (no multi-agent overhead)
|
|
||||||
2. **Unified State**: API and Skill share `{projectRoot}/.workflow/.loop/{loopId}.json` state file
|
|
||||||
3. **Control Signals**: Skill checks `status` field before each action (paused/stopped → graceful exit)
|
|
||||||
4. **File-Driven Progress**: All progress documented in `{projectRoot}/.workflow/.loop/{loopId}.progress/`
|
|
||||||
5. **Resumable**: Continue any loop with `--loop-id`
|
|
||||||
6. **Dual Trigger**: Supports API trigger (`--loop-id`) and direct call (task description)
|
|
||||||
|
|
||||||
## 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 |
|
|
||||||
| --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 `.task/*.json` files 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` (.task/*.json)
|
|
||||||
- `analyze-with-file` (.task/*.json)
|
|
||||||
- `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
|
|
||||||
|
|
||||||
User manually selects each action via menu.
|
|
||||||
|
|
||||||
```
|
|
||||||
User -> MENU -> Select action -> Execute -> View results -> MENU -> ...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode 2: Auto-Loop
|
|
||||||
|
|
||||||
Automatic execution using `selectNextAction` logic.
|
|
||||||
|
|
||||||
```
|
|
||||||
INIT -> DEVELOP -> ... -> VALIDATE -> (if fail) -> DEBUG -> VALIDATE -> COMPLETE
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Input Parsing:
|
|
||||||
└─ Parse arguments (TASK | --loop-id + --auto)
|
|
||||||
└─ Convert to structured context (loopId, state, progressDir)
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
Phase 2: Orchestration Loop
|
|
||||||
└─ Ref: phases/02-orchestration-loop.md
|
|
||||||
├─ Spawn single executor agent
|
|
||||||
├─ Main while loop: wait → parse → dispatch → send_input
|
|
||||||
├─ Handle: COMPLETED / PAUSED / STOPPED / WAITING_INPUT / next action
|
|
||||||
├─ Update iteration count per cycle
|
|
||||||
└─ close_agent on exit
|
|
||||||
```
|
|
||||||
|
|
||||||
**Phase Reference Documents** (read on-demand when phase executes):
|
|
||||||
|
|
||||||
| 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 |
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
User Input (TASK | --loop-id + --auto)
|
|
||||||
↓
|
|
||||||
[Parse Arguments]
|
|
||||||
↓ loopId, state, progressDir
|
|
||||||
|
|
||||||
Phase 1: Session Initialization
|
|
||||||
↓ loopId, state (initialized/resumed), progressDir
|
|
||||||
|
|
||||||
Phase 2: Orchestration Loop
|
|
||||||
↓ spawn agent → [INIT] → wait → parse
|
|
||||||
↓
|
|
||||||
┌─── Main Loop (while iteration < max) ──────────┐
|
|
||||||
│ wait() → parseActionResult(output) │
|
|
||||||
│ ├─ COMPLETED → close_agent, return │
|
|
||||||
│ ├─ PAUSED → close_agent, return │
|
|
||||||
│ ├─ STOPPED → close_agent, return │
|
|
||||||
│ ├─ WAITING_INPUT → collect input → send_input │
|
|
||||||
│ └─ next_action → send_input(continue) │
|
|
||||||
│ Update iteration in state file │
|
|
||||||
└──────────────────────────────────────────────────┘
|
|
||||||
↓
|
|
||||||
close_agent → return finalState
|
|
||||||
```
|
|
||||||
|
|
||||||
## Session Structure
|
|
||||||
|
|
||||||
```
|
|
||||||
{projectRoot}/.workflow/.loop/
|
|
||||||
├── {loopId}.json # Master state file (API + Skill shared)
|
|
||||||
├── {loopId}/.task/ # Task files directory (API managed)
|
|
||||||
│ └── TASK-{id}.json # Individual task files (task-schema.json)
|
|
||||||
└── {loopId}.progress/ # Skill progress files
|
|
||||||
├── develop.md # Development progress timeline
|
|
||||||
├── debug.md # Understanding evolution document
|
|
||||||
├── validate.md # Validation report
|
|
||||||
├── changes.log # Code changes log (NDJSON)
|
|
||||||
├── debug.log # Debug log (NDJSON)
|
|
||||||
├── hypotheses.json # Debug hypotheses tracking
|
|
||||||
├── test-results.json # Test execution results
|
|
||||||
├── coverage.json # Coverage data
|
|
||||||
└── summary.md # Completion summary
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Management
|
|
||||||
|
|
||||||
Master state file: `{projectRoot}/.workflow/.loop/{loopId}.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"loop_id": "loop-v2-20260122T100000-abc123",
|
|
||||||
"title": "Task title",
|
|
||||||
"description": "Full task description",
|
|
||||||
"max_iterations": 10,
|
|
||||||
"status": "created | running | paused | completed | failed | user_exit",
|
|
||||||
"current_iteration": 0,
|
|
||||||
"created_at": "ISO8601",
|
|
||||||
"updated_at": "ISO8601",
|
|
||||||
"completed_at": "ISO8601 (optional)",
|
|
||||||
"failure_reason": "string (optional)",
|
|
||||||
|
|
||||||
"skill_state": {
|
|
||||||
"current_action": "init | develop | debug | validate | complete | null",
|
|
||||||
"last_action": "string | null",
|
|
||||||
"completed_actions": [],
|
|
||||||
"mode": "interactive | auto",
|
|
||||||
|
|
||||||
"develop": {
|
|
||||||
"total": 0, "completed": 0, "current_task": null,
|
|
||||||
"tasks": [{ "id": "task-001", "description": "...", "tool": "gemini", "mode": "write", "status": "pending", "files_changed": [], "created_at": "ISO8601", "completed_at": null }],
|
|
||||||
"last_progress_at": null
|
|
||||||
},
|
|
||||||
|
|
||||||
"debug": {
|
|
||||||
"active_bug": null, "hypotheses_count": 0,
|
|
||||||
"hypotheses": [{ "id": "H1", "description": "...", "testable_condition": "...", "logging_point": "file:func:line", "evidence_criteria": { "confirm": "...", "reject": "..." }, "likelihood": 1, "status": "pending", "evidence": null, "verdict_reason": null }],
|
|
||||||
"confirmed_hypothesis": null, "iteration": 0, "last_analysis_at": null
|
|
||||||
},
|
|
||||||
|
|
||||||
"validate": {
|
|
||||||
"pass_rate": 0, "coverage": 0,
|
|
||||||
"test_results": [{ "test_name": "...", "suite": "...", "status": "passed | failed | skipped", "duration_ms": 0, "error_message": null, "stack_trace": null }],
|
|
||||||
"passed": false, "failed_tests": [], "last_run_at": null
|
|
||||||
},
|
|
||||||
|
|
||||||
"errors": [{ "action": "...", "message": "...", "timestamp": "ISO8601" }],
|
|
||||||
|
|
||||||
"summary": { "duration": 0, "iterations": 0, "develop": {}, "debug": {}, "validate": {} }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Control Signal Checking**: Agent checks `state.status` before every action:
|
|
||||||
- `running` → continue
|
|
||||||
- `paused` → exit gracefully, wait for resume
|
|
||||||
- `failed` → terminate
|
|
||||||
- Other → stop
|
|
||||||
|
|
||||||
**Recovery**: If state corrupted, rebuild `skill_state` from `{projectRoot}/.workflow/.loop/{loopId}.progress/` markdown files and logs.
|
|
||||||
|
|
||||||
## Action Catalog
|
|
||||||
|
|
||||||
| Action | Purpose | Preconditions | Output Files | Trigger |
|
|
||||||
|--------|---------|---------------|--------------|---------|
|
|
||||||
| [INIT](actions/action-init.md) | Initialize session | status=running, skill_state=null | develop.md, state.json | First run |
|
|
||||||
| [DEVELOP](actions/action-develop.md) | Execute dev task | pending tasks > 0 | develop.md, changes.log | Has pending tasks |
|
|
||||||
| [DEBUG](actions/action-debug.md) | Hypothesis debug | needs debugging | debug.md, debug.log | Test failures |
|
|
||||||
| [VALIDATE](actions/action-validate.md) | Run tests | needs validation | validate.md, test-results.json | After develop/debug |
|
|
||||||
| [COMPLETE](actions/action-complete.md) | Finish loop | all done | summary.md | All tasks done |
|
|
||||||
| [MENU](actions/action-menu.md) | Display menu | interactive mode | - | Interactive mode |
|
|
||||||
|
|
||||||
### Action Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
spawn_agent (ccw-loop-executor)
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-------+
|
|
||||||
| INIT | (if skill_state is null)
|
|
||||||
+-------+
|
|
||||||
|
|
|
||||||
v
|
|
||||||
+-------+ send_input
|
|
||||||
| MENU | <------------- (user selection in interactive mode)
|
|
||||||
+-------+
|
|
||||||
|
|
|
||||||
+---+---+---+---+
|
|
||||||
| | | | |
|
|
||||||
v v v v v
|
|
||||||
DEV DBG VAL CMP EXIT
|
|
||||||
|
|
|
||||||
v
|
|
||||||
wait() -> get result
|
|
||||||
|
|
|
||||||
v
|
|
||||||
[Loop continues via send_input]
|
|
||||||
|
|
|
||||||
v
|
|
||||||
close_agent()
|
|
||||||
```
|
|
||||||
|
|
||||||
### Action Dependencies
|
|
||||||
|
|
||||||
| Action | Depends On | Leads To |
|
|
||||||
|--------|------------|----------|
|
|
||||||
| INIT | - | MENU or DEVELOP |
|
|
||||||
| MENU | INIT | User selection |
|
|
||||||
| DEVELOP | INIT | DEVELOP, DEBUG, VALIDATE |
|
|
||||||
| DEBUG | INIT | DEVELOP, VALIDATE |
|
|
||||||
| VALIDATE | DEVELOP or DEBUG | COMPLETE, DEBUG, DEVELOP |
|
|
||||||
| COMPLETE | - | Terminal |
|
|
||||||
|
|
||||||
### Action Sequences
|
|
||||||
|
|
||||||
```
|
|
||||||
Happy Path (Auto): INIT → DEVELOP → DEVELOP → VALIDATE (pass) → COMPLETE
|
|
||||||
Debug Iteration: INIT → DEVELOP → VALIDATE (fail) → DEBUG → VALIDATE (pass) → COMPLETE
|
|
||||||
Interactive Path: INIT → MENU → DEVELOP → MENU → VALIDATE → MENU → COMPLETE
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auto Mode Selection Logic
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function selectNextAction(state) {
|
|
||||||
const skillState = state.skill_state
|
|
||||||
|
|
||||||
// 1. Terminal conditions
|
|
||||||
if (state.status === 'completed') return null
|
|
||||||
if (state.status === 'failed') return null
|
|
||||||
if (state.current_iteration >= state.max_iterations) return 'COMPLETE'
|
|
||||||
|
|
||||||
// 2. Initialization check
|
|
||||||
if (!skillState) return 'INIT'
|
|
||||||
|
|
||||||
// 3. Auto selection based on state
|
|
||||||
const hasPendingDevelop = skillState.develop.tasks.some(t => t.status === 'pending')
|
|
||||||
|
|
||||||
if (hasPendingDevelop) return 'DEVELOP'
|
|
||||||
|
|
||||||
if (skillState.last_action === 'DEVELOP') {
|
|
||||||
if (skillState.develop.completed < skillState.develop.total) return 'DEBUG'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skillState.last_action === 'DEBUG' || skillState.debug.confirmed_hypothesis) {
|
|
||||||
return 'VALIDATE'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skillState.last_action === 'VALIDATE') {
|
|
||||||
if (!skillState.validate.passed) return 'DEVELOP'
|
|
||||||
}
|
|
||||||
|
|
||||||
if (skillState.validate.passed && !hasPendingDevelop) return 'COMPLETE'
|
|
||||||
|
|
||||||
return 'DEVELOP'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Coordination Protocol
|
|
||||||
|
|
||||||
### Agent → Orchestrator (ACTION_RESULT)
|
|
||||||
|
|
||||||
Every action MUST output:
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: {ACTION_NAME}
|
|
||||||
- status: success | failed | needs_input
|
|
||||||
- message: {user-facing message}
|
|
||||||
- state_updates: { ... }
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {file_path}: {description}
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {ACTION_NAME} | WAITING_INPUT | COMPLETED | PAUSED
|
|
||||||
```
|
|
||||||
|
|
||||||
### Orchestrator → Agent (send_input)
|
|
||||||
|
|
||||||
Auto mode continuation:
|
|
||||||
|
|
||||||
```
|
|
||||||
## CONTINUE EXECUTION
|
|
||||||
|
|
||||||
Previous action completed: {action}
|
|
||||||
Result: {status}
|
|
||||||
|
|
||||||
## EXECUTE NEXT ACTION
|
|
||||||
|
|
||||||
Continue with: {next_action}
|
|
||||||
Read action instructions and execute.
|
|
||||||
Output ACTION_RESULT when complete.
|
|
||||||
```
|
|
||||||
|
|
||||||
Interactive mode user input:
|
|
||||||
|
|
||||||
```
|
|
||||||
## USER INPUT RECEIVED
|
|
||||||
|
|
||||||
Action selected: {action}
|
|
||||||
|
|
||||||
## EXECUTE SELECTED ACTION
|
|
||||||
|
|
||||||
Read action instructions and execute: {action}
|
|
||||||
Update state and progress files accordingly.
|
|
||||||
Output ACTION_RESULT when complete.
|
|
||||||
```
|
|
||||||
|
|
||||||
### Codex Subagent API
|
|
||||||
|
|
||||||
| API | Purpose |
|
|
||||||
|-----|---------|
|
|
||||||
| `spawn_agent({ message })` | Create subagent, returns `agent_id` |
|
|
||||||
| `wait({ ids, timeout_ms })` | Wait for results (only way to get output) |
|
|
||||||
| `send_input({ id, message })` | Continue interaction / follow-up |
|
|
||||||
| `close_agent({ id })` | Close and reclaim (irreversible) |
|
|
||||||
|
|
||||||
**Rules**: Single agent for entire loop. `send_input` for multi-phase. `close_agent` only after confirming no more interaction needed.
|
|
||||||
|
|
||||||
## TodoWrite Pattern
|
|
||||||
|
|
||||||
### Phase-Level Tracking (Attached)
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
|
||||||
{"content": "Phase 2: Orchestration Loop", "status": "in_progress"},
|
|
||||||
{"content": " → Action: INIT", "status": "completed"},
|
|
||||||
{"content": " → Action: DEVELOP (task 1/3)", "status": "in_progress"},
|
|
||||||
{"content": " → Action: VALIDATE", "status": "pending"},
|
|
||||||
{"content": " → Action: COMPLETE", "status": "pending"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
### Iteration Tracking (Collapsed)
|
|
||||||
|
|
||||||
```json
|
|
||||||
[
|
|
||||||
{"content": "Phase 1: Session Initialization", "status": "completed"},
|
|
||||||
{"content": "Iteration 1: DEVELOP x3 + VALIDATE (pass)", "status": "completed"},
|
|
||||||
{"content": "Phase 2: COMPLETE", "status": "in_progress"}
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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 ACTION_RESULT from agent output for next decision
|
|
||||||
4. **Auto-Continue**: After each action, execute next pending action automatically (auto mode)
|
|
||||||
5. **Track Progress**: Update TodoWrite dynamically with attachment/collapse pattern
|
|
||||||
6. **Single Writer**: Orchestrator updates master state file; agent writes to progress files
|
|
||||||
7. **File References**: Pass file paths between orchestrator and agent, not content
|
|
||||||
8. **DO NOT STOP**: Continuous execution until COMPLETED, PAUSED, STOPPED, or max iterations
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Agent timeout | send_input requesting convergence, then retry |
|
|
||||||
| State corrupted | Rebuild from progress markdown files and logs |
|
|
||||||
| Agent closed unexpectedly | Re-spawn with previous output in message |
|
|
||||||
| Action failed | Log error, continue or prompt user |
|
|
||||||
| Tests fail | Loop back to DEVELOP or DEBUG |
|
|
||||||
| Max iterations reached | Generate summary with remaining issues documented |
|
|
||||||
| Session not found | Create new session |
|
|
||||||
|
|
||||||
## Coordinator Checklist
|
|
||||||
|
|
||||||
### Before Each Phase
|
|
||||||
|
|
||||||
- [ ] Read phase reference document
|
|
||||||
- [ ] Check current state for dependencies
|
|
||||||
- [ ] Update TodoWrite with phase tasks
|
|
||||||
|
|
||||||
### After Each Phase
|
|
||||||
|
|
||||||
- [ ] Parse agent outputs (ACTION_RESULT)
|
|
||||||
- [ ] Update master state file (iteration count, updated_at)
|
|
||||||
- [ ] Collapse TodoWrite sub-tasks
|
|
||||||
- [ ] Determine next action (continue / iterate / complete)
|
|
||||||
|
|
||||||
## Reference Documents
|
|
||||||
|
|
||||||
| Document | Purpose |
|
|
||||||
|----------|---------|
|
|
||||||
| [actions/](actions/) | Action definitions (INIT, DEVELOP, DEBUG, VALIDATE, COMPLETE, MENU) |
|
|
||||||
|
|
||||||
## Usage
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Start new loop (direct call)
|
|
||||||
/ccw-loop TASK="Implement user authentication"
|
|
||||||
|
|
||||||
# Auto-cycle mode
|
|
||||||
/ccw-loop --auto TASK="Fix login bug and add tests"
|
|
||||||
|
|
||||||
# Continue existing loop
|
|
||||||
/ccw-loop --loop-id=loop-v2-20260122-abc123
|
|
||||||
|
|
||||||
# API triggered auto-cycle
|
|
||||||
/ccw-loop --loop-id=loop-v2-20260122-abc123 --auto
|
|
||||||
```
|
|
||||||
@@ -1,269 +0,0 @@
|
|||||||
# Action: COMPLETE
|
|
||||||
|
|
||||||
Complete CCW Loop session and generate summary report.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Generate completion report
|
|
||||||
- Aggregate all phase results
|
|
||||||
- Provide follow-up recommendations
|
|
||||||
- Offer expansion to issues
|
|
||||||
- Mark status as completed
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state !== null
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Verify Control Signals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${projectRoot}/.workflow/.loop/${loopId}.json`))
|
|
||||||
|
|
||||||
if (state.status !== 'running') {
|
|
||||||
return {
|
|
||||||
action: 'COMPLETE',
|
|
||||||
status: 'failed',
|
|
||||||
message: `Cannot complete: status is ${state.status}`,
|
|
||||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Aggregate Statistics
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const stats = {
|
|
||||||
// Time statistics
|
|
||||||
duration: Date.now() - new Date(state.created_at).getTime(),
|
|
||||||
iterations: state.current_iteration,
|
|
||||||
|
|
||||||
// Development statistics
|
|
||||||
develop: {
|
|
||||||
total_tasks: state.skill_state.develop.total,
|
|
||||||
completed_tasks: state.skill_state.develop.completed,
|
|
||||||
completion_rate: state.skill_state.develop.total > 0
|
|
||||||
? ((state.skill_state.develop.completed / state.skill_state.develop.total) * 100).toFixed(1)
|
|
||||||
: 0
|
|
||||||
},
|
|
||||||
|
|
||||||
// Debug statistics
|
|
||||||
debug: {
|
|
||||||
iterations: state.skill_state.debug.iteration,
|
|
||||||
hypotheses_tested: state.skill_state.debug.hypotheses.length,
|
|
||||||
root_cause_found: state.skill_state.debug.confirmed_hypothesis !== null
|
|
||||||
},
|
|
||||||
|
|
||||||
// Validation statistics
|
|
||||||
validate: {
|
|
||||||
runs: state.skill_state.validate.test_results.length,
|
|
||||||
passed: state.skill_state.validate.passed,
|
|
||||||
coverage: state.skill_state.validate.coverage,
|
|
||||||
failed_tests: state.skill_state.validate.failed_tests.length
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Generate Summary Report
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const timestamp = getUtc8ISOString()
|
|
||||||
|
|
||||||
const summaryReport = `# CCW Loop Session Summary
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Task**: ${state.description}
|
|
||||||
**Started**: ${state.created_at}
|
|
||||||
**Completed**: ${timestamp}
|
|
||||||
**Duration**: ${formatDuration(stats.duration)}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
|
|
||||||
${state.skill_state.validate.passed
|
|
||||||
? 'All tests passed, validation successful'
|
|
||||||
: state.skill_state.develop.completed === state.skill_state.develop.total
|
|
||||||
? 'Development complete, validation not passed - needs debugging'
|
|
||||||
: 'Task partially complete - pending items remain'}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Development Phase
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Total Tasks | ${stats.develop.total_tasks} |
|
|
||||||
| Completed | ${stats.develop.completed_tasks} |
|
|
||||||
| Completion Rate | ${stats.develop.completion_rate}% |
|
|
||||||
|
|
||||||
### Completed Tasks
|
|
||||||
|
|
||||||
${state.skill_state.develop.tasks.filter(t => t.status === 'completed').map(t => `
|
|
||||||
- ${t.description}
|
|
||||||
- Files: ${t.files_changed?.join(', ') || 'N/A'}
|
|
||||||
- Completed: ${t.completed_at}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
### Pending Tasks
|
|
||||||
|
|
||||||
${state.skill_state.develop.tasks.filter(t => t.status !== 'completed').map(t => `
|
|
||||||
- ${t.description}
|
|
||||||
`).join('\n') || '_None_'}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Debug Phase
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Iterations | ${stats.debug.iterations} |
|
|
||||||
| Hypotheses Tested | ${stats.debug.hypotheses_tested} |
|
|
||||||
| Root Cause Found | ${stats.debug.root_cause_found ? 'Yes' : 'No'} |
|
|
||||||
|
|
||||||
${stats.debug.root_cause_found ? `
|
|
||||||
### Confirmed Root Cause
|
|
||||||
|
|
||||||
${state.skill_state.debug.confirmed_hypothesis}: ${state.skill_state.debug.hypotheses.find(h => h.id === state.skill_state.debug.confirmed_hypothesis)?.description}
|
|
||||||
` : ''}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Validation Phase
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Test Runs | ${stats.validate.runs} |
|
|
||||||
| Status | ${stats.validate.passed ? 'PASSED' : 'FAILED'} |
|
|
||||||
| Coverage | ${stats.validate.coverage || 'N/A'}% |
|
|
||||||
| Failed Tests | ${stats.validate.failed_tests} |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Recommendations
|
|
||||||
|
|
||||||
${generateRecommendations(stats, state)}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Session Artifacts
|
|
||||||
|
|
||||||
| File | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| \`develop.md\` | Development progress timeline |
|
|
||||||
| \`debug.md\` | Debug exploration and learnings |
|
|
||||||
| \`validate.md\` | Validation report |
|
|
||||||
| \`test-results.json\` | Test execution results |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
*Generated by CCW Loop at ${timestamp}*
|
|
||||||
`
|
|
||||||
|
|
||||||
Write(`${progressDir}/summary.md`, summaryReport)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Update State to Completed
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
state.status = 'completed'
|
|
||||||
state.completed_at = timestamp
|
|
||||||
state.updated_at = timestamp
|
|
||||||
state.skill_state.last_action = 'COMPLETE'
|
|
||||||
state.skill_state.summary = stats
|
|
||||||
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: COMPLETE
|
|
||||||
- status: success
|
|
||||||
- message: Loop completed. Duration: {duration}, Iterations: {N}
|
|
||||||
- state_updates: {
|
|
||||||
"status": "completed",
|
|
||||||
"completed_at": "{timestamp}"
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.json: Status set to completed
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/summary.md: Summary report generated
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: COMPLETED
|
|
||||||
```
|
|
||||||
|
|
||||||
## Expansion Options
|
|
||||||
|
|
||||||
After completion, offer expansion to issues:
|
|
||||||
|
|
||||||
```
|
|
||||||
## Expansion Options
|
|
||||||
|
|
||||||
Would you like to create follow-up issues?
|
|
||||||
|
|
||||||
1. [test] Add more test cases
|
|
||||||
2. [enhance] Feature enhancements
|
|
||||||
3. [refactor] Code refactoring
|
|
||||||
4. [doc] Documentation updates
|
|
||||||
5. [none] No expansion needed
|
|
||||||
|
|
||||||
Select options (comma-separated) or 'none':
|
|
||||||
```
|
|
||||||
|
|
||||||
## 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`
|
|
||||||
} else if (minutes > 0) {
|
|
||||||
return `${minutes}m ${seconds % 60}s`
|
|
||||||
} else {
|
|
||||||
return `${seconds}s`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function generateRecommendations(stats, state) {
|
|
||||||
const recommendations = []
|
|
||||||
|
|
||||||
if (stats.develop.completion_rate < 100) {
|
|
||||||
recommendations.push('- Complete remaining development tasks')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!stats.validate.passed) {
|
|
||||||
recommendations.push('- Fix failing tests')
|
|
||||||
}
|
|
||||||
|
|
||||||
if (stats.validate.coverage && stats.validate.coverage < 80) {
|
|
||||||
recommendations.push(`- Improve test coverage (current: ${stats.validate.coverage}%)`)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (recommendations.length === 0) {
|
|
||||||
recommendations.push('- Consider code review')
|
|
||||||
recommendations.push('- Update documentation')
|
|
||||||
recommendations.push('- Prepare for deployment')
|
|
||||||
}
|
|
||||||
|
|
||||||
return recommendations.join('\n')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Report generation failed | Show basic stats, skip file write |
|
|
||||||
| Issue creation failed | Log error, continue completion |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
- None (terminal state)
|
|
||||||
- To continue: Use `/ccw-loop --loop-id={loopId}` to reopen (will set status back to running)
|
|
||||||
@@ -1,286 +0,0 @@
|
|||||||
# Action: DEBUG
|
|
||||||
|
|
||||||
Hypothesis-driven debugging with understanding evolution documentation.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Locate error source
|
|
||||||
- Generate testable hypotheses
|
|
||||||
- Add NDJSON instrumentation
|
|
||||||
- Analyze log evidence
|
|
||||||
- Correct understanding based on evidence
|
|
||||||
- Apply fixes
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state !== null
|
|
||||||
|
|
||||||
## Mode Detection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const understandingPath = `${progressDir}/debug.md`
|
|
||||||
const debugLogPath = `${progressDir}/debug.log`
|
|
||||||
|
|
||||||
const understandingExists = fs.existsSync(understandingPath)
|
|
||||||
const logHasContent = fs.existsSync(debugLogPath) && fs.statSync(debugLogPath).size > 0
|
|
||||||
|
|
||||||
const debugMode = logHasContent ? 'analyze' : (understandingExists ? 'continue' : 'explore')
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Mode: Explore (First Debug)
|
|
||||||
|
|
||||||
#### Step E1: Get Bug Description
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// From test failures or user input
|
|
||||||
const bugDescription = state.skill_state.validate?.failed_tests?.[0]
|
|
||||||
|| await getUserInput('Describe the bug:')
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E2: Search Codebase
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Use ACE search_context to find related code
|
|
||||||
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',
|
|
||||||
logging_point: 'file.ts:functionName:42',
|
|
||||||
evidence_criteria: {
|
|
||||||
confirm: 'If we see X, hypothesis confirmed',
|
|
||||||
reject: 'If we see Y, hypothesis rejected'
|
|
||||||
},
|
|
||||||
likelihood: 1,
|
|
||||||
status: 'pending',
|
|
||||||
evidence: null,
|
|
||||||
verdict_reason: null
|
|
||||||
},
|
|
||||||
// H2, H3...
|
|
||||||
]
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E4: Create Understanding Document
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const initialUnderstanding = `# Understanding Document
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Bug Description**: ${bugDescription}
|
|
||||||
**Started**: ${getUtc8ISOString()}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Exploration Timeline
|
|
||||||
|
|
||||||
### Iteration 1 - Initial Exploration (${getUtc8ISOString()})
|
|
||||||
|
|
||||||
#### Current Understanding
|
|
||||||
|
|
||||||
Based on bug description and code search:
|
|
||||||
|
|
||||||
- Error pattern: [identified pattern]
|
|
||||||
- Affected areas: [files/modules]
|
|
||||||
- Initial hypothesis: [first thoughts]
|
|
||||||
|
|
||||||
#### Evidence from Code Search
|
|
||||||
|
|
||||||
[Search results summary]
|
|
||||||
|
|
||||||
#### Hypotheses
|
|
||||||
|
|
||||||
${hypotheses.map(h => `
|
|
||||||
**${h.id}**: ${h.description}
|
|
||||||
- Testable condition: ${h.testable_condition}
|
|
||||||
- Logging point: ${h.logging_point}
|
|
||||||
- Likelihood: ${h.likelihood}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Current Consolidated Understanding
|
|
||||||
|
|
||||||
[Summary of what we know so far]
|
|
||||||
`
|
|
||||||
|
|
||||||
Write(understandingPath, initialUnderstanding)
|
|
||||||
Write(`${progressDir}/hypotheses.json`, JSON.stringify({ hypotheses, iteration: 1 }, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step E5: Add NDJSON Logging Points
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// For each hypothesis, add instrumentation
|
|
||||||
for (const hypothesis of hypotheses) {
|
|
||||||
const [file, func, line] = hypothesis.logging_point.split(':')
|
|
||||||
|
|
||||||
const logStatement = `console.log(JSON.stringify({
|
|
||||||
hid: "${hypothesis.id}",
|
|
||||||
ts: Date.now(),
|
|
||||||
func: "${func}",
|
|
||||||
data: { /* relevant context */ }
|
|
||||||
}))`
|
|
||||||
|
|
||||||
// Add to file using Edit tool
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Mode: Analyze (Has Logs)
|
|
||||||
|
|
||||||
#### Step A1: Parse Debug Log
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const logContent = Read(debugLogPath)
|
|
||||||
const entries = logContent.split('\n')
|
|
||||||
.filter(l => l.trim())
|
|
||||||
.map(l => JSON.parse(l))
|
|
||||||
|
|
||||||
// Group by hypothesis ID
|
|
||||||
const byHypothesis = entries.reduce((acc, e) => {
|
|
||||||
acc[e.hid] = acc[e.hid] || []
|
|
||||||
acc[e.hid].push(e)
|
|
||||||
return acc
|
|
||||||
}, {})
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step A2: Evaluate Evidence
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const hypothesesData = JSON.parse(Read(`${progressDir}/hypotheses.json`))
|
|
||||||
|
|
||||||
for (const hypothesis of hypothesesData.hypotheses) {
|
|
||||||
const evidence = byHypothesis[hypothesis.id] || []
|
|
||||||
|
|
||||||
// Evaluate against criteria
|
|
||||||
if (matchesConfirmCriteria(evidence, hypothesis.evidence_criteria.confirm)) {
|
|
||||||
hypothesis.status = 'confirmed'
|
|
||||||
hypothesis.evidence = evidence
|
|
||||||
hypothesis.verdict_reason = 'Evidence matches confirm criteria'
|
|
||||||
} else if (matchesRejectCriteria(evidence, hypothesis.evidence_criteria.reject)) {
|
|
||||||
hypothesis.status = 'rejected'
|
|
||||||
hypothesis.evidence = evidence
|
|
||||||
hypothesis.verdict_reason = 'Evidence matches reject criteria'
|
|
||||||
} else {
|
|
||||||
hypothesis.status = 'inconclusive'
|
|
||||||
hypothesis.evidence = evidence
|
|
||||||
hypothesis.verdict_reason = 'Insufficient evidence'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Step A3: Update Understanding
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const iteration = hypothesesData.iteration + 1
|
|
||||||
const timestamp = getUtc8ISOString()
|
|
||||||
|
|
||||||
const analysisEntry = `
|
|
||||||
### Iteration ${iteration} - Evidence Analysis (${timestamp})
|
|
||||||
|
|
||||||
#### Log Analysis Results
|
|
||||||
|
|
||||||
${hypothesesData.hypotheses.map(h => `
|
|
||||||
**${h.id}**: ${h.status.toUpperCase()}
|
|
||||||
- Evidence: ${JSON.stringify(h.evidence?.slice(0, 3))}
|
|
||||||
- Reasoning: ${h.verdict_reason}
|
|
||||||
`).join('\n')}
|
|
||||||
|
|
||||||
#### Corrected Understanding
|
|
||||||
|
|
||||||
[Any corrections to previous assumptions]
|
|
||||||
|
|
||||||
${confirmedHypothesis ? `
|
|
||||||
#### Root Cause Identified
|
|
||||||
|
|
||||||
**${confirmedHypothesis.id}**: ${confirmedHypothesis.description}
|
|
||||||
` : `
|
|
||||||
#### Next Steps
|
|
||||||
|
|
||||||
[What to investigate next]
|
|
||||||
`}
|
|
||||||
|
|
||||||
---
|
|
||||||
`
|
|
||||||
|
|
||||||
const existingUnderstanding = Read(understandingPath)
|
|
||||||
Write(understandingPath, existingUnderstanding + analysisEntry)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
state.skill_state.debug.active_bug = bugDescription
|
|
||||||
state.skill_state.debug.hypotheses = hypothesesData.hypotheses
|
|
||||||
state.skill_state.debug.hypotheses_count = hypothesesData.hypotheses.length
|
|
||||||
state.skill_state.debug.iteration = iteration
|
|
||||||
state.skill_state.debug.last_analysis_at = timestamp
|
|
||||||
|
|
||||||
if (confirmedHypothesis) {
|
|
||||||
state.skill_state.debug.confirmed_hypothesis = confirmedHypothesis.id
|
|
||||||
}
|
|
||||||
|
|
||||||
state.skill_state.last_action = 'DEBUG'
|
|
||||||
state.updated_at = timestamp
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: DEBUG
|
|
||||||
- status: success
|
|
||||||
- message: {Mode description} - {result summary}
|
|
||||||
- state_updates: {
|
|
||||||
"debug.iteration": {N},
|
|
||||||
"debug.confirmed_hypothesis": "{id or null}"
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/debug.md: Understanding updated
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/hypotheses.json: Hypotheses updated
|
|
||||||
- [Source files]: Instrumentation added
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {DEBUG | VALIDATE | DEVELOP | MENU}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Action Selection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (confirmedHypothesis) {
|
|
||||||
// Root cause found, apply fix and validate
|
|
||||||
return 'VALIDATE'
|
|
||||||
} else if (allRejected) {
|
|
||||||
// Generate new hypotheses
|
|
||||||
return 'DEBUG'
|
|
||||||
} else {
|
|
||||||
// Need more evidence - prompt user to reproduce bug
|
|
||||||
return 'WAITING_INPUT' // User needs to trigger bug
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Empty debug.log | Prompt user to reproduce bug |
|
|
||||||
| All hypotheses rejected | Generate new hypotheses |
|
|
||||||
| >5 iterations | Suggest escalation |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
- Root cause found: `VALIDATE`
|
|
||||||
- Need more evidence: `DEBUG` (after reproduction)
|
|
||||||
- All rejected: `DEBUG` (new hypotheses)
|
|
||||||
@@ -1,183 +0,0 @@
|
|||||||
# Action: DEVELOP
|
|
||||||
|
|
||||||
Execute development task and record progress to develop.md.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Execute next pending development task
|
|
||||||
- Implement code changes
|
|
||||||
- Record progress to Markdown file
|
|
||||||
- Update task status in state
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state !== null
|
|
||||||
- [ ] state.skill_state.develop.tasks.some(t => t.status === 'pending')
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Verify Control Signals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${projectRoot}/.workflow/.loop/${loopId}.json`))
|
|
||||||
|
|
||||||
if (state.status !== 'running') {
|
|
||||||
return {
|
|
||||||
action: 'DEVELOP',
|
|
||||||
status: 'failed',
|
|
||||||
message: `Cannot develop: status is ${state.status}`,
|
|
||||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Find Next Pending Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const tasks = state.skill_state.develop.tasks
|
|
||||||
const currentTask = tasks.find(t => t.status === 'pending')
|
|
||||||
|
|
||||||
if (!currentTask) {
|
|
||||||
return {
|
|
||||||
action: 'DEVELOP',
|
|
||||||
status: 'success',
|
|
||||||
message: 'All development tasks completed',
|
|
||||||
next_action: mode === 'auto' ? 'VALIDATE' : 'MENU'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Mark as in_progress
|
|
||||||
currentTask.status = 'in_progress'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Execute Development Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
console.log(`Executing task: ${currentTask.description}`)
|
|
||||||
|
|
||||||
// Use appropriate tools based on task type
|
|
||||||
// - ACE search_context for finding patterns
|
|
||||||
// - Read for loading files
|
|
||||||
// - Edit/Write for making changes
|
|
||||||
|
|
||||||
// Record files changed
|
|
||||||
const filesChanged = []
|
|
||||||
|
|
||||||
// Implementation logic...
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Record Changes to Log (NDJSON)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const changesLogPath = `${progressDir}/changes.log`
|
|
||||||
const timestamp = getUtc8ISOString()
|
|
||||||
|
|
||||||
const changeEntry = {
|
|
||||||
timestamp: timestamp,
|
|
||||||
task_id: currentTask.id,
|
|
||||||
description: currentTask.description,
|
|
||||||
files_changed: filesChanged,
|
|
||||||
result: 'success'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append to NDJSON log
|
|
||||||
const existingLog = Read(changesLogPath) || ''
|
|
||||||
Write(changesLogPath, existingLog + JSON.stringify(changeEntry) + '\n')
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Update Progress Document
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const progressPath = `${progressDir}/develop.md`
|
|
||||||
const iteration = state.skill_state.develop.completed + 1
|
|
||||||
|
|
||||||
const progressEntry = `
|
|
||||||
### Iteration ${iteration} - ${currentTask.description} (${timestamp})
|
|
||||||
|
|
||||||
#### Task Details
|
|
||||||
|
|
||||||
- **ID**: ${currentTask.id}
|
|
||||||
- **Tool**: ${currentTask.tool}
|
|
||||||
- **Mode**: ${currentTask.mode}
|
|
||||||
|
|
||||||
#### Implementation Summary
|
|
||||||
|
|
||||||
[Implementation description]
|
|
||||||
|
|
||||||
#### Files Changed
|
|
||||||
|
|
||||||
${filesChanged.map(f => `- \`${f}\``).join('\n') || '- No files changed'}
|
|
||||||
|
|
||||||
#### Status: COMPLETED
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
const existingProgress = Read(progressPath)
|
|
||||||
Write(progressPath, existingProgress + progressEntry)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
currentTask.status = 'completed'
|
|
||||||
currentTask.completed_at = timestamp
|
|
||||||
currentTask.files_changed = filesChanged
|
|
||||||
|
|
||||||
state.skill_state.develop.completed += 1
|
|
||||||
state.skill_state.develop.current_task = null
|
|
||||||
state.skill_state.develop.last_progress_at = timestamp
|
|
||||||
state.skill_state.last_action = 'DEVELOP'
|
|
||||||
state.skill_state.completed_actions.push('DEVELOP')
|
|
||||||
state.updated_at = timestamp
|
|
||||||
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: DEVELOP
|
|
||||||
- status: success
|
|
||||||
- message: Task completed: {task_description}
|
|
||||||
- state_updates: {
|
|
||||||
"develop.completed": {N},
|
|
||||||
"develop.last_progress_at": "{timestamp}"
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.json: Task status updated
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/develop.md: Progress entry added
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/changes.log: Change entry added
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {DEVELOP | DEBUG | VALIDATE | MENU}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Auto Mode Next Action Selection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const pendingTasks = tasks.filter(t => t.status === 'pending')
|
|
||||||
|
|
||||||
if (pendingTasks.length > 0) {
|
|
||||||
return 'DEVELOP' // More tasks to do
|
|
||||||
} else {
|
|
||||||
return 'DEBUG' // All done, check for issues
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Task execution failed | Mark task as failed, continue to next |
|
|
||||||
| File write failed | Retry once, then report error |
|
|
||||||
| All tasks done | Move to DEBUG or VALIDATE |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
- More pending tasks: `DEVELOP`
|
|
||||||
- All tasks complete: `DEBUG` (auto) or `MENU` (interactive)
|
|
||||||
- Task failed: `DEVELOP` (retry) or `DEBUG` (investigate)
|
|
||||||
@@ -1,174 +0,0 @@
|
|||||||
# Action: INIT
|
|
||||||
|
|
||||||
Initialize CCW Loop session, create directory structure and initial state.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Create session directory structure
|
|
||||||
- Initialize state file with skill_state
|
|
||||||
- Analyze task description to generate development tasks
|
|
||||||
- Prepare execution environment
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state === null
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Verify Control Signals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${projectRoot}/.workflow/.loop/${loopId}.json`))
|
|
||||||
|
|
||||||
if (state.status !== 'running') {
|
|
||||||
return {
|
|
||||||
action: 'INIT',
|
|
||||||
status: 'failed',
|
|
||||||
message: `Cannot init: status is ${state.status}`,
|
|
||||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Create Directory Structure
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const progressDir = `${projectRoot}/.workflow/.loop/${loopId}.progress`
|
|
||||||
|
|
||||||
// Directories created by orchestrator, verify they exist
|
|
||||||
// mkdir -p ${progressDir}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Analyze Task and Generate Tasks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 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
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const progressPath = `${progressDir}/develop.md`
|
|
||||||
|
|
||||||
const progressInitial = `# Development Progress
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Task**: ${taskDescription}
|
|
||||||
**Started**: ${getUtc8ISOString()}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Task List
|
|
||||||
|
|
||||||
${tasks.map((t, i) => `${i + 1}. [ ] ${t.description}`).join('\n')}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Progress Timeline
|
|
||||||
|
|
||||||
`
|
|
||||||
|
|
||||||
Write(progressPath, progressInitial)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const skillState = {
|
|
||||||
current_action: 'init',
|
|
||||||
last_action: null,
|
|
||||||
completed_actions: [],
|
|
||||||
mode: mode,
|
|
||||||
|
|
||||||
develop: {
|
|
||||||
total: tasks.length,
|
|
||||||
completed: 0,
|
|
||||||
current_task: null,
|
|
||||||
tasks: tasks,
|
|
||||||
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: []
|
|
||||||
}
|
|
||||||
|
|
||||||
state.skill_state = skillState
|
|
||||||
state.updated_at = getUtc8ISOString()
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: INIT
|
|
||||||
- status: success
|
|
||||||
- message: Session initialized with {N} development tasks
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.json: skill_state initialized
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/develop.md: Progress document created
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {DEVELOP (auto) | MENU (interactive)}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Directory creation failed | Report error, stop |
|
|
||||||
| Task analysis failed | Create single generic task |
|
|
||||||
| State write failed | Retry once, then stop |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
- Success (auto mode): `DEVELOP`
|
|
||||||
- Success (interactive): `MENU`
|
|
||||||
- Failed: Report error
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
# Action: MENU
|
|
||||||
|
|
||||||
Display interactive action menu for user selection.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Show current state summary
|
|
||||||
- Display available actions
|
|
||||||
- Wait for user selection
|
|
||||||
- Return selected action
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state !== null
|
|
||||||
- [ ] mode === 'interactive'
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Verify Control Signals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${projectRoot}/.workflow/.loop/${loopId}.json`))
|
|
||||||
|
|
||||||
if (state.status !== 'running') {
|
|
||||||
return {
|
|
||||||
action: 'MENU',
|
|
||||||
status: 'failed',
|
|
||||||
message: `Cannot show menu: status is ${state.status}`,
|
|
||||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: Generate Status Summary
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Development progress
|
|
||||||
const developProgress = state.skill_state.develop.total > 0
|
|
||||||
? `${state.skill_state.develop.completed}/${state.skill_state.develop.total} (${((state.skill_state.develop.completed / state.skill_state.develop.total) * 100).toFixed(0)}%)`
|
|
||||||
: 'Not started'
|
|
||||||
|
|
||||||
// Debug status
|
|
||||||
const debugStatus = state.skill_state.debug.confirmed_hypothesis
|
|
||||||
? 'Root cause found'
|
|
||||||
: state.skill_state.debug.iteration > 0
|
|
||||||
? `Iteration ${state.skill_state.debug.iteration}`
|
|
||||||
: 'Not started'
|
|
||||||
|
|
||||||
// Validation status
|
|
||||||
const validateStatus = state.skill_state.validate.passed
|
|
||||||
? 'PASSED'
|
|
||||||
: state.skill_state.validate.test_results.length > 0
|
|
||||||
? `FAILED (${state.skill_state.validate.failed_tests.length} failures)`
|
|
||||||
: 'Not run'
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3: Display Menu
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const menuDisplay = `
|
|
||||||
================================================================
|
|
||||||
CCW Loop - ${loopId}
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
Task: ${state.description}
|
|
||||||
Iteration: ${state.current_iteration}
|
|
||||||
|
|
||||||
+-----------------------------------------------------+
|
|
||||||
| Phase | Status |
|
|
||||||
+-----------------------------------------------------+
|
|
||||||
| Develop | ${developProgress.padEnd(35)}|
|
|
||||||
| Debug | ${debugStatus.padEnd(35)}|
|
|
||||||
| Validate | ${validateStatus.padEnd(35)}|
|
|
||||||
+-----------------------------------------------------+
|
|
||||||
|
|
||||||
================================================================
|
|
||||||
|
|
||||||
MENU_OPTIONS:
|
|
||||||
1. [develop] Continue Development - ${state.skill_state.develop.total - state.skill_state.develop.completed} tasks pending
|
|
||||||
2. [debug] Start Debugging - ${debugStatus}
|
|
||||||
3. [validate] Run Validation - ${validateStatus}
|
|
||||||
4. [status] View Detailed Status
|
|
||||||
5. [complete] Complete Loop
|
|
||||||
6. [exit] Exit (save and quit)
|
|
||||||
`
|
|
||||||
|
|
||||||
console.log(menuDisplay)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: MENU
|
|
||||||
- status: success
|
|
||||||
- message: ${menuDisplay}
|
|
||||||
|
|
||||||
MENU_OPTIONS:
|
|
||||||
1. [develop] Continue Development - {N} tasks pending
|
|
||||||
2. [debug] Start Debugging - {status}
|
|
||||||
3. [validate] Run Validation - {status}
|
|
||||||
4. [status] View Detailed Status
|
|
||||||
5. [complete] Complete Loop
|
|
||||||
6. [exit] Exit (save and quit)
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: WAITING_INPUT
|
|
||||||
```
|
|
||||||
|
|
||||||
## User Input Handling
|
|
||||||
|
|
||||||
When user provides input, orchestrator sends it back via `send_input`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// User selects "develop"
|
|
||||||
send_input({
|
|
||||||
id: agent,
|
|
||||||
message: `
|
|
||||||
## USER INPUT RECEIVED
|
|
||||||
|
|
||||||
Action selected: develop
|
|
||||||
|
|
||||||
## EXECUTE SELECTED ACTION
|
|
||||||
|
|
||||||
Execute DEVELOP action.
|
|
||||||
`
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Status Detail View
|
|
||||||
|
|
||||||
If user selects "status":
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const detailView = `
|
|
||||||
## Detailed Status
|
|
||||||
|
|
||||||
### Development Progress
|
|
||||||
|
|
||||||
${Read(`${progressDir}/develop.md`)?.substring(0, 1000) || 'No progress recorded'}
|
|
||||||
|
|
||||||
### Debug Status
|
|
||||||
|
|
||||||
${state.skill_state.debug.hypotheses.length > 0
|
|
||||||
? state.skill_state.debug.hypotheses.map(h => ` ${h.id}: ${h.status} - ${h.description.substring(0, 50)}...`).join('\n')
|
|
||||||
: ' No debugging started'}
|
|
||||||
|
|
||||||
### Validation Results
|
|
||||||
|
|
||||||
${state.skill_state.validate.test_results.length > 0
|
|
||||||
? ` Last run: ${state.skill_state.validate.last_run_at}
|
|
||||||
Pass rate: ${state.skill_state.validate.pass_rate}%`
|
|
||||||
: ' No validation run yet'}
|
|
||||||
`
|
|
||||||
|
|
||||||
console.log(detailView)
|
|
||||||
|
|
||||||
// Return to menu
|
|
||||||
return {
|
|
||||||
action: 'MENU',
|
|
||||||
status: 'success',
|
|
||||||
message: detailView,
|
|
||||||
next_action: 'MENU' // Show menu again
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Exit Handling
|
|
||||||
|
|
||||||
If user selects "exit":
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Save current state
|
|
||||||
state.status = 'user_exit'
|
|
||||||
state.updated_at = getUtc8ISOString()
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
|
|
||||||
return {
|
|
||||||
action: 'MENU',
|
|
||||||
status: 'success',
|
|
||||||
message: 'Session saved. Use --loop-id to resume.',
|
|
||||||
next_action: 'COMPLETED'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Action Mapping
|
|
||||||
|
|
||||||
| User Selection | Next Action |
|
|
||||||
|----------------|-------------|
|
|
||||||
| develop | DEVELOP |
|
|
||||||
| debug | DEBUG |
|
|
||||||
| validate | VALIDATE |
|
|
||||||
| status | MENU (after showing details) |
|
|
||||||
| complete | COMPLETE |
|
|
||||||
| exit | COMPLETED (save and exit) |
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Invalid selection | Show menu again |
|
|
||||||
| User cancels | Return to menu |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
Based on user selection - forwarded via `send_input` by orchestrator.
|
|
||||||
@@ -1,250 +0,0 @@
|
|||||||
# Action: VALIDATE
|
|
||||||
|
|
||||||
Run tests and verify implementation, record results to validate.md.
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
- Run unit tests
|
|
||||||
- Run integration tests
|
|
||||||
- Check code coverage
|
|
||||||
- Generate validation report
|
|
||||||
- Determine pass/fail status
|
|
||||||
|
|
||||||
## Preconditions
|
|
||||||
|
|
||||||
- [ ] state.status === 'running'
|
|
||||||
- [ ] state.skill_state !== null
|
|
||||||
- [ ] (develop.completed > 0) OR (debug.confirmed_hypothesis !== null)
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
|
|
||||||
### Step 1: Verify Control Signals
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${projectRoot}/.workflow/.loop/${loopId}.json`))
|
|
||||||
|
|
||||||
if (state.status !== 'running') {
|
|
||||||
return {
|
|
||||||
action: 'VALIDATE',
|
|
||||||
status: 'failed',
|
|
||||||
message: `Cannot validate: status is ${state.status}`,
|
|
||||||
next_action: state.status === 'paused' ? 'PAUSED' : 'STOPPED'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2: 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 3: Run Tests
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const testResult = await Bash({
|
|
||||||
command: testScript,
|
|
||||||
timeout: 300000 // 5 minutes
|
|
||||||
})
|
|
||||||
|
|
||||||
// Parse test output based on framework
|
|
||||||
const testResults = parseTestOutput(testResult.stdout, testResult.stderr)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4: Run Coverage (if available)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let coverageData = null
|
|
||||||
|
|
||||||
if (coverageScript) {
|
|
||||||
const coverageResult = await Bash({
|
|
||||||
command: coverageScript,
|
|
||||||
timeout: 300000
|
|
||||||
})
|
|
||||||
|
|
||||||
coverageData = parseCoverageReport(coverageResult.stdout)
|
|
||||||
Write(`${progressDir}/coverage.json`, JSON.stringify(coverageData, null, 2))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5: Generate Validation Report
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const timestamp = getUtc8ISOString()
|
|
||||||
const iteration = (state.skill_state.validate.test_results?.length || 0) + 1
|
|
||||||
|
|
||||||
const validationReport = `# Validation Report
|
|
||||||
|
|
||||||
**Loop ID**: ${loopId}
|
|
||||||
**Task**: ${state.description}
|
|
||||||
**Validated**: ${timestamp}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Iteration ${iteration} - Validation Run
|
|
||||||
|
|
||||||
### Test Execution Summary
|
|
||||||
|
|
||||||
| Metric | Value |
|
|
||||||
|--------|-------|
|
|
||||||
| Total Tests | ${testResults.total} |
|
|
||||||
| Passed | ${testResults.passed} |
|
|
||||||
| Failed | ${testResults.failed} |
|
|
||||||
| Skipped | ${testResults.skipped} |
|
|
||||||
| Duration | ${testResults.duration_ms}ms |
|
|
||||||
| **Pass Rate** | **${((testResults.passed / testResults.total) * 100).toFixed(1)}%** |
|
|
||||||
|
|
||||||
### Coverage Report
|
|
||||||
|
|
||||||
${coverageData ? `
|
|
||||||
| File | Statements | Branches | Functions | Lines |
|
|
||||||
|------|------------|----------|-----------|-------|
|
|
||||||
${coverageData.files.map(f => `| ${f.path} | ${f.statements}% | ${f.branches}% | ${f.functions}% | ${f.lines}% |`).join('\n')}
|
|
||||||
|
|
||||||
**Overall Coverage**: ${coverageData.overall.statements}%
|
|
||||||
` : '_No coverage data available_'}
|
|
||||||
|
|
||||||
### Failed Tests
|
|
||||||
|
|
||||||
${testResults.failed > 0 ? testResults.failures.map(f => `
|
|
||||||
#### ${f.test_name}
|
|
||||||
|
|
||||||
- **Suite**: ${f.suite}
|
|
||||||
- **Error**: ${f.error_message}
|
|
||||||
`).join('\n') : '_All tests passed_'}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Validation Decision
|
|
||||||
|
|
||||||
**Result**: ${testResults.failed === 0 ? 'PASS' : 'FAIL'}
|
|
||||||
|
|
||||||
${testResults.failed > 0 ? `
|
|
||||||
### Next Actions
|
|
||||||
|
|
||||||
1. Review failed tests
|
|
||||||
2. Debug failures using DEBUG action
|
|
||||||
3. Fix issues and re-run validation
|
|
||||||
` : `
|
|
||||||
### Next Actions
|
|
||||||
|
|
||||||
1. Consider code review
|
|
||||||
2. Complete loop
|
|
||||||
`}
|
|
||||||
`
|
|
||||||
|
|
||||||
Write(`${progressDir}/validate.md`, validationReport)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 6: Save Test Results
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const testResultsData = {
|
|
||||||
iteration,
|
|
||||||
timestamp,
|
|
||||||
summary: {
|
|
||||||
total: testResults.total,
|
|
||||||
passed: testResults.passed,
|
|
||||||
failed: testResults.failed,
|
|
||||||
skipped: testResults.skipped,
|
|
||||||
pass_rate: ((testResults.passed / testResults.total) * 100).toFixed(1),
|
|
||||||
duration_ms: testResults.duration_ms
|
|
||||||
},
|
|
||||||
tests: testResults.tests,
|
|
||||||
failures: testResults.failures,
|
|
||||||
coverage: coverageData?.overall || null
|
|
||||||
}
|
|
||||||
|
|
||||||
Write(`${progressDir}/test-results.json`, JSON.stringify(testResultsData, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 7: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const validationPassed = testResults.failed === 0 && testResults.passed > 0
|
|
||||||
|
|
||||||
state.skill_state.validate.test_results.push(testResultsData)
|
|
||||||
state.skill_state.validate.pass_rate = parseFloat(testResultsData.summary.pass_rate)
|
|
||||||
state.skill_state.validate.coverage = coverageData?.overall?.statements || 0
|
|
||||||
state.skill_state.validate.passed = validationPassed
|
|
||||||
state.skill_state.validate.failed_tests = testResults.failures.map(f => f.test_name)
|
|
||||||
state.skill_state.validate.last_run_at = timestamp
|
|
||||||
|
|
||||||
state.skill_state.last_action = 'VALIDATE'
|
|
||||||
state.updated_at = timestamp
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output Format
|
|
||||||
|
|
||||||
```
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: VALIDATE
|
|
||||||
- status: success
|
|
||||||
- message: Validation {PASSED | FAILED} - {pass_count}/{total_count} tests passed
|
|
||||||
- state_updates: {
|
|
||||||
"validate.passed": {true | false},
|
|
||||||
"validate.pass_rate": {N},
|
|
||||||
"validate.failed_tests": [{list}]
|
|
||||||
}
|
|
||||||
|
|
||||||
FILES_UPDATED:
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/validate.md: Validation report created
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/test-results.json: Test results saved
|
|
||||||
- {projectRoot}/.workflow/.loop/{loopId}.progress/coverage.json: Coverage data saved (if available)
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {COMPLETE | DEBUG | DEVELOP | MENU}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Next Action Selection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (validationPassed) {
|
|
||||||
const pendingTasks = state.skill_state.develop.tasks.filter(t => t.status === 'pending')
|
|
||||||
if (pendingTasks.length === 0) {
|
|
||||||
return 'COMPLETE'
|
|
||||||
} else {
|
|
||||||
return 'DEVELOP'
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Tests failed - need debugging
|
|
||||||
return 'DEBUG'
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Test Output Parsers
|
|
||||||
|
|
||||||
### Jest/Vitest Parser
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parseJestOutput(stdout) {
|
|
||||||
const summaryMatch = stdout.match(/Tests:\s+(\d+)\s+passed.*?(\d+)\s+failed.*?(\d+)\s+total/)
|
|
||||||
// ... implementation
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Pytest Parser
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parsePytestOutput(stdout) {
|
|
||||||
const summaryMatch = stdout.match(/(\d+)\s+passed.*?(\d+)\s+failed/)
|
|
||||||
// ... implementation
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error Type | Recovery |
|
|
||||||
|------------|----------|
|
|
||||||
| Tests don't run | Check test script config, report error |
|
|
||||||
| All tests fail | Suggest DEBUG action |
|
|
||||||
| Coverage tool missing | Skip coverage, run tests only |
|
|
||||||
| Timeout | Increase timeout or split tests |
|
|
||||||
|
|
||||||
## Next Actions
|
|
||||||
|
|
||||||
- Validation passed, no pending: `COMPLETE`
|
|
||||||
- Validation passed, has pending: `DEVELOP`
|
|
||||||
- Validation failed: `DEBUG`
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
# 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",
|
|
||||||
"task_dir": "absolute path to source .task/ directory",
|
|
||||||
"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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## .task/*.json Schema (task-schema.json)
|
|
||||||
|
|
||||||
One task per file in `.task/` directory, each following `task-schema.json` with ccw-loop 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 dir | `.task/` directory exists with *.json files | Skip prep, use default INIT |
|
|
||||||
| 6 | tasks content | At least 1 valid task JSON in `.task/` | 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 .task/*.json
|
|
||||||
const taskDir = `${projectRoot}/.workflow/.loop/.task`
|
|
||||||
const prepTasks = loadPrepTasks(taskDir)
|
|
||||||
// → 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
|
|
||||||
@@ -1,300 +0,0 @@
|
|||||||
# Phase 1: Session Initialization
|
|
||||||
|
|
||||||
Create or resume a development loop, initialize state file and directory structure.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Parse user arguments (TASK, --loop-id, --auto)
|
|
||||||
- Create new loop with unique ID OR resume existing loop
|
|
||||||
- Initialize directory structure for progress files
|
|
||||||
- Create master state file
|
|
||||||
- Output: loopId, state, progressDir
|
|
||||||
|
|
||||||
## 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 & Load Prep Package
|
|
||||||
|
|
||||||
```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' }
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 taskDir = `${projectRoot}/.workflow/.loop/.task`
|
|
||||||
prepTasks = loadPrepTasks(taskDir)
|
|
||||||
|
|
||||||
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(`Warning: Prep tasks directory 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: .task/ directory must exist with task files
|
|
||||||
const taskDir = `${projectRoot}/.workflow/.loop/.task`
|
|
||||||
const taskFiles = Glob(`${taskDir}/*.json`)
|
|
||||||
if (fs.existsSync(taskDir) && taskFiles.length > 0) {
|
|
||||||
passed.push(`.task/ exists (${taskFiles.length} files)`)
|
|
||||||
} else {
|
|
||||||
failures.push('.task/ directory not found or empty')
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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 .task/*.json directory.
|
|
||||||
* Returns array of task objects or null on failure.
|
|
||||||
*/
|
|
||||||
function loadPrepTasks(taskDir) {
|
|
||||||
if (!fs.existsSync(taskDir)) return null
|
|
||||||
|
|
||||||
const taskFiles = Glob(`${taskDir}/*.json`).sort()
|
|
||||||
const tasks = []
|
|
||||||
|
|
||||||
for (const filePath of taskFiles) {
|
|
||||||
try {
|
|
||||||
const content = Read(filePath)
|
|
||||||
const task = JSON.parse(content)
|
|
||||||
if (task.id && task.description) {
|
|
||||||
tasks.push(task)
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
console.warn(`Warning: Skipping invalid task file ${filePath}: ${e.message}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tasks.length > 0 ? tasks : null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.2: Utility Functions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
|
||||||
|
|
||||||
function readLoopState(loopId) {
|
|
||||||
const stateFile = `${projectRoot}/.workflow/.loop/${loopId}.json`
|
|
||||||
if (!fs.existsSync(stateFile)) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
return JSON.parse(Read(stateFile))
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.3: New Loop Creation
|
|
||||||
|
|
||||||
When `TASK` is provided (no `--loop-id`):
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Generate unique loop ID
|
|
||||||
const timestamp = getUtc8ISOString().replace(/[-:]/g, '').split('.')[0]
|
|
||||||
const random = Math.random().toString(36).substring(2, 10)
|
|
||||||
const loopId = `loop-v2-${timestamp}-${random}`
|
|
||||||
|
|
||||||
console.log(`Creating new loop: ${loopId}`)
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Create Directory Structure
|
|
||||||
|
|
||||||
```bash
|
|
||||||
mkdir -p ${projectRoot}/.workflow/.loop/${loopId}.progress
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Initialize State File
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function createLoopState(loopId, taskDescription) {
|
|
||||||
const stateFile = `${projectRoot}/.workflow/.loop/${loopId}.json`
|
|
||||||
const now = getUtc8ISOString()
|
|
||||||
|
|
||||||
const state = {
|
|
||||||
// API compatible fields
|
|
||||||
loop_id: loopId,
|
|
||||||
title: taskDescription.substring(0, 100),
|
|
||||||
description: taskDescription,
|
|
||||||
max_iterations: prepPackage?.auto_loop?.max_iterations || 10,
|
|
||||||
status: 'running',
|
|
||||||
current_iteration: 0,
|
|
||||||
created_at: now,
|
|
||||||
updated_at: now,
|
|
||||||
|
|
||||||
// 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))
|
|
||||||
return state
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.4: Resume Existing Loop
|
|
||||||
|
|
||||||
When `--loop-id` is provided:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const loopId = existingLoopId
|
|
||||||
const state = readLoopState(loopId)
|
|
||||||
|
|
||||||
if (!state) {
|
|
||||||
console.error(`Loop not found: ${loopId}`)
|
|
||||||
return { status: 'error', message: 'Loop not found' }
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Resuming loop: ${loopId}`)
|
|
||||||
console.log(`Status: ${state.status}`)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.5: Control Signal Check
|
|
||||||
|
|
||||||
Before proceeding, verify loop status allows continuation:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function checkControlSignals(loopId) {
|
|
||||||
const state = readLoopState(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**: `mode` - `'interactive'` or `'auto'`
|
|
||||||
- **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,309 +0,0 @@
|
|||||||
# Phase 2: Orchestration Loop
|
|
||||||
|
|
||||||
Spawn single executor agent and run main orchestration loop until completion, pause, or max iterations.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Spawn single executor agent with loop context
|
|
||||||
- Run main while loop: wait → parse → dispatch → send_input
|
|
||||||
- Handle terminal conditions (COMPLETED, PAUSED, STOPPED)
|
|
||||||
- Handle interactive mode (WAITING_INPUT → user choice → send_input)
|
|
||||||
- Handle auto mode (next action → send_input)
|
|
||||||
- Update iteration count per cycle
|
|
||||||
- Close agent on exit
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 2.1: Spawn Executor Agent
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const agent = spawn_agent({
|
|
||||||
message: `
|
|
||||||
## TASK ASSIGNMENT
|
|
||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
|
||||||
1. **Read role definition**: ~/.codex/agents/ccw-loop-executor.md (MUST read first)
|
|
||||||
2. Read: ${projectRoot}/.workflow/project-tech.json (if exists)
|
|
||||||
3. Read: ${projectRoot}/.workflow/project-guidelines.json (if exists)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## LOOP CONTEXT
|
|
||||||
|
|
||||||
- **Loop ID**: ${loopId}
|
|
||||||
- **State File**: ${projectRoot}/.workflow/.loop/${loopId}.json
|
|
||||||
- **Progress Dir**: ${progressDir}
|
|
||||||
- **Mode**: ${mode}
|
|
||||||
|
|
||||||
## CURRENT STATE
|
|
||||||
|
|
||||||
${JSON.stringify(state, null, 2)}
|
|
||||||
|
|
||||||
## TASK DESCRIPTION
|
|
||||||
|
|
||||||
${state.description || task}
|
|
||||||
|
|
||||||
## EXECUTION INSTRUCTIONS
|
|
||||||
|
|
||||||
You are executing CCW Loop orchestrator. Your job:
|
|
||||||
|
|
||||||
1. **Check Control Signals**
|
|
||||||
- Read ${projectRoot}/.workflow/.loop/${loopId}.json
|
|
||||||
- If status === 'paused' -> Output "PAUSED" and stop
|
|
||||||
- If status === 'failed' -> Output "STOPPED" and stop
|
|
||||||
- If status === 'running' -> Continue
|
|
||||||
|
|
||||||
2. **Select Next Action**
|
|
||||||
Based on skill_state:
|
|
||||||
- If not initialized -> Execute INIT
|
|
||||||
- If mode === 'interactive' -> Output MENU and wait for input
|
|
||||||
- If mode === 'auto' -> Auto-select based on state
|
|
||||||
|
|
||||||
3. **Execute Action**
|
|
||||||
- Follow action instructions from ~/.codex/skills/ccw-loop/actions/
|
|
||||||
- Update progress files in ${progressDir}/
|
|
||||||
- Update state in ${projectRoot}/.workflow/.loop/${loopId}.json
|
|
||||||
|
|
||||||
4. **Output Format**
|
|
||||||
\`\`\`
|
|
||||||
ACTION_RESULT:
|
|
||||||
- action: {action_name}
|
|
||||||
- status: success | failed | needs_input
|
|
||||||
- message: {user message}
|
|
||||||
- state_updates: {JSON of skill_state updates}
|
|
||||||
|
|
||||||
NEXT_ACTION_NEEDED: {action_name} | WAITING_INPUT | COMPLETED | PAUSED
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## FIRST ACTION
|
|
||||||
|
|
||||||
${!state.skill_state ? 'Execute: INIT' : mode === 'auto' ? 'Auto-select next action' : 'Show MENU'}
|
|
||||||
`
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.2: Main Orchestration Loop
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let iteration = state.current_iteration || 0
|
|
||||||
const maxIterations = state.max_iterations || 10
|
|
||||||
let continueLoop = true
|
|
||||||
|
|
||||||
while (continueLoop && iteration < maxIterations) {
|
|
||||||
iteration++
|
|
||||||
|
|
||||||
// Wait for agent output
|
|
||||||
const result = wait({ ids: [agent], timeout_ms: 600000 })
|
|
||||||
|
|
||||||
// Handle timeout
|
|
||||||
if (result.timed_out) {
|
|
||||||
console.log('Agent timeout, requesting convergence...')
|
|
||||||
send_input({
|
|
||||||
id: agent,
|
|
||||||
message: `
|
|
||||||
## TIMEOUT NOTIFICATION
|
|
||||||
|
|
||||||
Execution timeout reached. Please:
|
|
||||||
1. Output current progress
|
|
||||||
2. Save any pending state updates
|
|
||||||
3. Return ACTION_RESULT with current status
|
|
||||||
`
|
|
||||||
})
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
const output = result.status[agent].completed
|
|
||||||
|
|
||||||
// Parse action result
|
|
||||||
const actionResult = parseActionResult(output)
|
|
||||||
|
|
||||||
console.log(`\n[Iteration ${iteration}] Action: ${actionResult.action}, Status: ${actionResult.status}`)
|
|
||||||
|
|
||||||
// Update iteration in state
|
|
||||||
state = readLoopState(loopId)
|
|
||||||
state.current_iteration = iteration
|
|
||||||
state.updated_at = getUtc8ISOString()
|
|
||||||
Write(`${projectRoot}/.workflow/.loop/${loopId}.json`, JSON.stringify(state, null, 2))
|
|
||||||
|
|
||||||
// Handle different outcomes
|
|
||||||
switch (actionResult.next_action) {
|
|
||||||
case 'COMPLETED':
|
|
||||||
console.log('Loop completed successfully')
|
|
||||||
continueLoop = false
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'PAUSED':
|
|
||||||
console.log('Loop paused by API, exiting gracefully')
|
|
||||||
continueLoop = false
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'STOPPED':
|
|
||||||
console.log('Loop stopped by API')
|
|
||||||
continueLoop = false
|
|
||||||
break
|
|
||||||
|
|
||||||
case 'WAITING_INPUT':
|
|
||||||
// Interactive mode: display menu, get user choice
|
|
||||||
if (mode === 'interactive') {
|
|
||||||
const userChoice = await displayMenuAndGetChoice(actionResult)
|
|
||||||
|
|
||||||
send_input({
|
|
||||||
id: agent,
|
|
||||||
message: `
|
|
||||||
## USER INPUT RECEIVED
|
|
||||||
|
|
||||||
Action selected: ${userChoice.action}
|
|
||||||
${userChoice.data ? `Additional data: ${JSON.stringify(userChoice.data)}` : ''}
|
|
||||||
|
|
||||||
## EXECUTE SELECTED ACTION
|
|
||||||
|
|
||||||
Read action instructions and execute: ${userChoice.action}
|
|
||||||
Update state and progress files accordingly.
|
|
||||||
Output ACTION_RESULT when complete.
|
|
||||||
`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
break
|
|
||||||
|
|
||||||
default:
|
|
||||||
// Continue with next action
|
|
||||||
if (actionResult.next_action && actionResult.next_action !== 'NONE') {
|
|
||||||
send_input({
|
|
||||||
id: agent,
|
|
||||||
message: `
|
|
||||||
## CONTINUE EXECUTION
|
|
||||||
|
|
||||||
Previous action completed: ${actionResult.action}
|
|
||||||
Result: ${actionResult.status}
|
|
||||||
${actionResult.message ? `Message: ${actionResult.message}` : ''}
|
|
||||||
|
|
||||||
## EXECUTE NEXT ACTION
|
|
||||||
|
|
||||||
Continue with: ${actionResult.next_action}
|
|
||||||
Read action instructions and execute.
|
|
||||||
Output ACTION_RESULT when complete.
|
|
||||||
`
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
if (actionResult.status === 'failed') {
|
|
||||||
console.log(`Action failed: ${actionResult.message}`)
|
|
||||||
}
|
|
||||||
continueLoop = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.3: Iteration Limit Check
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (iteration >= maxIterations) {
|
|
||||||
console.log(`\nReached maximum iterations (${maxIterations})`)
|
|
||||||
console.log('Consider breaking down the task or taking a break.')
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.4: Cleanup
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
close_agent({ id: agent })
|
|
||||||
|
|
||||||
console.log('\n=== CCW Loop Orchestrator Finished ===')
|
|
||||||
|
|
||||||
const finalState = readLoopState(loopId)
|
|
||||||
return {
|
|
||||||
status: finalState.status,
|
|
||||||
loop_id: loopId,
|
|
||||||
iterations: iteration,
|
|
||||||
final_state: finalState
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### parseActionResult
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parseActionResult(output) {
|
|
||||||
const result = {
|
|
||||||
action: 'unknown',
|
|
||||||
status: 'unknown',
|
|
||||||
message: '',
|
|
||||||
state_updates: {},
|
|
||||||
next_action: 'NONE'
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse ACTION_RESULT block
|
|
||||||
const actionMatch = output.match(/ACTION_RESULT:\s*([\s\S]*?)(?:FILES_UPDATED:|NEXT_ACTION_NEEDED:|$)/)
|
|
||||||
if (actionMatch) {
|
|
||||||
const lines = actionMatch[1].split('\n')
|
|
||||||
for (const line of lines) {
|
|
||||||
const match = line.match(/^-\s*(\w+):\s*(.+)$/)
|
|
||||||
if (match) {
|
|
||||||
const [, key, value] = match
|
|
||||||
if (key === 'state_updates') {
|
|
||||||
try {
|
|
||||||
result.state_updates = JSON.parse(value)
|
|
||||||
} catch (e) {
|
|
||||||
// Try parsing multi-line JSON
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
result[key] = value.trim()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse NEXT_ACTION_NEEDED
|
|
||||||
const nextMatch = output.match(/NEXT_ACTION_NEEDED:\s*(\S+)/)
|
|
||||||
if (nextMatch) {
|
|
||||||
result.next_action = nextMatch[1]
|
|
||||||
}
|
|
||||||
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### displayMenuAndGetChoice
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
async function displayMenuAndGetChoice(actionResult) {
|
|
||||||
const menuMatch = actionResult.message.match(/MENU_OPTIONS:\s*([\s\S]*?)(?:WAITING_INPUT:|$)/)
|
|
||||||
|
|
||||||
if (menuMatch) {
|
|
||||||
console.log('\n' + menuMatch[1])
|
|
||||||
}
|
|
||||||
|
|
||||||
const response = await ASK_USER([{
|
|
||||||
id: "Action", type: "select",
|
|
||||||
prompt: "Select next action:",
|
|
||||||
options: [
|
|
||||||
{ label: "develop", description: "Continue development" },
|
|
||||||
{ label: "debug", description: "Start debugging" },
|
|
||||||
{ label: "validate", description: "Run validation" },
|
|
||||||
{ label: "complete", description: "Complete loop" },
|
|
||||||
{ label: "exit", description: "Exit and save" }
|
|
||||||
]
|
|
||||||
}]) // BLOCKS (wait for user response)
|
|
||||||
|
|
||||||
return { action: response["Action"] }
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Termination Conditions
|
|
||||||
|
|
||||||
1. **API Paused**: `state.status === 'paused'` (Skill exits, wait for resume)
|
|
||||||
2. **API Stopped**: `state.status === 'failed'` (Skill terminates)
|
|
||||||
3. **Task Complete**: `NEXT_ACTION_NEEDED === 'COMPLETED'`
|
|
||||||
4. **Iteration Limit**: `current_iteration >= max_iterations`
|
|
||||||
5. **User Exit**: User selects 'exit' in interactive mode
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Variable**: `finalState` - Final loop state after all iterations
|
|
||||||
- **Return**: `{ status, loop_id, iterations, final_state }`
|
|
||||||
- **TodoWrite**: Mark Phase 2 completed
|
|
||||||
|
|
||||||
## Next Phase
|
|
||||||
|
|
||||||
None. Phase 2 is the terminal phase of the orchestrator.
|
|
||||||
@@ -11,6 +11,7 @@ import {
|
|||||||
AlertCircle,
|
AlertCircle,
|
||||||
ListChecks,
|
ListChecks,
|
||||||
Info,
|
Info,
|
||||||
|
FolderOpen,
|
||||||
LayoutGrid,
|
LayoutGrid,
|
||||||
Columns2,
|
Columns2,
|
||||||
Rows2,
|
Rows2,
|
||||||
@@ -47,7 +48,7 @@ import { CliConfigModal, type CliSessionConfig } from './CliConfigModal';
|
|||||||
|
|
||||||
// ========== Types ==========
|
// ========== Types ==========
|
||||||
|
|
||||||
export type PanelId = 'issues' | 'queue' | 'inspector';
|
export type PanelId = 'issues' | 'queue' | 'inspector' | 'files';
|
||||||
|
|
||||||
interface DashboardToolbarProps {
|
interface DashboardToolbarProps {
|
||||||
activePanel: PanelId | null;
|
activePanel: PanelId | null;
|
||||||
@@ -292,6 +293,12 @@ export function DashboardToolbar({ activePanel, onTogglePanel }: DashboardToolba
|
|||||||
onClick={() => onTogglePanel('inspector')}
|
onClick={() => onTogglePanel('inspector')}
|
||||||
dot={hasChain}
|
dot={hasChain}
|
||||||
/>
|
/>
|
||||||
|
<ToolbarButton
|
||||||
|
icon={FolderOpen}
|
||||||
|
label={formatMessage({ id: 'terminalDashboard.toolbar.files', defaultMessage: 'Files' })}
|
||||||
|
isActive={activePanel === 'files'}
|
||||||
|
onClick={() => onTogglePanel('files')}
|
||||||
|
/>
|
||||||
|
|
||||||
{/* Separator */}
|
{/* Separator */}
|
||||||
<div className="w-px h-5 bg-border mx-1" />
|
<div className="w-px h-5 bg-border mx-1" />
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ export interface FloatingFileBrowserProps {
|
|||||||
rootPath: string;
|
rootPath: string;
|
||||||
onInsertPath?: (path: string) => void;
|
onInsertPath?: (path: string) => void;
|
||||||
initialSelectedPath?: string | null;
|
initialSelectedPath?: string | null;
|
||||||
|
width?: number | string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function FloatingFileBrowser({
|
export function FloatingFileBrowser({
|
||||||
@@ -28,6 +29,7 @@ export function FloatingFileBrowser({
|
|||||||
rootPath,
|
rootPath,
|
||||||
onInsertPath,
|
onInsertPath,
|
||||||
initialSelectedPath = null,
|
initialSelectedPath = null,
|
||||||
|
width = 400,
|
||||||
}: FloatingFileBrowserProps) {
|
}: FloatingFileBrowserProps) {
|
||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
|
|
||||||
@@ -89,7 +91,7 @@ export function FloatingFileBrowser({
|
|||||||
onClose={onClose}
|
onClose={onClose}
|
||||||
title={formatMessage({ id: 'terminalDashboard.fileBrowser.title' })}
|
title={formatMessage({ id: 'terminalDashboard.fileBrowser.title' })}
|
||||||
side="right"
|
side="right"
|
||||||
width={400}
|
width={width}
|
||||||
>
|
>
|
||||||
<div className="flex flex-col h-full">
|
<div className="flex flex-col h-full">
|
||||||
{/* Toolbar */}
|
{/* Toolbar */}
|
||||||
@@ -148,7 +150,7 @@ export function FloatingFileBrowser({
|
|||||||
{/* Body */}
|
{/* Body */}
|
||||||
<div className="flex-1 min-h-0 flex overflow-hidden">
|
<div className="flex-1 min-h-0 flex overflow-hidden">
|
||||||
{/* Tree */}
|
{/* Tree */}
|
||||||
<div className="w-[180px] shrink-0 border-r border-border overflow-y-auto">
|
<div className="w-[240px] shrink-0 border-r border-border overflow-y-auto">
|
||||||
{isLoading ? (
|
{isLoading ? (
|
||||||
<div className="flex items-center justify-center py-8 text-muted-foreground">
|
<div className="flex items-center justify-center py-8 text-muted-foreground">
|
||||||
<Loader2 className="w-5 h-5 animate-spin" />
|
<Loader2 className="w-5 h-5 animate-spin" />
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ interface FloatingPanelProps {
|
|||||||
onClose: () => void;
|
onClose: () => void;
|
||||||
title: string;
|
title: string;
|
||||||
side?: 'left' | 'right';
|
side?: 'left' | 'right';
|
||||||
width?: number;
|
width?: number | string;
|
||||||
children: React.ReactNode;
|
children: React.ReactNode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,7 +77,7 @@ export function FloatingPanel({
|
|||||||
style={{
|
style={{
|
||||||
top: '40px', // Below toolbar
|
top: '40px', // Below toolbar
|
||||||
height: 'calc(100vh - 40px)', // Full height below toolbar
|
height: 'calc(100vh - 40px)', // Full height below toolbar
|
||||||
width: `${width}px`,
|
width: typeof width === 'number' ? `${width}px` : width,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{/* Panel header */}
|
{/* Panel header */}
|
||||||
|
|||||||
@@ -82,8 +82,9 @@ function IssueItem({
|
|||||||
);
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<button
|
<div
|
||||||
type="button"
|
role="button"
|
||||||
|
tabIndex={0}
|
||||||
className={cn(
|
className={cn(
|
||||||
'w-full text-left px-2.5 py-1.5 rounded-md transition-colors',
|
'w-full text-left px-2.5 py-1.5 rounded-md transition-colors',
|
||||||
'hover:bg-muted/60 focus:outline-none focus:ring-1 focus:ring-primary/30',
|
'hover:bg-muted/60 focus:outline-none focus:ring-1 focus:ring-primary/30',
|
||||||
@@ -91,6 +92,7 @@ function IssueItem({
|
|||||||
isHighlighted && !isSelected && 'bg-accent/50'
|
isHighlighted && !isSelected && 'bg-accent/50'
|
||||||
)}
|
)}
|
||||||
onClick={onSelect}
|
onClick={onSelect}
|
||||||
|
onKeyDown={(e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onSelect(); } }}
|
||||||
>
|
>
|
||||||
<div className="flex items-center justify-between gap-2">
|
<div className="flex items-center justify-between gap-2">
|
||||||
<div className="flex items-center gap-2 min-w-0">
|
<div className="flex items-center gap-2 min-w-0">
|
||||||
@@ -129,7 +131,7 @@ function IssueItem({
|
|||||||
</>
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
</button>
|
</div>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"issues": "Issues",
|
"issues": "Issues",
|
||||||
"queue": "Queue",
|
"queue": "Queue",
|
||||||
"inspector": "Inspector",
|
"inspector": "Inspector",
|
||||||
|
"files": "Files",
|
||||||
"layoutSingle": "Single",
|
"layoutSingle": "Single",
|
||||||
"layoutSplitH": "Split Horizontal",
|
"layoutSplitH": "Split Horizontal",
|
||||||
"layoutSplitV": "Split Vertical",
|
"layoutSplitV": "Split Vertical",
|
||||||
|
|||||||
@@ -73,6 +73,7 @@
|
|||||||
"issues": "问题",
|
"issues": "问题",
|
||||||
"queue": "队列",
|
"queue": "队列",
|
||||||
"inspector": "检查器",
|
"inspector": "检查器",
|
||||||
|
"files": "文件",
|
||||||
"layoutSingle": "单窗格",
|
"layoutSingle": "单窗格",
|
||||||
"layoutSplitH": "左右分割",
|
"layoutSplitH": "左右分割",
|
||||||
"layoutSplitV": "上下分割",
|
"layoutSplitV": "上下分割",
|
||||||
|
|||||||
@@ -18,6 +18,10 @@ import { AgentList } from '@/components/terminal-dashboard/AgentList';
|
|||||||
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel';
|
import { IssuePanel } from '@/components/terminal-dashboard/IssuePanel';
|
||||||
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel';
|
import { QueuePanel } from '@/components/terminal-dashboard/QueuePanel';
|
||||||
import { InspectorContent } from '@/components/terminal-dashboard/BottomInspector';
|
import { InspectorContent } from '@/components/terminal-dashboard/BottomInspector';
|
||||||
|
import { FloatingFileBrowser } from '@/components/terminal-dashboard/FloatingFileBrowser';
|
||||||
|
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||||
|
import { useTerminalGridStore, selectTerminalGridFocusedPaneId } from '@/stores/terminalGridStore';
|
||||||
|
import { sendCliSessionText } from '@/lib/api';
|
||||||
|
|
||||||
// ========== Main Page Component ==========
|
// ========== Main Page Component ==========
|
||||||
|
|
||||||
@@ -25,6 +29,10 @@ export function TerminalDashboardPage() {
|
|||||||
const { formatMessage } = useIntl();
|
const { formatMessage } = useIntl();
|
||||||
const [activePanel, setActivePanel] = useState<PanelId | null>(null);
|
const [activePanel, setActivePanel] = useState<PanelId | null>(null);
|
||||||
|
|
||||||
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
|
const focusedPaneId = useTerminalGridStore(selectTerminalGridFocusedPaneId);
|
||||||
|
const panes = useTerminalGridStore((s) => s.panes);
|
||||||
|
|
||||||
const togglePanel = useCallback((panelId: PanelId) => {
|
const togglePanel = useCallback((panelId: PanelId) => {
|
||||||
setActivePanel((prev) => (prev === panelId ? null : panelId));
|
setActivePanel((prev) => (prev === panelId ? null : panelId));
|
||||||
}, []);
|
}, []);
|
||||||
@@ -33,6 +41,20 @@ export function TerminalDashboardPage() {
|
|||||||
setActivePanel(null);
|
setActivePanel(null);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleInsertPath = useCallback(
|
||||||
|
(path: string) => {
|
||||||
|
if (!focusedPaneId) return;
|
||||||
|
const sessionId = panes[focusedPaneId]?.sessionId;
|
||||||
|
if (!sessionId) return;
|
||||||
|
sendCliSessionText(
|
||||||
|
sessionId,
|
||||||
|
{ text: path, appendNewline: false },
|
||||||
|
projectPath ?? undefined
|
||||||
|
).catch((err) => console.error('[TerminalDashboard] insert path failed:', err));
|
||||||
|
},
|
||||||
|
[focusedPaneId, panes, projectPath]
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="-m-4 md:-m-6 flex flex-col h-[calc(100vh-56px)] overflow-hidden">
|
<div className="-m-4 md:-m-6 flex flex-col h-[calc(100vh-56px)] overflow-hidden">
|
||||||
<AssociationHighlightProvider>
|
<AssociationHighlightProvider>
|
||||||
@@ -90,6 +112,15 @@ export function TerminalDashboardPage() {
|
|||||||
>
|
>
|
||||||
<InspectorContent />
|
<InspectorContent />
|
||||||
</FloatingPanel>
|
</FloatingPanel>
|
||||||
|
|
||||||
|
{/* File browser (half screen, right side) */}
|
||||||
|
<FloatingFileBrowser
|
||||||
|
isOpen={activePanel === 'files'}
|
||||||
|
onClose={closePanel}
|
||||||
|
rootPath={projectPath ?? '/'}
|
||||||
|
onInsertPath={focusedPaneId ? handleInsertPath : undefined}
|
||||||
|
width="50vw"
|
||||||
|
/>
|
||||||
</AssociationHighlightProvider>
|
</AssociationHighlightProvider>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -235,6 +235,10 @@ export const useConfigStore = create<ConfigStore>()(
|
|||||||
secondaryModel: t.secondaryModel || '',
|
secondaryModel: t.secondaryModel || '',
|
||||||
tags: t.tags || [],
|
tags: t.tags || [],
|
||||||
type: t.type || 'builtin',
|
type: t.type || 'builtin',
|
||||||
|
// Load additional fields from backend (fixes cross-browser config sync)
|
||||||
|
envFile: t.envFile,
|
||||||
|
settingsFile: t.settingsFile,
|
||||||
|
availableModels: t.availableModels,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
if (Object.keys(cliTools).length > 0) {
|
if (Object.keys(cliTools).length > 0) {
|
||||||
|
|||||||
@@ -442,9 +442,8 @@ import sys
|
|||||||
def test_imports_inside_function(self) -> None:
|
def test_imports_inside_function(self) -> None:
|
||||||
"""Test simple import inside a function scope is recorded.
|
"""Test simple import inside a function scope is recorded.
|
||||||
|
|
||||||
Note: tree-sitter parser requires a scope to record imports.
|
Note: module-level imports are recorded under a synthetic "<module>" scope.
|
||||||
Module-level imports without any function/class are not recorded
|
This test ensures imports inside a function scope are also recorded.
|
||||||
because scope_stack is empty at module level.
|
|
||||||
"""
|
"""
|
||||||
code = """
|
code = """
|
||||||
def my_function():
|
def my_function():
|
||||||
|
|||||||
Reference in New Issue
Block a user