mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-12 02:37:45 +08:00
Add benchmark results and tests for StandaloneLspManager path normalization
- Created a new JSON file with benchmark results from the run on 2026-02-09. - Added tests for the StandaloneLspManager to verify path normalization on Windows, including handling of percent-encoded URIs and ensuring plain Windows paths remain unchanged.
This commit is contained in:
836
.claude/agents/cli-roadmap-plan-agent.md
Normal file
836
.claude/agents/cli-roadmap-plan-agent.md
Normal file
@@ -0,0 +1,836 @@
|
|||||||
|
---
|
||||||
|
name: cli-roadmap-plan-agent
|
||||||
|
description: |
|
||||||
|
Specialized agent for requirement-level roadmap planning with JSONL output.
|
||||||
|
Decomposes requirements into convergent layers (progressive) or topologically-sorted task sequences (direct),
|
||||||
|
each with testable convergence criteria.
|
||||||
|
|
||||||
|
Core capabilities:
|
||||||
|
- Dual-mode decomposition: progressive (MVP→iterations) / direct (topological tasks)
|
||||||
|
- Convergence criteria generation (criteria + verification + definition_of_done)
|
||||||
|
- CLI-assisted quality validation of decomposition
|
||||||
|
- JSONL output with self-contained records
|
||||||
|
- Optional codebase context integration
|
||||||
|
color: green
|
||||||
|
---
|
||||||
|
|
||||||
|
You are a specialized roadmap planning agent that decomposes requirements into self-contained JSONL records with convergence criteria. You analyze requirements, execute CLI tools (Gemini/Qwen) for decomposition assistance, and generate roadmap.jsonl + roadmap.md conforming to the specified mode (progressive or direct).
|
||||||
|
|
||||||
|
**CRITICAL**: After generating roadmap.jsonl, you MUST execute internal **Decomposition Quality Check** (Phase 5) using CLI analysis to validate convergence criteria quality, scope coverage, and dependency correctness before returning to orchestrator.
|
||||||
|
|
||||||
|
## Output Artifacts
|
||||||
|
|
||||||
|
| Artifact | Description |
|
||||||
|
|----------|-------------|
|
||||||
|
| `roadmap.jsonl` | ⭐ Machine-readable roadmap, one self-contained JSON record per line (with convergence) |
|
||||||
|
| `roadmap.md` | ⭐ Human-readable roadmap with tables and convergence details |
|
||||||
|
|
||||||
|
## Input Context
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
// Required
|
||||||
|
requirement: string, // Original requirement description
|
||||||
|
selected_mode: "progressive" | "direct", // Decomposition strategy
|
||||||
|
session: { id, folder }, // Session metadata
|
||||||
|
|
||||||
|
// Strategy context
|
||||||
|
strategy_assessment: {
|
||||||
|
uncertainty_level: "high" | "medium" | "low",
|
||||||
|
goal: string,
|
||||||
|
constraints: string[],
|
||||||
|
stakeholders: string[],
|
||||||
|
domain_keywords: string[]
|
||||||
|
},
|
||||||
|
|
||||||
|
// Optional codebase context
|
||||||
|
exploration_context: { // From cli-explore-agent (null if no codebase)
|
||||||
|
relevant_modules: [{name, path, relevance}],
|
||||||
|
existing_patterns: [{pattern, files, description}],
|
||||||
|
integration_points: [{location, description, risk}],
|
||||||
|
architecture_constraints: string[],
|
||||||
|
tech_stack: object
|
||||||
|
} | null,
|
||||||
|
|
||||||
|
// CLI configuration
|
||||||
|
cli_config: {
|
||||||
|
tool: string, // Default: "gemini"
|
||||||
|
fallback: string, // Default: "qwen"
|
||||||
|
timeout: number // Default: 60000
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## JSONL Record Schemas
|
||||||
|
|
||||||
|
### Progressive Mode - Layer Record
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: "L{n}", // L0, L1, L2, L3
|
||||||
|
name: string, // Layer name: MVP / 可用 / 完善 / 优化
|
||||||
|
goal: string, // Layer goal (one sentence)
|
||||||
|
scope: [string], // Features included in this layer
|
||||||
|
excludes: [string], // Features explicitly excluded from this layer
|
||||||
|
convergence: {
|
||||||
|
criteria: [string], // Testable conditions (can be asserted or manually verified)
|
||||||
|
verification: string, // How to verify (command, script, or explicit steps)
|
||||||
|
definition_of_done: string // Business-language completion definition
|
||||||
|
},
|
||||||
|
risk_items: [string], // Risk items for this layer
|
||||||
|
effort: "small" | "medium" | "large", // Effort estimate
|
||||||
|
depends_on: ["L{n}"] // Preceding layers
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct Mode - Task Record
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: "T{n}", // T1, T2, T3, ...
|
||||||
|
title: string, // Task title
|
||||||
|
type: "infrastructure" | "feature" | "enhancement" | "testing",
|
||||||
|
scope: string, // Task scope description
|
||||||
|
inputs: [string], // Input dependencies (files/modules)
|
||||||
|
outputs: [string], // Outputs produced (files/modules)
|
||||||
|
convergence: {
|
||||||
|
criteria: [string], // Testable conditions
|
||||||
|
verification: string, // Verification method
|
||||||
|
definition_of_done: string // Business-language completion definition
|
||||||
|
},
|
||||||
|
depends_on: ["T{n}"], // Preceding tasks
|
||||||
|
parallel_group: number // Parallel group number (same group = parallelizable)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Convergence Quality Requirements
|
||||||
|
|
||||||
|
Every `convergence` field MUST satisfy:
|
||||||
|
|
||||||
|
| Field | Requirement | Bad Example | Good Example |
|
||||||
|
|-------|-------------|-------------|--------------|
|
||||||
|
| `criteria[]` | **Testable** - can write assertions or manual steps | `"系统工作正常"` | `"API 返回 200 且响应体包含 user_id 字段"` |
|
||||||
|
| `verification` | **Executable** - command, script, or clear steps | `"检查一下"` | `"jest --testPathPattern=auth && curl -s localhost:3000/health"` |
|
||||||
|
| `definition_of_done` | **Business language** - non-technical person can judge | `"代码通过编译"` | `"新用户可完成注册→登录→执行核心操作的完整流程"` |
|
||||||
|
|
||||||
|
## Execution Flow
|
||||||
|
|
||||||
|
```
|
||||||
|
Phase 1: Context Loading & Requirement Analysis
|
||||||
|
├─ Read input context (strategy, exploration, constraints)
|
||||||
|
├─ Parse requirement into goal / constraints / stakeholders
|
||||||
|
└─ Determine decomposition approach for selected mode
|
||||||
|
|
||||||
|
Phase 2: CLI-Assisted Decomposition
|
||||||
|
├─ Construct CLI prompt with requirement + context + mode
|
||||||
|
├─ Execute Gemini (fallback: Qwen → manual decomposition)
|
||||||
|
├─ Timeout: 60 minutes
|
||||||
|
└─ Parse CLI output into structured records
|
||||||
|
|
||||||
|
Phase 3: Record Enhancement & Validation
|
||||||
|
├─ Validate each record against schema
|
||||||
|
├─ Enhance convergence criteria quality
|
||||||
|
├─ Validate dependency graph (no cycles)
|
||||||
|
├─ Progressive: verify scope coverage (no overlap, no gaps)
|
||||||
|
├─ Direct: verify inputs/outputs chain, assign parallel_groups
|
||||||
|
└─ Generate roadmap.jsonl
|
||||||
|
|
||||||
|
Phase 4: Human-Readable Output
|
||||||
|
├─ Generate roadmap.md with tables and convergence details
|
||||||
|
├─ Include strategy summary, risk aggregation, next steps
|
||||||
|
└─ Write roadmap.md
|
||||||
|
|
||||||
|
Phase 5: Decomposition Quality Check (MANDATORY)
|
||||||
|
├─ Execute CLI quality check using Gemini (Qwen fallback)
|
||||||
|
├─ Analyze quality dimensions:
|
||||||
|
│ ├─ Requirement coverage (all aspects of original requirement addressed)
|
||||||
|
│ ├─ Convergence quality (criteria testable, verification executable, DoD business-readable)
|
||||||
|
│ ├─ Scope integrity (progressive: no overlap; direct: inputs/outputs chain)
|
||||||
|
│ ├─ Dependency correctness (no circular deps, proper ordering)
|
||||||
|
│ └─ Effort balance (no single layer/task disproportionately large)
|
||||||
|
├─ Parse check results
|
||||||
|
└─ Decision:
|
||||||
|
├─ PASS → Return to orchestrator
|
||||||
|
├─ AUTO_FIX → Fix convergence wording, rebalance scope → Update files → Return
|
||||||
|
└─ NEEDS_REVIEW → Report critical issues to orchestrator
|
||||||
|
```
|
||||||
|
|
||||||
|
## CLI Command Templates
|
||||||
|
|
||||||
|
### Progressive Mode Decomposition
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw cli -p "
|
||||||
|
PURPOSE: Decompose requirement into progressive layers (MVP→iterations) with convergence criteria
|
||||||
|
Success: 2-4 self-contained layers, each with testable convergence, no scope overlap
|
||||||
|
|
||||||
|
REQUIREMENT:
|
||||||
|
${requirement}
|
||||||
|
|
||||||
|
STRATEGY CONTEXT:
|
||||||
|
- Uncertainty: ${strategy_assessment.uncertainty_level}
|
||||||
|
- Goal: ${strategy_assessment.goal}
|
||||||
|
- Constraints: ${strategy_assessment.constraints.join(', ')}
|
||||||
|
- Stakeholders: ${strategy_assessment.stakeholders.join(', ')}
|
||||||
|
|
||||||
|
${exploration_context ? `CODEBASE CONTEXT:
|
||||||
|
- Relevant modules: ${exploration_context.relevant_modules.map(m => m.name).join(', ')}
|
||||||
|
- Existing patterns: ${exploration_context.existing_patterns.map(p => p.pattern).join(', ')}
|
||||||
|
- Architecture constraints: ${exploration_context.architecture_constraints.join(', ')}
|
||||||
|
- Tech stack: ${JSON.stringify(exploration_context.tech_stack)}` : 'NO CODEBASE (pure requirement decomposition)'}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
• Define 2-4 progressive layers from MVP to full implementation
|
||||||
|
• L0 (MVP): Minimum viable closed loop - core path works end-to-end
|
||||||
|
• L1 (Usable): Critical user paths, basic error handling
|
||||||
|
• L2 (Complete): Edge cases, performance, security hardening
|
||||||
|
• L3 (Optimized): Advanced features, observability, operations support
|
||||||
|
• Each layer: explicit scope (included) and excludes (not included)
|
||||||
|
• Each layer: convergence with testable criteria, executable verification, business-language DoD
|
||||||
|
• Risk items per layer
|
||||||
|
|
||||||
|
MODE: analysis
|
||||||
|
CONTEXT: @**/*
|
||||||
|
EXPECTED:
|
||||||
|
For each layer output:
|
||||||
|
## L{n}: {Name}
|
||||||
|
**Goal**: {one sentence}
|
||||||
|
**Scope**: {comma-separated features}
|
||||||
|
**Excludes**: {comma-separated excluded features}
|
||||||
|
**Convergence**:
|
||||||
|
- Criteria: {bullet list of testable conditions}
|
||||||
|
- Verification: {executable command or steps}
|
||||||
|
- Definition of Done: {business language sentence}
|
||||||
|
**Risk Items**: {bullet list}
|
||||||
|
**Effort**: {small|medium|large}
|
||||||
|
**Depends On**: {layer IDs or none}
|
||||||
|
|
||||||
|
CONSTRAINTS:
|
||||||
|
- Each feature belongs to exactly ONE layer (no overlap)
|
||||||
|
- Criteria must be testable (can write assertions)
|
||||||
|
- Verification must be executable (commands or explicit steps)
|
||||||
|
- Definition of Done must be understandable by non-technical stakeholders
|
||||||
|
- L0 must be a complete closed loop (end-to-end path works)
|
||||||
|
" --tool ${cli_config.tool} --mode analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Direct Mode Decomposition
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw cli -p "
|
||||||
|
PURPOSE: Decompose requirement into topologically-sorted task sequence with convergence criteria
|
||||||
|
Success: Self-contained tasks with clear inputs/outputs, testable convergence, correct dependency order
|
||||||
|
|
||||||
|
REQUIREMENT:
|
||||||
|
${requirement}
|
||||||
|
|
||||||
|
STRATEGY CONTEXT:
|
||||||
|
- Goal: ${strategy_assessment.goal}
|
||||||
|
- Constraints: ${strategy_assessment.constraints.join(', ')}
|
||||||
|
|
||||||
|
${exploration_context ? `CODEBASE CONTEXT:
|
||||||
|
- Relevant modules: ${exploration_context.relevant_modules.map(m => m.name).join(', ')}
|
||||||
|
- Existing patterns: ${exploration_context.existing_patterns.map(p => p.pattern).join(', ')}
|
||||||
|
- Tech stack: ${JSON.stringify(exploration_context.tech_stack)}` : 'NO CODEBASE (pure requirement decomposition)'}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
• Decompose into vertical slices with clear boundaries
|
||||||
|
• Each task: type (infrastructure|feature|enhancement|testing)
|
||||||
|
• Each task: explicit inputs (what it needs) and outputs (what it produces)
|
||||||
|
• Each task: convergence with testable criteria, executable verification, business-language DoD
|
||||||
|
• Topological sort: respect dependency order
|
||||||
|
• Assign parallel_group numbers (same group = can run in parallel)
|
||||||
|
|
||||||
|
MODE: analysis
|
||||||
|
CONTEXT: @**/*
|
||||||
|
EXPECTED:
|
||||||
|
For each task output:
|
||||||
|
## T{n}: {Title}
|
||||||
|
**Type**: {infrastructure|feature|enhancement|testing}
|
||||||
|
**Scope**: {description}
|
||||||
|
**Inputs**: {comma-separated files/modules or 'none'}
|
||||||
|
**Outputs**: {comma-separated files/modules}
|
||||||
|
**Convergence**:
|
||||||
|
- Criteria: {bullet list of testable conditions}
|
||||||
|
- Verification: {executable command or steps}
|
||||||
|
- Definition of Done: {business language sentence}
|
||||||
|
**Depends On**: {task IDs or none}
|
||||||
|
**Parallel Group**: {number}
|
||||||
|
|
||||||
|
CONSTRAINTS:
|
||||||
|
- Inputs must come from preceding task outputs or existing resources
|
||||||
|
- No circular dependencies
|
||||||
|
- Criteria must be testable
|
||||||
|
- Verification must be executable
|
||||||
|
- Tasks in same parallel_group must be truly independent
|
||||||
|
" --tool ${cli_config.tool} --mode analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Functions
|
||||||
|
|
||||||
|
### CLI Output Parsing
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Parse progressive layers from CLI output
|
||||||
|
function parseProgressiveLayers(cliOutput) {
|
||||||
|
const layers = []
|
||||||
|
const layerBlocks = cliOutput.split(/## L(\d+):/).slice(1)
|
||||||
|
|
||||||
|
for (let i = 0; i < layerBlocks.length; i += 2) {
|
||||||
|
const layerId = `L${layerBlocks[i].trim()}`
|
||||||
|
const text = layerBlocks[i + 1]
|
||||||
|
|
||||||
|
const nameMatch = /^(.+?)(?=\n)/.exec(text)
|
||||||
|
const goalMatch = /\*\*Goal\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const scopeMatch = /\*\*Scope\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const excludesMatch = /\*\*Excludes\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const effortMatch = /\*\*Effort\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const dependsMatch = /\*\*Depends On\*\*:\s*(.+?)(?=\n|$)/.exec(text)
|
||||||
|
const riskMatch = /\*\*Risk Items\*\*:\n((?:- .+?\n)*)/.exec(text)
|
||||||
|
|
||||||
|
const convergence = parseConvergence(text)
|
||||||
|
|
||||||
|
layers.push({
|
||||||
|
id: layerId,
|
||||||
|
name: nameMatch?.[1].trim() || `Layer ${layerId}`,
|
||||||
|
goal: goalMatch?.[1].trim() || "",
|
||||||
|
scope: scopeMatch?.[1].split(/[,,]/).map(s => s.trim()).filter(Boolean) || [],
|
||||||
|
excludes: excludesMatch?.[1].split(/[,,]/).map(s => s.trim()).filter(Boolean) || [],
|
||||||
|
convergence,
|
||||||
|
risk_items: riskMatch
|
||||||
|
? riskMatch[1].split('\n').map(s => s.replace(/^- /, '').trim()).filter(Boolean)
|
||||||
|
: [],
|
||||||
|
effort: normalizeEffort(effortMatch?.[1].trim()),
|
||||||
|
depends_on: parseDependsOn(dependsMatch?.[1], 'L')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return layers
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse direct tasks from CLI output
|
||||||
|
function parseDirectTasks(cliOutput) {
|
||||||
|
const tasks = []
|
||||||
|
const taskBlocks = cliOutput.split(/## T(\d+):/).slice(1)
|
||||||
|
|
||||||
|
for (let i = 0; i < taskBlocks.length; i += 2) {
|
||||||
|
const taskId = `T${taskBlocks[i].trim()}`
|
||||||
|
const text = taskBlocks[i + 1]
|
||||||
|
|
||||||
|
const titleMatch = /^(.+?)(?=\n)/.exec(text)
|
||||||
|
const typeMatch = /\*\*Type\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const scopeMatch = /\*\*Scope\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const inputsMatch = /\*\*Inputs\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const outputsMatch = /\*\*Outputs\*\*:\s*(.+?)(?=\n)/.exec(text)
|
||||||
|
const dependsMatch = /\*\*Depends On\*\*:\s*(.+?)(?=\n|$)/.exec(text)
|
||||||
|
const groupMatch = /\*\*Parallel Group\*\*:\s*(\d+)/.exec(text)
|
||||||
|
|
||||||
|
const convergence = parseConvergence(text)
|
||||||
|
|
||||||
|
tasks.push({
|
||||||
|
id: taskId,
|
||||||
|
title: titleMatch?.[1].trim() || `Task ${taskId}`,
|
||||||
|
type: normalizeType(typeMatch?.[1].trim()),
|
||||||
|
scope: scopeMatch?.[1].trim() || "",
|
||||||
|
inputs: parseList(inputsMatch?.[1]),
|
||||||
|
outputs: parseList(outputsMatch?.[1]),
|
||||||
|
convergence,
|
||||||
|
depends_on: parseDependsOn(dependsMatch?.[1], 'T'),
|
||||||
|
parallel_group: parseInt(groupMatch?.[1]) || 1
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return tasks
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse convergence section from a record block
|
||||||
|
function parseConvergence(text) {
|
||||||
|
const criteriaMatch = /- Criteria:\s*((?:.+\n?)+?)(?=- Verification:)/.exec(text)
|
||||||
|
const verificationMatch = /- Verification:\s*(.+?)(?=\n- Definition)/.exec(text)
|
||||||
|
const dodMatch = /- Definition of Done:\s*(.+?)(?=\n\*\*|$)/.exec(text)
|
||||||
|
|
||||||
|
const criteria = criteriaMatch
|
||||||
|
? criteriaMatch[1].split('\n')
|
||||||
|
.map(s => s.replace(/^\s*[-•]\s*/, '').trim())
|
||||||
|
.filter(s => s && !s.startsWith('Verification') && !s.startsWith('Definition'))
|
||||||
|
: []
|
||||||
|
|
||||||
|
return {
|
||||||
|
criteria: criteria.length > 0 ? criteria : ["Task completed successfully"],
|
||||||
|
verification: verificationMatch?.[1].trim() || "Manual verification",
|
||||||
|
definition_of_done: dodMatch?.[1].trim() || "Feature works as expected"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: normalize effort string
|
||||||
|
function normalizeEffort(effort) {
|
||||||
|
if (!effort) return "medium"
|
||||||
|
const lower = effort.toLowerCase()
|
||||||
|
if (lower.includes('small') || lower.includes('low')) return "small"
|
||||||
|
if (lower.includes('large') || lower.includes('high')) return "large"
|
||||||
|
return "medium"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: normalize task type
|
||||||
|
function normalizeType(type) {
|
||||||
|
if (!type) return "feature"
|
||||||
|
const lower = type.toLowerCase()
|
||||||
|
if (lower.includes('infra')) return "infrastructure"
|
||||||
|
if (lower.includes('enhance')) return "enhancement"
|
||||||
|
if (lower.includes('test')) return "testing"
|
||||||
|
return "feature"
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: parse comma-separated list
|
||||||
|
function parseList(text) {
|
||||||
|
if (!text || text.toLowerCase() === 'none') return []
|
||||||
|
return text.split(/[,,]/).map(s => s.trim()).filter(Boolean)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper: parse depends_on field
|
||||||
|
function parseDependsOn(text, prefix) {
|
||||||
|
if (!text || text.toLowerCase() === 'none' || text === '[]') return []
|
||||||
|
const pattern = new RegExp(`${prefix}\\d+`, 'g')
|
||||||
|
return (text.match(pattern) || [])
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Validation Functions
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Validate progressive layers
|
||||||
|
function validateProgressiveLayers(layers) {
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
// Check scope overlap
|
||||||
|
const allScopes = new Map()
|
||||||
|
layers.forEach(layer => {
|
||||||
|
layer.scope.forEach(feature => {
|
||||||
|
if (allScopes.has(feature)) {
|
||||||
|
errors.push(`Scope overlap: "${feature}" in both ${allScopes.get(feature)} and ${layer.id}`)
|
||||||
|
}
|
||||||
|
allScopes.set(feature, layer.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check circular dependencies
|
||||||
|
const cycleErrors = detectCycles(layers, 'L')
|
||||||
|
errors.push(...cycleErrors)
|
||||||
|
|
||||||
|
// Check convergence quality
|
||||||
|
layers.forEach(layer => {
|
||||||
|
errors.push(...validateConvergence(layer.id, layer.convergence))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check L0 is self-contained (no depends_on)
|
||||||
|
const l0 = layers.find(l => l.id === 'L0')
|
||||||
|
if (l0 && l0.depends_on.length > 0) {
|
||||||
|
errors.push("L0 (MVP) should not have dependencies")
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate direct tasks
|
||||||
|
function validateDirectTasks(tasks) {
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
// Check inputs/outputs chain
|
||||||
|
const availableOutputs = new Set()
|
||||||
|
const sortedTasks = topologicalSort(tasks)
|
||||||
|
|
||||||
|
sortedTasks.forEach(task => {
|
||||||
|
task.inputs.forEach(input => {
|
||||||
|
if (!availableOutputs.has(input)) {
|
||||||
|
// Check if it's an existing resource (not from a task)
|
||||||
|
// Only warn, don't error - existing files are valid inputs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
task.outputs.forEach(output => availableOutputs.add(output))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check circular dependencies
|
||||||
|
const cycleErrors = detectCycles(tasks, 'T')
|
||||||
|
errors.push(...cycleErrors)
|
||||||
|
|
||||||
|
// Check convergence quality
|
||||||
|
tasks.forEach(task => {
|
||||||
|
errors.push(...validateConvergence(task.id, task.convergence))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check parallel_group consistency
|
||||||
|
const groups = new Map()
|
||||||
|
tasks.forEach(task => {
|
||||||
|
if (!groups.has(task.parallel_group)) groups.set(task.parallel_group, [])
|
||||||
|
groups.get(task.parallel_group).push(task)
|
||||||
|
})
|
||||||
|
groups.forEach((groupTasks, groupId) => {
|
||||||
|
if (groupTasks.length > 1) {
|
||||||
|
// Tasks in same group should not depend on each other
|
||||||
|
const ids = new Set(groupTasks.map(t => t.id))
|
||||||
|
groupTasks.forEach(task => {
|
||||||
|
task.depends_on.forEach(dep => {
|
||||||
|
if (ids.has(dep)) {
|
||||||
|
errors.push(`Parallel group ${groupId}: ${task.id} depends on ${dep} but both in same group`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate convergence quality
|
||||||
|
function validateConvergence(recordId, convergence) {
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
// Check criteria are testable (not vague)
|
||||||
|
const vaguePatterns = /正常|正确|好|可以|没问题|works|fine|good|correct/i
|
||||||
|
convergence.criteria.forEach((criterion, i) => {
|
||||||
|
if (vaguePatterns.test(criterion) && criterion.length < 15) {
|
||||||
|
errors.push(`${recordId} criteria[${i}]: Too vague - "${criterion}"`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check verification is executable
|
||||||
|
if (convergence.verification.length < 10) {
|
||||||
|
errors.push(`${recordId} verification: Too short, needs executable steps`)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check definition_of_done is business language
|
||||||
|
const technicalPatterns = /compile|build|lint|npm|npx|jest|tsc|eslint/i
|
||||||
|
if (technicalPatterns.test(convergence.definition_of_done)) {
|
||||||
|
errors.push(`${recordId} definition_of_done: Should be business language, not technical commands`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detect circular dependencies
|
||||||
|
function detectCycles(records, prefix) {
|
||||||
|
const errors = []
|
||||||
|
const graph = new Map(records.map(r => [r.id, r.depends_on]))
|
||||||
|
const visited = new Set()
|
||||||
|
const inStack = new Set()
|
||||||
|
|
||||||
|
function dfs(node, path) {
|
||||||
|
if (inStack.has(node)) {
|
||||||
|
errors.push(`Circular dependency detected: ${[...path, node].join(' → ')}`)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (visited.has(node)) return
|
||||||
|
|
||||||
|
visited.add(node)
|
||||||
|
inStack.add(node)
|
||||||
|
;(graph.get(node) || []).forEach(dep => dfs(dep, [...path, node]))
|
||||||
|
inStack.delete(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
records.forEach(r => {
|
||||||
|
if (!visited.has(r.id)) dfs(r.id, [])
|
||||||
|
})
|
||||||
|
|
||||||
|
return errors
|
||||||
|
}
|
||||||
|
|
||||||
|
// Topological sort
|
||||||
|
function topologicalSort(tasks) {
|
||||||
|
const result = []
|
||||||
|
const visited = new Set()
|
||||||
|
const taskMap = new Map(tasks.map(t => [t.id, t]))
|
||||||
|
|
||||||
|
function visit(taskId) {
|
||||||
|
if (visited.has(taskId)) return
|
||||||
|
visited.add(taskId)
|
||||||
|
const task = taskMap.get(taskId)
|
||||||
|
if (task) {
|
||||||
|
task.depends_on.forEach(dep => visit(dep))
|
||||||
|
result.push(task)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tasks.forEach(t => visit(t.id))
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### JSONL & Markdown Generation
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Generate roadmap.jsonl
|
||||||
|
function generateJsonl(records) {
|
||||||
|
return records.map(record => JSON.stringify(record)).join('\n') + '\n'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate roadmap.md for progressive mode
|
||||||
|
function generateProgressiveRoadmapMd(layers, input) {
|
||||||
|
return `# 需求路线图
|
||||||
|
|
||||||
|
**Session**: ${input.session.id}
|
||||||
|
**需求**: ${input.requirement}
|
||||||
|
**策略**: progressive
|
||||||
|
**不确定性**: ${input.strategy_assessment.uncertainty_level}
|
||||||
|
**生成时间**: ${new Date().toISOString()}
|
||||||
|
|
||||||
|
## 策略评估
|
||||||
|
|
||||||
|
- 目标: ${input.strategy_assessment.goal}
|
||||||
|
- 约束: ${input.strategy_assessment.constraints.join(', ') || '无'}
|
||||||
|
- 利益方: ${input.strategy_assessment.stakeholders.join(', ') || '无'}
|
||||||
|
|
||||||
|
## 路线图概览
|
||||||
|
|
||||||
|
| 层级 | 名称 | 目标 | 工作量 | 依赖 |
|
||||||
|
|------|------|------|--------|------|
|
||||||
|
${layers.map(l => `| ${l.id} | ${l.name} | ${l.goal} | ${l.effort} | ${l.depends_on.length ? l.depends_on.join(', ') : '-'} |`).join('\n')}
|
||||||
|
|
||||||
|
## 各层详情
|
||||||
|
|
||||||
|
${layers.map(l => `### ${l.id}: ${l.name}
|
||||||
|
|
||||||
|
**目标**: ${l.goal}
|
||||||
|
|
||||||
|
**范围**: ${l.scope.join('、')}
|
||||||
|
|
||||||
|
**排除**: ${l.excludes.join('、') || '无'}
|
||||||
|
|
||||||
|
**收敛标准**:
|
||||||
|
${l.convergence.criteria.map(c => `- ✅ ${c}`).join('\n')}
|
||||||
|
- 🔍 **验证方法**: ${l.convergence.verification}
|
||||||
|
- 🎯 **完成定义**: ${l.convergence.definition_of_done}
|
||||||
|
|
||||||
|
**风险项**: ${l.risk_items.length ? l.risk_items.map(r => `\n- ⚠️ ${r}`).join('') : '无'}
|
||||||
|
|
||||||
|
**工作量**: ${l.effort}
|
||||||
|
`).join('\n---\n\n')}
|
||||||
|
|
||||||
|
## 风险汇总
|
||||||
|
|
||||||
|
${layers.flatMap(l => l.risk_items.map(r => `- **${l.id}**: ${r}`)).join('\n') || '无已识别风险'}
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
每个层级可独立执行:
|
||||||
|
\`\`\`bash
|
||||||
|
/workflow:lite-plan "${layers[0]?.name}: ${layers[0]?.scope.join(', ')}"
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
路线图 JSONL 文件: \`${input.session.folder}/roadmap.jsonl\`
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate roadmap.md for direct mode
|
||||||
|
function generateDirectRoadmapMd(tasks, input) {
|
||||||
|
return `# 需求路线图
|
||||||
|
|
||||||
|
**Session**: ${input.session.id}
|
||||||
|
**需求**: ${input.requirement}
|
||||||
|
**策略**: direct
|
||||||
|
**生成时间**: ${new Date().toISOString()}
|
||||||
|
|
||||||
|
## 策略评估
|
||||||
|
|
||||||
|
- 目标: ${input.strategy_assessment.goal}
|
||||||
|
- 约束: ${input.strategy_assessment.constraints.join(', ') || '无'}
|
||||||
|
|
||||||
|
## 任务序列
|
||||||
|
|
||||||
|
| 组 | ID | 标题 | 类型 | 依赖 |
|
||||||
|
|----|-----|------|------|------|
|
||||||
|
${tasks.map(t => `| ${t.parallel_group} | ${t.id} | ${t.title} | ${t.type} | ${t.depends_on.length ? t.depends_on.join(', ') : '-'} |`).join('\n')}
|
||||||
|
|
||||||
|
## 各任务详情
|
||||||
|
|
||||||
|
${tasks.map(t => `### ${t.id}: ${t.title}
|
||||||
|
|
||||||
|
**类型**: ${t.type} | **并行组**: ${t.parallel_group}
|
||||||
|
|
||||||
|
**范围**: ${t.scope}
|
||||||
|
|
||||||
|
**输入**: ${t.inputs.length ? t.inputs.join(', ') : '无(起始任务)'}
|
||||||
|
**输出**: ${t.outputs.join(', ')}
|
||||||
|
|
||||||
|
**收敛标准**:
|
||||||
|
${t.convergence.criteria.map(c => `- ✅ ${c}`).join('\n')}
|
||||||
|
- 🔍 **验证方法**: ${t.convergence.verification}
|
||||||
|
- 🎯 **完成定义**: ${t.convergence.definition_of_done}
|
||||||
|
`).join('\n---\n\n')}
|
||||||
|
|
||||||
|
## 下一步
|
||||||
|
|
||||||
|
每个任务可独立执行:
|
||||||
|
\`\`\`bash
|
||||||
|
/workflow:lite-plan "${tasks[0]?.title}: ${tasks[0]?.scope}"
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
路线图 JSONL 文件: \`${input.session.folder}/roadmap.jsonl\`
|
||||||
|
`
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fallback Decomposition
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Manual decomposition when CLI fails
|
||||||
|
function manualProgressiveDecomposition(requirement, context) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "L0", name: "MVP", goal: "最小可用闭环",
|
||||||
|
scope: ["核心功能"], excludes: ["高级功能", "优化"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["核心路径端到端可跑通"],
|
||||||
|
verification: "手动测试核心流程",
|
||||||
|
definition_of_done: "用户可完成一次核心操作的完整流程"
|
||||||
|
},
|
||||||
|
risk_items: ["技术选型待验证"], effort: "medium", depends_on: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "L1", name: "可用", goal: "关键用户路径完善",
|
||||||
|
scope: ["错误处理", "输入校验"], excludes: ["性能优化", "监控"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["所有用户输入有校验", "错误场景有提示"],
|
||||||
|
verification: "单元测试 + 手动测试错误场景",
|
||||||
|
definition_of_done: "用户遇到问题时有清晰的引导和恢复路径"
|
||||||
|
},
|
||||||
|
risk_items: [], effort: "medium", depends_on: ["L0"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
function manualDirectDecomposition(requirement, context) {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
id: "T1", title: "基础设施搭建", type: "infrastructure",
|
||||||
|
scope: "项目骨架和基础配置",
|
||||||
|
inputs: [], outputs: ["project-structure"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["项目可构建无报错", "基础配置完成"],
|
||||||
|
verification: "npm run build (或对应构建命令)",
|
||||||
|
definition_of_done: "项目基础框架就绪,可开始功能开发"
|
||||||
|
},
|
||||||
|
depends_on: [], parallel_group: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "T2", title: "核心功能实现", type: "feature",
|
||||||
|
scope: "核心业务逻辑",
|
||||||
|
inputs: ["project-structure"], outputs: ["core-module"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["核心 API/功能可调用", "返回预期结果"],
|
||||||
|
verification: "运行核心功能测试",
|
||||||
|
definition_of_done: "核心业务功能可正常使用"
|
||||||
|
},
|
||||||
|
depends_on: ["T1"], parallel_group: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 5: Decomposition Quality Check (MANDATORY)
|
||||||
|
|
||||||
|
### Overview
|
||||||
|
|
||||||
|
After generating roadmap.jsonl, **MUST** execute CLI quality check before returning to orchestrator.
|
||||||
|
|
||||||
|
### Quality Dimensions
|
||||||
|
|
||||||
|
| Dimension | Check Criteria | Critical? |
|
||||||
|
|-----------|---------------|-----------|
|
||||||
|
| **Requirement Coverage** | All aspects of original requirement addressed in layers/tasks | Yes |
|
||||||
|
| **Convergence Quality** | criteria testable, verification executable, DoD business-readable | Yes |
|
||||||
|
| **Scope Integrity** | Progressive: no overlap/gaps; Direct: inputs/outputs chain valid | Yes |
|
||||||
|
| **Dependency Correctness** | No circular deps, proper ordering | Yes |
|
||||||
|
| **Effort Balance** | No single layer/task disproportionately large | No |
|
||||||
|
|
||||||
|
### CLI Quality Check Command
|
||||||
|
|
||||||
|
```bash
|
||||||
|
ccw cli -p "
|
||||||
|
PURPOSE: Validate roadmap decomposition quality
|
||||||
|
Success: All quality dimensions pass
|
||||||
|
|
||||||
|
ORIGINAL REQUIREMENT:
|
||||||
|
${requirement}
|
||||||
|
|
||||||
|
ROADMAP (${selected_mode} mode):
|
||||||
|
${roadmapJsonlContent}
|
||||||
|
|
||||||
|
TASK:
|
||||||
|
• Requirement Coverage: Does the roadmap address ALL aspects of the requirement?
|
||||||
|
• Convergence Quality: Are criteria testable? Is verification executable? Is DoD business-readable?
|
||||||
|
• Scope Integrity: ${selected_mode === 'progressive' ? 'No scope overlap between layers, no feature gaps' : 'Inputs/outputs chain is valid, parallel groups are correct'}
|
||||||
|
• Dependency Correctness: No circular dependencies
|
||||||
|
• Effort Balance: No disproportionately large items
|
||||||
|
|
||||||
|
MODE: analysis
|
||||||
|
EXPECTED:
|
||||||
|
## Quality Check Results
|
||||||
|
### Requirement Coverage: PASS|FAIL
|
||||||
|
[details]
|
||||||
|
### Convergence Quality: PASS|FAIL
|
||||||
|
[details and specific issues per record]
|
||||||
|
### Scope Integrity: PASS|FAIL
|
||||||
|
[details]
|
||||||
|
### Dependency Correctness: PASS|FAIL
|
||||||
|
[details]
|
||||||
|
### Effort Balance: PASS|FAIL
|
||||||
|
[details]
|
||||||
|
|
||||||
|
## Recommendation: PASS|AUTO_FIX|NEEDS_REVIEW
|
||||||
|
## Fixes (if AUTO_FIX):
|
||||||
|
[specific fixes as JSON patches]
|
||||||
|
|
||||||
|
CONSTRAINTS: Read-only validation, do not modify files
|
||||||
|
" --tool ${cli_config.tool} --mode analysis
|
||||||
|
```
|
||||||
|
|
||||||
|
### Auto-Fix Strategy
|
||||||
|
|
||||||
|
| Issue Type | Auto-Fix Action |
|
||||||
|
|-----------|----------------|
|
||||||
|
| Vague criteria | Replace with specific, testable conditions |
|
||||||
|
| Technical DoD | Rewrite in business language |
|
||||||
|
| Missing scope items | Add to appropriate layer/task |
|
||||||
|
| Effort imbalance | Suggest split (report to orchestrator) |
|
||||||
|
|
||||||
|
After fixes, update `roadmap.jsonl` and `roadmap.md`.
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Fallback chain: Gemini → Qwen → manual decomposition
|
||||||
|
try {
|
||||||
|
result = executeCLI(cli_config.tool, prompt)
|
||||||
|
} catch (error) {
|
||||||
|
try {
|
||||||
|
result = executeCLI(cli_config.fallback, prompt)
|
||||||
|
} catch {
|
||||||
|
// Manual fallback
|
||||||
|
records = selected_mode === 'progressive'
|
||||||
|
? manualProgressiveDecomposition(requirement, exploration_context)
|
||||||
|
: manualDirectDecomposition(requirement, exploration_context)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Key Reminders
|
||||||
|
|
||||||
|
**ALWAYS**:
|
||||||
|
- Parse CLI output into structured records with full convergence fields
|
||||||
|
- Validate all records against schema before writing JSONL
|
||||||
|
- Check for circular dependencies
|
||||||
|
- Ensure convergence criteria are testable (not vague)
|
||||||
|
- Ensure verification is executable (commands or explicit steps)
|
||||||
|
- Ensure definition_of_done uses business language
|
||||||
|
- Run Phase 5 quality check before returning
|
||||||
|
- Write both roadmap.jsonl AND roadmap.md
|
||||||
|
|
||||||
|
**Bash Tool**:
|
||||||
|
- Use `run_in_background=false` for all Bash/CLI calls
|
||||||
|
|
||||||
|
**NEVER**:
|
||||||
|
- Output vague convergence criteria ("works correctly", "系统正常")
|
||||||
|
- Create circular dependencies
|
||||||
|
- Skip convergence validation
|
||||||
|
- Skip Phase 5 quality check
|
||||||
|
- Return without writing both output files
|
||||||
692
.claude/commands/workflow/req-plan-with-file.md
Normal file
692
.claude/commands/workflow/req-plan-with-file.md
Normal file
@@ -0,0 +1,692 @@
|
|||||||
|
---
|
||||||
|
name: req-plan-with-file
|
||||||
|
description: Requirement-level progressive roadmap planning with JSONL output. Decomposes requirements into convergent layers (MVP→iterations) or topologically-sorted task sequences, each with testable completion criteria.
|
||||||
|
argument-hint: "[-y|--yes] [-c|--continue] [-m|--mode progressive|direct|auto] \"requirement description\""
|
||||||
|
allowed-tools: TodoWrite(*), Task(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*)
|
||||||
|
---
|
||||||
|
|
||||||
|
## Auto Mode
|
||||||
|
|
||||||
|
When `--yes` or `-y`: Auto-confirm strategy selection, use recommended mode, skip interactive validation rounds.
|
||||||
|
|
||||||
|
# Workflow Req-Plan Command (/workflow:req-plan-with-file)
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic usage
|
||||||
|
/workflow:req-plan-with-file "Implement user authentication system with OAuth and 2FA"
|
||||||
|
|
||||||
|
# With mode selection
|
||||||
|
/workflow:req-plan-with-file -m progressive "Build real-time notification system" # Layered MVP→iterations
|
||||||
|
/workflow:req-plan-with-file -m direct "Refactor payment module" # Topologically-sorted task sequence
|
||||||
|
/workflow:req-plan-with-file -m auto "Add data export feature" # Auto-select strategy
|
||||||
|
|
||||||
|
# Continue existing session
|
||||||
|
/workflow:req-plan-with-file --continue "user authentication system"
|
||||||
|
|
||||||
|
# Auto mode
|
||||||
|
/workflow:req-plan-with-file -y "Implement caching layer"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Context Source**: cli-explore-agent (optional) + requirement analysis
|
||||||
|
**Output Directory**: `.workflow/.req-plan/{session-id}/`
|
||||||
|
**Core Innovation**: JSONL roadmap where each record is self-contained + has convergence criteria, independently executable via lite-plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Requirement-level layered roadmap planning command. Decomposes a requirement into **convergent layers or task sequences**, each record containing explicit completion criteria (convergence), independently executable via `lite-plan`.
|
||||||
|
|
||||||
|
**Dual Modes**:
|
||||||
|
- **Progressive**: Layered MVP→iterations, suitable for high-uncertainty requirements (validate first, then refine)
|
||||||
|
- **Direct**: Topologically-sorted task sequence, suitable for low-uncertainty requirements (clear tasks, directly ordered)
|
||||||
|
- **Auto**: Automatically selects based on uncertainty level
|
||||||
|
|
||||||
|
**Core Workflow**: Requirement Understanding → Strategy Selection → Context Collection (optional) → Decomposition → Validation → Output
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ REQ-PLAN ROADMAP WORKFLOW │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Phase 1: Requirement Understanding & Strategy Selection │
|
||||||
|
│ ├─ Parse requirement: goal / constraints / stakeholders │
|
||||||
|
│ ├─ Assess uncertainty level │
|
||||||
|
│ │ ├─ High uncertainty → recommend progressive │
|
||||||
|
│ │ └─ Low uncertainty → recommend direct │
|
||||||
|
│ ├─ User confirms strategy (-m skips, -y auto-selects recommended) │
|
||||||
|
│ └─ Initialize strategy-assessment.json + roadmap.md skeleton │
|
||||||
|
│ │
|
||||||
|
│ Phase 2: Context Collection (Optional) │
|
||||||
|
│ ├─ Detect codebase: package.json / go.mod / src / ... │
|
||||||
|
│ ├─ Has codebase → cli-explore-agent explores relevant modules │
|
||||||
|
│ └─ No codebase → skip, pure requirement decomposition │
|
||||||
|
│ │
|
||||||
|
│ Phase 3: Decomposition Execution (cli-roadmap-plan-agent) │
|
||||||
|
│ ├─ Progressive: define 2-4 layers, each with full convergence │
|
||||||
|
│ ├─ Direct: vertical slicing + topological sort, each with convergence│
|
||||||
|
│ └─ Generate roadmap.jsonl (one self-contained record per line) │
|
||||||
|
│ │
|
||||||
|
│ Phase 4: Interactive Validation & Final Output │
|
||||||
|
│ ├─ Display decomposition results (tabular + convergence criteria) │
|
||||||
|
│ ├─ User feedback loop (up to 5 rounds) │
|
||||||
|
│ ├─ Generate final roadmap.md │
|
||||||
|
│ └─ Next steps: layer-by-layer lite-plan / create issue / export │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
## Output
|
||||||
|
|
||||||
|
```
|
||||||
|
.workflow/.req-plan/RPLAN-{slug}-{YYYY-MM-DD}/
|
||||||
|
├── roadmap.md # ⭐ Human-readable roadmap
|
||||||
|
├── roadmap.jsonl # ⭐ Machine-readable, one self-contained record per line (with convergence)
|
||||||
|
├── strategy-assessment.json # Strategy assessment result
|
||||||
|
└── exploration-codebase.json # Codebase context (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
| File | Phase | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| `strategy-assessment.json` | 1 | Uncertainty analysis + mode recommendation + extracted goal/constraints/stakeholders |
|
||||||
|
| `roadmap.md` (skeleton) | 1 | Initial skeleton with placeholders, finalized in Phase 4 |
|
||||||
|
| `exploration-codebase.json` | 2 | Codebase context: relevant modules, patterns, integration points (only when codebase exists) |
|
||||||
|
| `roadmap.jsonl` | 3 | One self-contained JSON record per line with convergence criteria |
|
||||||
|
| `roadmap.md` (final) | 4 | Human-readable roadmap with tabular display + convergence details, revised per user feedback |
|
||||||
|
|
||||||
|
**roadmap.md template**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: RPLAN-{slug}-{date}
|
||||||
|
**Requirement**: {requirement}
|
||||||
|
**Strategy**: {progressive|direct}
|
||||||
|
**Generated**: {timestamp}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Uncertainty level: {high|medium|low}
|
||||||
|
- Decomposition mode: {progressive|direct}
|
||||||
|
- Assessment basis: {factors summary}
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
{Tabular display of layers/tasks}
|
||||||
|
|
||||||
|
## Convergence Criteria Details
|
||||||
|
{Expanded convergence for each layer/task}
|
||||||
|
|
||||||
|
## Risk Items
|
||||||
|
{Aggregated risk_items}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
{Execution guidance}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `-y, --yes` | false | Auto-confirm all decisions |
|
||||||
|
| `-c, --continue` | false | Continue existing session |
|
||||||
|
| `-m, --mode` | auto | Decomposition strategy: progressive / direct / auto |
|
||||||
|
|
||||||
|
**Session ID format**: `RPLAN-{slug}-{YYYY-MM-DD}`
|
||||||
|
- slug: lowercase, alphanumeric + CJK characters, max 40 chars
|
||||||
|
- date: YYYY-MM-DD (UTC+8)
|
||||||
|
- Auto-detect continue: session folder + roadmap.jsonl exists → continue mode
|
||||||
|
|
||||||
|
## JSONL Schema Design
|
||||||
|
|
||||||
|
### Convergence Criteria (convergence field)
|
||||||
|
|
||||||
|
Each JSONL record's `convergence` object contains three levels:
|
||||||
|
|
||||||
|
| Field | Purpose | Requirement |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| `criteria[]` | List of checkable specific conditions | **Testable** (can be written as assertions or manual steps) |
|
||||||
|
| `verification` | How to verify these conditions | **Executable** (command, script, or explicit steps) |
|
||||||
|
| `definition_of_done` | One-sentence completion definition | **Business language** (non-technical person can judge) |
|
||||||
|
|
||||||
|
### Progressive Mode
|
||||||
|
|
||||||
|
Each line = one layer. Layer naming convention:
|
||||||
|
|
||||||
|
| Layer | Name | Typical Goal |
|
||||||
|
|-------|------|--------------|
|
||||||
|
| L0 | MVP | Minimum viable closed loop, core path works end-to-end |
|
||||||
|
| L1 | Usable | Key user paths refined, basic error handling |
|
||||||
|
| L2 | Refined | Edge case handling, performance optimization, security hardening |
|
||||||
|
| L3 | Optimized | Advanced features, observability, operations support |
|
||||||
|
|
||||||
|
**Schema**: `id, name, goal, scope[], excludes[], convergence{}, risk_items[], effort, depends_on[]`
|
||||||
|
|
||||||
|
```jsonl
|
||||||
|
{"id":"L0","name":"MVP","goal":"Minimum viable closed loop","scope":["User registration and login","Basic CRUD"],"excludes":["OAuth","2FA"],"convergence":{"criteria":["End-to-end register→login→operate flow works","Core API returns correct responses"],"verification":"curl/Postman manual testing or smoke test script","definition_of_done":"New user can complete the full flow of register→login→perform one core operation"},"risk_items":["JWT library selection needs validation"],"effort":"medium","depends_on":[]}
|
||||||
|
{"id":"L1","name":"Usable","goal":"Complete key user paths","scope":["Password reset","Input validation","Error messages"],"excludes":["Audit logs","Rate limiting"],"convergence":{"criteria":["All form fields have frontend+backend validation","Password reset email can be sent and reset completed","Error scenarios show user-friendly messages"],"verification":"Unit tests cover validation logic + manual test of reset flow","definition_of_done":"Users have a clear recovery path when encountering input errors or forgotten passwords"},"risk_items":[],"effort":"medium","depends_on":["L0"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constraints**: 2-4 layers, L0 must be a self-contained closed loop with no dependencies, each feature belongs to exactly ONE layer (no scope overlap).
|
||||||
|
|
||||||
|
### Direct Mode
|
||||||
|
|
||||||
|
Each line = one task. Task type convention:
|
||||||
|
|
||||||
|
| Type | Use Case |
|
||||||
|
|------|----------|
|
||||||
|
| infrastructure | Data models, configuration, scaffolding |
|
||||||
|
| feature | API, UI, business logic implementation |
|
||||||
|
| enhancement | Validation, error handling, edge cases |
|
||||||
|
| testing | Unit tests, integration tests, E2E |
|
||||||
|
|
||||||
|
**Schema**: `id, title, type, scope, inputs[], outputs[], convergence{}, depends_on[], parallel_group`
|
||||||
|
|
||||||
|
```jsonl
|
||||||
|
{"id":"T1","title":"Establish data model","type":"infrastructure","scope":"DB schema + TypeScript types","inputs":[],"outputs":["schema.prisma","types/user.ts"],"convergence":{"criteria":["Migration executes without errors","TypeScript types compile successfully","Fields cover all business entities"],"verification":"npx prisma migrate dev && npx tsc --noEmit","definition_of_done":"Database schema migrates correctly, type definitions can be referenced by other modules"},"depends_on":[],"parallel_group":1}
|
||||||
|
{"id":"T2","title":"Implement core API","type":"feature","scope":"CRUD endpoints for User","inputs":["schema.prisma","types/user.ts"],"outputs":["routes/user.ts","controllers/user.ts"],"convergence":{"criteria":["GET/POST/PUT/DELETE return correct status codes","Request/response conforms to schema","No N+1 queries"],"verification":"jest --testPathPattern=user.test.ts","definition_of_done":"All User CRUD endpoints pass integration tests"},"depends_on":["T1"],"parallel_group":2}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constraints**: Inputs must come from preceding task outputs or existing resources, tasks in same parallel_group must be truly independent, no circular dependencies.
|
||||||
|
|
||||||
|
## Implementation
|
||||||
|
|
||||||
|
### Session Initialization
|
||||||
|
|
||||||
|
**Objective**: Create session context and directory structure.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||||
|
|
||||||
|
// Parse flags
|
||||||
|
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||||
|
const continueMode = $ARGUMENTS.includes('--continue') || $ARGUMENTS.includes('-c')
|
||||||
|
const modeMatch = $ARGUMENTS.match(/(?:--mode|-m)\s+(progressive|direct|auto)/)
|
||||||
|
const requestedMode = modeMatch ? modeMatch[1] : 'auto'
|
||||||
|
|
||||||
|
// Clean requirement text (remove flags)
|
||||||
|
const requirement = $ARGUMENTS
|
||||||
|
.replace(/--yes|-y|--continue|-c|--mode\s+\w+|-m\s+\w+/g, '')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
const slug = requirement.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
|
||||||
|
.substring(0, 40)
|
||||||
|
const dateStr = getUtc8ISOString().substring(0, 10)
|
||||||
|
const sessionId = `RPLAN-${slug}-${dateStr}`
|
||||||
|
const sessionFolder = `.workflow/.req-plan/${sessionId}`
|
||||||
|
|
||||||
|
// Auto-detect continue: session folder + roadmap.jsonl exists → continue mode
|
||||||
|
Bash(`mkdir -p ${sessionFolder}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 1: Requirement Understanding & Strategy Selection
|
||||||
|
|
||||||
|
**Objective**: Parse requirement, assess uncertainty, select decomposition strategy.
|
||||||
|
|
||||||
|
**Prerequisites**: Session initialized, requirement description available.
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
|
||||||
|
1. **Parse Requirement**
|
||||||
|
- Extract core goal (what to achieve)
|
||||||
|
- Identify constraints (tech stack, timeline, compatibility, etc.)
|
||||||
|
- Identify stakeholders (users, admins, developers, etc.)
|
||||||
|
- Identify keywords to determine domain
|
||||||
|
|
||||||
|
2. **Assess Uncertainty Level**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const uncertaintyFactors = {
|
||||||
|
scope_clarity: 'low|medium|high',
|
||||||
|
technical_risk: 'low|medium|high',
|
||||||
|
dependency_unknown: 'low|medium|high',
|
||||||
|
domain_familiarity: 'low|medium|high',
|
||||||
|
requirement_stability: 'low|medium|high'
|
||||||
|
}
|
||||||
|
// high uncertainty (>=3 high) → progressive
|
||||||
|
// low uncertainty (>=3 low) → direct
|
||||||
|
// otherwise → ask user preference
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Strategy Selection** (skip if `-m` already specified)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (requestedMode !== 'auto') {
|
||||||
|
selectedMode = requestedMode
|
||||||
|
} else if (autoYes) {
|
||||||
|
selectedMode = recommendedMode
|
||||||
|
} else {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Decomposition strategy selection:\n\nUncertainty assessment: ${uncertaintyLevel}\nRecommended strategy: ${recommendedMode}\n\nSelect decomposition strategy:`,
|
||||||
|
header: "Strategy",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: recommendedMode === 'progressive' ? "Progressive (Recommended)" : "Progressive",
|
||||||
|
description: "Layered MVP→iterations, validate core first then refine progressively. Suitable for high-uncertainty requirements needing quick validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: recommendedMode === 'direct' ? "Direct (Recommended)" : "Direct",
|
||||||
|
description: "Topologically-sorted task sequence with explicit dependencies. Suitable for clear requirements with confirmed technical approach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Generate strategy-assessment.json**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const strategyAssessment = {
|
||||||
|
session_id: sessionId,
|
||||||
|
requirement: requirement,
|
||||||
|
timestamp: getUtc8ISOString(),
|
||||||
|
uncertainty_factors: uncertaintyFactors,
|
||||||
|
uncertainty_level: uncertaintyLevel, // 'high' | 'medium' | 'low'
|
||||||
|
recommended_mode: recommendedMode,
|
||||||
|
selected_mode: selectedMode,
|
||||||
|
goal: extractedGoal,
|
||||||
|
constraints: extractedConstraints,
|
||||||
|
stakeholders: extractedStakeholders,
|
||||||
|
domain_keywords: extractedKeywords
|
||||||
|
}
|
||||||
|
Write(`${sessionFolder}/strategy-assessment.json`, JSON.stringify(strategyAssessment, null, 2))
|
||||||
|
```
|
||||||
|
|
||||||
|
5. **Initialize roadmap.md skeleton** (placeholder sections, finalized in Phase 4)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const roadmapMdSkeleton = `# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${requirement}
|
||||||
|
**Strategy**: ${selectedMode}
|
||||||
|
**Status**: Planning
|
||||||
|
**Created**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Uncertainty level: ${uncertaintyLevel}
|
||||||
|
- Decomposition mode: ${selectedMode}
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Convergence Criteria Details
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Risk Items
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
> To be populated after Phase 4 validation
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/roadmap.md`, roadmapMdSkeleton)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- Requirement goal, constraints, stakeholders identified
|
||||||
|
- Uncertainty level assessed
|
||||||
|
- Strategy selected (progressive or direct)
|
||||||
|
- strategy-assessment.json generated
|
||||||
|
- roadmap.md skeleton initialized
|
||||||
|
|
||||||
|
### Phase 2: Context Collection (Optional)
|
||||||
|
|
||||||
|
**Objective**: If a codebase exists, collect relevant context to enhance decomposition quality.
|
||||||
|
|
||||||
|
**Prerequisites**: Phase 1 complete.
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
|
||||||
|
1. **Detect Codebase**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const hasCodebase = Bash(`
|
||||||
|
test -f package.json && echo "nodejs" ||
|
||||||
|
test -f go.mod && echo "golang" ||
|
||||||
|
test -f Cargo.toml && echo "rust" ||
|
||||||
|
test -f pyproject.toml && echo "python" ||
|
||||||
|
test -f pom.xml && echo "java" ||
|
||||||
|
test -d src && echo "generic" ||
|
||||||
|
echo "none"
|
||||||
|
`).trim()
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Codebase Exploration** (only when hasCodebase !== 'none')
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (hasCodebase !== 'none') {
|
||||||
|
Task({
|
||||||
|
subagent_type: "cli-explore-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Explore codebase: ${slug}`,
|
||||||
|
prompt: `
|
||||||
|
## Exploration Context
|
||||||
|
Requirement: ${requirement}
|
||||||
|
Strategy: ${selectedMode}
|
||||||
|
Project Type: ${hasCodebase}
|
||||||
|
Session: ${sessionFolder}
|
||||||
|
|
||||||
|
## MANDATORY FIRST STEPS
|
||||||
|
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||||
|
2. Execute relevant searches based on requirement keywords
|
||||||
|
3. Read: .workflow/project-tech.json (if exists)
|
||||||
|
4. Read: .workflow/project-guidelines.json (if exists)
|
||||||
|
|
||||||
|
## Exploration Focus
|
||||||
|
- Identify modules/components related to the requirement
|
||||||
|
- Find existing patterns that should be followed
|
||||||
|
- Locate integration points for new functionality
|
||||||
|
- Assess current architecture constraints
|
||||||
|
|
||||||
|
## Output
|
||||||
|
Write findings to: ${sessionFolder}/exploration-codebase.json
|
||||||
|
|
||||||
|
Schema: {
|
||||||
|
project_type: "${hasCodebase}",
|
||||||
|
relevant_modules: [{name, path, relevance}],
|
||||||
|
existing_patterns: [{pattern, files, description}],
|
||||||
|
integration_points: [{location, description, risk}],
|
||||||
|
architecture_constraints: [string],
|
||||||
|
tech_stack: {languages, frameworks, tools},
|
||||||
|
_metadata: {timestamp, exploration_scope}
|
||||||
|
}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
}
|
||||||
|
// No codebase → skip, proceed directly to Phase 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- Codebase detection complete
|
||||||
|
- When codebase exists, exploration-codebase.json generated
|
||||||
|
- When no codebase, skipped and logged
|
||||||
|
|
||||||
|
### Phase 3: Decomposition Execution
|
||||||
|
|
||||||
|
**Objective**: Execute requirement decomposition via `cli-roadmap-plan-agent`, generating roadmap.jsonl + roadmap.md.
|
||||||
|
|
||||||
|
**Prerequisites**: Phase 1, Phase 2 complete. Strategy selected. Context collected (if applicable).
|
||||||
|
|
||||||
|
**Agent**: `cli-roadmap-plan-agent` (dedicated requirement roadmap planning agent, supports CLI-assisted decomposition + built-in quality checks)
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
|
||||||
|
1. **Prepare Context**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const strategy = JSON.parse(Read(`${sessionFolder}/strategy-assessment.json`))
|
||||||
|
let explorationContext = null
|
||||||
|
if (file_exists(`${sessionFolder}/exploration-codebase.json`)) {
|
||||||
|
explorationContext = JSON.parse(Read(`${sessionFolder}/exploration-codebase.json`))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **Invoke cli-roadmap-plan-agent**
|
||||||
|
|
||||||
|
The agent internally executes a 5-phase flow:
|
||||||
|
- Phase 1: Context loading + requirement analysis
|
||||||
|
- Phase 2: CLI-assisted decomposition (Gemini → Qwen → manual fallback)
|
||||||
|
- Phase 3: Record enhancement + validation (schema compliance, dependency checks, convergence quality)
|
||||||
|
- Phase 4: Generate roadmap.jsonl + roadmap.md
|
||||||
|
- Phase 5: CLI decomposition quality check (**MANDATORY** - requirement coverage, convergence criteria quality, dependency correctness)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Task({
|
||||||
|
subagent_type: "cli-roadmap-plan-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Roadmap decomposition: ${slug}`,
|
||||||
|
prompt: `
|
||||||
|
## Roadmap Decomposition Task
|
||||||
|
|
||||||
|
### Input Context
|
||||||
|
- **Requirement**: ${requirement}
|
||||||
|
- **Selected Mode**: ${selectedMode}
|
||||||
|
- **Session ID**: ${sessionId}
|
||||||
|
- **Session Folder**: ${sessionFolder}
|
||||||
|
|
||||||
|
### Strategy Assessment
|
||||||
|
${JSON.stringify(strategy, null, 2)}
|
||||||
|
|
||||||
|
### Codebase Context
|
||||||
|
${explorationContext
|
||||||
|
? `File: ${sessionFolder}/exploration-codebase.json\n${JSON.stringify(explorationContext, null, 2)}`
|
||||||
|
: 'No codebase detected - pure requirement decomposition'}
|
||||||
|
|
||||||
|
### CLI Configuration
|
||||||
|
- Primary tool: gemini
|
||||||
|
- Fallback: qwen
|
||||||
|
- Timeout: 60000ms
|
||||||
|
|
||||||
|
### Expected Output
|
||||||
|
1. **${sessionFolder}/roadmap.jsonl** - One JSON record per line with convergence field
|
||||||
|
2. **${sessionFolder}/roadmap.md** - Human-readable roadmap with tables and convergence details
|
||||||
|
|
||||||
|
### Mode-Specific Requirements
|
||||||
|
|
||||||
|
${selectedMode === 'progressive' ? `**Progressive Mode**:
|
||||||
|
- 2-4 layers from MVP to full implementation
|
||||||
|
- Each layer: id (L0-L3), name, goal, scope, excludes, convergence, risk_items, effort, depends_on
|
||||||
|
- L0 (MVP) must be a self-contained closed loop with no dependencies
|
||||||
|
- Scope: each feature belongs to exactly ONE layer (no overlap)
|
||||||
|
- Layer names: MVP / Usable / Refined / Optimized` :
|
||||||
|
|
||||||
|
`**Direct Mode**:
|
||||||
|
- Topologically-sorted task sequence
|
||||||
|
- Each task: id (T1-Tn), title, type, scope, inputs, outputs, convergence, depends_on, parallel_group
|
||||||
|
- Inputs must come from preceding task outputs or existing resources
|
||||||
|
- Tasks in same parallel_group must be truly independent`}
|
||||||
|
|
||||||
|
### Convergence Quality Requirements
|
||||||
|
- criteria[]: MUST be testable (can write assertions or manual verification steps)
|
||||||
|
- verification: MUST be executable (command, script, or explicit steps)
|
||||||
|
- definition_of_done: MUST use business language (non-technical person can judge)
|
||||||
|
|
||||||
|
### Execution
|
||||||
|
1. Analyze requirement and build decomposition context
|
||||||
|
2. Execute CLI-assisted decomposition (Gemini, fallback Qwen)
|
||||||
|
3. Parse output, validate records, enhance convergence quality
|
||||||
|
4. Write roadmap.jsonl + roadmap.md
|
||||||
|
5. Execute mandatory quality check (Phase 5)
|
||||||
|
6. Return brief completion summary
|
||||||
|
`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- roadmap.jsonl generated, each line independently JSON.parse-able
|
||||||
|
- roadmap.md generated (follows template in Output section)
|
||||||
|
- Each record contains convergence (criteria + verification + definition_of_done)
|
||||||
|
- Agent's internal quality check passed
|
||||||
|
- No circular dependencies
|
||||||
|
- Progressive: 2-4 layers, no scope overlap
|
||||||
|
- Direct: tasks have explicit inputs/outputs, parallel_group assigned
|
||||||
|
|
||||||
|
### Phase 4: Interactive Validation & Final Output
|
||||||
|
|
||||||
|
**Objective**: Display decomposition results, collect user feedback, generate final artifacts.
|
||||||
|
|
||||||
|
**Prerequisites**: Phase 3 complete, roadmap.jsonl generated.
|
||||||
|
|
||||||
|
**Steps**:
|
||||||
|
|
||||||
|
1. **Display Decomposition Results** (tabular format)
|
||||||
|
|
||||||
|
**Progressive Mode**:
|
||||||
|
```markdown
|
||||||
|
## Roadmap Overview
|
||||||
|
|
||||||
|
| Layer | Name | Goal | Scope | Effort | Dependencies |
|
||||||
|
|-------|------|------|-------|--------|--------------|
|
||||||
|
| L0 | MVP | ... | ... | medium | - |
|
||||||
|
| L1 | Usable | ... | ... | medium | L0 |
|
||||||
|
|
||||||
|
### Convergence Criteria
|
||||||
|
**L0 - MVP**:
|
||||||
|
- ✅ Criteria: [criteria list]
|
||||||
|
- 🔍 Verification: [verification]
|
||||||
|
- 🎯 Definition of Done: [definition_of_done]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct Mode**:
|
||||||
|
```markdown
|
||||||
|
## Task Sequence
|
||||||
|
|
||||||
|
| Group | ID | Title | Type | Dependencies |
|
||||||
|
|-------|----|-------|------|--------------|
|
||||||
|
| 1 | T1 | ... | infrastructure | - |
|
||||||
|
| 2 | T2 | ... | feature | T1 |
|
||||||
|
|
||||||
|
### Convergence Criteria
|
||||||
|
**T1 - Establish Data Model**:
|
||||||
|
- ✅ Criteria: [criteria list]
|
||||||
|
- 🔍 Verification: [verification]
|
||||||
|
- 🎯 Definition of Done: [definition_of_done]
|
||||||
|
```
|
||||||
|
|
||||||
|
2. **User Feedback Loop** (up to 5 rounds, skipped when autoYes)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (!autoYes) {
|
||||||
|
let round = 0
|
||||||
|
let continueLoop = true
|
||||||
|
|
||||||
|
while (continueLoop && round < 5) {
|
||||||
|
round++
|
||||||
|
const feedback = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Roadmap validation (round ${round}):\nAny feedback on the current decomposition?`,
|
||||||
|
header: "Feedback",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Approve", description: "Decomposition is reasonable, generate final artifacts" },
|
||||||
|
{ label: "Adjust Scope", description: "Some layer/task scopes need adjustment" },
|
||||||
|
{ label: "Modify Convergence", description: "Convergence criteria are not specific or testable enough" },
|
||||||
|
{ label: "Re-decompose", description: "Overall strategy or layering approach needs change" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
|
||||||
|
if (feedback === 'Approve') {
|
||||||
|
continueLoop = false
|
||||||
|
} else {
|
||||||
|
// Handle adjustment based on feedback type
|
||||||
|
// After adjustment, re-display and return to loop top
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Finalize roadmap.md** (populate template from Output section with actual data)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const roadmapMd = `
|
||||||
|
# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${requirement}
|
||||||
|
**Strategy**: ${selectedMode}
|
||||||
|
**Generated**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Uncertainty level: ${strategy.uncertainty_level}
|
||||||
|
- Decomposition mode: ${selectedMode}
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
${generateRoadmapTable(items, selectedMode)}
|
||||||
|
|
||||||
|
## Convergence Criteria Details
|
||||||
|
${items.map(item => generateConvergenceSection(item, selectedMode)).join('\n\n')}
|
||||||
|
|
||||||
|
## Risk Items
|
||||||
|
${generateRiskSection(items)}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
Each layer/task can be executed independently:
|
||||||
|
\`\`\`bash
|
||||||
|
/workflow:lite-plan "${items[0].name || items[0].title}: ${items[0].scope}"
|
||||||
|
\`\`\`
|
||||||
|
Roadmap JSONL file: \`${sessionFolder}/roadmap.jsonl\`
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/roadmap.md`, roadmapMd)
|
||||||
|
```
|
||||||
|
|
||||||
|
4. **Post-Completion Options**
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (!autoYes) {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: "Roadmap generated. Next step:",
|
||||||
|
header: "Next Step",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Execute First Layer", description: `Launch lite-plan to execute ${items[0].id}` },
|
||||||
|
{ label: "Create Issue", description: "Create GitHub Issue based on roadmap" },
|
||||||
|
{ label: "Export Report", description: "Generate standalone shareable roadmap report" },
|
||||||
|
{ label: "Done", description: "Save roadmap only, execute later" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Selection | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| Execute First Layer | `Skill(skill="workflow:lite-plan", args="${firstItem.scope}")` |
|
||||||
|
| Create Issue | `Skill(skill="issue:new", args="...")` |
|
||||||
|
| Export Report | Copy roadmap.md + roadmap.jsonl to user-specified location, or generate standalone HTML/Markdown report |
|
||||||
|
| Done | Display roadmap file paths, end |
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- User feedback processed (or skipped via autoYes)
|
||||||
|
- roadmap.md finalized
|
||||||
|
- roadmap.jsonl final version updated
|
||||||
|
- Post-completion options provided
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Error | Resolution |
|
||||||
|
|-------|------------|
|
||||||
|
| cli-explore-agent failure | Skip code exploration, proceed with pure requirement decomposition |
|
||||||
|
| No codebase | Normal flow, skip Phase 2 |
|
||||||
|
| Circular dependency detected | Prompt user to adjust dependencies, re-decompose |
|
||||||
|
| User feedback timeout | Save current state, display `--continue` recovery command |
|
||||||
|
| Max feedback rounds reached | Use current version to generate final artifacts |
|
||||||
|
| Session folder conflict | Append timestamp suffix |
|
||||||
|
| JSONL format error | Validate line by line, report problematic lines and fix |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Clear requirement description**: Detailed description → more accurate uncertainty assessment and decomposition
|
||||||
|
2. **Validate MVP first**: In progressive mode, L0 should be the minimum verifiable closed loop
|
||||||
|
3. **Testable convergence**: criteria must be writable as assertions or manual steps; definition_of_done should be judgeable by non-technical stakeholders (see Convergence Criteria in JSONL Schema Design)
|
||||||
|
4. **Agent-First for Exploration**: Delegate codebase exploration to cli-explore-agent, do not analyze directly in main flow
|
||||||
|
5. **Incremental validation**: Use `--continue` to iterate on existing roadmaps
|
||||||
|
6. **Independently executable**: Each JSONL record should be independently passable to lite-plan for execution
|
||||||
|
|
||||||
|
## Usage Recommendations
|
||||||
|
|
||||||
|
**Use `/workflow:req-plan-with-file` when:**
|
||||||
|
- You need to decompose a large requirement into a progressively executable roadmap
|
||||||
|
- Unsure where to start, need an MVP strategy
|
||||||
|
- Need to generate a trackable task sequence for the team
|
||||||
|
- Requirement involves multiple stages or iterations
|
||||||
|
|
||||||
|
**Use `/workflow:lite-plan` when:**
|
||||||
|
- You have a clear single task to execute
|
||||||
|
- The requirement is already a layer/task from the roadmap
|
||||||
|
- No layered planning needed
|
||||||
|
|
||||||
|
**Use `/workflow:collaborative-plan-with-file` when:**
|
||||||
|
- A single complex task needs multi-agent parallel planning
|
||||||
|
- Need to analyze the same task from multiple domain perspectives
|
||||||
|
|
||||||
|
**Use `/workflow:analyze-with-file` when:**
|
||||||
|
- Need in-depth analysis of a technical problem
|
||||||
|
- Not about planning execution, but understanding and discussion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Now execute req-plan-with-file for**: $ARGUMENTS
|
||||||
@@ -2,214 +2,568 @@
|
|||||||
|
|
||||||
> **Trigger**: User selects "Quick Execute" after Phase 4 completion
|
> **Trigger**: User selects "Quick Execute" after Phase 4 completion
|
||||||
> **Prerequisites**: `conclusions.json` + `explorations.json`/`perspectives.json` already exist
|
> **Prerequisites**: `conclusions.json` + `explorations.json`/`perspectives.json` already exist
|
||||||
> **Core Principle**: No additional agent exploration - analysis phase has already gathered sufficient context
|
> **Core Principle**: No additional exploration — analysis phase has already gathered sufficient context. No CLI delegation — execute tasks directly inline.
|
||||||
|
|
||||||
## Execution Flow
|
## Execution Flow
|
||||||
|
|
||||||
```
|
```
|
||||||
conclusions.json → quick-plan.json → User Confirmation → Serial Execution → execution-log.md
|
conclusions.json → execution-plan.jsonl → User Confirmation → Direct Inline Execution → execution.md + execution-events.md
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 1: Generate quick-plan.json
|
## Step 1: Generate execution-plan.jsonl
|
||||||
|
|
||||||
Convert `conclusions.json` recommendations directly into executable tasks.
|
Convert `conclusions.json` recommendations directly into JSONL execution list. Each line is a self-contained task with convergence criteria.
|
||||||
|
|
||||||
**Conversion Logic**:
|
**Conversion Logic**:
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const quickPlan = {
|
const conclusions = JSON.parse(Read(`${sessionFolder}/conclusions.json`))
|
||||||
session_id: sessionId,
|
const explorations = file_exists(`${sessionFolder}/explorations.json`)
|
||||||
source: "analysis",
|
? JSON.parse(Read(`${sessionFolder}/explorations.json`))
|
||||||
source_file: `${sessionFolder}/conclusions.json`,
|
: file_exists(`${sessionFolder}/perspectives.json`)
|
||||||
generated_at: new Date().toISOString(),
|
? JSON.parse(Read(`${sessionFolder}/perspectives.json`))
|
||||||
|
: null
|
||||||
|
|
||||||
tasks: conclusions.recommendations.map((rec, index) => ({
|
const tasks = conclusions.recommendations.map((rec, index) => ({
|
||||||
id: `TASK-${String(index + 1).padStart(3, '0')}`,
|
id: `TASK-${String(index + 1).padStart(3, '0')}`,
|
||||||
title: rec.action,
|
title: rec.action,
|
||||||
description: rec.rationale,
|
description: rec.rationale,
|
||||||
priority: rec.priority, // high/medium/low
|
type: inferTaskType(rec), // fix | refactor | feature | enhancement | testing
|
||||||
status: "pending",
|
priority: rec.priority, // high | medium | low
|
||||||
files_to_modify: extractFilesFromEvidence(rec, explorations),
|
files_to_modify: extractFilesFromEvidence(rec, explorations),
|
||||||
depends_on: [], // Serial execution - no dependencies needed
|
depends_on: [], // Serial by default; add dependencies if task ordering matters
|
||||||
context: {
|
convergence: {
|
||||||
source_conclusions: conclusions.key_conclusions,
|
criteria: generateCriteria(rec), // Testable conditions
|
||||||
evidence: explorations.relevant_files || perspectives.aggregated_findings
|
verification: generateVerification(rec), // Executable command or steps
|
||||||
}
|
definition_of_done: generateDoD(rec) // Business language
|
||||||
})),
|
},
|
||||||
|
context: {
|
||||||
|
source_conclusions: conclusions.key_conclusions,
|
||||||
|
evidence: rec.evidence || []
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
|
||||||
execution_mode: "serial",
|
// Write one task per line
|
||||||
total_tasks: conclusions.recommendations.length
|
const jsonlContent = tasks.map(t => JSON.stringify(t)).join('\n')
|
||||||
}
|
Write(`${sessionFolder}/execution-plan.jsonl`, jsonlContent)
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Task Type Inference**:
|
||||||
|
|
||||||
|
| Recommendation Pattern | Inferred Type |
|
||||||
|
|------------------------|---------------|
|
||||||
|
| "fix", "resolve", "repair" | `fix` |
|
||||||
|
| "refactor", "restructure", "extract" | `refactor` |
|
||||||
|
| "add", "implement", "create" | `feature` |
|
||||||
|
| "improve", "optimize", "enhance" | `enhancement` |
|
||||||
|
| "test", "coverage", "validate" | `testing` |
|
||||||
|
|
||||||
**File Extraction Logic**:
|
**File Extraction Logic**:
|
||||||
- Parse evidence from `explorations.json` or `perspectives.json`
|
- Parse evidence from `explorations.json` or `perspectives.json`
|
||||||
- Match recommendation action keywords to relevant_files
|
- Match recommendation action keywords to `relevant_files`
|
||||||
- If no specific files, use pattern matching from findings
|
- If no specific files found, use pattern matching from findings
|
||||||
|
- Include both files to modify and files as read-only context
|
||||||
|
|
||||||
**Output**: `${sessionFolder}/quick-plan.json`
|
**Convergence Generation**:
|
||||||
|
|
||||||
|
Each task's `convergence` must satisfy quality standards (same as req-plan-with-file):
|
||||||
|
|
||||||
|
| Field | Requirement | Bad Example | Good Example |
|
||||||
|
|-------|-------------|-------------|--------------|
|
||||||
|
| `criteria[]` | **Testable** — assertions or manual steps | `"Code works"` | `"Function returns correct result for edge cases [], null, undefined"` |
|
||||||
|
| `verification` | **Executable** — command or explicit steps | `"Check it"` | `"jest --testPathPattern=auth.test.ts && npx tsc --noEmit"` |
|
||||||
|
| `definition_of_done` | **Business language** | `"No errors"` | `"Authentication flow handles all user-facing error scenarios gracefully"` |
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Quality validation before writing
|
||||||
|
const vaguePatterns = /正常|正确|好|可以|没问题|works|fine|good|correct/i
|
||||||
|
tasks.forEach(task => {
|
||||||
|
task.convergence.criteria.forEach((criterion, i) => {
|
||||||
|
if (vaguePatterns.test(criterion) && criterion.length < 15) {
|
||||||
|
// Auto-fix: replace with specific condition from rec.evidence
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const technicalPatterns = /compile|build|lint|npm|npx|jest|tsc|eslint/i
|
||||||
|
if (technicalPatterns.test(task.convergence.definition_of_done)) {
|
||||||
|
// Auto-fix: rewrite in business language
|
||||||
|
}
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Output**: `${sessionFolder}/execution-plan.jsonl`
|
||||||
|
|
||||||
|
**JSONL Schema** (one task per line):
|
||||||
|
|
||||||
|
```jsonl
|
||||||
|
{"id":"TASK-001","title":"Fix authentication token refresh","description":"Token refresh fails silently when...","type":"fix","priority":"high","files_to_modify":["src/auth/token.ts","src/middleware/auth.ts"],"depends_on":[],"convergence":{"criteria":["Token refresh returns new valid token","Expired token triggers refresh automatically","Failed refresh redirects to login"],"verification":"jest --testPathPattern=token.test.ts","definition_of_done":"Users remain logged in across token expiration without manual re-login"},"context":{"source_conclusions":[...],"evidence":[...]}}
|
||||||
|
{"id":"TASK-002","title":"Add input validation to user endpoints","description":"Missing validation allows...","type":"enhancement","priority":"medium","files_to_modify":["src/routes/user.ts","src/validators/user.ts"],"depends_on":["TASK-001"],"convergence":{"criteria":["All user inputs validated against schema","Invalid inputs return 400 with specific error message","SQL injection patterns rejected"],"verification":"jest --testPathPattern=user.validation.test.ts","definition_of_done":"All user-facing inputs are validated with clear error feedback"},"context":{"source_conclusions":[...],"evidence":[...]}}
|
||||||
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 2: User Confirmation
|
## Step 2: Pre-Execution Analysis
|
||||||
|
|
||||||
|
Validate feasibility before starting execution. Reference: unified-execute-with-file Phase 2.
|
||||||
|
|
||||||
|
##### Step 2.1: Build Execution Order
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const tasks = Read(`${sessionFolder}/execution-plan.jsonl`)
|
||||||
|
.split('\n').filter(l => l.trim()).map(l => JSON.parse(l))
|
||||||
|
|
||||||
|
// 1. Dependency validation
|
||||||
|
const taskIds = new Set(tasks.map(t => t.id))
|
||||||
|
const errors = []
|
||||||
|
tasks.forEach(task => {
|
||||||
|
task.depends_on.forEach(dep => {
|
||||||
|
if (!taskIds.has(dep)) errors.push(`${task.id}: depends on unknown task ${dep}`)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Circular dependency detection
|
||||||
|
function detectCycles(tasks) {
|
||||||
|
const graph = new Map(tasks.map(t => [t.id, t.depends_on]))
|
||||||
|
const visited = new Set(), inStack = new Set(), cycles = []
|
||||||
|
function dfs(node, path) {
|
||||||
|
if (inStack.has(node)) { cycles.push([...path, node].join(' → ')); return }
|
||||||
|
if (visited.has(node)) return
|
||||||
|
visited.add(node); inStack.add(node)
|
||||||
|
;(graph.get(node) || []).forEach(dep => dfs(dep, [...path, node]))
|
||||||
|
inStack.delete(node)
|
||||||
|
}
|
||||||
|
tasks.forEach(t => { if (!visited.has(t.id)) dfs(t.id, []) })
|
||||||
|
return cycles
|
||||||
|
}
|
||||||
|
const cycles = detectCycles(tasks)
|
||||||
|
if (cycles.length) errors.push(`Circular dependencies: ${cycles.join('; ')}`)
|
||||||
|
|
||||||
|
// 3. Topological sort for execution order
|
||||||
|
function topoSort(tasks) {
|
||||||
|
const inDegree = new Map(tasks.map(t => [t.id, 0]))
|
||||||
|
tasks.forEach(t => t.depends_on.forEach(dep => {
|
||||||
|
inDegree.set(dep, (inDegree.get(dep) || 0)) // ensure dep exists
|
||||||
|
inDegree.set(t.id, inDegree.get(t.id) + 1)
|
||||||
|
}))
|
||||||
|
const queue = tasks.filter(t => inDegree.get(t.id) === 0).map(t => t.id)
|
||||||
|
const order = []
|
||||||
|
while (queue.length) {
|
||||||
|
const id = queue.shift()
|
||||||
|
order.push(id)
|
||||||
|
tasks.forEach(t => {
|
||||||
|
if (t.depends_on.includes(id)) {
|
||||||
|
inDegree.set(t.id, inDegree.get(t.id) - 1)
|
||||||
|
if (inDegree.get(t.id) === 0) queue.push(t.id)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return order
|
||||||
|
}
|
||||||
|
const executionOrder = topoSort(tasks)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 2.2: Analyze File Conflicts
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Check files modified by multiple tasks
|
||||||
|
const fileTaskMap = new Map() // file → [taskIds]
|
||||||
|
tasks.forEach(task => {
|
||||||
|
task.files_to_modify.forEach(file => {
|
||||||
|
if (!fileTaskMap.has(file)) fileTaskMap.set(file, [])
|
||||||
|
fileTaskMap.get(file).push(task.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
const conflicts = []
|
||||||
|
fileTaskMap.forEach((taskIds, file) => {
|
||||||
|
if (taskIds.length > 1) {
|
||||||
|
conflicts.push({ file, tasks: taskIds, resolution: "Execute in dependency order" })
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check file existence
|
||||||
|
const missingFiles = []
|
||||||
|
tasks.forEach(task => {
|
||||||
|
task.files_to_modify.forEach(file => {
|
||||||
|
if (!file_exists(file)) missingFiles.push({ file, task: task.id, action: "Will be created" })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 3: Initialize Execution Artifacts
|
||||||
|
|
||||||
|
Create `execution.md` and `execution-events.md` before starting.
|
||||||
|
|
||||||
|
##### Step 3.1: Generate execution.md
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const executionMd = `# Execution Overview
|
||||||
|
|
||||||
|
## Session Info
|
||||||
|
- **Session ID**: ${sessionId}
|
||||||
|
- **Plan Source**: execution-plan.jsonl (from analysis conclusions)
|
||||||
|
- **Started**: ${getUtc8ISOString()}
|
||||||
|
- **Total Tasks**: ${tasks.length}
|
||||||
|
- **Execution Mode**: Direct inline (serial)
|
||||||
|
|
||||||
|
## Source Analysis
|
||||||
|
- **Conclusions**: ${sessionFolder}/conclusions.json
|
||||||
|
- **Explorations**: ${explorations ? 'Available' : 'N/A'}
|
||||||
|
- **Key Conclusions**: ${conclusions.key_conclusions.length} items
|
||||||
|
|
||||||
|
## Task Overview
|
||||||
|
|
||||||
|
| # | ID | Title | Type | Priority | Dependencies | Status |
|
||||||
|
|---|-----|-------|------|----------|--------------|--------|
|
||||||
|
${tasks.map((t, i) => `| ${i+1} | ${t.id} | ${t.title} | ${t.type} | ${t.priority} | ${t.depends_on.join(', ') || '-'} | pending |`).join('\n')}
|
||||||
|
|
||||||
|
## Pre-Execution Analysis
|
||||||
|
|
||||||
|
### File Conflicts
|
||||||
|
${conflicts.length
|
||||||
|
? conflicts.map(c => `- **${c.file}**: modified by ${c.tasks.join(', ')} → ${c.resolution}`).join('\n')
|
||||||
|
: 'No file conflicts detected'}
|
||||||
|
|
||||||
|
### Missing Files
|
||||||
|
${missingFiles.length
|
||||||
|
? missingFiles.map(f => `- **${f.file}** (${f.task}): ${f.action}`).join('\n')
|
||||||
|
: 'All target files exist'}
|
||||||
|
|
||||||
|
### Dependency Validation
|
||||||
|
${errors.length ? errors.map(e => `- ⚠ ${e}`).join('\n') : 'No dependency issues'}
|
||||||
|
|
||||||
|
### Execution Order
|
||||||
|
${executionOrder.map((id, i) => `${i+1}. ${id}`).join('\n')}
|
||||||
|
|
||||||
|
## Execution Timeline
|
||||||
|
> Updated as tasks complete
|
||||||
|
|
||||||
|
## Execution Summary
|
||||||
|
> Updated after all tasks complete
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/execution.md`, executionMd)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 3.2: Initialize execution-events.md
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const eventsHeader = `# Execution Events
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Started**: ${getUtc8ISOString()}
|
||||||
|
**Source**: execution-plan.jsonl
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/execution-events.md`, eventsHeader)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 4: User Confirmation
|
||||||
|
|
||||||
Present generated plan for user approval before execution.
|
Present generated plan for user approval before execution.
|
||||||
|
|
||||||
**Confirmation Display**:
|
**Confirmation Display**:
|
||||||
- Total tasks to execute
|
- Total tasks to execute
|
||||||
- Task list with IDs, titles, priorities
|
- Task list with IDs, titles, types, priorities
|
||||||
- Files to be modified
|
- Files to be modified
|
||||||
- Execution mode: Serial
|
- File conflicts and dependency warnings (if any)
|
||||||
|
- Execution order
|
||||||
|
|
||||||
**User Options** (ASK_USER - single select):
|
|
||||||
|
|
||||||
| Option | Action |
|
|
||||||
|--------|--------|
|
|
||||||
| **Start Execution** | Proceed with serial execution |
|
|
||||||
| **Adjust Tasks** | Allow user to modify/remove tasks |
|
|
||||||
| **Cancel** | Cancel execution, keep quick-plan.json |
|
|
||||||
|
|
||||||
**Adjustment Mode** (if selected):
|
|
||||||
- Display task list with checkboxes
|
|
||||||
- User can deselect tasks to skip
|
|
||||||
- User can reorder priorities
|
|
||||||
- Regenerate quick-plan.json with adjustments
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Serial Task Execution
|
|
||||||
|
|
||||||
Execute tasks one by one without spawning subagents.
|
|
||||||
|
|
||||||
**IMPORTANT**: This phase does NOT use spawn_agent. Main process executes tasks directly via CLI.
|
|
||||||
|
|
||||||
**Execution Loop**:
|
|
||||||
```
|
|
||||||
For each task in quick-plan.tasks:
|
|
||||||
├─ Update task status: "in_progress"
|
|
||||||
├─ Execute via CLI (synchronous)
|
|
||||||
├─ Record result to execution-log.md
|
|
||||||
├─ Update task status: "completed" | "failed"
|
|
||||||
└─ Continue to next task
|
|
||||||
```
|
|
||||||
|
|
||||||
**CLI Execution Pattern**:
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Execute task from analysis
|
|
||||||
TASK ID: ${task.id}
|
|
||||||
TASK: ${task.title}
|
|
||||||
DESCRIPTION: ${task.description}
|
|
||||||
FILES: ${task.files_to_modify.join(', ')}
|
|
||||||
CONTEXT: ${JSON.stringify(task.context)}
|
|
||||||
MODE: write
|
|
||||||
EXPECTED: Task completed, files modified as specified
|
|
||||||
" --tool codex --mode write
|
|
||||||
```
|
|
||||||
|
|
||||||
**Execution Behavior**:
|
|
||||||
- One task at a time (serial, no parallel agents)
|
|
||||||
- Wait for CLI completion before next task
|
|
||||||
- Record result immediately after completion
|
|
||||||
- Stop on critical failure (user can choose to continue)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Record Execution Log
|
|
||||||
|
|
||||||
Maintain `execution-log.md` as unified execution history.
|
|
||||||
|
|
||||||
**Output**: `${sessionFolder}/execution-log.md`
|
|
||||||
|
|
||||||
**execution-log.md Structure**:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Execution Log
|
|
||||||
|
|
||||||
## Session Info
|
|
||||||
- **Session ID**: ${sessionId}
|
|
||||||
- **Plan Source**: quick-plan.json (from analysis)
|
|
||||||
- **Started**: ${startTime}
|
|
||||||
- **Mode**: Serial Execution
|
|
||||||
|
|
||||||
## Task Execution Timeline
|
|
||||||
|
|
||||||
### ${timestamp} - TASK-001: ${title}
|
|
||||||
- **Status**: completed | failed
|
|
||||||
- **Duration**: ${duration}s
|
|
||||||
- **Files Modified**: ${files.join(', ')}
|
|
||||||
- **Summary**: ${resultSummary}
|
|
||||||
- **Notes**: ${issues or discoveries}
|
|
||||||
|
|
||||||
### ${timestamp} - TASK-002: ${title}
|
|
||||||
...
|
|
||||||
|
|
||||||
## Execution Summary
|
|
||||||
- **Total Tasks**: ${total}
|
|
||||||
- **Completed**: ${completed}
|
|
||||||
- **Failed**: ${failed}
|
|
||||||
- **Success Rate**: ${rate}%
|
|
||||||
- **Total Duration**: ${duration}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Log Entry Fields**:
|
|
||||||
|
|
||||||
| Field | Content |
|
|
||||||
|-------|---------|
|
|
||||||
| Timestamp | ISO format execution time |
|
|
||||||
| Task ID | TASK-XXX identifier |
|
|
||||||
| Title | Task title from plan |
|
|
||||||
| Status | completed / failed |
|
|
||||||
| Duration | Execution time in seconds |
|
|
||||||
| Files Modified | List of changed files |
|
|
||||||
| Summary | Brief result description |
|
|
||||||
| Notes | Issues discovered or special conditions |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Update quick-plan.json
|
|
||||||
|
|
||||||
After each task, update plan with execution results.
|
|
||||||
|
|
||||||
**Task Status Updates**:
|
|
||||||
```javascript
|
```javascript
|
||||||
task.status = "completed" | "failed"
|
if (!autoYes) {
|
||||||
task.executed_at = timestamp
|
const confirmation = AskUserQuestion({
|
||||||
task.duration = seconds
|
questions: [{
|
||||||
task.result = {
|
question: `Execute ${tasks.length} tasks directly?\n\nTasks:\n${tasks.map(t =>
|
||||||
success: boolean,
|
` ${t.id}: ${t.title} (${t.priority})`).join('\n')}\n\nExecution: Direct inline, serial`,
|
||||||
files_modified: string[],
|
header: "Confirm",
|
||||||
summary: string,
|
multiSelect: false,
|
||||||
error: string | null
|
options: [
|
||||||
|
{ label: "Start Execution", description: "Execute all tasks serially" },
|
||||||
|
{ label: "Adjust Tasks", description: "Modify, reorder, or remove tasks" },
|
||||||
|
{ label: "Cancel", description: "Cancel execution, keep execution-plan.jsonl" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
// "Adjust Tasks": display task list, user deselects/reorders, regenerate execution-plan.jsonl
|
||||||
|
// "Cancel": end workflow, keep artifacts
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Step 6: Completion Summary
|
## Step 5: Direct Inline Execution
|
||||||
|
|
||||||
Present final execution results.
|
Execute tasks one by one directly using tools (Read, Edit, Write, Grep, Glob, Bash). **No CLI delegation** — main process handles all modifications.
|
||||||
|
|
||||||
**Summary Display**:
|
### Execution Loop
|
||||||
- Session ID and folder path
|
|
||||||
- Execution statistics (completed/failed/total)
|
|
||||||
- Success rate percentage
|
|
||||||
- Failed tasks requiring attention (if any)
|
|
||||||
- Link to execution-log.md for details
|
|
||||||
|
|
||||||
**Post-Execution Options** (ASK_USER - single select):
|
```
|
||||||
|
For each taskId in executionOrder:
|
||||||
|
├─ Load task from execution-plan.jsonl
|
||||||
|
├─ Check dependencies satisfied (all deps completed)
|
||||||
|
├─ Record START event to execution-events.md
|
||||||
|
├─ Execute task directly:
|
||||||
|
│ ├─ Read target files
|
||||||
|
│ ├─ Analyze what changes are needed (using task.description + task.context)
|
||||||
|
│ ├─ Apply modifications (Edit/Write)
|
||||||
|
│ ├─ Verify convergence criteria
|
||||||
|
│ └─ Capture files_modified list
|
||||||
|
├─ Record COMPLETE/FAIL event to execution-events.md
|
||||||
|
├─ Update execution.md task status
|
||||||
|
├─ Auto-commit if enabled
|
||||||
|
└─ Continue to next task (or pause on failure)
|
||||||
|
```
|
||||||
|
|
||||||
| Option | Action |
|
##### Step 5.1: Task Execution
|
||||||
|--------|--------|
|
|
||||||
| **Retry Failed** | Re-execute only failed tasks |
|
For each task, execute directly using the AI's own tools:
|
||||||
| **View Log** | Display execution-log.md content |
|
|
||||||
| **Create Issue** | Create issue from failed tasks |
|
```javascript
|
||||||
| **Done** | End workflow |
|
for (const taskId of executionOrder) {
|
||||||
|
const task = tasks.find(t => t.id === taskId)
|
||||||
|
const startTime = getUtc8ISOString()
|
||||||
|
|
||||||
|
// 1. Check dependencies
|
||||||
|
const unmetDeps = task.depends_on.filter(dep => !completedTasks.has(dep))
|
||||||
|
if (unmetDeps.length) {
|
||||||
|
recordEvent(task, 'BLOCKED', `Unmet dependencies: ${unmetDeps.join(', ')}`)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Record START event
|
||||||
|
appendToEvents(`## ${getUtc8ISOString()} — ${task.id}: ${task.title}
|
||||||
|
|
||||||
|
**Type**: ${task.type} | **Priority**: ${task.priority}
|
||||||
|
**Status**: ⏳ IN PROGRESS
|
||||||
|
**Files**: ${task.files_to_modify.join(', ')}
|
||||||
|
**Description**: ${task.description}
|
||||||
|
|
||||||
|
### Execution Log
|
||||||
|
`)
|
||||||
|
|
||||||
|
// 3. Execute task directly
|
||||||
|
// - Read each file in task.files_to_modify
|
||||||
|
// - Analyze what changes satisfy task.description + task.convergence.criteria
|
||||||
|
// - Apply changes using Edit (preferred) or Write (for new files)
|
||||||
|
// - Use Grep/Glob for discovery if needed
|
||||||
|
// - Use Bash for build/test verification commands
|
||||||
|
|
||||||
|
// 4. Verify convergence
|
||||||
|
// - Run task.convergence.verification (if it's a command)
|
||||||
|
// - Check each criterion in task.convergence.criteria
|
||||||
|
// - Record verification results
|
||||||
|
|
||||||
|
const endTime = getUtc8ISOString()
|
||||||
|
const filesModified = getModifiedFiles() // from git status or execution tracking
|
||||||
|
|
||||||
|
// 5. Record completion event
|
||||||
|
appendToEvents(`
|
||||||
|
**Status**: ✅ COMPLETED
|
||||||
|
**Duration**: ${calculateDuration(startTime, endTime)}
|
||||||
|
**Files Modified**: ${filesModified.join(', ')}
|
||||||
|
|
||||||
|
#### Changes Summary
|
||||||
|
${changeSummary}
|
||||||
|
|
||||||
|
#### Convergence Verification
|
||||||
|
${task.convergence.criteria.map((c, i) => `- [${verified[i] ? 'x' : ' '}] ${c}`).join('\n')}
|
||||||
|
- **Verification**: ${verificationResult}
|
||||||
|
- **Definition of Done**: ${task.convergence.definition_of_done}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
|
||||||
|
// 6. Update execution.md task status
|
||||||
|
updateTaskStatus(task.id, 'completed', filesModified, changeSummary)
|
||||||
|
|
||||||
|
completedTasks.add(task.id)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 5.2: Failure Handling
|
||||||
|
|
||||||
|
When a task fails during execution:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// On task failure:
|
||||||
|
appendToEvents(`
|
||||||
|
**Status**: ❌ FAILED
|
||||||
|
**Duration**: ${calculateDuration(startTime, endTime)}
|
||||||
|
**Error**: ${errorMessage}
|
||||||
|
|
||||||
|
#### Failure Details
|
||||||
|
${failureDetails}
|
||||||
|
|
||||||
|
#### Attempted Changes
|
||||||
|
${attemptedChanges}
|
||||||
|
|
||||||
|
---
|
||||||
|
`)
|
||||||
|
|
||||||
|
updateTaskStatus(task.id, 'failed', [], errorMessage)
|
||||||
|
failedTasks.add(task.id)
|
||||||
|
|
||||||
|
// Ask user how to proceed
|
||||||
|
if (!autoYes) {
|
||||||
|
const decision = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Task ${task.id} failed: ${errorMessage}\nHow to proceed?`,
|
||||||
|
header: "Failure",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Skip & Continue", description: "Skip this task, continue with next" },
|
||||||
|
{ label: "Retry", description: "Retry this task" },
|
||||||
|
{ label: "Abort", description: "Stop execution, keep progress" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 5.3: Auto-Commit (if enabled)
|
||||||
|
|
||||||
|
After each successful task, optionally commit changes:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (autoCommit && task.status === 'completed') {
|
||||||
|
// 1. Stage modified files
|
||||||
|
Bash(`git add ${filesModified.join(' ')}`)
|
||||||
|
|
||||||
|
// 2. Generate conventional commit message
|
||||||
|
const commitType = {
|
||||||
|
fix: 'fix', refactor: 'refactor', feature: 'feat',
|
||||||
|
enhancement: 'feat', testing: 'test'
|
||||||
|
}[task.type] || 'chore'
|
||||||
|
|
||||||
|
const scope = inferScope(filesModified) // e.g., "auth", "user", "api"
|
||||||
|
|
||||||
|
// 3. Commit
|
||||||
|
Bash(`git commit -m "${commitType}(${scope}): ${task.title}\n\nTask: ${task.id}\nSource: ${sessionId}"`)
|
||||||
|
|
||||||
|
appendToEvents(`**Commit**: \`${commitType}(${scope}): ${task.title}\`\n`)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 6: Finalize Execution Artifacts
|
||||||
|
|
||||||
|
##### Step 6.1: Update execution.md Summary
|
||||||
|
|
||||||
|
After all tasks complete, append final summary to `execution.md`:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const summary = `
|
||||||
|
## Execution Summary
|
||||||
|
|
||||||
|
- **Completed**: ${getUtc8ISOString()}
|
||||||
|
- **Total Tasks**: ${tasks.length}
|
||||||
|
- **Succeeded**: ${completedTasks.size}
|
||||||
|
- **Failed**: ${failedTasks.size}
|
||||||
|
- **Skipped**: ${skippedTasks.size}
|
||||||
|
- **Success Rate**: ${Math.round(completedTasks.size / tasks.length * 100)}%
|
||||||
|
|
||||||
|
### Task Results
|
||||||
|
|
||||||
|
| ID | Title | Status | Files Modified |
|
||||||
|
|----|-------|--------|----------------|
|
||||||
|
${tasks.map(t => `| ${t.id} | ${t.title} | ${t.status} | ${(t.result?.files_modified || []).join(', ') || '-'} |`).join('\n')}
|
||||||
|
|
||||||
|
${failedTasks.size > 0 ? `### Failed Tasks Requiring Attention
|
||||||
|
|
||||||
|
${[...failedTasks].map(id => {
|
||||||
|
const t = tasks.find(t => t.id === id)
|
||||||
|
return `- **${t.id}**: ${t.title} — ${t.result?.error || 'Unknown error'}`
|
||||||
|
}).join('\n')}
|
||||||
|
` : ''}
|
||||||
|
### Artifacts
|
||||||
|
- **Execution Plan**: ${sessionFolder}/execution-plan.jsonl
|
||||||
|
- **Execution Overview**: ${sessionFolder}/execution.md
|
||||||
|
- **Execution Events**: ${sessionFolder}/execution-events.md
|
||||||
|
`
|
||||||
|
// Append summary to execution.md
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 6.2: Finalize execution-events.md
|
||||||
|
|
||||||
|
Append session footer:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
appendToEvents(`
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session Summary
|
||||||
|
|
||||||
|
- **Session**: ${sessionId}
|
||||||
|
- **Completed**: ${getUtc8ISOString()}
|
||||||
|
- **Tasks**: ${completedTasks.size} completed, ${failedTasks.size} failed, ${skippedTasks.size} skipped
|
||||||
|
- **Total Events**: ${totalEvents}
|
||||||
|
`)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 6.3: Update execution-plan.jsonl
|
||||||
|
|
||||||
|
Rewrite JSONL with execution results per task:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const updatedJsonl = tasks.map(task => JSON.stringify({
|
||||||
|
...task,
|
||||||
|
status: task.status, // "completed" | "failed" | "skipped" | "pending"
|
||||||
|
executed_at: task.executed_at, // ISO timestamp
|
||||||
|
result: {
|
||||||
|
success: task.status === 'completed',
|
||||||
|
files_modified: task.result?.files_modified || [],
|
||||||
|
summary: task.result?.summary || '',
|
||||||
|
error: task.result?.error || null
|
||||||
|
}
|
||||||
|
})).join('\n')
|
||||||
|
Write(`${sessionFolder}/execution-plan.jsonl`, updatedJsonl)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Step 7: Completion & Follow-up
|
||||||
|
|
||||||
|
Present final execution results and offer next actions.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Display: session ID, statistics, failed tasks (if any), artifact paths
|
||||||
|
|
||||||
|
if (!autoYes) {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Execution complete: ${completedTasks.size}/${tasks.length} succeeded.\nNext step:`,
|
||||||
|
header: "Post-Execute",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Retry Failed", description: `Re-execute ${failedTasks.size} failed tasks` },
|
||||||
|
{ label: "View Events", description: "Display execution-events.md" },
|
||||||
|
{ label: "Create Issue", description: "Create issue from failed tasks" },
|
||||||
|
{ label: "Done", description: "End workflow" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Selection | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| Retry Failed | Filter tasks with status "failed", re-execute in order, append retry events |
|
||||||
|
| View Events | Display execution-events.md content |
|
||||||
|
| Create Issue | `Skill(skill="issue:new", args="...")` from failed task details |
|
||||||
|
| Done | Display artifact paths, end workflow |
|
||||||
|
|
||||||
**Retry Logic**:
|
**Retry Logic**:
|
||||||
- Filter tasks with status: "failed"
|
- Filter tasks with `status: "failed"`
|
||||||
- Re-execute in original order
|
- Re-execute in original dependency order
|
||||||
- Append retry results to execution-log.md
|
- Append retry events to execution-events.md with `[RETRY]` prefix
|
||||||
|
- Update execution.md and execution-plan.jsonl
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -220,8 +574,77 @@ When Quick Execute is activated, session folder expands with:
|
|||||||
```
|
```
|
||||||
{projectRoot}/.workflow/.analysis/ANL-{slug}-{date}/
|
{projectRoot}/.workflow/.analysis/ANL-{slug}-{date}/
|
||||||
├── ... # Phase 1-4 artifacts
|
├── ... # Phase 1-4 artifacts
|
||||||
├── quick-plan.json # Executable task plan
|
├── execution-plan.jsonl # ⭐ JSONL execution list (one task per line, with convergence)
|
||||||
└── execution-log.md # Execution history
|
├── execution.md # Plan overview + task table + execution summary
|
||||||
|
└── execution-events.md # ⭐ Unified event log (all task executions with details)
|
||||||
|
```
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `execution-plan.jsonl` | Self-contained task list from conclusions, each line has convergence criteria |
|
||||||
|
| `execution.md` | Overview: plan source, task table, pre-execution analysis, execution timeline, final summary |
|
||||||
|
| `execution-events.md` | Chronological event stream: task start/complete/fail with details, changes, verification results |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## execution-events.md Template
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
# Execution Events
|
||||||
|
|
||||||
|
**Session**: ANL-xxx-2025-01-21
|
||||||
|
**Started**: 2025-01-21T10:00:00+08:00
|
||||||
|
**Source**: execution-plan.jsonl
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2025-01-21T10:01:00+08:00 — TASK-001: Fix authentication token refresh
|
||||||
|
|
||||||
|
**Type**: fix | **Priority**: high
|
||||||
|
**Status**: ⏳ IN PROGRESS
|
||||||
|
**Files**: src/auth/token.ts, src/middleware/auth.ts
|
||||||
|
**Description**: Token refresh fails silently when...
|
||||||
|
|
||||||
|
### Execution Log
|
||||||
|
- Read src/auth/token.ts (245 lines)
|
||||||
|
- Found issue at line 89: missing await on refreshToken()
|
||||||
|
- Applied fix: added await keyword
|
||||||
|
- Read src/middleware/auth.ts (120 lines)
|
||||||
|
- Updated error handler at line 45
|
||||||
|
|
||||||
|
**Status**: ✅ COMPLETED
|
||||||
|
**Duration**: 45s
|
||||||
|
**Files Modified**: src/auth/token.ts, src/middleware/auth.ts
|
||||||
|
|
||||||
|
#### Changes Summary
|
||||||
|
- Added `await` to `refreshToken()` call in token.ts:89
|
||||||
|
- Updated error handler to propagate refresh failures in auth.ts:45
|
||||||
|
|
||||||
|
#### Convergence Verification
|
||||||
|
- [x] Token refresh returns new valid token
|
||||||
|
- [x] Expired token triggers refresh automatically
|
||||||
|
- [x] Failed refresh redirects to login
|
||||||
|
- **Verification**: jest --testPathPattern=token.test.ts → PASS
|
||||||
|
- **Definition of Done**: Users remain logged in across token expiration without manual re-login
|
||||||
|
|
||||||
|
**Commit**: `fix(auth): Fix authentication token refresh`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2025-01-21T10:02:30+08:00 — TASK-002: Add input validation
|
||||||
|
|
||||||
|
**Type**: enhancement | **Priority**: medium
|
||||||
|
**Status**: ⏳ IN PROGRESS
|
||||||
|
...
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Session Summary
|
||||||
|
|
||||||
|
- **Session**: ANL-xxx-2025-01-21
|
||||||
|
- **Completed**: 2025-01-21T10:05:00+08:00
|
||||||
|
- **Tasks**: 2 completed, 0 failed, 0 skipped
|
||||||
|
- **Total Events**: 2
|
||||||
```
|
```
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -230,16 +653,21 @@ When Quick Execute is activated, session folder expands with:
|
|||||||
|
|
||||||
| Situation | Action | Recovery |
|
| Situation | Action | Recovery |
|
||||||
|-----------|--------|----------|
|
|-----------|--------|----------|
|
||||||
| Task execution fails | Record failure in execution-log.md, ask user | Retry, skip, or abort remaining tasks |
|
| Task execution fails | Record failure in execution-events.md, ask user | Retry, skip, or abort |
|
||||||
| CLI timeout | Mark task as failed with timeout reason | User can retry or skip |
|
| Verification command fails | Mark criterion as unverified, continue | Note in events, manual check needed |
|
||||||
| No recommendations in conclusions | Cannot generate quick-plan.json | Inform user, suggest using lite-plan instead |
|
| No recommendations in conclusions | Cannot generate execution-plan.jsonl | Inform user, suggest lite-plan |
|
||||||
| File conflict during execution | Document in execution-log.md | Resolve manually or adjust task order |
|
| File conflict during execution | Document in execution-events.md | Resolve in dependency order |
|
||||||
|
| Circular dependencies detected | Stop, report error | Fix dependencies in execution-plan.jsonl |
|
||||||
|
| All tasks fail | Record all failures, suggest analysis review | Re-run analysis or manual intervention |
|
||||||
|
| Missing target file | Attempt to create if task.type is "feature" | Log as warning for other types |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Success Criteria
|
## Success Criteria
|
||||||
|
|
||||||
- All tasks executed (or explicitly skipped)
|
- `execution-plan.jsonl` generated with convergence criteria per task
|
||||||
- `quick-plan.json` updated with final statuses
|
- `execution.md` contains plan overview, task table, pre-execution analysis, final summary
|
||||||
- `execution-log.md` contains complete history
|
- `execution-events.md` contains chronological event stream with convergence verification
|
||||||
|
- All tasks executed (or explicitly skipped) via direct inline execution
|
||||||
|
- Each task's convergence criteria checked and recorded
|
||||||
- User informed of results and next steps
|
- User informed of results and next steps
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,39 +1,82 @@
|
|||||||
---
|
---
|
||||||
name: collaborative-plan-with-file
|
name: collaborative-plan-with-file
|
||||||
description: Parallel collaborative planning with Plan Note - Multi-agent parallel task generation, unified plan-note.md, conflict detection. Codex subagent-optimized.
|
description: Serial collaborative planning with Plan Note - Multi-domain serial task generation, unified plan-note.md, conflict detection. No agent delegation.
|
||||||
argument-hint: "TASK=\"<description>\" [--max-agents=5]"
|
argument-hint: "[-y|--yes] <task description> [--max-domains=5]"
|
||||||
---
|
---
|
||||||
|
|
||||||
# Codex Collaborative-Plan-With-File Workflow
|
# Collaborative-Plan-With-File Workflow
|
||||||
|
|
||||||
## Quick Start
|
## Quick Start
|
||||||
|
|
||||||
Parallel collaborative planning workflow using **Plan Note** architecture. Spawns parallel subagents for each sub-domain, generates task plans concurrently, and detects conflicts across domains.
|
Serial collaborative planning workflow using **Plan Note** architecture. Analyzes requirements, identifies sub-domains, generates detailed plans per domain serially, and detects conflicts across domains.
|
||||||
|
|
||||||
**Core workflow**: Understand → Template → Parallel Subagent Planning → Conflict Detection → Completion
|
```bash
|
||||||
|
# Basic usage
|
||||||
|
/codex:collaborative-plan-with-file "Implement real-time notification system"
|
||||||
|
|
||||||
|
# With options
|
||||||
|
/codex:collaborative-plan-with-file "Refactor authentication module" --max-domains=4
|
||||||
|
/codex:collaborative-plan-with-file "Add payment gateway support" -y
|
||||||
|
```
|
||||||
|
|
||||||
|
**Core workflow**: Understand → Template → Serial Domain Planning → Conflict Detection → Completion
|
||||||
|
|
||||||
**Key features**:
|
**Key features**:
|
||||||
- **plan-note.md**: Shared collaborative document with pre-allocated sections
|
- **plan-note.md**: Shared collaborative document with pre-allocated sections per domain
|
||||||
- **Parallel subagent planning**: Each sub-domain planned by its own subagent concurrently
|
- **Serial domain planning**: Each sub-domain planned sequentially with full codebase context
|
||||||
- **Conflict detection**: Automatic file, dependency, and strategy conflict scanning
|
- **Conflict detection**: Automatic file, dependency, and strategy conflict scanning
|
||||||
- **No merge needed**: Pre-allocated sections eliminate merge conflicts
|
- **No merge needed**: Pre-allocated sections eliminate merge conflicts
|
||||||
|
|
||||||
**Codex-Specific Features**:
|
## Auto Mode
|
||||||
- Parallel subagent execution via `spawn_agent` + batch `wait({ ids: [...] })`
|
|
||||||
- Role loading via path (agent reads `~/.codex/agents/*.md` itself)
|
When `--yes` or `-y`: Auto-approve splits, skip confirmations.
|
||||||
- Pre-allocated sections per agent = no write conflicts
|
|
||||||
- Explicit lifecycle management with `close_agent`
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
This workflow enables structured planning through parallel-capable phases:
|
This workflow enables structured planning through sequential phases:
|
||||||
|
|
||||||
1. **Understanding & Template** - Analyze requirements, identify sub-domains, create plan-note.md template
|
1. **Understanding & Template** — Analyze requirements, identify sub-domains, create plan-note.md template
|
||||||
2. **Parallel Planning** - Spawn subagent per sub-domain, batch wait for all results
|
2. **Serial Domain Planning** — Plan each sub-domain sequentially using direct search and analysis
|
||||||
3. **Conflict Detection** - Scan plan-note.md for conflicts across all domains
|
3. **Conflict Detection** — Scan plan-note.md for conflicts across all domains
|
||||||
4. **Completion** - Generate human-readable plan.md summary
|
4. **Completion** — Generate human-readable plan.md summary
|
||||||
|
|
||||||
The key innovation is the **Plan Note** architecture - a shared collaborative document with pre-allocated sections per sub-domain, eliminating merge conflicts. Combined with Codex's true parallel subagent execution, all domains are planned simultaneously.
|
The key innovation is the **Plan Note** architecture — a shared collaborative document with pre-allocated sections per sub-domain, eliminating merge conflicts even in serial execution.
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ PLAN NOTE COLLABORATIVE PLANNING │
|
||||||
|
├─────────────────────────────────────────────────────────────────────────┤
|
||||||
|
│ │
|
||||||
|
│ Phase 1: Understanding & Template Creation │
|
||||||
|
│ ├─ Analyze requirements (inline search & analysis) │
|
||||||
|
│ ├─ Identify 2-5 sub-domains (focus areas) │
|
||||||
|
│ ├─ Create plan-note.md with pre-allocated sections │
|
||||||
|
│ └─ Assign TASK ID ranges (no conflicts) │
|
||||||
|
│ │
|
||||||
|
│ Phase 2: Serial Domain Planning │
|
||||||
|
│ ┌──────────────┐ │
|
||||||
|
│ │ Domain 1 │→ Explore codebase → Generate plan.json │
|
||||||
|
│ │ Section 1 │→ Fill task pool + evidence in plan-note.md │
|
||||||
|
│ └──────┬───────┘ │
|
||||||
|
│ ┌──────▼───────┐ │
|
||||||
|
│ │ Domain 2 │→ Explore codebase → Generate plan.json │
|
||||||
|
│ │ Section 2 │→ Fill task pool + evidence in plan-note.md │
|
||||||
|
│ └──────┬───────┘ │
|
||||||
|
│ ┌──────▼───────┐ │
|
||||||
|
│ │ Domain N │→ ... │
|
||||||
|
│ └──────────────┘ │
|
||||||
|
│ │
|
||||||
|
│ Phase 3: Conflict Detection (Single Source) │
|
||||||
|
│ ├─ Parse plan-note.md (all sections) │
|
||||||
|
│ ├─ Detect file/dependency/strategy conflicts │
|
||||||
|
│ └─ Update plan-note.md conflict section │
|
||||||
|
│ │
|
||||||
|
│ Phase 4: Completion (No Merge) │
|
||||||
|
│ ├─ Generate plan.md (human-readable) │
|
||||||
|
│ └─ Ready for execution │
|
||||||
|
│ │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
## Output Structure
|
## Output Structure
|
||||||
|
|
||||||
@@ -41,7 +84,7 @@ The key innovation is the **Plan Note** architecture - a shared collaborative do
|
|||||||
{projectRoot}/.workflow/.planning/CPLAN-{slug}-{date}/
|
{projectRoot}/.workflow/.planning/CPLAN-{slug}-{date}/
|
||||||
├── plan-note.md # ⭐ Core: Requirements + Tasks + Conflicts
|
├── plan-note.md # ⭐ Core: Requirements + Tasks + Conflicts
|
||||||
├── requirement-analysis.json # Phase 1: Sub-domain assignments
|
├── requirement-analysis.json # Phase 1: Sub-domain assignments
|
||||||
├── agents/ # Phase 2: Per-domain plans (serial)
|
├── domains/ # Phase 2: Per-domain plans
|
||||||
│ ├── {domain-1}/
|
│ ├── {domain-1}/
|
||||||
│ │ └── plan.json # Detailed plan
|
│ │ └── plan.json # Detailed plan
|
||||||
│ ├── {domain-2}/
|
│ ├── {domain-2}/
|
||||||
@@ -60,12 +103,12 @@ The key innovation is the **Plan Note** architecture - a shared collaborative do
|
|||||||
| `plan-note.md` | Collaborative template with pre-allocated task pool and evidence sections per domain |
|
| `plan-note.md` | Collaborative template with pre-allocated task pool and evidence sections per domain |
|
||||||
| `requirement-analysis.json` | Sub-domain assignments, TASK ID ranges, complexity assessment |
|
| `requirement-analysis.json` | Sub-domain assignments, TASK ID ranges, complexity assessment |
|
||||||
|
|
||||||
### Phase 2: Parallel Planning
|
### Phase 2: Serial Domain Planning
|
||||||
|
|
||||||
| Artifact | Purpose |
|
| Artifact | Purpose |
|
||||||
|----------|---------|
|
|----------|---------|
|
||||||
| `agents/{domain}/plan.json` | Detailed implementation plan per domain (from parallel subagent) |
|
| `domains/{domain}/plan.json` | Detailed implementation plan per domain |
|
||||||
| Updated `plan-note.md` | Task pool and evidence sections filled by each subagent |
|
| Updated `plan-note.md` | Task pool and evidence sections filled for each domain |
|
||||||
|
|
||||||
### Phase 3: Conflict Detection
|
### Phase 3: Conflict Detection
|
||||||
|
|
||||||
@@ -86,31 +129,43 @@ The key innovation is the **Plan Note** architecture - a shared collaborative do
|
|||||||
|
|
||||||
### Session Initialization
|
### Session Initialization
|
||||||
|
|
||||||
##### Step 0: Determine Project Root
|
##### Step 0: Initialize Session
|
||||||
|
|
||||||
检测项目根目录,确保 `.workflow/` 产物位置正确:
|
```javascript
|
||||||
|
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||||
|
|
||||||
```bash
|
// Detect project root
|
||||||
PROJECT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null || pwd)
|
const projectRoot = Bash(`git rev-parse --show-toplevel 2>/dev/null || pwd`).trim()
|
||||||
|
|
||||||
|
// Parse arguments
|
||||||
|
const autoMode = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||||
|
const maxDomainsMatch = $ARGUMENTS.match(/--max-domains=(\d+)/)
|
||||||
|
const maxDomains = maxDomainsMatch ? parseInt(maxDomainsMatch[1]) : 5
|
||||||
|
|
||||||
|
// Clean task description
|
||||||
|
const taskDescription = $ARGUMENTS
|
||||||
|
.replace(/--yes|-y|--max-domains=\d+/g, '')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
const slug = taskDescription.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
|
||||||
|
.substring(0, 30)
|
||||||
|
const dateStr = getUtc8ISOString().substring(0, 10)
|
||||||
|
const sessionId = `CPLAN-${slug}-${dateStr}`
|
||||||
|
const sessionFolder = `${projectRoot}/.workflow/.planning/${sessionId}`
|
||||||
|
|
||||||
|
// Auto-detect continue: session folder + plan-note.md exists → continue mode
|
||||||
|
// If continue → load existing state and resume from incomplete phase
|
||||||
|
Bash(`mkdir -p ${sessionFolder}/domains`)
|
||||||
```
|
```
|
||||||
|
|
||||||
优先通过 git 获取仓库根目录;非 git 项目回退到 `pwd` 取当前绝对路径。
|
|
||||||
存储为 `{projectRoot}`,后续所有 `.workflow/` 路径必须以此为前缀。
|
|
||||||
|
|
||||||
The workflow automatically generates a unique session identifier and directory structure.
|
|
||||||
|
|
||||||
**Session ID Format**: `CPLAN-{slug}-{date}`
|
|
||||||
- `slug`: Lowercase alphanumeric, max 30 chars
|
|
||||||
- `date`: YYYY-MM-DD format (UTC+8)
|
|
||||||
|
|
||||||
**Session Directory**: `{projectRoot}/.workflow/.planning/{sessionId}/`
|
|
||||||
|
|
||||||
**Auto-Detection**: If session folder exists with plan-note.md, automatically enters continue mode.
|
|
||||||
|
|
||||||
**Session Variables**:
|
**Session Variables**:
|
||||||
- `sessionId`: Unique session identifier
|
- `sessionId`: Unique session identifier
|
||||||
- `sessionFolder`: Base directory for all artifacts
|
- `sessionFolder`: Base directory for all artifacts
|
||||||
- `maxDomains`: Maximum number of sub-domains (default: 5)
|
- `maxDomains`: Maximum number of sub-domains (default: 5)
|
||||||
|
- `autoMode`: Boolean for auto-confirmation
|
||||||
|
|
||||||
|
**Auto-Detection**: If session folder exists with plan-note.md, automatically enters continue mode.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -120,13 +175,17 @@ The workflow automatically generates a unique session identifier and directory s
|
|||||||
|
|
||||||
### Step 1.1: Analyze Task Description
|
### Step 1.1: Analyze Task Description
|
||||||
|
|
||||||
Use built-in tools to understand the task scope and identify sub-domains.
|
Use built-in tools directly to understand the task scope and identify sub-domains.
|
||||||
|
|
||||||
**Analysis Activities**:
|
**Analysis Activities**:
|
||||||
1. **Extract task keywords** - Identify key terms and concepts from the task description
|
1. **Search for references** — Find related documentation, README files, and architecture guides
|
||||||
2. **Identify sub-domains** - Split into 2-5 parallelizable focus areas based on task complexity
|
- Use: `mcp__ace-tool__search_context`, Grep, Glob, Read
|
||||||
3. **Assess complexity** - Evaluate overall task complexity (Low/Medium/High)
|
- Read: `.workflow/project-tech.json`, `.workflow/project-guidelines.json` (if exists)
|
||||||
4. **Search for references** - Find related documentation, README files, and architecture guides
|
2. **Extract task keywords** — Identify key terms and concepts from the task description
|
||||||
|
3. **Identify ambiguities** — List any unclear points or multiple possible interpretations
|
||||||
|
4. **Clarify with user** — If ambiguities found, use AskUserQuestion for clarification
|
||||||
|
5. **Identify sub-domains** — Split into 2-{maxDomains} parallelizable focus areas based on task complexity
|
||||||
|
6. **Assess complexity** — Evaluate overall task complexity (Low/Medium/High)
|
||||||
|
|
||||||
**Sub-Domain Identification Patterns**:
|
**Sub-Domain Identification Patterns**:
|
||||||
|
|
||||||
@@ -138,43 +197,59 @@ Use built-in tools to understand the task scope and identify sub-domains.
|
|||||||
| Testing | 测试, 验证, QA |
|
| Testing | 测试, 验证, QA |
|
||||||
| Infrastructure | 部署, 基础, 运维, 配置 |
|
| Infrastructure | 部署, 基础, 运维, 配置 |
|
||||||
|
|
||||||
**Ambiguity Handling**: When the task description is unclear or has multiple interpretations, gather user clarification before proceeding.
|
**Guideline**: Prioritize identifying latest documentation (README, design docs, architecture guides). When ambiguities exist, ask user for clarification instead of assuming interpretations.
|
||||||
|
|
||||||
### Step 1.2: Create plan-note.md Template
|
### Step 1.2: Create plan-note.md Template
|
||||||
|
|
||||||
Generate a structured template with pre-allocated sections for each sub-domain.
|
Generate a structured template with pre-allocated sections for each sub-domain.
|
||||||
|
|
||||||
**plan-note.md Structure**:
|
**plan-note.md Structure**:
|
||||||
- **YAML Frontmatter**: session_id, original_requirement, created_at, complexity, sub_domains, status
|
|
||||||
- **Section: 需求理解**: Core objectives, key points, constraints, split strategy
|
```yaml
|
||||||
- **Section: 任务池 - {Domain N}**: Pre-allocated task section per domain (TASK-{range})
|
---
|
||||||
- **Section: 依赖关系**: Auto-generated after all domains complete
|
session_id: CPLAN-{slug}-{date}
|
||||||
- **Section: 冲突标记**: Populated in Phase 3
|
original_requirement: "{task description}"
|
||||||
- **Section: 上下文证据 - {Domain N}**: Evidence section per domain
|
created_at: "{ISO timestamp}"
|
||||||
|
complexity: Low | Medium | High
|
||||||
|
sub_domains: ["{domain-1}", "{domain-2}", ...]
|
||||||
|
domain_task_id_ranges:
|
||||||
|
"{domain-1}": [1, 100]
|
||||||
|
"{domain-2}": [101, 200]
|
||||||
|
status: planning
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
**Sections**:
|
||||||
|
- `## 需求理解` — Core objectives, key points, constraints, split strategy
|
||||||
|
- `## 任务池 - {Domain N}` — Pre-allocated task section per domain (TASK-{range})
|
||||||
|
- `## 依赖关系` — Auto-generated after all domains complete
|
||||||
|
- `## 冲突标记` — Populated in Phase 3
|
||||||
|
- `## 上下文证据 - {Domain N}` — Evidence section per domain
|
||||||
|
|
||||||
**TASK ID Range Allocation**: Each domain receives a non-overlapping range of 100 IDs (e.g., Domain 1: TASK-001~100, Domain 2: TASK-101~200).
|
**TASK ID Range Allocation**: Each domain receives a non-overlapping range of 100 IDs (e.g., Domain 1: TASK-001~100, Domain 2: TASK-101~200).
|
||||||
|
|
||||||
### Step 1.3: Generate requirement-analysis.json
|
### Step 1.3: Generate requirement-analysis.json
|
||||||
|
|
||||||
Create the sub-domain configuration document.
|
```javascript
|
||||||
|
Write(`${sessionFolder}/requirement-analysis.json`, JSON.stringify({
|
||||||
**requirement-analysis.json Structure**:
|
session_id: sessionId,
|
||||||
|
original_requirement: taskDescription,
|
||||||
| Field | Purpose |
|
complexity: complexity, // Low | Medium | High
|
||||||
|-------|---------|
|
sub_domains: subDomains.map(sub => ({
|
||||||
| `session_id` | Session identifier |
|
focus_area: sub.focus_area,
|
||||||
| `original_requirement` | Task description |
|
description: sub.description,
|
||||||
| `complexity` | Low / Medium / High |
|
task_id_range: sub.task_id_range,
|
||||||
| `sub_domains[]` | Array of focus areas with descriptions |
|
estimated_effort: sub.estimated_effort,
|
||||||
| `sub_domains[].focus_area` | Domain name |
|
dependencies: sub.dependencies // cross-domain dependencies
|
||||||
| `sub_domains[].description` | Domain scope description |
|
})),
|
||||||
| `sub_domains[].task_id_range` | Non-overlapping TASK ID range |
|
total_domains: subDomains.length
|
||||||
| `sub_domains[].estimated_effort` | Effort estimate |
|
}, null, 2))
|
||||||
| `sub_domains[].dependencies` | Cross-domain dependencies |
|
```
|
||||||
| `total_domains` | Number of domains identified |
|
|
||||||
|
|
||||||
**Success Criteria**:
|
**Success Criteria**:
|
||||||
- 2-5 clear sub-domains identified
|
- Latest documentation identified and referenced (if available)
|
||||||
|
- Ambiguities resolved via user clarification (if any found)
|
||||||
|
- 2-{maxDomains} clear sub-domains identified
|
||||||
- Each sub-domain can be planned independently
|
- Each sub-domain can be planned independently
|
||||||
- Plan Note template includes all pre-allocated sections
|
- Plan Note template includes all pre-allocated sections
|
||||||
- TASK ID ranges have no overlap (100 IDs per domain)
|
- TASK ID ranges have no overlap (100 IDs per domain)
|
||||||
@@ -182,163 +257,134 @@ Create the sub-domain configuration document.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Phase 2: Parallel Sub-Domain Planning
|
## Phase 2: Serial Sub-Domain Planning
|
||||||
|
|
||||||
**Objective**: Spawn parallel subagents for each sub-domain, generating detailed plans and updating plan-note.md concurrently.
|
**Objective**: Plan each sub-domain sequentially, generating detailed plans and updating plan-note.md.
|
||||||
|
|
||||||
**Execution Model**: Parallel subagent execution - all domains planned simultaneously via `spawn_agent` + batch `wait`.
|
**Execution Model**: Serial inline execution — each domain explored and planned directly using search tools, one at a time.
|
||||||
|
|
||||||
**Key API Pattern**:
|
|
||||||
```
|
|
||||||
spawn_agent × N → wait({ ids: [...] }) → verify outputs → close_agent × N
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.1: User Confirmation (unless autoMode)
|
### Step 2.1: User Confirmation (unless autoMode)
|
||||||
|
|
||||||
Display identified sub-domains and confirm before spawning agents.
|
Display identified sub-domains and confirm before starting.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// User confirmation
|
|
||||||
if (!autoMode) {
|
if (!autoMode) {
|
||||||
// Display sub-domains for user approval
|
AskUserQuestion({
|
||||||
// Options: "开始规划" / "调整拆分" / "取消"
|
questions: [{
|
||||||
|
question: `已识别 ${subDomains.length} 个子领域:\n${subDomains.map((s, i) =>
|
||||||
|
`${i+1}. ${s.focus_area}: ${s.description}`).join('\n')}\n\n确认开始规划?`,
|
||||||
|
header: "Confirm",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "开始规划", description: "逐域进行规划" },
|
||||||
|
{ label: "调整拆分", description: "修改子领域划分" },
|
||||||
|
{ label: "取消", description: "退出规划" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 2.2: Parallel Subagent Planning
|
### Step 2.2: Serial Domain Planning
|
||||||
|
|
||||||
**⚠️ IMPORTANT**: Role files are NOT read by main process. Pass path in message, agent reads itself.
|
For each sub-domain, execute the full planning cycle inline:
|
||||||
|
|
||||||
**Spawn All Domain Agents in Parallel**:
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Create agent directories first
|
for (const sub of subDomains) {
|
||||||
subDomains.forEach(sub => {
|
// 1. Create domain directory
|
||||||
// mkdir: ${sessionFolder}/agents/${sub.focus_area}/
|
Bash(`mkdir -p ${sessionFolder}/domains/${sub.focus_area}`)
|
||||||
})
|
|
||||||
|
|
||||||
// Parallel spawn - all agents start immediately
|
// 2. Explore codebase for domain-relevant context
|
||||||
const agentIds = subDomains.map(sub => {
|
// Use: mcp__ace-tool__search_context, Grep, Glob, Read
|
||||||
return spawn_agent({
|
// Focus on:
|
||||||
message: `
|
// - Modules/components related to this domain
|
||||||
## TASK ASSIGNMENT
|
// - Existing patterns to follow
|
||||||
|
// - Integration points with other domains
|
||||||
|
// - Architecture constraints
|
||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
// 3. Generate detailed plan.json
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-lite-planning-agent.md (MUST read first)
|
const plan = {
|
||||||
2. Read: ${projectRoot}/.workflow/project-tech.json
|
session_id: sessionId,
|
||||||
3. Read: ${projectRoot}/.workflow/project-guidelines.json
|
focus_area: sub.focus_area,
|
||||||
4. Read: ${sessionFolder}/plan-note.md (understand template structure)
|
description: sub.description,
|
||||||
5. Read: ${sessionFolder}/requirement-analysis.json (understand full context)
|
task_id_range: sub.task_id_range,
|
||||||
|
generated_at: getUtc8ISOString(),
|
||||||
|
tasks: [
|
||||||
|
// For each task within the assigned ID range:
|
||||||
|
{
|
||||||
|
id: `TASK-${String(sub.task_id_range[0]).padStart(3, '0')}`,
|
||||||
|
title: "...",
|
||||||
|
complexity: "Low | Medium | High",
|
||||||
|
depends_on: [], // TASK-xxx references
|
||||||
|
scope: "...", // Brief scope description
|
||||||
|
modification_points: [ // file:line → change summary
|
||||||
|
{ file: "...", location: "...", change: "..." }
|
||||||
|
],
|
||||||
|
conflict_risk: "Low | Medium | High",
|
||||||
|
estimated_effort: "..."
|
||||||
|
}
|
||||||
|
// ... more tasks
|
||||||
|
],
|
||||||
|
evidence: {
|
||||||
|
relevant_files: [...],
|
||||||
|
existing_patterns: [...],
|
||||||
|
constraints: [...]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Write(`${sessionFolder}/domains/${sub.focus_area}/plan.json`, JSON.stringify(plan, null, 2))
|
||||||
|
|
||||||
---
|
// 4. Sync summary to plan-note.md
|
||||||
|
// Read current plan-note.md
|
||||||
|
// Locate pre-allocated sections:
|
||||||
|
// - Task Pool: "## 任务池 - ${toTitleCase(sub.focus_area)}"
|
||||||
|
// - Evidence: "## 上下文证据 - ${toTitleCase(sub.focus_area)}"
|
||||||
|
// Fill with task summaries and evidence
|
||||||
|
// Write back plan-note.md
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
## Sub-Domain Context
|
**Task Summary Format** (for plan-note.md task pool sections):
|
||||||
**Focus Area**: ${sub.focus_area}
|
|
||||||
**Description**: ${sub.description}
|
|
||||||
**TASK ID Range**: ${sub.task_id_range[0]}-${sub.task_id_range[1]}
|
|
||||||
**Session**: ${sessionId}
|
|
||||||
|
|
||||||
## Dual Output Tasks
|
```markdown
|
||||||
|
### TASK-{ID}: {Title} [{focus-area}]
|
||||||
### Task 1: Generate Complete plan.json
|
|
||||||
Output: ${sessionFolder}/agents/${sub.focus_area}/plan.json
|
|
||||||
|
|
||||||
Include:
|
|
||||||
- Task breakdown with IDs from assigned range (${sub.task_id_range[0]}-${sub.task_id_range[1]})
|
|
||||||
- Dependencies within and across domains
|
|
||||||
- Files to modify with specific locations
|
|
||||||
- Effort and complexity estimates per task
|
|
||||||
- Conflict risk assessment for each task
|
|
||||||
|
|
||||||
### Task 2: Sync Summary to plan-note.md
|
|
||||||
|
|
||||||
**Locate Your Sections** (pre-allocated, ONLY modify these):
|
|
||||||
- Task Pool: "## 任务池 - ${toTitleCase(sub.focus_area)}"
|
|
||||||
- Evidence: "## 上下文证据 - ${toTitleCase(sub.focus_area)}"
|
|
||||||
|
|
||||||
**Task Summary Format**:
|
|
||||||
### TASK-{ID}: {Title} [${sub.focus_area}]
|
|
||||||
- **状态**: pending
|
- **状态**: pending
|
||||||
- **复杂度**: Low/Medium/High
|
- **复杂度**: Low/Medium/High
|
||||||
- **依赖**: TASK-xxx (if any)
|
- **依赖**: TASK-xxx (if any)
|
||||||
- **范围**: Brief scope description
|
- **范围**: Brief scope description
|
||||||
- **修改点**: file:line - change summary
|
- **修改点**: `file:location`: change summary
|
||||||
- **冲突风险**: Low/Medium/High
|
- **冲突风险**: Low/Medium/High
|
||||||
|
|
||||||
**Evidence Format**:
|
|
||||||
- 相关文件: File list with relevance
|
|
||||||
- 现有模式: Patterns identified
|
|
||||||
- 约束: Constraints discovered
|
|
||||||
|
|
||||||
## Execution Steps
|
|
||||||
1. Explore codebase for domain-relevant files
|
|
||||||
2. Generate complete plan.json
|
|
||||||
3. Extract task summaries from plan.json
|
|
||||||
4. Read ${sessionFolder}/plan-note.md
|
|
||||||
5. Locate and fill your pre-allocated task pool section
|
|
||||||
6. Locate and fill your pre-allocated evidence section
|
|
||||||
7. Write back plan-note.md
|
|
||||||
|
|
||||||
## Important Rules
|
|
||||||
- ONLY modify your pre-allocated sections (do NOT touch other domains)
|
|
||||||
- Use assigned TASK ID range exclusively: ${sub.task_id_range[0]}-${sub.task_id_range[1]}
|
|
||||||
- Include conflict_risk assessment for each task
|
|
||||||
|
|
||||||
## Success Criteria
|
|
||||||
- [ ] Role definition read
|
|
||||||
- [ ] plan.json generated with detailed tasks
|
|
||||||
- [ ] plan-note.md updated with task pool and evidence
|
|
||||||
- [ ] All tasks within assigned ID range
|
|
||||||
`
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
// Batch wait - TRUE PARALLELISM (key Codex advantage)
|
|
||||||
const results = wait({
|
|
||||||
ids: agentIds,
|
|
||||||
timeout_ms: 900000 // 15 minutes for all planning agents
|
|
||||||
})
|
|
||||||
|
|
||||||
// Handle timeout
|
|
||||||
if (results.timed_out) {
|
|
||||||
const completed = agentIds.filter(id => results.status[id].completed)
|
|
||||||
const pending = agentIds.filter(id => !results.status[id].completed)
|
|
||||||
|
|
||||||
// Option: Continue waiting or use partial results
|
|
||||||
// If most agents completed, proceed with partial results
|
|
||||||
}
|
|
||||||
|
|
||||||
// Verify outputs exist
|
|
||||||
subDomains.forEach((sub, index) => {
|
|
||||||
const agentId = agentIds[index]
|
|
||||||
if (results.status[agentId].completed) {
|
|
||||||
// Verify: agents/${sub.focus_area}/plan.json exists
|
|
||||||
// Verify: plan-note.md sections populated
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// Batch cleanup
|
|
||||||
agentIds.forEach(id => close_agent({ id }))
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
**Evidence Format** (for plan-note.md evidence sections):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
- **相关文件**: file list with relevance
|
||||||
|
- **现有模式**: patterns identified
|
||||||
|
- **约束**: constraints discovered
|
||||||
|
```
|
||||||
|
|
||||||
|
**Domain Planning Rules**:
|
||||||
|
- Each domain modifies ONLY its pre-allocated sections in plan-note.md
|
||||||
|
- Use assigned TASK ID range exclusively
|
||||||
|
- Include `conflict_risk` assessment for each task
|
||||||
|
- Reference cross-domain dependencies explicitly
|
||||||
|
|
||||||
### Step 2.3: Verify plan-note.md Consistency
|
### Step 2.3: Verify plan-note.md Consistency
|
||||||
|
|
||||||
After all agents complete, verify the shared document.
|
After all domains are planned, verify the shared document.
|
||||||
|
|
||||||
**Verification Activities**:
|
**Verification Activities**:
|
||||||
1. Read final plan-note.md
|
1. Read final plan-note.md
|
||||||
2. Verify all task pool sections are populated
|
2. Verify all task pool sections are populated
|
||||||
3. Verify all evidence sections are populated
|
3. Verify all evidence sections are populated
|
||||||
4. Check for any accidental cross-section modifications
|
4. Validate TASK ID uniqueness across all domains
|
||||||
5. Validate TASK ID uniqueness across all domains
|
5. Check for any section format inconsistencies
|
||||||
|
|
||||||
**Success Criteria**:
|
**Success Criteria**:
|
||||||
- All subagents spawned and completed (or timeout handled)
|
- `domains/{domain}/plan.json` created for each domain
|
||||||
- `agents/{domain}/plan.json` created for each domain
|
|
||||||
- `plan-note.md` updated with all task pools and evidence sections
|
- `plan-note.md` updated with all task pools and evidence sections
|
||||||
- Task summaries follow consistent format
|
- Task summaries follow consistent format
|
||||||
- No TASK ID overlaps across domains
|
- No TASK ID overlaps across domains
|
||||||
- All agents closed properly
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -350,13 +396,28 @@ After all agents complete, verify the shared document.
|
|||||||
|
|
||||||
Extract all tasks from all "任务池" sections.
|
Extract all tasks from all "任务池" sections.
|
||||||
|
|
||||||
**Extraction Activities**:
|
```javascript
|
||||||
1. Read plan-note.md content
|
// parsePlanNote(markdown)
|
||||||
2. Parse YAML frontmatter for session metadata
|
// - Extract YAML frontmatter between `---` markers
|
||||||
3. Identify all "任务池" sections by heading pattern
|
// - Scan for heading patterns: /^(#{2,})\s+(.+)$/
|
||||||
4. Extract tasks matching pattern: `### TASK-{ID}: {Title} [{domain}]`
|
// - Build sections array: { level, heading, start, content }
|
||||||
5. Parse task details: status, complexity, dependencies, modification points, conflict risk
|
// - Return: { frontmatter, sections }
|
||||||
6. Consolidate into unified task list
|
|
||||||
|
// extractTasksFromSection(content, sectionHeading)
|
||||||
|
// - Match: /### (TASK-\d+):\s+(.+?)\s+\[(.+?)\]/
|
||||||
|
// - For each: extract taskId, title, author
|
||||||
|
// - Parse details: status, complexity, depends_on, modification_points, conflict_risk
|
||||||
|
// - Return: array of task objects
|
||||||
|
|
||||||
|
// parseTaskDetails(content)
|
||||||
|
// - Extract via regex:
|
||||||
|
// - /\*\*状态\*\*:\s*(.+)/ → status
|
||||||
|
// - /\*\*复杂度\*\*:\s*(.+)/ → complexity
|
||||||
|
// - /\*\*依赖\*\*:\s*(.+)/ → depends_on (extract TASK-\d+ references)
|
||||||
|
// - /\*\*冲突风险\*\*:\s*(.+)/ → conflict_risk
|
||||||
|
// - Extract modification points: /- `([^`]+):\s*([^`]+)`:\s*(.+)/ → file, location, summary
|
||||||
|
// - Return: { status, complexity, depends_on[], modification_points[], conflict_risk }
|
||||||
|
```
|
||||||
|
|
||||||
### Step 3.2: Detect Conflicts
|
### Step 3.2: Detect Conflicts
|
||||||
|
|
||||||
@@ -370,21 +431,74 @@ Scan all tasks for three categories of conflicts.
|
|||||||
| dependency_cycle | critical | Circular dependencies in task graph (DFS detection) | Remove or reorganize dependencies |
|
| dependency_cycle | critical | Circular dependencies in task graph (DFS detection) | Remove or reorganize dependencies |
|
||||||
| strategy_conflict | medium | Multiple high-risk tasks in same file from different domains | Review approaches and align on single strategy |
|
| strategy_conflict | medium | Multiple high-risk tasks in same file from different domains | Review approaches and align on single strategy |
|
||||||
|
|
||||||
**Detection Activities**:
|
**Detection Functions**:
|
||||||
1. **File Conflicts**: Group modification points by file:location, identify locations modified by multiple domains
|
|
||||||
2. **Dependency Cycles**: Build dependency graph from task dependencies, detect cycles using depth-first search
|
```javascript
|
||||||
3. **Strategy Conflicts**: Group tasks by files they modify, identify files with high-risk tasks from multiple domains
|
// detectFileConflicts(tasks)
|
||||||
|
// Build fileMap: { "file:location": [{ task_id, task_title, source_domain, change }] }
|
||||||
|
// For each location with modifications from multiple domains:
|
||||||
|
// → conflict: type='file_conflict', severity='high'
|
||||||
|
// → include: location, tasks_involved, domains_involved, modifications
|
||||||
|
// → resolution: 'Coordinate modification order or merge changes'
|
||||||
|
|
||||||
|
// detectDependencyCycles(tasks)
|
||||||
|
// Build dependency graph: { taskId: [dependsOn_taskIds] }
|
||||||
|
// DFS with recursion stack to detect cycles:
|
||||||
|
function detectCycles(tasks) {
|
||||||
|
const graph = new Map(tasks.map(t => [t.id, t.depends_on || []]))
|
||||||
|
const visited = new Set(), inStack = new Set(), cycles = []
|
||||||
|
function dfs(node, path) {
|
||||||
|
if (inStack.has(node)) { cycles.push([...path, node].join(' → ')); return }
|
||||||
|
if (visited.has(node)) return
|
||||||
|
visited.add(node); inStack.add(node)
|
||||||
|
;(graph.get(node) || []).forEach(dep => dfs(dep, [...path, node]))
|
||||||
|
inStack.delete(node)
|
||||||
|
}
|
||||||
|
tasks.forEach(t => { if (!visited.has(t.id)) dfs(t.id, []) })
|
||||||
|
return cycles
|
||||||
|
}
|
||||||
|
|
||||||
|
// detectStrategyConflicts(tasks)
|
||||||
|
// Group tasks by files they modify
|
||||||
|
// For each file with tasks from multiple domains:
|
||||||
|
// Filter for high/medium conflict_risk tasks
|
||||||
|
// If >1 high-risk from different domains:
|
||||||
|
// → conflict: type='strategy_conflict', severity='medium'
|
||||||
|
// → resolution: 'Review approaches and align on single strategy'
|
||||||
|
```
|
||||||
|
|
||||||
### Step 3.3: Generate Conflict Artifacts
|
### Step 3.3: Generate Conflict Artifacts
|
||||||
|
|
||||||
Write conflict results and update plan-note.md.
|
Write conflict results and update plan-note.md.
|
||||||
|
|
||||||
**conflicts.json Structure**:
|
```javascript
|
||||||
- `detected_at`: Detection timestamp
|
// 1. Write conflicts.json
|
||||||
- `total_conflicts`: Number of conflicts found
|
Write(`${sessionFolder}/conflicts.json`, JSON.stringify({
|
||||||
- `conflicts[]`: Array of conflict objects with type, severity, tasks involved, description, suggested resolution
|
detected_at: getUtc8ISOString(),
|
||||||
|
total_tasks: allTasks.length,
|
||||||
|
total_domains: subDomains.length,
|
||||||
|
total_conflicts: allConflicts.length,
|
||||||
|
conflicts: allConflicts // { type, severity, tasks_involved, description, suggested_resolution }
|
||||||
|
}, null, 2))
|
||||||
|
|
||||||
**plan-note.md Update**: Locate "冲突标记" section and populate with conflict summary markdown. If no conflicts found, mark as "✅ 无冲突检测到".
|
// 2. Update plan-note.md "## 冲突标记" section
|
||||||
|
// generateConflictMarkdown(conflicts):
|
||||||
|
// If empty: return '✅ 无冲突检测到'
|
||||||
|
// For each conflict:
|
||||||
|
// ### CONFLICT-{padded_index}: {description}
|
||||||
|
// - **严重程度**: critical | high | medium
|
||||||
|
// - **涉及任务**: TASK-xxx, TASK-yyy
|
||||||
|
// - **涉及领域**: domain-a, domain-b
|
||||||
|
// - **问题详情**: (based on conflict type)
|
||||||
|
// - **建议解决方案**: ...
|
||||||
|
// - **决策状态**: [ ] 待解决
|
||||||
|
|
||||||
|
// replaceSectionContent(markdown, sectionHeading, newContent):
|
||||||
|
// Find section heading position via regex
|
||||||
|
// Find next heading of same or higher level
|
||||||
|
// Replace content between heading and next section
|
||||||
|
// If section not found: append at end
|
||||||
|
```
|
||||||
|
|
||||||
**Success Criteria**:
|
**Success Criteria**:
|
||||||
- All tasks extracted and analyzed
|
- All tasks extracted and analyzed
|
||||||
@@ -413,16 +527,85 @@ Create a human-readable summary from plan-note.md content.
|
|||||||
| 冲突报告 (Conflict Report) | Summary of detected conflicts or "无冲突" |
|
| 冲突报告 (Conflict Report) | Summary of detected conflicts or "无冲突" |
|
||||||
| 执行指令 (Execution) | Command to execute the plan |
|
| 执行指令 (Execution) | Command to execute the plan |
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const planMd = `# Collaborative Plan
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${taskDescription}
|
||||||
|
**Created**: ${getUtc8ISOString()}
|
||||||
|
**Complexity**: ${complexity}
|
||||||
|
**Domains**: ${subDomains.length}
|
||||||
|
|
||||||
|
## 需求理解
|
||||||
|
|
||||||
|
${requirementSection}
|
||||||
|
|
||||||
|
## 子领域拆分
|
||||||
|
|
||||||
|
| # | Focus Area | Description | TASK Range | Effort |
|
||||||
|
|---|-----------|-------------|------------|--------|
|
||||||
|
${subDomains.map((s, i) => `| ${i+1} | ${s.focus_area} | ${s.description} | ${s.task_id_range[0]}-${s.task_id_range[1]} | ${s.estimated_effort} |`).join('\n')}
|
||||||
|
|
||||||
|
## 任务概览
|
||||||
|
|
||||||
|
${subDomains.map(sub => {
|
||||||
|
const domainTasks = allTasks.filter(t => t.source_domain === sub.focus_area)
|
||||||
|
return `### ${sub.focus_area}\n\n` +
|
||||||
|
domainTasks.map(t => `- **${t.id}**: ${t.title} (${t.complexity}) ${t.depends_on.length ? '← ' + t.depends_on.join(', ') : ''}`).join('\n')
|
||||||
|
}).join('\n\n')}
|
||||||
|
|
||||||
|
## 冲突报告
|
||||||
|
|
||||||
|
${allConflicts.length === 0
|
||||||
|
? '✅ 无冲突检测到'
|
||||||
|
: allConflicts.map(c => `- **${c.type}** (${c.severity}): ${c.description}`).join('\n')}
|
||||||
|
|
||||||
|
## 执行
|
||||||
|
|
||||||
|
\`\`\`bash
|
||||||
|
/workflow:unified-execute-with-file "${sessionFolder}/plan-note.md"
|
||||||
|
\`\`\`
|
||||||
|
|
||||||
|
**Session artifacts**: \`${sessionFolder}/\`
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/plan.md`, planMd)
|
||||||
|
```
|
||||||
|
|
||||||
### Step 4.2: Display Completion Summary
|
### Step 4.2: Display Completion Summary
|
||||||
|
|
||||||
Present session statistics and next steps.
|
Present session statistics and next steps.
|
||||||
|
|
||||||
**Summary Content**:
|
```javascript
|
||||||
- Session ID and directory path
|
// Display:
|
||||||
- Total domains planned
|
// - Session ID and directory path
|
||||||
- Total tasks generated
|
// - Total domains planned
|
||||||
- Conflict status
|
// - Total tasks generated
|
||||||
- Execution command for next step
|
// - Conflict status (count and severity)
|
||||||
|
// - Execution command for next step
|
||||||
|
|
||||||
|
if (!autoMode) {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `规划完成:\n- ${subDomains.length} 个子领域\n- ${allTasks.length} 个任务\n- ${allConflicts.length} 个冲突\n\n下一步:`,
|
||||||
|
header: "Next Step",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Execute Plan", description: "使用 unified-execute 执行计划" },
|
||||||
|
{ label: "Review Conflicts", description: "查看并解决冲突" },
|
||||||
|
{ label: "Export", description: "导出 plan.md" },
|
||||||
|
{ label: "Done", description: "保存产物,稍后执行" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Selection | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| Execute Plan | `Skill(skill="workflow:unified-execute-with-file", args="${sessionFolder}/plan-note.md")` |
|
||||||
|
| Review Conflicts | Display conflicts.json content for manual resolution |
|
||||||
|
| Export | Copy plan.md + plan-note.md to user-specified location |
|
||||||
|
| Done | Display artifact paths, end workflow |
|
||||||
|
|
||||||
**Success Criteria**:
|
**Success Criteria**:
|
||||||
- `plan.md` generated with complete summary
|
- `plan.md` generated with complete summary
|
||||||
@@ -433,73 +616,27 @@ Present session statistics and next steps.
|
|||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
| Parameter | Default | Description |
|
| Flag | Default | Description |
|
||||||
|-----------|---------|-------------|
|
|------|---------|-------------|
|
||||||
| `--max-domains` | 5 | Maximum sub-domains to identify |
|
| `--max-domains` | 5 | Maximum sub-domains to identify |
|
||||||
|
| `-y, --yes` | false | Auto-confirm all decisions |
|
||||||
---
|
|
||||||
|
|
||||||
## Error Handling & Recovery
|
|
||||||
|
|
||||||
| Situation | Action | Recovery |
|
|
||||||
|-----------|--------|----------|
|
|
||||||
| **Subagent timeout** | Check `results.timed_out`, continue `wait()` or use partial results | Reduce scope, plan remaining domains with new agent |
|
|
||||||
| **Agent closed prematurely** | Cannot recover closed agent | Spawn new agent with domain context |
|
|
||||||
| **Parallel agent partial failure** | Some domains complete, some fail | Use completed results, re-spawn for failed domains |
|
|
||||||
| **plan-note.md write conflict** | Multiple agents write simultaneously | Pre-allocated sections prevent this; if detected, re-read and verify |
|
|
||||||
| **Section not found in plan-note** | Agent creates section defensively | Continue with new section |
|
|
||||||
| **No tasks generated** | Review domain description | Retry with refined description via new agent |
|
|
||||||
| **Conflict detection fails** | Continue with empty conflicts | Note in completion summary |
|
|
||||||
| **Session folder conflict** | Append timestamp suffix | Create unique folder |
|
|
||||||
|
|
||||||
### Codex-Specific Error Patterns
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Safe parallel planning with error handling
|
|
||||||
try {
|
|
||||||
const agentIds = subDomains.map(sub => spawn_agent({ message: buildPlanPrompt(sub) }))
|
|
||||||
|
|
||||||
const results = wait({ ids: agentIds, timeout_ms: 900000 })
|
|
||||||
|
|
||||||
if (results.timed_out) {
|
|
||||||
const completed = agentIds.filter(id => results.status[id].completed)
|
|
||||||
const pending = agentIds.filter(id => !results.status[id].completed)
|
|
||||||
|
|
||||||
// Re-spawn for timed-out domains
|
|
||||||
const retryIds = pending.map((id, i) => {
|
|
||||||
const sub = subDomains[agentIds.indexOf(id)]
|
|
||||||
return spawn_agent({ message: buildPlanPrompt(sub) })
|
|
||||||
})
|
|
||||||
|
|
||||||
const retryResults = wait({ ids: retryIds, timeout_ms: 600000 })
|
|
||||||
retryIds.forEach(id => { try { close_agent({ id }) } catch(e) {} })
|
|
||||||
}
|
|
||||||
|
|
||||||
} finally {
|
|
||||||
// ALWAYS cleanup
|
|
||||||
agentIds.forEach(id => {
|
|
||||||
try { close_agent({ id }) } catch (e) { /* ignore */ }
|
|
||||||
})
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Iteration Patterns
|
## Iteration Patterns
|
||||||
|
|
||||||
### New Planning Session (Parallel Mode)
|
### New Planning Session
|
||||||
|
|
||||||
```
|
```
|
||||||
User initiates: TASK="task description"
|
User initiates: TASK="task description"
|
||||||
├─ No session exists → New session mode
|
├─ No session exists → New session mode
|
||||||
├─ Analyze task and identify sub-domains
|
├─ Analyze task with inline search tools
|
||||||
|
├─ Identify sub-domains
|
||||||
├─ Create plan-note.md template
|
├─ Create plan-note.md template
|
||||||
├─ Generate requirement-analysis.json
|
├─ Generate requirement-analysis.json
|
||||||
│
|
│
|
||||||
├─ Execute parallel planning:
|
├─ Serial domain planning:
|
||||||
│ ├─ spawn_agent × N (one per sub-domain)
|
│ ├─ Domain 1: explore → plan.json → fill plan-note.md
|
||||||
│ ├─ wait({ ids: [...] }) ← TRUE PARALLELISM
|
│ ├─ Domain 2: explore → plan.json → fill plan-note.md
|
||||||
│ └─ close_agent × N
|
│ └─ Domain N: ...
|
||||||
│
|
│
|
||||||
├─ Verify plan-note.md consistency
|
├─ Verify plan-note.md consistency
|
||||||
├─ Detect conflicts
|
├─ Detect conflicts
|
||||||
@@ -514,24 +651,24 @@ User resumes: TASK="same task"
|
|||||||
├─ Session exists → Continue mode
|
├─ Session exists → Continue mode
|
||||||
├─ Load plan-note.md and requirement-analysis.json
|
├─ Load plan-note.md and requirement-analysis.json
|
||||||
├─ Identify incomplete domains (empty task pool sections)
|
├─ Identify incomplete domains (empty task pool sections)
|
||||||
├─ Spawn agents for incomplete domains only
|
├─ Plan remaining domains serially
|
||||||
└─ Continue with conflict detection
|
└─ Continue with conflict detection
|
||||||
```
|
```
|
||||||
|
|
||||||
### Agent Lifecycle Management
|
---
|
||||||
|
|
||||||
```
|
## Error Handling & Recovery
|
||||||
Subagent lifecycle:
|
|
||||||
├─ spawn_agent({ message }) → Create with role path + task
|
|
||||||
├─ wait({ ids, timeout_ms }) → Get results (ONLY way to get output)
|
|
||||||
└─ close_agent({ id }) → Cleanup (MUST do, cannot recover)
|
|
||||||
|
|
||||||
Key rules:
|
| Situation | Action | Recovery |
|
||||||
├─ Pre-allocated sections = no write conflicts
|
|-----------|--------|----------|
|
||||||
├─ ALWAYS use wait() to get results, NOT close_agent()
|
| No codebase detected | Normal flow, pure requirement planning | Proceed without codebase context |
|
||||||
├─ Batch wait for all domain agents: wait({ ids: [a, b, c, ...] })
|
| Codebase search fails | Continue with available context | Note limitation in plan-note.md |
|
||||||
└─ Verify plan-note.md after batch completion
|
| Domain planning fails | Record error, continue with next domain | Retry failed domain or plan manually |
|
||||||
```
|
| Section not found in plan-note | Create section defensively | Continue with new section |
|
||||||
|
| No tasks generated for a domain | Review domain description | Refine scope and retry |
|
||||||
|
| Conflict detection fails | Continue with empty conflicts | Note in completion summary |
|
||||||
|
| Session folder conflict | Append timestamp suffix | Create unique folder |
|
||||||
|
| plan-note.md format inconsistency | Validate and fix format after each domain | Re-read and normalize |
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -540,25 +677,17 @@ Key rules:
|
|||||||
### Before Starting Planning
|
### Before Starting Planning
|
||||||
|
|
||||||
1. **Clear Task Description**: Detailed requirements lead to better sub-domain splitting
|
1. **Clear Task Description**: Detailed requirements lead to better sub-domain splitting
|
||||||
2. **Reference Documentation**: Ensure latest README and design docs are identified
|
2. **Reference Documentation**: Ensure latest README and design docs are identified during Phase 1
|
||||||
3. **Clarify Ambiguities**: Resolve unclear requirements before committing to sub-domains
|
3. **Clarify Ambiguities**: Resolve unclear requirements before committing to sub-domains
|
||||||
|
|
||||||
### During Planning
|
### During Planning
|
||||||
|
|
||||||
1. **Review Plan Note**: Check plan-note.md between phases to verify progress
|
1. **Review Plan Note**: Check plan-note.md between domains to verify progress
|
||||||
2. **Verify Domains**: Ensure sub-domains are truly independent and parallelizable
|
2. **Verify Independence**: Ensure sub-domains are truly independent and have minimal overlap
|
||||||
3. **Check Dependencies**: Cross-domain dependencies should be documented explicitly
|
3. **Check Dependencies**: Cross-domain dependencies should be documented explicitly
|
||||||
4. **Inspect Details**: Review `agents/{domain}/plan.json` for specifics when needed
|
4. **Inspect Details**: Review `domains/{domain}/plan.json` for specifics when needed
|
||||||
|
5. **Consistent Format**: Follow task summary format strictly across all domains
|
||||||
### Codex Subagent Best Practices
|
6. **TASK ID Isolation**: Use pre-assigned non-overlapping ranges to prevent ID conflicts
|
||||||
|
|
||||||
1. **Role Path, Not Content**: Pass `~/.codex/agents/*.md` path in message, let agent read itself
|
|
||||||
2. **Pre-allocated Sections**: Each agent only writes to its own sections - no write conflicts
|
|
||||||
3. **Batch wait**: Use `wait({ ids: [a, b, c] })` for all domain agents, not sequential waits
|
|
||||||
4. **Handle Timeouts**: Check `results.timed_out`, re-spawn for failed domains
|
|
||||||
5. **Explicit Cleanup**: Always `close_agent` when done, even on errors (use try/finally)
|
|
||||||
6. **Verify After Batch**: Read plan-note.md after all agents complete to verify consistency
|
|
||||||
7. **TASK ID Isolation**: Pre-assigned non-overlapping ranges prevent ID conflicts
|
|
||||||
|
|
||||||
### After Planning
|
### After Planning
|
||||||
|
|
||||||
@@ -566,6 +695,26 @@ Key rules:
|
|||||||
2. **Review Summary**: Check plan.md for completeness and accuracy
|
2. **Review Summary**: Check plan.md for completeness and accuracy
|
||||||
3. **Validate Tasks**: Ensure all tasks have clear scope and modification targets
|
3. **Validate Tasks**: Ensure all tasks have clear scope and modification targets
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
**Use collaborative-plan-with-file when:**
|
||||||
|
- A complex task spans multiple sub-domains (backend + frontend + database, etc.)
|
||||||
|
- Need structured multi-domain task breakdown with conflict detection
|
||||||
|
- Planning a feature that touches many parts of the codebase
|
||||||
|
- Want pre-allocated section organization for clear domain separation
|
||||||
|
|
||||||
|
**Use lite-plan when:**
|
||||||
|
- Single domain, clear task with no sub-domain splitting needed
|
||||||
|
- Quick planning without conflict detection
|
||||||
|
|
||||||
|
**Use req-plan-with-file when:**
|
||||||
|
- Requirement-level progressive roadmap needed (MVP → iterations)
|
||||||
|
- Higher-level decomposition before detailed planning
|
||||||
|
|
||||||
|
**Use analyze-with-file when:**
|
||||||
|
- Need in-depth analysis before planning
|
||||||
|
- Understanding and discussion, not task generation
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Now execute collaborative-plan-with-file for**: $TASK
|
**Now execute collaborative-plan-with-file for**: $ARGUMENTS
|
||||||
|
|||||||
920
.codex/skills/req-plan-with-file/SKILL.md
Normal file
920
.codex/skills/req-plan-with-file/SKILL.md
Normal file
@@ -0,0 +1,920 @@
|
|||||||
|
---
|
||||||
|
name: req-plan-with-file
|
||||||
|
description: Requirement-level progressive roadmap planning with JSONL output. Decomposes requirements into convergent layers (MVP→iterations) or topologically-sorted task sequences, each with testable completion criteria.
|
||||||
|
argument-hint: "[-y|--yes] [-c|--continue] [-m|--mode progressive|direct|auto] \"requirement description\""
|
||||||
|
---
|
||||||
|
|
||||||
|
# Codex Req-Plan-With-File Prompt
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Requirement-level roadmap planning with **JSONL output and convergence criteria**. Decomposes a requirement into self-contained layers or tasks, each with testable completion criteria, independently executable via `lite-plan`.
|
||||||
|
|
||||||
|
**Core workflow**: Requirement Understanding → Strategy Selection → Context Collection (optional) → Decomposition → Validation → Quality Check → Output
|
||||||
|
|
||||||
|
**Dual modes**:
|
||||||
|
- **Progressive**: Layered MVP→iterations, suitable for high-uncertainty requirements (validate first, then refine)
|
||||||
|
- **Direct**: Topologically-sorted task sequence, suitable for low-uncertainty requirements (clear tasks, directly ordered)
|
||||||
|
- **Auto**: Automatically selects based on uncertainty assessment
|
||||||
|
|
||||||
|
## Auto Mode
|
||||||
|
|
||||||
|
When `--yes` or `-y`: Auto-confirm strategy selection, use recommended mode, skip interactive validation rounds.
|
||||||
|
|
||||||
|
## Quick Start
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Basic usage
|
||||||
|
/codex:req-plan-with-file "Implement user authentication system with OAuth and 2FA"
|
||||||
|
|
||||||
|
# With mode selection
|
||||||
|
/codex:req-plan-with-file -m progressive "Build real-time notification system"
|
||||||
|
/codex:req-plan-with-file -m direct "Refactor payment module"
|
||||||
|
/codex:req-plan-with-file -m auto "Add data export feature"
|
||||||
|
|
||||||
|
# Continue existing session
|
||||||
|
/codex:req-plan-with-file --continue "user authentication system"
|
||||||
|
|
||||||
|
# Auto mode (skip all confirmations)
|
||||||
|
/codex:req-plan-with-file -y "Implement caching layer"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Target Requirement
|
||||||
|
|
||||||
|
**$ARGUMENTS**
|
||||||
|
|
||||||
|
## Execution Process
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 0: Session Setup
|
||||||
|
├─ Parse flags (-y, -c, -m) and requirement text
|
||||||
|
├─ Generate session ID: RPLAN-{slug}-{date}
|
||||||
|
└─ Create session folder (or detect existing → continue mode)
|
||||||
|
|
||||||
|
Step 1: Parse Requirement & Select Strategy
|
||||||
|
├─ Extract goal, constraints, stakeholders, domain keywords
|
||||||
|
├─ Assess uncertainty (5 dimensions)
|
||||||
|
├─ Select mode: progressive / direct
|
||||||
|
├─ Write strategy-assessment.json
|
||||||
|
└─ Initialize roadmap.md skeleton
|
||||||
|
|
||||||
|
Step 2: Explore Codebase (Optional)
|
||||||
|
├─ Detect project markers (package.json, go.mod, etc.)
|
||||||
|
├─ Has codebase → search relevant modules, patterns, integration points
|
||||||
|
│ ├─ Read project-tech.json / project-guidelines.json (if exists)
|
||||||
|
│ ├─ Search modules/components related to the requirement
|
||||||
|
│ └─ Write exploration-codebase.json
|
||||||
|
└─ No codebase → skip
|
||||||
|
|
||||||
|
Step 3: Decompose Requirement → roadmap.jsonl
|
||||||
|
├─ Step 3.1: Build decomposition (requirement + strategy + context)
|
||||||
|
│ ├─ Generate JSONL records with convergence criteria
|
||||||
|
│ └─ Apply convergence quality requirements
|
||||||
|
├─ Step 3.2: Validate records
|
||||||
|
│ ├─ Schema compliance, scope integrity
|
||||||
|
│ ├─ No circular dependencies (topological sort)
|
||||||
|
│ ├─ Convergence quality (testable criteria, executable verification, business DoD)
|
||||||
|
│ └─ Write roadmap.jsonl
|
||||||
|
└─ Step 3.3: Quality check (MANDATORY)
|
||||||
|
├─ Requirement coverage, convergence quality, scope integrity
|
||||||
|
├─ Dependency correctness, effort balance
|
||||||
|
└─ Auto-fix or report issues
|
||||||
|
|
||||||
|
Step 4: Validate & Finalize → roadmap.md
|
||||||
|
├─ Display decomposition (tabular + convergence details)
|
||||||
|
├─ User feedback loop (up to 5 rounds, skip if -y)
|
||||||
|
├─ Write final roadmap.md
|
||||||
|
└─ Next step options: lite-plan / issue / export / done
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
| Flag | Default | Description |
|
||||||
|
|------|---------|-------------|
|
||||||
|
| `-y, --yes` | false | Auto-confirm all decisions |
|
||||||
|
| `-c, --continue` | false | Continue existing session |
|
||||||
|
| `-m, --mode` | auto | Decomposition strategy: progressive / direct / auto |
|
||||||
|
|
||||||
|
**Session ID format**: `RPLAN-{slug}-{YYYY-MM-DD}`
|
||||||
|
- slug: lowercase, alphanumeric + CJK characters, max 40 chars
|
||||||
|
- date: YYYY-MM-DD (UTC+8)
|
||||||
|
- Auto-detect continue: session folder + roadmap.jsonl exists → continue mode
|
||||||
|
|
||||||
|
## Implementation Details
|
||||||
|
|
||||||
|
### Session Setup
|
||||||
|
|
||||||
|
##### Step 0: Initialize Session
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||||
|
|
||||||
|
// Parse flags
|
||||||
|
const autoYes = $ARGUMENTS.includes('--yes') || $ARGUMENTS.includes('-y')
|
||||||
|
const continueMode = $ARGUMENTS.includes('--continue') || $ARGUMENTS.includes('-c')
|
||||||
|
const modeMatch = $ARGUMENTS.match(/(?:--mode|-m)\s+(progressive|direct|auto)/)
|
||||||
|
const requestedMode = modeMatch ? modeMatch[1] : 'auto'
|
||||||
|
|
||||||
|
// Clean requirement text
|
||||||
|
const requirement = $ARGUMENTS
|
||||||
|
.replace(/--yes|-y|--continue|-c|--mode\s+\w+|-m\s+\w+/g, '')
|
||||||
|
.trim()
|
||||||
|
|
||||||
|
const slug = requirement.toLowerCase()
|
||||||
|
.replace(/[^a-z0-9\u4e00-\u9fa5]+/g, '-')
|
||||||
|
.substring(0, 40)
|
||||||
|
const dateStr = getUtc8ISOString().substring(0, 10)
|
||||||
|
const sessionId = `RPLAN-${slug}-${dateStr}`
|
||||||
|
const sessionFolder = `.workflow/.req-plan/${sessionId}`
|
||||||
|
|
||||||
|
// Auto-detect continue: session folder + roadmap.jsonl exists → continue mode
|
||||||
|
// If continue → skip to Step 4 (load existing roadmap.jsonl, display, collect feedback)
|
||||||
|
Bash(`mkdir -p ${sessionFolder}`)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 1: Parse Requirement & Select Strategy
|
||||||
|
|
||||||
|
**Objective**: Parse requirement, assess uncertainty, select decomposition strategy.
|
||||||
|
|
||||||
|
##### Step 1.1: Analyze Requirement & Select Strategy
|
||||||
|
|
||||||
|
Parse the requirement, evaluate uncertainty across 5 dimensions, and select decomposition strategy in one step:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 1. Extract from requirement text
|
||||||
|
const goal = extractCoreGoal(requirement) // What to achieve
|
||||||
|
const constraints = extractConstraints(requirement) // Tech stack, timeline, compatibility
|
||||||
|
const stakeholders = extractStakeholders(requirement) // Users, admins, developers
|
||||||
|
const domainKeywords = extractDomainKeywords(requirement) // Domain-specific terms
|
||||||
|
|
||||||
|
// 2. Assess uncertainty (each: low | medium | high)
|
||||||
|
const uncertaintyFactors = {
|
||||||
|
scope_clarity: '...', // Is scope well-defined?
|
||||||
|
technical_risk: '...', // Known tech vs experimental?
|
||||||
|
dependency_unknown: '...', // Are dependencies clear?
|
||||||
|
domain_familiarity: '...', // Team knows this domain?
|
||||||
|
requirement_stability: '...' // Will requirements change?
|
||||||
|
}
|
||||||
|
// >=3 high → progressive, >=3 low → direct, otherwise → ask
|
||||||
|
|
||||||
|
// 3. Select strategy
|
||||||
|
let selectedMode
|
||||||
|
if (requestedMode !== 'auto') {
|
||||||
|
selectedMode = requestedMode
|
||||||
|
} else if (autoYes) {
|
||||||
|
selectedMode = recommendedMode
|
||||||
|
} else {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Decomposition strategy selection:\n\nUncertainty assessment: ${uncertaintyLevel}\nRecommended strategy: ${recommendedMode}\n\nSelect decomposition strategy:`,
|
||||||
|
header: "Strategy",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
label: recommendedMode === 'progressive' ? "Progressive (Recommended)" : "Progressive",
|
||||||
|
description: "Layered MVP→iterations, validate core first then refine progressively. Suitable for high-uncertainty requirements needing quick validation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: recommendedMode === 'direct' ? "Direct (Recommended)" : "Direct",
|
||||||
|
description: "Topologically-sorted task sequence with explicit dependencies. Suitable for clear requirements with confirmed technical approach"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Write strategy assessment
|
||||||
|
Write(`${sessionFolder}/strategy-assessment.json`, JSON.stringify({
|
||||||
|
session_id: sessionId,
|
||||||
|
requirement, timestamp: getUtc8ISOString(),
|
||||||
|
uncertainty_factors: uncertaintyFactors,
|
||||||
|
uncertainty_level: uncertaintyLevel,
|
||||||
|
recommended_mode: recommendedMode,
|
||||||
|
selected_mode: selectedMode,
|
||||||
|
goal, constraints, stakeholders,
|
||||||
|
domain_keywords: domainKeywords
|
||||||
|
}, null, 2))
|
||||||
|
|
||||||
|
// 5. Initialize roadmap.md skeleton
|
||||||
|
const roadmapMdSkeleton = `# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${requirement}
|
||||||
|
**Strategy**: ${selectedMode}
|
||||||
|
**Status**: Planning
|
||||||
|
**Created**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Uncertainty level: ${uncertaintyLevel}
|
||||||
|
- Decomposition mode: ${selectedMode}
|
||||||
|
- Assessment basis: ${Object.entries(uncertaintyFactors).map(([k,v]) => `${k}=${v}`).join(', ')}
|
||||||
|
|
||||||
|
## Roadmap
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Convergence Criteria Details
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Risk Items
|
||||||
|
> To be populated after Phase 3 decomposition
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
> To be populated after Phase 4 validation
|
||||||
|
`
|
||||||
|
Write(`${sessionFolder}/roadmap.md`, roadmapMdSkeleton)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- Requirement goal, constraints, stakeholders, domain keywords identified
|
||||||
|
- Uncertainty level assessed across 5 dimensions
|
||||||
|
- Strategy selected (progressive or direct)
|
||||||
|
- strategy-assessment.json generated
|
||||||
|
- roadmap.md skeleton initialized
|
||||||
|
|
||||||
|
### Phase 2: Explore Codebase (Optional)
|
||||||
|
|
||||||
|
**Objective**: If a codebase exists, collect relevant context to enhance decomposition quality.
|
||||||
|
|
||||||
|
##### Step 2.1: Detect & Explore
|
||||||
|
|
||||||
|
If a codebase exists, directly search for relevant context. No agent delegation — use search tools inline.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const hasCodebase = Bash(`
|
||||||
|
test -f package.json && echo "nodejs" ||
|
||||||
|
test -f go.mod && echo "golang" ||
|
||||||
|
test -f Cargo.toml && echo "rust" ||
|
||||||
|
test -f pyproject.toml && echo "python" ||
|
||||||
|
test -f pom.xml && echo "java" ||
|
||||||
|
test -d src && echo "generic" ||
|
||||||
|
echo "none"
|
||||||
|
`).trim()
|
||||||
|
|
||||||
|
if (hasCodebase !== 'none') {
|
||||||
|
// 1. Read project metadata (if exists)
|
||||||
|
// - .workflow/project-tech.json (tech stack info)
|
||||||
|
// - .workflow/project-guidelines.json (project conventions)
|
||||||
|
|
||||||
|
// 2. Search codebase for modules related to the requirement
|
||||||
|
// Use: Grep, Glob, Read, or mcp__ace-tool__search_context
|
||||||
|
// Focus on:
|
||||||
|
// - Modules/components related to the requirement
|
||||||
|
// - Existing patterns to follow
|
||||||
|
// - Integration points for new functionality
|
||||||
|
// - Architecture constraints
|
||||||
|
|
||||||
|
// 3. Write findings to exploration-codebase.json:
|
||||||
|
Write(`${sessionFolder}/exploration-codebase.json`, JSON.stringify({
|
||||||
|
project_type: hasCodebase,
|
||||||
|
relevant_modules: [...], // [{name, path, relevance}]
|
||||||
|
existing_patterns: [...], // [{pattern, files, description}]
|
||||||
|
integration_points: [...], // [{location, description, risk}]
|
||||||
|
architecture_constraints: [...],
|
||||||
|
tech_stack: {languages, frameworks, tools},
|
||||||
|
_metadata: {timestamp: getUtc8ISOString(), exploration_scope: '...'}
|
||||||
|
}, null, 2))
|
||||||
|
}
|
||||||
|
// No codebase → skip, proceed to Phase 3
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- Codebase detection complete
|
||||||
|
- When codebase exists, exploration-codebase.json generated with relevant modules, patterns, integration points
|
||||||
|
- When no codebase, skipped and proceed to Phase 3
|
||||||
|
|
||||||
|
### Phase 3: Decompose Requirement
|
||||||
|
|
||||||
|
**Objective**: Execute requirement decomposition, generating validated roadmap.jsonl.
|
||||||
|
|
||||||
|
##### Step 3.1: Generate JSONL Records
|
||||||
|
|
||||||
|
Directly decompose the requirement into JSONL records based on the selected mode. Analyze the requirement yourself and produce the records, referencing strategy assessment and codebase context (if available).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Load context
|
||||||
|
const strategy = JSON.parse(Read(`${sessionFolder}/strategy-assessment.json`))
|
||||||
|
let explorationContext = null
|
||||||
|
if (file_exists(`${sessionFolder}/exploration-codebase.json`)) {
|
||||||
|
explorationContext = JSON.parse(Read(`${sessionFolder}/exploration-codebase.json`))
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Progressive mode** — generate 2-4 layers:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Each layer must have:
|
||||||
|
// - id: L0, L1, L2, L3
|
||||||
|
// - name: MVP / Usable / Refined / Optimized
|
||||||
|
// - goal: what this layer achieves
|
||||||
|
// - scope[]: features included
|
||||||
|
// - excludes[]: features explicitly deferred
|
||||||
|
// - convergence: { criteria[], verification, definition_of_done }
|
||||||
|
// - risk_items[], effort (small|medium|large), depends_on[]
|
||||||
|
//
|
||||||
|
// Rules:
|
||||||
|
// - L0 (MVP) = self-contained closed loop, no dependencies
|
||||||
|
// - Each feature in exactly ONE layer (no overlap)
|
||||||
|
// - 2-4 layers total
|
||||||
|
// - Convergence MUST satisfy quality requirements (see below)
|
||||||
|
|
||||||
|
const layers = [
|
||||||
|
{
|
||||||
|
id: "L0", name: "MVP",
|
||||||
|
goal: "...",
|
||||||
|
scope: ["..."], excludes: ["..."],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["... (testable)"],
|
||||||
|
verification: "... (executable command or steps)",
|
||||||
|
definition_of_done: "... (business language)"
|
||||||
|
},
|
||||||
|
risk_items: [], effort: "medium", depends_on: []
|
||||||
|
},
|
||||||
|
// L1, L2, ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct mode** — generate topologically-sorted tasks:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Each task must have:
|
||||||
|
// - id: T1, T2, ...
|
||||||
|
// - title, type (infrastructure|feature|enhancement|testing)
|
||||||
|
// - scope, inputs[], outputs[]
|
||||||
|
// - convergence: { criteria[], verification, definition_of_done }
|
||||||
|
// - depends_on[], parallel_group
|
||||||
|
//
|
||||||
|
// Rules:
|
||||||
|
// - Inputs must come from preceding task outputs or existing resources
|
||||||
|
// - Same parallel_group = truly independent
|
||||||
|
// - No circular dependencies
|
||||||
|
// - Convergence MUST satisfy quality requirements (see below)
|
||||||
|
|
||||||
|
const tasks = [
|
||||||
|
{
|
||||||
|
id: "T1", title: "...", type: "infrastructure",
|
||||||
|
scope: "...", inputs: [], outputs: ["..."],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["... (testable)"],
|
||||||
|
verification: "... (executable)",
|
||||||
|
definition_of_done: "... (business language)"
|
||||||
|
},
|
||||||
|
depends_on: [], parallel_group: 1
|
||||||
|
},
|
||||||
|
// T2, T3, ...
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 3.2: Validate Records
|
||||||
|
|
||||||
|
Validate all records before writing JSONL. Fix any issues found.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const items = selectedMode === 'progressive' ? layers : tasks
|
||||||
|
const errors = []
|
||||||
|
|
||||||
|
// 1. Schema validation: each record has convergence with all 3 fields
|
||||||
|
items.forEach(item => {
|
||||||
|
if (!item.convergence?.criteria?.length) errors.push(`${item.id}: missing convergence.criteria`)
|
||||||
|
if (!item.convergence?.verification) errors.push(`${item.id}: missing convergence.verification`)
|
||||||
|
if (!item.convergence?.definition_of_done) errors.push(`${item.id}: missing convergence.definition_of_done`)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 2. Convergence quality check
|
||||||
|
const vaguePatterns = /正常|正确|好|可以|没问题|works|fine|good|correct/i
|
||||||
|
items.forEach(item => {
|
||||||
|
item.convergence.criteria.forEach((criterion, i) => {
|
||||||
|
if (vaguePatterns.test(criterion) && criterion.length < 15) {
|
||||||
|
errors.push(`${item.id} criteria[${i}]: Too vague - "${criterion}"`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (item.convergence.verification.length < 10) {
|
||||||
|
errors.push(`${item.id} verification: Too short, needs executable steps`)
|
||||||
|
}
|
||||||
|
const technicalPatterns = /compile|build|lint|npm|npx|jest|tsc|eslint/i
|
||||||
|
if (technicalPatterns.test(item.convergence.definition_of_done)) {
|
||||||
|
errors.push(`${item.id} definition_of_done: Should be business language, not technical commands`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 3. Circular dependency detection
|
||||||
|
function detectCycles(records, prefix) {
|
||||||
|
const graph = new Map(records.map(r => [r.id, r.depends_on]))
|
||||||
|
const visited = new Set(), inStack = new Set(), cycleErrors = []
|
||||||
|
function dfs(node, path) {
|
||||||
|
if (inStack.has(node)) { cycleErrors.push(`Circular: ${[...path, node].join(' → ')}`); return }
|
||||||
|
if (visited.has(node)) return
|
||||||
|
visited.add(node); inStack.add(node)
|
||||||
|
;(graph.get(node) || []).forEach(dep => dfs(dep, [...path, node]))
|
||||||
|
inStack.delete(node)
|
||||||
|
}
|
||||||
|
records.forEach(r => { if (!visited.has(r.id)) dfs(r.id, []) })
|
||||||
|
return cycleErrors
|
||||||
|
}
|
||||||
|
errors.push(...detectCycles(items, selectedMode === 'progressive' ? 'L' : 'T'))
|
||||||
|
|
||||||
|
// 4. Mode-specific validation
|
||||||
|
if (selectedMode === 'progressive') {
|
||||||
|
// Check 2-4 layers
|
||||||
|
if (items.length < 2 || items.length > 4) errors.push(`Expected 2-4 layers, got ${items.length}`)
|
||||||
|
|
||||||
|
// Check L0 is self-contained (no depends_on)
|
||||||
|
const l0 = items.find(l => l.id === 'L0')
|
||||||
|
if (l0 && l0.depends_on.length > 0) errors.push("L0 (MVP) should not have dependencies")
|
||||||
|
|
||||||
|
// Check scope overlap
|
||||||
|
const allScopes = new Map()
|
||||||
|
items.forEach(layer => {
|
||||||
|
layer.scope.forEach(feature => {
|
||||||
|
if (allScopes.has(feature)) {
|
||||||
|
errors.push(`Scope overlap: "${feature}" in both ${allScopes.get(feature)} and ${layer.id}`)
|
||||||
|
}
|
||||||
|
allScopes.set(feature, layer.id)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Check inputs/outputs chain
|
||||||
|
const availableOutputs = new Set()
|
||||||
|
items.forEach(task => {
|
||||||
|
task.inputs.forEach(input => {
|
||||||
|
if (!availableOutputs.has(input)) {
|
||||||
|
// Only warn for non-existing resources - existing files are valid inputs
|
||||||
|
}
|
||||||
|
})
|
||||||
|
task.outputs.forEach(output => availableOutputs.add(output))
|
||||||
|
})
|
||||||
|
|
||||||
|
// Check parallel_group consistency (same group tasks should not depend on each other)
|
||||||
|
const groups = new Map()
|
||||||
|
items.forEach(task => {
|
||||||
|
if (!groups.has(task.parallel_group)) groups.set(task.parallel_group, [])
|
||||||
|
groups.get(task.parallel_group).push(task)
|
||||||
|
})
|
||||||
|
groups.forEach((groupTasks, groupId) => {
|
||||||
|
if (groupTasks.length > 1) {
|
||||||
|
const ids = new Set(groupTasks.map(t => t.id))
|
||||||
|
groupTasks.forEach(task => {
|
||||||
|
task.depends_on.forEach(dep => {
|
||||||
|
if (ids.has(dep)) {
|
||||||
|
errors.push(`Parallel group ${groupId}: ${task.id} depends on ${dep} but both in same group`)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. Fix errors if any, then write
|
||||||
|
// If errors found → fix records and re-validate
|
||||||
|
// Write roadmap.jsonl (one JSON record per line)
|
||||||
|
const jsonlContent = items.map(item => JSON.stringify(item)).join('\n')
|
||||||
|
Write(`${sessionFolder}/roadmap.jsonl`, jsonlContent)
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 3.3: Quality Check (MANDATORY)
|
||||||
|
|
||||||
|
After generating roadmap.jsonl, execute a self-check across 5 quality dimensions before proceeding.
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const roadmapJsonlContent = Read(`${sessionFolder}/roadmap.jsonl`)
|
||||||
|
|
||||||
|
// Quality dimensions to verify:
|
||||||
|
//
|
||||||
|
// | Dimension | Check Criteria | Critical? |
|
||||||
|
// |------------------------|-------------------------------------------------------------|-----------|
|
||||||
|
// | Requirement Coverage | All aspects of original requirement addressed in layers/tasks | Yes |
|
||||||
|
// | Convergence Quality | criteria testable, verification executable, DoD business-readable | Yes |
|
||||||
|
// | Scope Integrity | Progressive: no overlap/gaps; Direct: inputs/outputs chain valid | Yes |
|
||||||
|
// | Dependency Correctness | No circular deps, proper ordering | Yes |
|
||||||
|
// | Effort Balance | No single layer/task disproportionately large | No |
|
||||||
|
//
|
||||||
|
// Decision after check:
|
||||||
|
// - PASS → proceed to Phase 4
|
||||||
|
// - AUTO_FIX → fix convergence wording, rebalance scope, update roadmap.jsonl
|
||||||
|
// - NEEDS_REVIEW → report issues to user in Phase 4 feedback
|
||||||
|
|
||||||
|
// Auto-fix strategy:
|
||||||
|
// | Issue Type | Auto-Fix Action |
|
||||||
|
// |-----------------|----------------------------------------------|
|
||||||
|
// | Vague criteria | Replace with specific, testable conditions |
|
||||||
|
// | Technical DoD | Rewrite in business language |
|
||||||
|
// | Missing scope | Add to appropriate layer/task |
|
||||||
|
// | Effort imbalance| Split oversized layer/task |
|
||||||
|
|
||||||
|
// After auto-fixes, update roadmap.jsonl
|
||||||
|
```
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- roadmap.jsonl generated, each line independently JSON.parse-able
|
||||||
|
- Each record contains convergence (criteria + verification + definition_of_done)
|
||||||
|
- Quality check passed (all critical dimensions)
|
||||||
|
- No circular dependencies
|
||||||
|
- Progressive: 2-4 layers, no scope overlap, L0 self-contained
|
||||||
|
- Direct: tasks have explicit inputs/outputs, parallel_group assigned correctly
|
||||||
|
|
||||||
|
### Phase 4: Validate & Finalize
|
||||||
|
|
||||||
|
**Objective**: Display decomposition results, collect user feedback, generate final artifacts.
|
||||||
|
|
||||||
|
##### Step 4.1: Display Results & Collect Feedback
|
||||||
|
|
||||||
|
Display the decomposition as a table with convergence criteria, then run feedback loop.
|
||||||
|
|
||||||
|
**Progressive Mode display format**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Roadmap Overview
|
||||||
|
|
||||||
|
| Layer | Name | Goal | Scope | Effort | Dependencies |
|
||||||
|
|-------|------|------|-------|--------|--------------|
|
||||||
|
| L0 | MVP | ... | ... | medium | - |
|
||||||
|
| L1 | Usable | ... | ... | medium | L0 |
|
||||||
|
|
||||||
|
### Convergence Criteria
|
||||||
|
**L0 - MVP**:
|
||||||
|
- Criteria: [criteria list]
|
||||||
|
- Verification: [verification]
|
||||||
|
- Definition of Done: [definition_of_done]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct Mode display format**:
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Task Sequence
|
||||||
|
|
||||||
|
| Group | ID | Title | Type | Dependencies |
|
||||||
|
|-------|----|-------|------|--------------|
|
||||||
|
| 1 | T1 | ... | infrastructure | - |
|
||||||
|
| 2 | T2 | ... | feature | T1 |
|
||||||
|
|
||||||
|
### Convergence Criteria
|
||||||
|
**T1 - Establish Data Model**:
|
||||||
|
- Criteria: [criteria list]
|
||||||
|
- Verification: [verification]
|
||||||
|
- Definition of Done: [definition_of_done]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Feedback loop**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const items = Read(`${sessionFolder}/roadmap.jsonl`)
|
||||||
|
.split('\n').filter(l => l.trim()).map(l => JSON.parse(l))
|
||||||
|
|
||||||
|
// Display tabular results + convergence for each item (using format above)
|
||||||
|
|
||||||
|
if (!autoYes) {
|
||||||
|
let round = 0, continueLoop = true
|
||||||
|
while (continueLoop && round < 5) {
|
||||||
|
round++
|
||||||
|
const feedback = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: `Roadmap validation (round ${round}):\nAny feedback on the current decomposition?`,
|
||||||
|
header: "Feedback",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Approve", description: "Decomposition is reasonable, generate final artifacts" },
|
||||||
|
{ label: "Adjust Scope", description: "Some layer/task scopes need adjustment" },
|
||||||
|
{ label: "Modify Convergence", description: "Convergence criteria not specific enough" },
|
||||||
|
{ label: "Re-decompose", description: "Overall strategy or layering needs change" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
if (feedback === 'Approve') continueLoop = false
|
||||||
|
// else: apply adjustment, re-write roadmap.jsonl, re-display, loop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Step 4.2: Write roadmap.md & Next Steps
|
||||||
|
|
||||||
|
Generate final roadmap.md using the generation templates below, then provide post-completion options.
|
||||||
|
|
||||||
|
**Progressive mode roadmap.md generation**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const roadmapMd = `# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${requirement}
|
||||||
|
**Strategy**: progressive
|
||||||
|
**Uncertainty**: ${strategy.uncertainty_level}
|
||||||
|
**Generated**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Uncertainty level: ${strategy.uncertainty_level}
|
||||||
|
- Decomposition mode: progressive
|
||||||
|
- Assessment basis: ${Object.entries(strategy.uncertainty_factors).map(([k,v]) => `${k}=${v}`).join(', ')}
|
||||||
|
- Goal: ${strategy.goal}
|
||||||
|
- Constraints: ${strategy.constraints.join(', ') || 'None'}
|
||||||
|
- Stakeholders: ${strategy.stakeholders.join(', ') || 'None'}
|
||||||
|
|
||||||
|
## Roadmap Overview
|
||||||
|
|
||||||
|
| Layer | Name | Goal | Effort | Dependencies |
|
||||||
|
|-------|------|------|--------|--------------|
|
||||||
|
${items.map(l => `| ${l.id} | ${l.name} | ${l.goal} | ${l.effort} | ${l.depends_on.length ? l.depends_on.join(', ') : '-'} |`).join('\n')}
|
||||||
|
|
||||||
|
## Layer Details
|
||||||
|
|
||||||
|
${items.map(l => `### ${l.id}: ${l.name}
|
||||||
|
|
||||||
|
**Goal**: ${l.goal}
|
||||||
|
|
||||||
|
**Scope**: ${l.scope.join(', ')}
|
||||||
|
|
||||||
|
**Excludes**: ${l.excludes.join(', ') || 'None'}
|
||||||
|
|
||||||
|
**Convergence Criteria**:
|
||||||
|
${l.convergence.criteria.map(c => \`- ${c}\`).join('\n')}
|
||||||
|
- **Verification**: ${l.convergence.verification}
|
||||||
|
- **Definition of Done**: ${l.convergence.definition_of_done}
|
||||||
|
|
||||||
|
**Risk Items**: ${l.risk_items.length ? l.risk_items.map(r => \`\n- ${r}\`).join('') : 'None'}
|
||||||
|
|
||||||
|
**Effort**: ${l.effort}
|
||||||
|
`).join('\n---\n\n')}
|
||||||
|
|
||||||
|
## Risk Summary
|
||||||
|
|
||||||
|
${items.flatMap(l => l.risk_items.map(r => \`- **${l.id}**: ${r}\`)).join('\n') || 'No identified risks'}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Each layer can be executed independently:
|
||||||
|
\\\`\\\`\\\`bash
|
||||||
|
/workflow:lite-plan "${items[0]?.name}: ${items[0]?.scope.join(', ')}"
|
||||||
|
\\\`\\\`\\\`
|
||||||
|
|
||||||
|
Roadmap JSONL file: \\\`${sessionFolder}/roadmap.jsonl\\\`
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct mode roadmap.md generation**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const roadmapMd = `# Requirement Roadmap
|
||||||
|
|
||||||
|
**Session**: ${sessionId}
|
||||||
|
**Requirement**: ${requirement}
|
||||||
|
**Strategy**: direct
|
||||||
|
**Generated**: ${getUtc8ISOString()}
|
||||||
|
|
||||||
|
## Strategy Assessment
|
||||||
|
- Goal: ${strategy.goal}
|
||||||
|
- Constraints: ${strategy.constraints.join(', ') || 'None'}
|
||||||
|
- Assessment basis: ${Object.entries(strategy.uncertainty_factors).map(([k,v]) => `${k}=${v}`).join(', ')}
|
||||||
|
|
||||||
|
## Task Sequence
|
||||||
|
|
||||||
|
| Group | ID | Title | Type | Dependencies |
|
||||||
|
|-------|----|-------|------|--------------|
|
||||||
|
${items.map(t => `| ${t.parallel_group} | ${t.id} | ${t.title} | ${t.type} | ${t.depends_on.length ? t.depends_on.join(', ') : '-'} |`).join('\n')}
|
||||||
|
|
||||||
|
## Task Details
|
||||||
|
|
||||||
|
${items.map(t => `### ${t.id}: ${t.title}
|
||||||
|
|
||||||
|
**Type**: ${t.type} | **Parallel Group**: ${t.parallel_group}
|
||||||
|
|
||||||
|
**Scope**: ${t.scope}
|
||||||
|
|
||||||
|
**Inputs**: ${t.inputs.length ? t.inputs.join(', ') : 'None (starting task)'}
|
||||||
|
**Outputs**: ${t.outputs.join(', ')}
|
||||||
|
|
||||||
|
**Convergence Criteria**:
|
||||||
|
${t.convergence.criteria.map(c => \`- ${c}\`).join('\n')}
|
||||||
|
- **Verification**: ${t.convergence.verification}
|
||||||
|
- **Definition of Done**: ${t.convergence.definition_of_done}
|
||||||
|
`).join('\n---\n\n')}
|
||||||
|
|
||||||
|
## Next Steps
|
||||||
|
|
||||||
|
Each task can be executed independently:
|
||||||
|
\\\`\\\`\\\`bash
|
||||||
|
/workflow:lite-plan "${items[0]?.title}: ${items[0]?.scope}"
|
||||||
|
\\\`\\\`\\\`
|
||||||
|
|
||||||
|
Roadmap JSONL file: \\\`${sessionFolder}/roadmap.jsonl\\\`
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
|
**Write and provide post-completion options**:
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Write(`${sessionFolder}/roadmap.md`, roadmapMd)
|
||||||
|
|
||||||
|
// Post-completion options
|
||||||
|
if (!autoYes) {
|
||||||
|
AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: "Roadmap generated. Next step:",
|
||||||
|
header: "Next Step",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Execute First Layer", description: `Launch lite-plan to execute ${items[0].id}` },
|
||||||
|
{ label: "Create Issue", description: "Create GitHub Issue based on roadmap" },
|
||||||
|
{ label: "Export Report", description: "Generate standalone shareable roadmap report" },
|
||||||
|
{ label: "Done", description: "Save roadmap only, execute later" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
| Selection | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| Execute First Layer | `Skill(skill="workflow:lite-plan", args="${firstItem.scope}")` |
|
||||||
|
| Create Issue | `Skill(skill="issue:new", args="...")` |
|
||||||
|
| Export Report | Copy roadmap.md + roadmap.jsonl to user-specified location |
|
||||||
|
| Done | Display roadmap file paths, end |
|
||||||
|
|
||||||
|
**Success Criteria**:
|
||||||
|
- User feedback processed (or skipped via autoYes)
|
||||||
|
- roadmap.md finalized with full tables and convergence details
|
||||||
|
- roadmap.jsonl final version updated
|
||||||
|
- Post-completion options provided
|
||||||
|
|
||||||
|
## Session Folder Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
.workflow/.req-plan/RPLAN-{slug}-{YYYY-MM-DD}/
|
||||||
|
├── roadmap.md # Human-readable roadmap
|
||||||
|
├── roadmap.jsonl # Machine-readable, one self-contained record per line
|
||||||
|
├── strategy-assessment.json # Strategy assessment result
|
||||||
|
└── exploration-codebase.json # Codebase context (optional)
|
||||||
|
```
|
||||||
|
|
||||||
|
| File | Phase | Description |
|
||||||
|
|------|-------|-------------|
|
||||||
|
| `strategy-assessment.json` | 1 | Uncertainty analysis + mode recommendation + extracted goal/constraints/stakeholders/domain_keywords |
|
||||||
|
| `roadmap.md` (skeleton) | 1 | Initial skeleton with placeholders, finalized in Phase 4 |
|
||||||
|
| `exploration-codebase.json` | 2 | Codebase context: relevant modules, patterns, integration points (only when codebase exists) |
|
||||||
|
| `roadmap.jsonl` | 3 | One self-contained JSON record per line with convergence criteria |
|
||||||
|
| `roadmap.md` (final) | 4 | Human-readable roadmap with tabular display + convergence details, revised per user feedback |
|
||||||
|
|
||||||
|
## JSONL Schema
|
||||||
|
|
||||||
|
### Convergence Criteria
|
||||||
|
|
||||||
|
Each record's `convergence` object:
|
||||||
|
|
||||||
|
| Field | Purpose | Requirement |
|
||||||
|
|-------|---------|-------------|
|
||||||
|
| `criteria[]` | List of checkable specific conditions | **Testable** - can be written as assertions or manual steps |
|
||||||
|
| `verification` | How to verify these conditions | **Executable** - command, script, or explicit steps |
|
||||||
|
| `definition_of_done` | One-sentence completion definition | **Business language** - non-technical person can judge |
|
||||||
|
|
||||||
|
### Progressive Mode (one layer per line)
|
||||||
|
|
||||||
|
| Layer | Name | Typical Goal |
|
||||||
|
|-------|------|--------------|
|
||||||
|
| L0 | MVP | Minimum viable closed loop, core path end-to-end |
|
||||||
|
| L1 | Usable | Key user paths refined, basic error handling |
|
||||||
|
| L2 | Refined | Edge cases, performance, security hardening |
|
||||||
|
| L3 | Optimized | Advanced features, observability, operations |
|
||||||
|
|
||||||
|
**Schema**: `id, name, goal, scope[], excludes[], convergence{}, risk_items[], effort, depends_on[]`
|
||||||
|
|
||||||
|
```jsonl
|
||||||
|
{"id":"L0","name":"MVP","goal":"Minimum viable closed loop","scope":["User registration and login","Basic CRUD"],"excludes":["OAuth","2FA"],"convergence":{"criteria":["End-to-end register→login→operate flow works","Core API returns correct responses"],"verification":"curl/Postman manual testing or smoke test script","definition_of_done":"New user can complete register→login→perform one core operation"},"risk_items":["JWT library selection needs validation"],"effort":"medium","depends_on":[]}
|
||||||
|
{"id":"L1","name":"Usable","goal":"Complete key user paths","scope":["Password reset","Input validation","Error messages"],"excludes":["Audit logs","Rate limiting"],"convergence":{"criteria":["All form fields have frontend+backend validation","Password reset email can be sent and reset completed","Error scenarios show user-friendly messages"],"verification":"Unit tests cover validation logic + manual test of reset flow","definition_of_done":"Users have a clear recovery path when encountering input errors or forgotten passwords"},"risk_items":[],"effort":"medium","depends_on":["L0"]}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constraints**: 2-4 layers, L0 must be a self-contained closed loop with no dependencies, each feature belongs to exactly ONE layer (no scope overlap).
|
||||||
|
|
||||||
|
### Direct Mode (one task per line)
|
||||||
|
|
||||||
|
| Type | Use Case |
|
||||||
|
|------|----------|
|
||||||
|
| infrastructure | Data models, configuration, scaffolding |
|
||||||
|
| feature | API, UI, business logic implementation |
|
||||||
|
| enhancement | Validation, error handling, edge cases |
|
||||||
|
| testing | Unit tests, integration tests, E2E |
|
||||||
|
|
||||||
|
**Schema**: `id, title, type, scope, inputs[], outputs[], convergence{}, depends_on[], parallel_group`
|
||||||
|
|
||||||
|
```jsonl
|
||||||
|
{"id":"T1","title":"Establish data model","type":"infrastructure","scope":"DB schema + TypeScript types","inputs":[],"outputs":["schema.prisma","types/user.ts"],"convergence":{"criteria":["Migration executes without errors","TypeScript types compile successfully","Fields cover all business entities"],"verification":"npx prisma migrate dev && npx tsc --noEmit","definition_of_done":"Database schema migrates correctly, type definitions can be referenced by other modules"},"depends_on":[],"parallel_group":1}
|
||||||
|
{"id":"T2","title":"Implement core API","type":"feature","scope":"CRUD endpoints for User","inputs":["schema.prisma","types/user.ts"],"outputs":["routes/user.ts","controllers/user.ts"],"convergence":{"criteria":["GET/POST/PUT/DELETE return correct status codes","Request/response conforms to schema","No N+1 queries"],"verification":"jest --testPathPattern=user.test.ts","definition_of_done":"All User CRUD endpoints pass integration tests"},"depends_on":["T1"],"parallel_group":2}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Constraints**: Inputs must come from preceding task outputs or existing resources, tasks in same parallel_group must be truly independent, no circular dependencies.
|
||||||
|
|
||||||
|
## Convergence Quality Requirements
|
||||||
|
|
||||||
|
Every `convergence` field MUST satisfy these quality standards:
|
||||||
|
|
||||||
|
| Field | Requirement | Bad Example | Good Example |
|
||||||
|
|-------|-------------|-------------|--------------|
|
||||||
|
| `criteria[]` | **Testable** - can write assertions or manual steps | `"System works correctly"` | `"API returns 200 and response body contains user_id field"` |
|
||||||
|
| `verification` | **Executable** - command, script, or clear steps | `"Check it"` | `"jest --testPathPattern=auth && curl -s localhost:3000/health"` |
|
||||||
|
| `definition_of_done` | **Business language** - non-technical person can judge | `"Code compiles"` | `"New user can complete register→login→perform core operation flow"` |
|
||||||
|
|
||||||
|
**NEVER** output vague convergence criteria ("works correctly", "system is normal"). **ALWAYS** ensure:
|
||||||
|
- criteria are testable (can be written as assertions or manual verification steps)
|
||||||
|
- verification is executable (commands or explicit steps)
|
||||||
|
- definition_of_done uses business language (non-technical stakeholders can judge)
|
||||||
|
|
||||||
|
## Fallback Decomposition
|
||||||
|
|
||||||
|
When normal decomposition fails or produces empty results, use fallback templates:
|
||||||
|
|
||||||
|
**Progressive fallback**:
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "L0", name: "MVP", goal: "Minimum viable closed loop",
|
||||||
|
scope: ["Core functionality"], excludes: ["Advanced features", "Optimization"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["Core path works end-to-end"],
|
||||||
|
verification: "Manual test of core flow",
|
||||||
|
definition_of_done: "User can complete one full core operation"
|
||||||
|
},
|
||||||
|
risk_items: ["Tech selection needs validation"], effort: "medium", depends_on: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "L1", name: "Usable", goal: "Refine key user paths",
|
||||||
|
scope: ["Error handling", "Input validation"], excludes: ["Performance optimization", "Monitoring"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["All user inputs validated", "Error scenarios show messages"],
|
||||||
|
verification: "Unit tests + manual error scenario testing",
|
||||||
|
definition_of_done: "Users have clear guidance and recovery paths when encountering problems"
|
||||||
|
},
|
||||||
|
risk_items: [], effort: "medium", depends_on: ["L0"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Direct fallback**:
|
||||||
|
```javascript
|
||||||
|
[
|
||||||
|
{
|
||||||
|
id: "T1", title: "Infrastructure setup", type: "infrastructure",
|
||||||
|
scope: "Project scaffolding and base configuration",
|
||||||
|
inputs: [], outputs: ["project-structure"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["Project builds without errors", "Base configuration complete"],
|
||||||
|
verification: "npm run build (or equivalent build command)",
|
||||||
|
definition_of_done: "Project foundation ready for feature development"
|
||||||
|
},
|
||||||
|
depends_on: [], parallel_group: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: "T2", title: "Core feature implementation", type: "feature",
|
||||||
|
scope: "Core business logic",
|
||||||
|
inputs: ["project-structure"], outputs: ["core-module"],
|
||||||
|
convergence: {
|
||||||
|
criteria: ["Core API/functionality callable", "Returns expected results"],
|
||||||
|
verification: "Run core feature tests",
|
||||||
|
definition_of_done: "Core business functionality works as expected"
|
||||||
|
},
|
||||||
|
depends_on: ["T1"], parallel_group: 2
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Situation | Action |
|
||||||
|
|-----------|--------|
|
||||||
|
| No codebase detected | Normal flow, skip Phase 2 |
|
||||||
|
| Codebase search fails | Proceed with pure requirement decomposition |
|
||||||
|
| Circular dependency in records | Fix dependency graph before writing JSONL |
|
||||||
|
| User feedback timeout | Save current state, display `--continue` recovery command |
|
||||||
|
| Max feedback rounds (5) | Use current version to generate final artifacts |
|
||||||
|
| Session folder conflict | Append timestamp suffix |
|
||||||
|
| JSONL format error | Validate line by line, report problematic lines and fix |
|
||||||
|
| Quality check fails | Auto-fix if possible, otherwise report to user in feedback loop |
|
||||||
|
| Decomposition produces empty results | Use fallback decomposition templates |
|
||||||
|
|
||||||
|
## Best Practices
|
||||||
|
|
||||||
|
1. **Clear requirement description**: Detailed description leads to more accurate uncertainty assessment and decomposition
|
||||||
|
2. **Validate MVP first**: In progressive mode, L0 should be the minimum verifiable closed loop
|
||||||
|
3. **Testable convergence**: criteria must be writable as assertions or manual steps; definition_of_done should be judgeable by non-technical stakeholders
|
||||||
|
4. **Incremental validation**: Use `--continue` to iterate on existing roadmaps
|
||||||
|
5. **Independently executable**: Each JSONL record should be independently passable to lite-plan for execution
|
||||||
|
|
||||||
|
## When to Use
|
||||||
|
|
||||||
|
**Use req-plan-with-file when:**
|
||||||
|
- You need to decompose a large requirement into a progressively executable roadmap
|
||||||
|
- Unsure where to start, need an MVP strategy
|
||||||
|
- Need to generate a trackable task sequence for the team
|
||||||
|
- Requirement involves multiple stages or iterations
|
||||||
|
|
||||||
|
**Use lite-plan when:**
|
||||||
|
- You have a clear single task to execute
|
||||||
|
- The requirement is already a layer/task from the roadmap
|
||||||
|
- No layered planning needed
|
||||||
|
|
||||||
|
**Use collaborative-plan-with-file when:**
|
||||||
|
- A single complex task needs multi-agent parallel planning
|
||||||
|
- Need to analyze the same task from multiple domain perspectives
|
||||||
|
|
||||||
|
**Use analyze-with-file when:**
|
||||||
|
- Need in-depth analysis of a technical problem
|
||||||
|
- Not about planning execution, but understanding and discussion
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Now execute the req-plan-with-file workflow for**: $ARGUMENTS
|
||||||
@@ -50,9 +50,19 @@ async function killProcess(pid: string): Promise<boolean> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stop command handler - stops the running CCW dashboard server
|
* Clean up React frontend process on the given port
|
||||||
* @param {Object} options - Command options
|
|
||||||
*/
|
*/
|
||||||
|
async function cleanupReactFrontend(reactPort: number): Promise<void> {
|
||||||
|
const reactPid = await findProcessOnPort(reactPort);
|
||||||
|
if (reactPid) {
|
||||||
|
console.log(chalk.gray(` Cleaning up React frontend on port ${reactPort}...`));
|
||||||
|
const killed = await killProcess(reactPid);
|
||||||
|
if (killed) {
|
||||||
|
console.log(chalk.green(' React frontend stopped!'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export async function stopCommand(options: StopOptions): Promise<void> {
|
export async function stopCommand(options: StopOptions): Promise<void> {
|
||||||
const port = Number(options.port) || 3456;
|
const port = Number(options.port) || 3456;
|
||||||
const reactPort = port + 1; // React frontend runs on port + 1
|
const reactPort = port + 1; // React frontend runs on port + 1
|
||||||
@@ -92,6 +102,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
await new Promise(resolve => setTimeout(resolve, 500));
|
await new Promise(resolve => setTimeout(resolve, 500));
|
||||||
|
|
||||||
if (shutdownResponse && 'ok' in shutdownResponse && shutdownResponse.ok) {
|
if (shutdownResponse && 'ok' in shutdownResponse && shutdownResponse.ok) {
|
||||||
|
await cleanupReactFrontend(reactPort);
|
||||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
@@ -102,6 +113,7 @@ export async function stopCommand(options: StopOptions): Promise<void> {
|
|||||||
}).catch(() => null);
|
}).catch(() => null);
|
||||||
|
|
||||||
if (!postCheck) {
|
if (!postCheck) {
|
||||||
|
await cleanupReactFrontend(reactPort);
|
||||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||||
process.exit(0);
|
process.exit(0);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -110,7 +110,7 @@ export async function viewCommand(options: ViewOptions): Promise<void> {
|
|||||||
if (frontend === 'react') {
|
if (frontend === 'react') {
|
||||||
urlPath = '/react';
|
urlPath = '/react';
|
||||||
}
|
}
|
||||||
const url = `http://${browserHost}:${port}${urlPath}/?path=${encodeURIComponent(result.path!)}`;
|
const url = `http://${browserHost}:${port}${urlPath}/?path=${encodeURIComponent(workspacePath)}`;
|
||||||
|
|
||||||
if (options.browser !== false) {
|
if (options.browser !== false) {
|
||||||
console.log(chalk.cyan(' Opening in browser...'));
|
console.log(chalk.cyan(' Opening in browser...'));
|
||||||
|
|||||||
453
codex-lens/benchmarks/results/compare_2026-02-09_run2.json
Normal file
453
codex-lens/benchmarks/results/compare_2026-02-09_run2.json
Normal file
@@ -0,0 +1,453 @@
|
|||||||
|
{
|
||||||
|
"summary": {
|
||||||
|
"timestamp": "2026-02-09 11:26:54",
|
||||||
|
"source": "src",
|
||||||
|
"k": 10,
|
||||||
|
"coarse_k": 100,
|
||||||
|
"query_count": 7,
|
||||||
|
"avg_jaccard_topk": 0.39589733329229126,
|
||||||
|
"avg_rbo_topk": 0.23139636799510202,
|
||||||
|
"staged": {
|
||||||
|
"success": 7,
|
||||||
|
"avg_latency_ms": 32194.107242865222
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"success": 7,
|
||||||
|
"avg_latency_ms": 2643.366857132741
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"comparisons": [
|
||||||
|
{
|
||||||
|
"query": "class Config",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "class Config",
|
||||||
|
"latency_ms": 43041.41250002384,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\api\\semantic.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\parsers\\factory.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\watcher\\file_watcher.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\lsp\\server.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\api\\references.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\__init__.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 9864.638805389404,
|
||||||
|
"stage2_expand_ms": 13012.29190826416,
|
||||||
|
"stage3_cluster_ms": 13297.565460205078,
|
||||||
|
"stage4_rerank_ms": 6821.892261505127
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 149,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "class Config",
|
||||||
|
"latency_ms": 3209.129799991846,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\query_parser.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.1111111111111111,
|
||||||
|
"rbo_topk": 0.05429729885142857,
|
||||||
|
"staged_unique_files_topk": 10,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 8,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "def search",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "def search",
|
||||||
|
"latency_ms": 37827.209600031376,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\ann_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 531.8794250488281,
|
||||||
|
"stage2_expand_ms": 27009.481191635132,
|
||||||
|
"stage3_cluster_ms": 7948.509931564331,
|
||||||
|
"stage4_rerank_ms": 2268.9380645751953
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 101,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "def search",
|
||||||
|
"latency_ms": 2540.472400009632,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\query_parser.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.26666666666666666,
|
||||||
|
"rbo_topk": 0.2983708721671428,
|
||||||
|
"staged_unique_files_topk": 9,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "LspBridge",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "LspBridge",
|
||||||
|
"latency_ms": 24744.686599999666,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\merkle_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 517.8542137145996,
|
||||||
|
"stage2_expand_ms": 12839.622735977173,
|
||||||
|
"stage3_cluster_ms": 9154.959678649902,
|
||||||
|
"stage4_rerank_ms": 2160.0701808929443
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 100,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "LspBridge",
|
||||||
|
"latency_ms": 2482.5908999741077,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\graph_expander.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.5384615384615384,
|
||||||
|
"rbo_topk": 0.36639083062285716,
|
||||||
|
"staged_unique_files_topk": 10,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "graph expansion",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "graph expansion",
|
||||||
|
"latency_ms": 25239.59050002694,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\gpu_support.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\vector_meta_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 631.9081783294678,
|
||||||
|
"stage2_expand_ms": 12570.756196975708,
|
||||||
|
"stage3_cluster_ms": 9557.724952697754,
|
||||||
|
"stage4_rerank_ms": 2409.7683429718018
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 100,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "graph expansion",
|
||||||
|
"latency_ms": 2574.1938000023365,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\migration_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.42857142857142855,
|
||||||
|
"rbo_topk": 0.13728894791142857,
|
||||||
|
"staged_unique_files_topk": 10,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "clustering strategy",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "clustering strategy",
|
||||||
|
"latency_ms": 28572.93939998746,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\global_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\__init__.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 659.6193313598633,
|
||||||
|
"stage2_expand_ms": 14207.426309585571,
|
||||||
|
"stage3_cluster_ms": 11513.370037078857,
|
||||||
|
"stage4_rerank_ms": 2117.546319961548
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 100,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "clustering strategy",
|
||||||
|
"latency_ms": 2536.551799982786,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\__init__.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\gpu_support.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.17647058823529413,
|
||||||
|
"rbo_topk": 0.07116480920571429,
|
||||||
|
"staged_unique_files_topk": 10,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "error handling",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "error handling",
|
||||||
|
"latency_ms": 23812.726000010967,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\vector_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\__init__.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 475.42428970336914,
|
||||||
|
"stage2_expand_ms": 12454.935789108276,
|
||||||
|
"stage3_cluster_ms": 8576.019525527954,
|
||||||
|
"stage4_rerank_ms": 2265.360116958618
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 100,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "error handling",
|
||||||
|
"latency_ms": 2648.7773999869823,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\__init__.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\embedding_manager.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.6666666666666666,
|
||||||
|
"rbo_topk": 0.21230026104857144,
|
||||||
|
"staged_unique_files_topk": 10,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"query": "how to parse json",
|
||||||
|
"staged": {
|
||||||
|
"strategy": "staged",
|
||||||
|
"query": "how to parse json",
|
||||||
|
"latency_ms": 42120.1860999763,
|
||||||
|
"num_results": 9,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\enrichment.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\registry.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py"
|
||||||
|
],
|
||||||
|
"stage_stats": {
|
||||||
|
"stage_times": {
|
||||||
|
"stage1_binary_ms": 570.8920955657959,
|
||||||
|
"stage2_expand_ms": 30054.06880378723,
|
||||||
|
"stage3_cluster_ms": 9285.51697731018,
|
||||||
|
"stage4_rerank_ms": 2142.771005630493
|
||||||
|
},
|
||||||
|
"stage_counts": {
|
||||||
|
"stage1_candidates": 100,
|
||||||
|
"stage2_expanded": 100,
|
||||||
|
"stage3_clustered": 20,
|
||||||
|
"stage4_reranked": 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"dense_rerank": {
|
||||||
|
"strategy": "dense_rerank",
|
||||||
|
"query": "how to parse json",
|
||||||
|
"latency_ms": 2511.8518999814987,
|
||||||
|
"num_results": 10,
|
||||||
|
"topk_paths": [
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\cli\\commands.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\chain_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\index_tree.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\code_extractor.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\dir_index.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\hybrid_search.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\search\\ranking.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\chunker.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\storage\\sqlite_store.py",
|
||||||
|
"d:\\claude_dms3\\codex-lens\\src\\codexlens\\semantic\\ann_index.py"
|
||||||
|
],
|
||||||
|
"stage_stats": null,
|
||||||
|
"error": null
|
||||||
|
},
|
||||||
|
"jaccard_topk": 0.5833333333333334,
|
||||||
|
"rbo_topk": 0.4799615561585714,
|
||||||
|
"staged_unique_files_topk": 9,
|
||||||
|
"dense_unique_files_topk": 10,
|
||||||
|
"staged_unique_dirs_topk": 4,
|
||||||
|
"dense_unique_dirs_topk": 4
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -517,10 +517,11 @@ class LspBridge:
|
|||||||
|
|
||||||
# Parse URI
|
# Parse URI
|
||||||
uri = from_item.get("uri", "")
|
uri = from_item.get("uri", "")
|
||||||
if uri.startswith("file:///"):
|
if uri.startswith("file://"):
|
||||||
fp = uri[8:] if uri[8:9].isalpha() and uri[9:10] == ":" else uri[7:]
|
raw = unquote(uri[7:]) # keep leading slash for Unix paths
|
||||||
elif uri.startswith("file://"):
|
if raw.startswith("/") and len(raw) > 2 and raw[2] == ":":
|
||||||
fp = uri[7:]
|
raw = raw[1:]
|
||||||
|
fp = raw
|
||||||
else:
|
else:
|
||||||
fp = uri
|
fp = uri
|
||||||
|
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ import sys
|
|||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Dict, List, Optional, Tuple
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
from urllib.parse import unquote, urlparse
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -341,6 +342,7 @@ class StandaloneLspManager:
|
|||||||
Returns:
|
Returns:
|
||||||
ServerState for the appropriate language server, or None
|
ServerState for the appropriate language server, or None
|
||||||
"""
|
"""
|
||||||
|
file_path = self._normalize_file_path(file_path)
|
||||||
language_id = self.get_language_id(file_path)
|
language_id = self.get_language_id(file_path)
|
||||||
if not language_id:
|
if not language_id:
|
||||||
logger.debug(f"No language server configured for: {file_path}")
|
logger.debug(f"No language server configured for: {file_path}")
|
||||||
@@ -357,6 +359,43 @@ class StandaloneLspManager:
|
|||||||
|
|
||||||
# Start new server
|
# Start new server
|
||||||
return await self._start_server(language_id)
|
return await self._start_server(language_id)
|
||||||
|
|
||||||
|
def _normalize_file_path(self, file_path_or_uri: str) -> str:
|
||||||
|
"""Normalize a file path that may be an LSP file URI or URI-path.
|
||||||
|
|
||||||
|
LSP responses often contain `file://` URIs with percent-encoding
|
||||||
|
(e.g. `file:///d%3A/...`). Some code paths may forward the parsed
|
||||||
|
URI path (`/d%3A/...`) without the scheme. On Windows, `Path(...)`
|
||||||
|
would interpret that as a root path on the current drive, producing
|
||||||
|
invalid paths like `D:\\d%3A\\...`.
|
||||||
|
"""
|
||||||
|
if not file_path_or_uri:
|
||||||
|
return file_path_or_uri
|
||||||
|
|
||||||
|
raw = str(file_path_or_uri).strip()
|
||||||
|
|
||||||
|
if raw.startswith("file:"):
|
||||||
|
try:
|
||||||
|
parsed = urlparse(raw)
|
||||||
|
if parsed.scheme == "file":
|
||||||
|
raw = unquote(parsed.path)
|
||||||
|
else:
|
||||||
|
raw = raw.replace("file:///", "").replace("file://", "")
|
||||||
|
except Exception:
|
||||||
|
raw = raw.replace("file:///", "").replace("file://", "")
|
||||||
|
|
||||||
|
# Decode percent-encoded segments (e.g. d%3A -> d:)
|
||||||
|
if "%3a" in raw.lower():
|
||||||
|
try:
|
||||||
|
raw = unquote(raw)
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
|
||||||
|
# Windows: file URI paths frequently look like "/C:/path"; strip the extra slash.
|
||||||
|
if raw.startswith("/") and len(raw) > 2 and raw[2] == ":":
|
||||||
|
raw = raw[1:]
|
||||||
|
|
||||||
|
return raw
|
||||||
|
|
||||||
async def _initialize_server(self, state: ServerState) -> None:
|
async def _initialize_server(self, state: ServerState) -> None:
|
||||||
"""Send initialize request and wait for response via the message queue.
|
"""Send initialize request and wait for response via the message queue.
|
||||||
@@ -771,6 +810,7 @@ class StandaloneLspManager:
|
|||||||
|
|
||||||
def _to_text_document_identifier(self, file_path: str) -> Dict[str, str]:
|
def _to_text_document_identifier(self, file_path: str) -> Dict[str, str]:
|
||||||
"""Create TextDocumentIdentifier from file path."""
|
"""Create TextDocumentIdentifier from file path."""
|
||||||
|
file_path = self._normalize_file_path(file_path)
|
||||||
uri = Path(file_path).resolve().as_uri()
|
uri = Path(file_path).resolve().as_uri()
|
||||||
return {"uri": uri}
|
return {"uri": uri}
|
||||||
|
|
||||||
@@ -783,6 +823,7 @@ class StandaloneLspManager:
|
|||||||
|
|
||||||
async def _open_document(self, state: ServerState, file_path: str) -> None:
|
async def _open_document(self, state: ServerState, file_path: str) -> None:
|
||||||
"""Send textDocument/didOpen notification."""
|
"""Send textDocument/didOpen notification."""
|
||||||
|
file_path = self._normalize_file_path(file_path)
|
||||||
resolved_path = Path(file_path).resolve()
|
resolved_path = Path(file_path).resolve()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
@@ -1044,7 +1085,7 @@ class StandaloneLspManager:
|
|||||||
"""
|
"""
|
||||||
# Determine language from item's uri
|
# Determine language from item's uri
|
||||||
uri = item.get("uri", "")
|
uri = item.get("uri", "")
|
||||||
file_path = uri.replace("file:///", "").replace("file://", "")
|
file_path = self._normalize_file_path(uri)
|
||||||
|
|
||||||
state = await self._get_server(file_path)
|
state = await self._get_server(file_path)
|
||||||
if not state:
|
if not state:
|
||||||
@@ -1075,7 +1116,7 @@ class StandaloneLspManager:
|
|||||||
"""
|
"""
|
||||||
# Determine language from item's uri
|
# Determine language from item's uri
|
||||||
uri = item.get("uri", "")
|
uri = item.get("uri", "")
|
||||||
file_path = uri.replace("file:///", "").replace("file://", "")
|
file_path = self._normalize_file_path(uri)
|
||||||
|
|
||||||
state = await self._get_server(file_path)
|
state = await self._get_server(file_path)
|
||||||
if not state:
|
if not state:
|
||||||
|
|||||||
48
codex-lens/tests/lsp/test_standalone_manager_paths.py
Normal file
48
codex-lens/tests/lsp/test_standalone_manager_paths.py
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
"""Tests for StandaloneLspManager path normalization (Windows URI handling)."""
|
||||||
|
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import platform
|
||||||
|
|
||||||
|
from codexlens.lsp.standalone_manager import StandaloneLspManager
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_file_uri_percent_encoded_windows_drive() -> None:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return
|
||||||
|
|
||||||
|
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
|
||||||
|
|
||||||
|
raw = "file:///d%3A/Claude_dms3/codex-lens/src/codexlens/lsp/standalone_manager.py"
|
||||||
|
normalized = manager._normalize_file_path(raw)
|
||||||
|
|
||||||
|
assert normalized.lower().startswith("d:/")
|
||||||
|
assert "%3a" not in normalized.lower()
|
||||||
|
assert "d%3a" not in normalized.lower()
|
||||||
|
assert "/d%3a" not in normalized.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_uri_path_percent_encoded_windows_drive() -> None:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return
|
||||||
|
|
||||||
|
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
|
||||||
|
|
||||||
|
raw = "/d%3A/Claude_dms3/codex-lens/src/codexlens/lsp/standalone_manager.py"
|
||||||
|
normalized = manager._normalize_file_path(raw)
|
||||||
|
|
||||||
|
assert normalized.lower().startswith("d:/")
|
||||||
|
assert "%3a" not in normalized.lower()
|
||||||
|
|
||||||
|
|
||||||
|
def test_normalize_plain_windows_path_is_unchanged() -> None:
|
||||||
|
if platform.system() != "Windows":
|
||||||
|
return
|
||||||
|
|
||||||
|
manager = StandaloneLspManager(workspace_root="D:/Claude_dms3/codex-lens")
|
||||||
|
|
||||||
|
raw = r"D:\Claude_dms3\codex-lens\src\codexlens\lsp\standalone_manager.py"
|
||||||
|
normalized = manager._normalize_file_path(raw)
|
||||||
|
|
||||||
|
assert normalized == raw
|
||||||
|
|
||||||
Reference in New Issue
Block a user