mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-27 20:00:44 +08:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b502ebcae1 | ||
|
|
97ed2ef213 | ||
|
|
fcd0b9a2c4 | ||
|
|
fab07c2e97 | ||
|
|
5d0000bcc5 | ||
|
|
c8840847d2 | ||
|
|
8953795c49 |
112
.claude/agents/workflow-research-agent.md
Normal file
112
.claude/agents/workflow-research-agent.md
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
---
|
||||||
|
name: workflow-research-agent
|
||||||
|
description: External research agent — web search for API details, design patterns, best practices, and technology validation. Returns structured markdown, does NOT write files.
|
||||||
|
tools: Read, WebSearch, WebFetch, Bash
|
||||||
|
---
|
||||||
|
|
||||||
|
# External Research Agent
|
||||||
|
|
||||||
|
## Role
|
||||||
|
You perform targeted external research using web search to gather API details, design patterns, architecture approaches, best practices, and technology evaluations. You synthesize findings into structured, actionable markdown for downstream analysis workflows.
|
||||||
|
|
||||||
|
Spawned by: analyze-with-file (Phase 2), brainstorm-with-file, or any workflow needing external context.
|
||||||
|
|
||||||
|
**CRITICAL**: Return structured markdown only. Do NOT write any files unless explicitly instructed in the prompt.
|
||||||
|
|
||||||
|
## Process
|
||||||
|
|
||||||
|
1. **Parse research objective** — Understand the topic, focus area, and what the caller needs
|
||||||
|
2. **Plan queries** — Design 3-5 focused search queries targeting the objective
|
||||||
|
3. **Execute searches** — Use `WebSearch` for general research, `WebFetch` for specific documentation pages
|
||||||
|
4. **Cross-reference** — If codebase files are provided in prompt, `Read` them to ground research in actual code context
|
||||||
|
5. **Synthesize findings** — Extract key insights, patterns, and recommendations from search results
|
||||||
|
6. **Return structured output** — Markdown-formatted research findings
|
||||||
|
|
||||||
|
## Research Modes
|
||||||
|
|
||||||
|
### Detail Verification (default for analyze)
|
||||||
|
Focus: verify assumptions, check best practices, validate technology choices, confirm patterns.
|
||||||
|
Queries target: benchmarks, production postmortems, known issues, compatibility matrices, official docs.
|
||||||
|
|
||||||
|
### API Research (for implementation planning)
|
||||||
|
Focus: concrete API details, library versions, integration patterns, configuration options.
|
||||||
|
Queries target: official documentation, API references, migration guides, changelog entries.
|
||||||
|
|
||||||
|
### Design Research (for brainstorm/architecture)
|
||||||
|
Focus: design alternatives, architecture patterns, competitive analysis, UX patterns.
|
||||||
|
Queries target: design systems, pattern libraries, case studies, comparison articles.
|
||||||
|
|
||||||
|
## Execution
|
||||||
|
|
||||||
|
### Query Strategy
|
||||||
|
```
|
||||||
|
1. Parse topic → extract key technologies, patterns, concepts
|
||||||
|
2. Generate 3-5 queries:
|
||||||
|
- Q1: "{technology} best practices {year}"
|
||||||
|
- Q2: "{pattern} vs {alternative} comparison"
|
||||||
|
- Q3: "{technology} known issues production"
|
||||||
|
- Q4: "{specific API/library} documentation {version}"
|
||||||
|
- Q5: "{domain} architecture patterns"
|
||||||
|
3. Execute queries via WebSearch
|
||||||
|
4. For promising results, WebFetch full content for detail extraction
|
||||||
|
5. Synthesize across all sources
|
||||||
|
```
|
||||||
|
|
||||||
|
### Codebase Grounding
|
||||||
|
When the prompt includes `codebase_context` (file paths, patterns, tech stack):
|
||||||
|
- Read referenced files to understand actual usage
|
||||||
|
- Compare external best practices against current implementation
|
||||||
|
- Flag gaps between current code and recommended patterns
|
||||||
|
|
||||||
|
## Output Format
|
||||||
|
|
||||||
|
Return structured markdown (do NOT write files):
|
||||||
|
|
||||||
|
```markdown
|
||||||
|
## Research: {topic}
|
||||||
|
|
||||||
|
### Key Findings
|
||||||
|
- **{Finding 1}**: {detail} (confidence: HIGH|MEDIUM|LOW, source: {url_or_reference})
|
||||||
|
- **{Finding 2}**: {detail} (confidence: HIGH|MEDIUM|LOW, source: {url_or_reference})
|
||||||
|
|
||||||
|
### Technology / API Details
|
||||||
|
- **{Library/API}**: version {X}, {key capabilities}
|
||||||
|
- Integration: {how to integrate}
|
||||||
|
- Caveats: {known issues or limitations}
|
||||||
|
|
||||||
|
### Best Practices
|
||||||
|
- {Practice 1}: {rationale} (source: {reference})
|
||||||
|
- {Practice 2}: {rationale} (source: {reference})
|
||||||
|
|
||||||
|
### Recommended Approach
|
||||||
|
{Prescriptive recommendation with rationale — "use X" not "consider X or Y" when evidence is strong}
|
||||||
|
|
||||||
|
### Alternatives Considered
|
||||||
|
| Option | Pros | Cons | Verdict |
|
||||||
|
|--------|------|------|---------|
|
||||||
|
| {A} | ... | ... | Recommended / Viable / Avoid |
|
||||||
|
|
||||||
|
### Pitfalls & Known Issues
|
||||||
|
- {Issue 1}: {mitigation} (source: {reference})
|
||||||
|
|
||||||
|
### Codebase Gaps (if codebase_context provided)
|
||||||
|
- {Gap}: current code does {X}, best practice recommends {Y}
|
||||||
|
|
||||||
|
### Sources
|
||||||
|
- {source title}: {url} — {key takeaway}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
- If WebSearch returns no results for a query: note "no results" and proceed with remaining queries
|
||||||
|
- If WebFetch fails for a URL: skip and note the intended lookup
|
||||||
|
- If all searches fail: return "research unavailable — proceed with codebase-only analysis" and list the queries that were attempted
|
||||||
|
- If codebase files referenced in prompt don't exist: proceed with external research only
|
||||||
|
|
||||||
|
## Constraints
|
||||||
|
- Be prescriptive ("use X") not exploratory ("consider X or Y") when evidence is strong
|
||||||
|
- Assign confidence levels (HIGH/MEDIUM/LOW) to all findings
|
||||||
|
- Cite sources for claims — include URLs
|
||||||
|
- Keep output under 200 lines
|
||||||
|
- Do NOT write any files — return structured markdown only
|
||||||
|
- Do NOT fabricate URLs or sources — only cite actual search results
|
||||||
|
- Bash calls MUST use `run_in_background: false` (subagent cannot receive hook callbacks)
|
||||||
@@ -1,359 +0,0 @@
|
|||||||
---
|
|
||||||
name: auto
|
|
||||||
description: Chain command - automated document-driven development flow. Detects project state and runs the appropriate chain for new or existing projects.
|
|
||||||
argument-hint: "[-y|--yes] [--skip-spec] [--skip-build] [--spec <session-id>] [--resume] \"project idea or task description\""
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: All sub-commands run in auto mode. Minimal human intervention.
|
|
||||||
|
|
||||||
# DDD Auto Command (/ddd:auto)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Orchestrate the full document-driven development lifecycle. **Adapts to project state** — works for both new projects and existing codebases.
|
|
||||||
|
|
||||||
## Flow Variants
|
|
||||||
|
|
||||||
### Variant 1: New Project (no code, no spec)
|
|
||||||
```
|
|
||||||
spec-generator → ddd:index-build → ddd:plan → ddd:execute → verify → ddd:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variant 2: Existing Project (has code, no spec)
|
|
||||||
```
|
|
||||||
ddd:scan → ddd:plan → ddd:execute → verify → ddd:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variant 3: Existing Project with Spec (has code + spec)
|
|
||||||
```
|
|
||||||
ddd:index-build → ddd:plan → ddd:execute → verify → ddd:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
### Variant 4: Index Exists (has doc-index.json)
|
|
||||||
```
|
|
||||||
ddd:plan → ddd:execute → verify → ddd:sync
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flow Diagram
|
|
||||||
|
|
||||||
```
|
|
||||||
┌────────────────────────────────────────────────────────────┐
|
|
||||||
│ /ddd:auto │
|
|
||||||
│ │
|
|
||||||
│ Stage 0: Detect Project State │
|
|
||||||
│ ┌───────────────────────────────────┐ │
|
|
||||||
│ │ has_codebase? has_spec? has_index?│ │
|
|
||||||
│ └────────────┬──────────────────────┘ │
|
|
||||||
│ │ │
|
|
||||||
│ ┌──────────┼──────────────┐ │
|
|
||||||
│ ▼ ▼ ▼ │
|
|
||||||
│ No Code Code Only Code + Spec Index Exists │
|
|
||||||
│ │ │ │ │ │
|
|
||||||
│ ▼ │ │ │ │
|
|
||||||
│ Stage 1 │ │ │ │
|
|
||||||
│ Spec Gen │ │ │ │
|
|
||||||
│ │ │ │ │ │
|
|
||||||
│ ▼ │ ▼ │ │
|
|
||||||
│ Stage 2a Stage 2b Stage 2a │ │
|
|
||||||
│ index-build ddd:scan index-build │ │
|
|
||||||
│ (Path A or Path B auto-detected) │ │
|
|
||||||
│ │ │ │
|
|
||||||
│ └───────────────────┬───────────────────┘ │
|
|
||||||
│ ▼ │
|
|
||||||
│ Stage 3: DDD Plan (enhanced) │
|
|
||||||
│ (doc-index query + exploration + │
|
|
||||||
│ clarification + task planning) │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ Stage 4: Execute │
|
|
||||||
│ (ddd:execute = doc-aware execution) │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ Stage 4.5: Verify Gate │
|
|
||||||
│ (convergence + build + lint + tests │
|
|
||||||
│ → execution-manifest.json) │
|
|
||||||
│ │ │
|
|
||||||
│ PASS / WARN → continue │
|
|
||||||
│ FAIL → ask user │
|
|
||||||
│ │ │
|
|
||||||
│ ▼ │
|
|
||||||
│ Stage 5: Doc Sync │
|
|
||||||
│ (auto-triggered with --from-manifest, │
|
|
||||||
│ or manual /ddd:sync) │
|
|
||||||
└────────────────────────────────────────────────────────────┘
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stage 0: Project State Detection
|
|
||||||
|
|
||||||
Automatically detect project state to determine which stages to run:
|
|
||||||
|
|
||||||
```
|
|
||||||
Check 1: doc-index.json exists? → has_index
|
|
||||||
Check 2: SPEC-* directories exist? → has_spec
|
|
||||||
Check 3: Source code directories? → has_codebase
|
|
||||||
Check 4: project-tech.json exists? → has_tech_analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### Decision Matrix
|
|
||||||
|
|
||||||
| has_codebase | has_spec | has_index | Action |
|
|
||||||
|:---:|:---:|:---:|--------|
|
|
||||||
| No | No | No | Stage 1 (spec-gen) → Stage 2a (index-build) → Stage 3-5 |
|
|
||||||
| No | Yes | No | Stage 2a (index-build) → Stage 3-5 |
|
|
||||||
| Yes | No | No | **Stage 2b (ddd:scan)** → Stage 3-5 |
|
|
||||||
| Yes | Yes | No | Stage 2a (index-build) → Stage 3-5 |
|
|
||||||
| Yes | * | Yes | **Skip to Stage 3** (index exists) |
|
|
||||||
|
|
||||||
### Override Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `--skip-spec` | Never run spec-generator |
|
|
||||||
| `--skip-build` | Never run index-build |
|
|
||||||
| `--spec <id>` | Use specific spec session, force Path A |
|
|
||||||
| `--from-scratch` | Rebuild index even if exists |
|
|
||||||
|
|
||||||
## Stage 1: Specification (conditional)
|
|
||||||
|
|
||||||
### Run When
|
|
||||||
- No codebase AND no spec AND `--skip-spec` not set
|
|
||||||
- User provides a new project idea (not an existing task description)
|
|
||||||
|
|
||||||
### Skip When
|
|
||||||
- `--skip-spec` flag
|
|
||||||
- Codebase already exists (existing project)
|
|
||||||
- `--spec <id>` pointing to existing session
|
|
||||||
|
|
||||||
### Execution
|
|
||||||
```
|
|
||||||
Invoke /spec-generator with user input
|
|
||||||
→ Output: .workflow/.doc-index/specs/SPEC-{slug}-{date}/
|
|
||||||
```
|
|
||||||
|
|
||||||
## Stage 2: Index Construction (conditional)
|
|
||||||
|
|
||||||
### Run When
|
|
||||||
- `doc-index.json` does not exist
|
|
||||||
- OR `--from-scratch` flag
|
|
||||||
|
|
||||||
### Route Selection
|
|
||||||
|
|
||||||
```
|
|
||||||
Has spec outputs → Stage 2a: /ddd:index-build (spec-first)
|
|
||||||
No spec, has code → Stage 2b: /ddd:scan (code-first)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stage 2a: /ddd:index-build (has spec)
|
|
||||||
```
|
|
||||||
Invoke /ddd:index-build [-y] [-s <spec-id>]
|
|
||||||
→ Output: doc-index.json from spec entities + code mapping
|
|
||||||
```
|
|
||||||
|
|
||||||
### Stage 2b: /ddd:scan (no spec, has code)
|
|
||||||
```
|
|
||||||
Invoke /ddd:scan [-y]
|
|
||||||
→ Output: doc-index.json from code analysis + inferred features
|
|
||||||
```
|
|
||||||
|
|
||||||
### Skip When
|
|
||||||
- `--skip-build` flag
|
|
||||||
- `doc-index.json` exists AND NOT `--from-scratch`
|
|
||||||
- In this case, suggest `/ddd:update` for incremental refresh
|
|
||||||
|
|
||||||
## Stage 3: Planning (always runs)
|
|
||||||
|
|
||||||
### Execution
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:plan [-y] "task description"
|
|
||||||
```
|
|
||||||
|
|
||||||
The enhanced `/ddd:plan` now performs:
|
|
||||||
1. Doc-index query (instant context from features, requirements, components, ADRs)
|
|
||||||
2. Doc-index-guided exploration (1-4 angles based on affected features)
|
|
||||||
3. Clarification (aggregate ambiguities from exploration + doc-index gaps)
|
|
||||||
4. Task planning (plan.json + TASK-*.json with doc_context traceability)
|
|
||||||
5. Handoff selection
|
|
||||||
|
|
||||||
Output:
|
|
||||||
- `plan.json` — plan overview with doc_context
|
|
||||||
- `.task/TASK-*.json` — individual tasks with doc_context
|
|
||||||
- `exploration-{angle}.json` — exploration results (if Phase 2 ran)
|
|
||||||
- `planning-context.md` — legacy context package
|
|
||||||
|
|
||||||
### Handoff Decision
|
|
||||||
|
|
||||||
After planning, `/ddd:plan` presents execution options:
|
|
||||||
|
|
||||||
| Option | Description | Auto-Select When |
|
|
||||||
|--------|-------------|-----------------|
|
|
||||||
| **ddd:execute** | Document-aware execution (recommended) | Default in ddd workflow |
|
|
||||||
| **lite-execute** | Standard execution (no doc awareness) | When doc traceability not needed |
|
|
||||||
| **direct** | Start coding with context | User prefers manual |
|
|
||||||
| **stop** | Just the plan context | Planning/research only |
|
|
||||||
|
|
||||||
With `-y`: Auto-select `ddd:execute`.
|
|
||||||
|
|
||||||
## Stage 4: Execution
|
|
||||||
|
|
||||||
Based on Stage 3 handoff decision:
|
|
||||||
|
|
||||||
| Mode | Delegates To |
|
|
||||||
|------|-------------|
|
|
||||||
| **ddd:execute** | `/ddd:execute --in-memory` with plan.json + doc-index enrichment |
|
|
||||||
| lite-execute | `/workflow:lite-execute` with plan.json path |
|
|
||||||
| direct | Output context package, developer works manually |
|
|
||||||
| stop | End here, no execution |
|
|
||||||
|
|
||||||
### ddd:execute Features (when selected)
|
|
||||||
- Doc-enriched task prompts (feature context + component docs + ADR constraints)
|
|
||||||
- Per-batch impact verification (changes stay within planned scope)
|
|
||||||
- Result persistence (`TASK-*.result.json` per task, `execution-manifest.json` per session)
|
|
||||||
- Post-execution verify gate (Stage 4.5, unless `--skip-verify`)
|
|
||||||
- Post-completion auto-sync with manifest (Stage 5 triggered automatically)
|
|
||||||
|
|
||||||
**Note**: When using `ddd:execute`, Stage 4.5 and Stage 5 are auto-triggered. For other modes, run Stage 5 manually.
|
|
||||||
|
|
||||||
## Stage 4.5: Verify Gate
|
|
||||||
|
|
||||||
Embedded within `ddd:execute` (Step 4.5). Runs after all batches complete, before doc sync.
|
|
||||||
|
|
||||||
### Purpose
|
|
||||||
|
|
||||||
Quality gate ensuring execution output is correct before committing to documentation updates. Prevents bad code from being "blessed" into the doc-index.
|
|
||||||
|
|
||||||
### Checks Performed
|
|
||||||
|
|
||||||
| Check | Description | Gate Behavior |
|
|
||||||
|-------|-------------|---------------|
|
|
||||||
| **Convergence** | Run `task.convergence.verification` for each task | FAIL if any critical task fails |
|
|
||||||
| **Build** | Run project build command (`tsc --noEmit`, etc.) | FAIL on build errors |
|
|
||||||
| **Lint** | Run project linter (`eslint`, etc.) | WARN only (non-blocking) |
|
|
||||||
| **Regression** | Run full test suite, compare to baseline | FAIL on new test failures |
|
|
||||||
|
|
||||||
### Gate Results
|
|
||||||
|
|
||||||
| Result | Action |
|
|
||||||
|--------|--------|
|
|
||||||
| **PASS** | All checks passed → proceed to Stage 5 |
|
|
||||||
| **WARN** | Non-critical issues (lint warnings) → proceed with warnings logged |
|
|
||||||
| **FAIL** | Critical issues → ask user: fix now / skip sync / abort |
|
|
||||||
| **FAIL + `-y`** | Log failures, set `error_state` in session, stop |
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
- `execution-manifest.json` — persisted to session folder, consumed by Stage 5
|
|
||||||
- Contains: task results, files_modified (with task attribution), verify gate results
|
|
||||||
|
|
||||||
## Stage 5: Post-Task Sync
|
|
||||||
|
|
||||||
### Trigger
|
|
||||||
- **Auto**: `/ddd:execute` triggers `/ddd:sync --from-manifest` automatically after verify gate passes
|
|
||||||
- **Manual**: User runs `/ddd:sync` after completing work in direct/lite-execute mode
|
|
||||||
- **Resume**: `/ddd:auto --resume` after task completion
|
|
||||||
|
|
||||||
### Execution
|
|
||||||
```
|
|
||||||
# Auto mode (from ddd:execute): uses manifest for precise change tracking
|
|
||||||
Invoke /ddd:sync [-y] --task-id <id> --from-manifest {session}/execution-manifest.json "task summary"
|
|
||||||
|
|
||||||
# Manual mode (from direct/lite-execute): falls back to git diff
|
|
||||||
Invoke /ddd:sync [-y] [--task-id <id>] "task summary"
|
|
||||||
|
|
||||||
→ Updates: doc-index.json, feature-maps/, tech-registry/, action-logs/
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Tracking
|
|
||||||
|
|
||||||
### Session File: `.workflow/.doc-index/.auto-session.json`
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"session_id": "DAUTO-{timestamp}",
|
|
||||||
"input": "user's original input",
|
|
||||||
"detected_state": {
|
|
||||||
"has_codebase": true,
|
|
||||||
"has_spec": false,
|
|
||||||
"has_index": false,
|
|
||||||
"build_path": "code-first"
|
|
||||||
},
|
|
||||||
"stages_completed": ["detect", "index-build", "plan"],
|
|
||||||
"current_stage": "execute",
|
|
||||||
"spec_session": "SPEC-{slug}-{date}|null",
|
|
||||||
"plan_session": "planning/{task-slug}-{date}/",
|
|
||||||
"plan_context": "planning/{task-slug}-{date}/plan.json",
|
|
||||||
"execution_mode": "ddd:execute|lite-execute|direct|stop",
|
|
||||||
"execution_manifest": "planning/{task-slug}-{date}/execution-manifest.json|null",
|
|
||||||
"verify_gate": "PASS|WARN|FAIL|null",
|
|
||||||
"error_state": null,
|
|
||||||
"last_error": {
|
|
||||||
"stage": "execute",
|
|
||||||
"message": "Task TASK-002 failed: compilation error",
|
|
||||||
"timestamp": "ISO8601",
|
|
||||||
"recoverable": true
|
|
||||||
},
|
|
||||||
"created_at": "ISO8601",
|
|
||||||
"last_updated": "ISO8601"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Resume
|
|
||||||
```
|
|
||||||
/ddd:auto --resume → Resume from current_stage in .auto-session.json
|
|
||||||
```
|
|
||||||
|
|
||||||
### Error Recovery
|
|
||||||
```
|
|
||||||
/ddd:auto --resume
|
|
||||||
IF error_state is set:
|
|
||||||
Display last error context
|
|
||||||
Ask: retry current stage / skip to next / abort
|
|
||||||
ELSE:
|
|
||||||
Resume from current_stage normally
|
|
||||||
```
|
|
||||||
|
|
||||||
## Example Workflows
|
|
||||||
|
|
||||||
### New Project (Full Flow)
|
|
||||||
```
|
|
||||||
/ddd:auto "Build a task management API with user auth and team features"
|
|
||||||
→ Stage 0: No code, no spec → need spec-gen
|
|
||||||
→ Stage 1: spec-generator produces full spec
|
|
||||||
→ Stage 2: index-build creates index from spec + empty codebase
|
|
||||||
→ Stage 3: ddd:plan produces plan.json + TASK-*.json with doc_context
|
|
||||||
→ Stage 4: ddd:execute runs tasks with feature context enrichment
|
|
||||||
→ Stage 4.5: verify gate — convergence ✓, build ✓, tests ✓ → PASS
|
|
||||||
→ Stage 5: ddd:sync --from-manifest auto-triggered, updates index
|
|
||||||
```
|
|
||||||
|
|
||||||
### Existing Project, No Spec (Code-First)
|
|
||||||
```
|
|
||||||
/ddd:auto "Add rate limiting to API endpoints"
|
|
||||||
→ Stage 0: Has code, no spec, no index
|
|
||||||
→ Stage 2b: ddd:scan analyzes code, infers features from codebase
|
|
||||||
→ Stage 3: ddd:plan queries index, explores with security + patterns angles
|
|
||||||
→ Stage 4: ddd:execute runs with rate-limit component docs as context
|
|
||||||
→ Stage 4.5: verify gate — convergence ✓, tests 41/42 (1 regression) → WARN
|
|
||||||
→ Stage 5: ddd:sync --from-manifest, registers new rate-limit component
|
|
||||||
```
|
|
||||||
|
|
||||||
### Existing Project with Index (Incremental)
|
|
||||||
```
|
|
||||||
/ddd:auto "Fix auth token expiration bug"
|
|
||||||
→ Stage 0: Has code, has index → skip to plan
|
|
||||||
→ Stage 3: ddd:plan finds feat-auth, REQ-002, tech-auth-service (Low complexity, skip exploration)
|
|
||||||
→ Stage 4: ddd:execute runs single task with auth feature context
|
|
||||||
→ Stage 4.5: verify gate — convergence ✓, build ✓, tests ✓ → PASS
|
|
||||||
→ Stage 5: ddd:sync --from-manifest, updates tech-auth-service code locations
|
|
||||||
```
|
|
||||||
|
|
||||||
### Planning Only
|
|
||||||
```
|
|
||||||
/ddd:auto "Investigate payment module architecture"
|
|
||||||
→ Stage 0-2: (as needed)
|
|
||||||
→ Stage 3: ddd:plan shows full context with exploration results
|
|
||||||
→ Stage 4: user selects "stop" → gets plan.json + context package only
|
|
||||||
```
|
|
||||||
@@ -1,222 +0,0 @@
|
|||||||
---
|
|
||||||
name: doc-generate
|
|
||||||
description: Generate full document tree from doc-index.json. Layer 3 (components) → Layer 2 (features) → Layer 1 (indexes/overview). Standalone or called by scan/index-build.
|
|
||||||
argument-hint: "[-y|--yes] [--layer <3|2|1|all>] [--force] [--skip-overview] [--skip-schema]"
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-generate all layers without confirmation prompts.
|
|
||||||
|
|
||||||
# DDD Doc Generate Command (/ddd:doc-generate)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Generate the complete document tree from `doc-index.json`. **Single source of truth** for all document generation logic, following bottom-up Layer 3 → 2 → 1 strategy.
|
|
||||||
|
|
||||||
```
|
|
||||||
doc-index.json → tech-registry/*.md (L3) → feature-maps/*.md (L2) → _index.md + README + ARCHITECTURE (L1)
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
| Scenario | Use |
|
|
||||||
|----------|-----|
|
|
||||||
| After `/ddd:scan` builds doc-index.json | **doc-generate** (auto-called by scan) |
|
|
||||||
| After `/ddd:index-build` builds doc-index.json | **doc-generate** (auto-called by index-build) |
|
|
||||||
| Rebuild all docs from existing index | **doc-generate --force** |
|
|
||||||
| Regenerate only component docs | **doc-generate --layer 3** |
|
|
||||||
| Regenerate only overview/index docs | **doc-generate --layer 1** |
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `doc-index.json` must exist at `.workflow/.doc-index/doc-index.json`
|
|
||||||
- If not found → error: "No doc-index.json found. Run /ddd:scan or /ddd:index-build first."
|
|
||||||
|
|
||||||
## Storage Output
|
|
||||||
|
|
||||||
```
|
|
||||||
.workflow/.doc-index/
|
|
||||||
├── doc-index.json ← Input (read-only, not modified)
|
|
||||||
├── SCHEMA.md ← Schema documentation
|
|
||||||
├── README.md ← Project overview (Layer 1)
|
|
||||||
├── ARCHITECTURE.md ← Architecture overview (Layer 1)
|
|
||||||
├── feature-maps/ ← Feature documentation (Layer 2)
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {feature-slug}.md
|
|
||||||
├── tech-registry/ ← Component documentation (Layer 3)
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {component-slug}.md
|
|
||||||
└── planning/ ← Planning sessions (Layer 1)
|
|
||||||
├── _index.md ← Planning sessions index
|
|
||||||
└── {task-slug}-{date}/ ← Individual session folders
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 1: Load & Validate
|
|
||||||
|
|
||||||
### 1.1 Load doc-index.json
|
|
||||||
|
|
||||||
```
|
|
||||||
Read .workflow/.doc-index/doc-index.json
|
|
||||||
Validate: features[], technicalComponents[] are non-empty arrays
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Schema Version Check
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const schemaVersion = docIndex.schema_version || '0.0';
|
|
||||||
if (schemaVersion !== '1.0') {
|
|
||||||
warn(`Schema version mismatch: found ${schemaVersion}, expected 1.0`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 Determine Generation Scope
|
|
||||||
|
|
||||||
```
|
|
||||||
IF --layer 3: generate Layer 3 only
|
|
||||||
IF --layer 2: generate Layer 2 only (requires Layer 3 exists)
|
|
||||||
IF --layer 1: generate Layer 1 only (requires Layer 2 exists)
|
|
||||||
IF --layer all (default): generate Layer 3 → 2 → 1
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.4 Check Existing Docs
|
|
||||||
|
|
||||||
```
|
|
||||||
IF docs already exist AND NOT --force:
|
|
||||||
Warn: "Documents already exist. Use --force to overwrite."
|
|
||||||
Ask user (unless -y → overwrite)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 2: Layer 3 -- Component Documentation
|
|
||||||
|
|
||||||
For each component in `technicalComponents[]`, call the generate_ddd_docs endpoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for COMPONENT_ID in "${technicalComponents[@]}"; do
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"component","entityId":"'"$COMPONENT_ID"'","tool":"gemini"}'
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
The endpoint handles:
|
|
||||||
- Loading the component entity from doc-index.json
|
|
||||||
- Building YAML frontmatter (layer: 3, component_id, name, type, features, code_locations, generated_at)
|
|
||||||
- Constructing the CLI prompt with code context paths
|
|
||||||
- **Including Change History section**: Pull related entries from `doc-index.json.actions[]` where `affectedComponents` includes this component ID. Display as timeline (date, action type, description)
|
|
||||||
- Writing output to `.workflow/.doc-index/tech-registry/{slug}.md`
|
|
||||||
- Tool fallback (gemini -> qwen -> codex) on failure
|
|
||||||
|
|
||||||
Output: `.workflow/.doc-index/tech-registry/{component-slug}.md`
|
|
||||||
|
|
||||||
## Phase 3: Layer 2 -- Feature Documentation
|
|
||||||
|
|
||||||
For each feature in `features[]`, call the generate_ddd_docs endpoint:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
for FEATURE_ID in "${features[@]}"; do
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"feature","entityId":"'"$FEATURE_ID"'","tool":"gemini"}'
|
|
||||||
done
|
|
||||||
```
|
|
||||||
|
|
||||||
The endpoint handles:
|
|
||||||
- Loading the feature entity from doc-index.json
|
|
||||||
- Building YAML frontmatter (layer: 2, feature_id, name, epic_id, status, requirements, components, tags, generated_at)
|
|
||||||
- Constructing the CLI prompt referencing Layer 3 component docs
|
|
||||||
- **Including Change History section**: Pull related entries from `doc-index.json.actions[]` where `affectedFeatures` includes this feature ID. Display as timeline (date, action type, description)
|
|
||||||
- Writing output to `.workflow/.doc-index/feature-maps/{slug}.md`
|
|
||||||
- Tool fallback (gemini -> qwen -> codex) on failure
|
|
||||||
|
|
||||||
Output: `.workflow/.doc-index/feature-maps/{feature-slug}.md`
|
|
||||||
|
|
||||||
## Phase 4: Layer 1 -- Index & Overview Documentation
|
|
||||||
|
|
||||||
### 4.1 Index Documents
|
|
||||||
|
|
||||||
Generate catalog files for each subdirectory:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Feature maps index
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"index","entityId":"feature-maps","tool":"gemini"}'
|
|
||||||
|
|
||||||
# Tech registry index
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"index","entityId":"tech-registry","tool":"gemini"}'
|
|
||||||
|
|
||||||
# Action logs index
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"index","entityId":"action-logs","tool":"gemini"}'
|
|
||||||
|
|
||||||
# Planning sessions index
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"index","entityId":"planning","tool":"gemini"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
Or generate all indexes at once (omit entityId):
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"index","tool":"gemini"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 README.md (unless --skip-overview)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"overview","tool":"gemini"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 ARCHITECTURE.md (unless --skip-overview)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"overview","entityId":"architecture","tool":"gemini"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 5: SCHEMA.md (unless --skip-schema)
|
|
||||||
|
|
||||||
### 5.1 Generate Schema Documentation
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw tool exec generate_ddd_docs '{"strategy":"schema","tool":"gemini"}'
|
|
||||||
```
|
|
||||||
|
|
||||||
### 5.2 Versioning Policy
|
|
||||||
|
|
||||||
**Semantic Versioning**:
|
|
||||||
- **Major** (X.0): Breaking changes (field removal, type changes, incompatible structure)
|
|
||||||
- **Minor** (X.Y): Non-breaking additions (new optional fields, new sections)
|
|
||||||
|
|
||||||
**Migration Protocol**:
|
|
||||||
1. Detect version mismatch in ddd:plan/ddd:sync
|
|
||||||
2. Log warning with migration instructions
|
|
||||||
3. Provide migration script or regeneration option
|
|
||||||
4. Update schema_version after successful migration
|
|
||||||
|
|
||||||
## Phase 6: Generation Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Document Generation Report
|
|
||||||
|
|
||||||
Project: {name}
|
|
||||||
Source: doc-index.json (build_path: {spec-first|code-first})
|
|
||||||
|
|
||||||
Generated:
|
|
||||||
Layer 3 (Components): {N} documents in tech-registry/
|
|
||||||
Layer 2 (Features): {N} documents in feature-maps/
|
|
||||||
Layer 1 (Indexes): {N} documents (_index.md, README, ARCHITECTURE)
|
|
||||||
Schema: SCHEMA.md
|
|
||||||
|
|
||||||
Total: {N} documents generated
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm all decisions |
|
|
||||||
| `--layer <3\|2\|1\|all>` | Generate specific layer only (default: all) |
|
|
||||||
| `--force` | Overwrite existing documents |
|
|
||||||
| `--skip-overview` | Skip README.md, ARCHITECTURE.md, planning/_index.md |
|
|
||||||
| `--skip-schema` | Skip SCHEMA.md generation |
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `doc-index.json` (from `/ddd:scan` or `/ddd:index-build`)
|
|
||||||
- **Called by**: `/ddd:scan` (after index assembly), `/ddd:index-build` (after index assembly)
|
|
||||||
- **Standalone**: Can be run independently on any project with existing doc-index.json
|
|
||||||
- **Output**: Complete document tree in `.workflow/.doc-index/`
|
|
||||||
- **Endpoint**: `ccw tool exec generate_ddd_docs` handles prompt construction, frontmatter, tool fallback, and file creation
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
---
|
|
||||||
name: doc-refresh
|
|
||||||
description: Incrementally update affected documents based on changed components/features. Layer 3 → 2 → 1 selective refresh. Called by sync/update or used standalone.
|
|
||||||
argument-hint: "[-y|--yes] [--components <id1,id2,...>] [--features <id1,id2,...>] [--minimal] [--dry-run]"
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-update affected documents without confirmation prompts.
|
|
||||||
|
|
||||||
# DDD Doc Refresh Command (/ddd:doc-refresh)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Incrementally update **only the affected documents** after code changes. Unlike `/ddd:doc-generate` (full generation), this command selectively refreshes documents for specific components and features.
|
|
||||||
|
|
||||||
```
|
|
||||||
affected component/feature IDs → update tech-registry (L3) → update feature-maps (L2) → update indexes (L1)
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
| Scenario | Use |
|
|
||||||
|----------|-----|
|
|
||||||
| After `/ddd:sync` detects code changes | **doc-refresh** (auto-called by sync) |
|
|
||||||
| After `/ddd:update` traces impact | **doc-refresh** (auto-called by update) |
|
|
||||||
| Manual refresh of specific component docs | **doc-refresh --components tech-auth-service** |
|
|
||||||
| Quick metadata-only update | **doc-refresh --minimal --components tech-auth-service** |
|
|
||||||
| Preview what would change | **doc-refresh --dry-run --components tech-auth-service** |
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `doc-index.json` must exist at `.workflow/.doc-index/doc-index.json`
|
|
||||||
- At least one of `--components` or `--features` must be provided (or received from caller)
|
|
||||||
- Corresponding documents must already exist (generated by `/ddd:doc-generate`)
|
|
||||||
- If documents missing → error: "Documents not found. Run /ddd:doc-generate first."
|
|
||||||
|
|
||||||
## Phase 1: Determine Refresh Scope
|
|
||||||
|
|
||||||
### 1.1 Resolve Affected Entities
|
|
||||||
|
|
||||||
```
|
|
||||||
IF --components provided:
|
|
||||||
affected_components = parse comma-separated IDs
|
|
||||||
affected_features = lookup featureIds for each component in doc-index.json
|
|
||||||
|
|
||||||
IF --features provided:
|
|
||||||
affected_features = parse comma-separated IDs
|
|
||||||
affected_components = union of all component IDs linked to features
|
|
||||||
|
|
||||||
Merge both sets if both flags provided.
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Validate Entities Exist
|
|
||||||
|
|
||||||
For each ID, verify it exists in doc-index.json. Warn and skip missing IDs.
|
|
||||||
|
|
||||||
### 1.3 Check Document Existence
|
|
||||||
|
|
||||||
For each affected entity, check if corresponding .md file exists:
|
|
||||||
- Missing Layer 3 doc → warn: "Component doc not found. Run /ddd:doc-generate first."
|
|
||||||
- Missing Layer 2 doc → warn: "Feature doc not found. Run /ddd:doc-generate first."
|
|
||||||
|
|
||||||
## Phase 2: Layer 3 — Update Component Docs
|
|
||||||
|
|
||||||
For each affected component's `tech-registry/{slug}.md`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Update component documentation for {component.name} after code changes
|
|
||||||
TASK:
|
|
||||||
• Update code locations and line ranges
|
|
||||||
• Update symbol list (add new exports, remove deleted)
|
|
||||||
• Add change entry to history table
|
|
||||||
• Refresh usage examples if API changed
|
|
||||||
MODE: write
|
|
||||||
CONTEXT: @{component.codeLocations[].path}
|
|
||||||
EXPECTED: Updated markdown with current API state
|
|
||||||
CONSTRAINTS: Preserve existing structure | Only update changed sections
|
|
||||||
" --tool gemini --mode write --cd .workflow/.doc-index/tech-registry/
|
|
||||||
```
|
|
||||||
|
|
||||||
Update frontmatter:
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
layer: 3
|
|
||||||
component_id: tech-auth-service
|
|
||||||
generated_at: {original}
|
|
||||||
last_updated: ISO8601
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.1 Minimal Mode (--minimal)
|
|
||||||
|
|
||||||
With `--minimal`, only update:
|
|
||||||
- `codeLocations` in frontmatter
|
|
||||||
- `last_updated` timestamp
|
|
||||||
- Skip CLI call for content regeneration
|
|
||||||
|
|
||||||
## Phase 3: Layer 2 — Update Feature Docs
|
|
||||||
|
|
||||||
For each affected feature's `feature-maps/{slug}.md`:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Update feature documentation for {feature.name} after component changes
|
|
||||||
TASK:
|
|
||||||
• Update component list if new components added
|
|
||||||
• Update status if requirements now fully implemented
|
|
||||||
• Add change entry to history table
|
|
||||||
• Refresh integration guide if component APIs changed
|
|
||||||
MODE: write
|
|
||||||
CONTEXT: @.workflow/.doc-index/tech-registry/{affected-components}.md
|
|
||||||
EXPECTED: Updated markdown reflecting current feature state
|
|
||||||
CONSTRAINTS: Reference updated Layer 3 docs | Preserve business language
|
|
||||||
" --tool gemini --mode write --cd .workflow/.doc-index/feature-maps/
|
|
||||||
```
|
|
||||||
|
|
||||||
Update frontmatter:
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
layer: 2
|
|
||||||
feature_id: feat-auth
|
|
||||||
depends_on_layer3: [tech-auth-service, tech-user-model]
|
|
||||||
generated_at: {original}
|
|
||||||
last_updated: ISO8601
|
|
||||||
---
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.1 Minimal Mode (--minimal)
|
|
||||||
|
|
||||||
With `--minimal`, only update:
|
|
||||||
- Component list in frontmatter
|
|
||||||
- Feature status (if requirements mapping changed)
|
|
||||||
- `last_updated` timestamp
|
|
||||||
- Skip CLI call for content regeneration
|
|
||||||
|
|
||||||
## Phase 4: Layer 1 — Update Index Docs (conditional)
|
|
||||||
|
|
||||||
### 4.1 Trigger Conditions
|
|
||||||
|
|
||||||
| Condition | Action |
|
|
||||||
|-----------|--------|
|
|
||||||
| New component registered | Update `tech-registry/_index.md` |
|
|
||||||
| Component removed | Update `tech-registry/_index.md` |
|
|
||||||
| Feature status changed | Update `feature-maps/_index.md` |
|
|
||||||
| New feature added | Update `feature-maps/_index.md` + README.md |
|
|
||||||
| Major architecture change | Update ARCHITECTURE.md |
|
|
||||||
| New action-log entry | Update `action-logs/_index.md` |
|
|
||||||
|
|
||||||
### 4.2 Update _index.md Files
|
|
||||||
|
|
||||||
Refresh table entries for affected features/components:
|
|
||||||
- `feature-maps/_index.md`
|
|
||||||
- `tech-registry/_index.md`
|
|
||||||
- `action-logs/_index.md` (if new action entry)
|
|
||||||
|
|
||||||
### 4.3 Update Overview Docs (only if significant)
|
|
||||||
|
|
||||||
If new features added or major status changes:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Update project overview docs after feature changes
|
|
||||||
TASK:
|
|
||||||
• Update README.md feature list
|
|
||||||
• Update ARCHITECTURE.md if new components added
|
|
||||||
• Update planning/_index.md with new planning sessions
|
|
||||||
MODE: write
|
|
||||||
CONTEXT: @.workflow/.doc-index/feature-maps/*.md @.workflow/.doc-index/doc-index.json
|
|
||||||
EXPECTED: Updated overview docs with current project state
|
|
||||||
CONSTRAINTS: High-level only | Link to Layer 2 for details
|
|
||||||
" --tool gemini --mode write --cd .workflow/.doc-index/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.4 Skip Layer 1
|
|
||||||
|
|
||||||
With `--minimal` or when changes are minor (only code location updates): skip Layer 1 entirely.
|
|
||||||
|
|
||||||
## Phase 5: Refresh Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Document Refresh Report
|
|
||||||
|
|
||||||
Affected Scope:
|
|
||||||
Components: {N} ({component IDs})
|
|
||||||
Features: {N} ({feature IDs})
|
|
||||||
|
|
||||||
Updated:
|
|
||||||
Layer 3 (Components): {N} documents refreshed
|
|
||||||
Layer 2 (Features): {N} documents refreshed
|
|
||||||
Layer 1 (Indexes): {N} documents refreshed (or "skipped")
|
|
||||||
|
|
||||||
Mode: {full|minimal}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Dry-Run Mode
|
|
||||||
|
|
||||||
With `--dry-run`:
|
|
||||||
- Execute Phase 1 (scope determination)
|
|
||||||
- Display what would be updated (affected files list)
|
|
||||||
- Skip all file writes (Phase 2-4)
|
|
||||||
- Output: "Dry-run complete. {N} documents would be refreshed."
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm all updates |
|
|
||||||
| `--components <ids>` | Comma-separated component IDs to refresh |
|
|
||||||
| `--features <ids>` | Comma-separated feature IDs to refresh |
|
|
||||||
| `--minimal` | Only update metadata/frontmatter, skip content regeneration |
|
|
||||||
| `--dry-run` | Preview what would change without modifying files |
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `doc-index.json`, affected entity IDs (from `/ddd:sync` or `/ddd:update`)
|
|
||||||
- **Called by**: `/ddd:sync` (after change detection + index update), `/ddd:update` (after impact tracing + index update)
|
|
||||||
- **Standalone**: Can be run independently to refresh specific component/feature docs
|
|
||||||
- **Prerequisite**: Documents must exist (generated by `/ddd:doc-generate`)
|
|
||||||
@@ -1,416 +0,0 @@
|
|||||||
---
|
|
||||||
name: execute
|
|
||||||
description: Document-aware execution engine — executes plan.json + TASK-*.json with doc-index context enrichment, per-batch impact verification, and post-completion doc sync.
|
|
||||||
argument-hint: "[-y|--yes] [--skip-sync] [--skip-verify] [--plan <path>] [--in-memory] \"optional task description\""
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-confirm all decisions, auto-sync after completion.
|
|
||||||
|
|
||||||
# DDD Execute Command (/ddd:execute)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Same execution engine model as lite-execute, but each step is **doc-index-aware**:
|
|
||||||
- Tasks are enriched with feature context, component docs, and architecture constraints
|
|
||||||
- Per-batch impact verification ensures changes stay within planned scope
|
|
||||||
- Post-completion automatically syncs the document index
|
|
||||||
|
|
||||||
### Core Differentiator
|
|
||||||
Unlike generic execution engines, ddd:execute leverages the document architecture:
|
|
||||||
- Feature-maps provide business context for each task
|
|
||||||
- Tech-registry provides implementation patterns to follow
|
|
||||||
- ADRs surface as hard constraints during execution
|
|
||||||
- Requirement acceptance criteria inform convergence verification
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `plan.json` + `.task/TASK-*.json` files from `/ddd:plan`
|
|
||||||
- `doc-index.json` at `.workflow/.doc-index/doc-index.json`
|
|
||||||
- If `--in-memory`: receives executionContext from `/ddd:plan` handoff
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 1: Initialize & Load Context
|
|
||||||
|
|
||||||
### 1.1 Locate Plan
|
|
||||||
|
|
||||||
```
|
|
||||||
IF --in-memory:
|
|
||||||
Load executionContext from ddd:plan handoff
|
|
||||||
plan_path = executionContext.plan_path
|
|
||||||
task_dir = executionContext.task_dir
|
|
||||||
ELIF --plan <path>:
|
|
||||||
plan_path = <path>
|
|
||||||
task_dir = {dirname(path)}/.task/
|
|
||||||
ELSE:
|
|
||||||
Scan .workflow/.doc-index/planning/ for most recent session
|
|
||||||
plan_path = {latest_session}/plan.json
|
|
||||||
task_dir = {latest_session}/.task/
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Load Plan & Tasks
|
|
||||||
|
|
||||||
- Read `plan.json` — validate against plan-overview-base-schema
|
|
||||||
- Read all `TASK-*.json` from `.task/` directory — validate against task-schema
|
|
||||||
- Read `doc-index.json` from `.workflow/.doc-index/`
|
|
||||||
|
|
||||||
### 1.3 Pre-Load Doc Context
|
|
||||||
|
|
||||||
For each task with `doc_context`:
|
|
||||||
- Load referenced `feature_docs` (feature-maps/{slug}.md)
|
|
||||||
- Load referenced `component_docs` (tech-registry/{slug}.md)
|
|
||||||
- Load ADR excerpts from doc-index `architectureDecisions[]`
|
|
||||||
- Extract requirement acceptance criteria from doc-index `requirements[]`
|
|
||||||
- Load `doc_context.symbol_docs[]` documentation content (if present)
|
|
||||||
|
|
||||||
### 1.4 Echo Strategy
|
|
||||||
|
|
||||||
Display execution summary:
|
|
||||||
|
|
||||||
```
|
|
||||||
DDD Execute: {plan.summary}
|
|
||||||
Complexity: {plan.complexity}
|
|
||||||
Tasks: {plan.task_count}
|
|
||||||
|
|
||||||
Doc-Index Impact:
|
|
||||||
Features: {doc_context.affected_features}
|
|
||||||
Requirements: {doc_context.affected_requirements}
|
|
||||||
Components: {doc_context.affected_components}
|
|
||||||
Constraints: {doc_context.architecture_constraints}
|
|
||||||
|
|
||||||
Execution plan: {batch count} batches, {parallel tasks} parallel where possible
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 2: Task Grouping & Batch Creation
|
|
||||||
|
|
||||||
### 2.1 Extract Dependencies
|
|
||||||
|
|
||||||
From each `TASK-*.json`, read `depends_on[]` to build dependency graph.
|
|
||||||
|
|
||||||
### 2.2 Group into Batches
|
|
||||||
|
|
||||||
```
|
|
||||||
Batch 1: Tasks with no dependencies (depends_on: [])
|
|
||||||
Batch 2: Tasks depending only on Batch 1 tasks
|
|
||||||
Batch 3: Tasks depending on Batch 1 + 2 tasks
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
Within each batch, tasks with the same `parallel_group` can run concurrently.
|
|
||||||
|
|
||||||
### 2.3 Assign Executor per Task
|
|
||||||
|
|
||||||
| Signal | Executor |
|
|
||||||
|--------|----------|
|
|
||||||
| `meta.execution_config.method == "cli"` | CLI tool (gemini/codex/qwen) |
|
|
||||||
| `meta.execution_config.method == "agent"` | Agent (code-developer/universal-executor) |
|
|
||||||
| Default | Agent (code-developer) |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 3: Doc-Enriched Execution
|
|
||||||
|
|
||||||
For each task in batch order, build an enriched prompt:
|
|
||||||
|
|
||||||
### 3.1 Task Prompt Template
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Goal
|
|
||||||
${plan.summary} — specifically: ${task.title}
|
|
||||||
|
|
||||||
## Document Context
|
|
||||||
|
|
||||||
### Feature: ${feature.name} (${feature.id})
|
|
||||||
${feature-map content excerpt — overview + requirements section}
|
|
||||||
|
|
||||||
### Components
|
|
||||||
${for each component in task.doc_context.component_ids:
|
|
||||||
tech-registry excerpt — responsibility + code locations + key patterns}
|
|
||||||
|
|
||||||
### Symbol Documentation
|
|
||||||
${if doc_context.symbol_docs is non-empty:
|
|
||||||
for each component in doc_context.component_ids:
|
|
||||||
#### ${component.name} Symbols (Top-5)
|
|
||||||
${for each symbol in component.symbol_docs.slice(0, 5):
|
|
||||||
- **${symbol.name}** (${symbol.type}): ${symbol.doc_summary}
|
|
||||||
Source: `${symbol.source_path}` | Freshness: ${symbol.freshness}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
### Architecture Constraints
|
|
||||||
${for each ADR in task.doc_context.adr_ids:
|
|
||||||
ADR title + decision + rationale from doc-index}
|
|
||||||
|
|
||||||
### Requirement Acceptance Criteria
|
|
||||||
${for each requirement in task.doc_context.requirement_ids:
|
|
||||||
requirement title + priority + success criteria from doc-index}
|
|
||||||
|
|
||||||
## Task Details
|
|
||||||
${task.description}
|
|
||||||
|
|
||||||
### Files to Modify
|
|
||||||
${task.files[] — path, action, changes}
|
|
||||||
|
|
||||||
### Implementation Steps
|
|
||||||
${task.implementation[] — step-by-step guide}
|
|
||||||
|
|
||||||
## Done When
|
|
||||||
${task.convergence.criteria[]}
|
|
||||||
${task.convergence.verification}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Execute Task
|
|
||||||
|
|
||||||
**Agent execution**:
|
|
||||||
```
|
|
||||||
Agent(subagent_type="code-developer", prompt="{enriched prompt}")
|
|
||||||
```
|
|
||||||
|
|
||||||
**CLI execution**:
|
|
||||||
```bash
|
|
||||||
ccw cli -p "{enriched prompt}" --tool {cli_tool} --mode write
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Record & Persist Result
|
|
||||||
|
|
||||||
After each task completes:
|
|
||||||
- Update `TASK-*.json` with `status`, `executed_at`, `result`
|
|
||||||
- Track `result.files_modified` for impact verification
|
|
||||||
- **Persist** result to `TASK-{id}.result.json` alongside the task file:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"task_id": "TASK-001",
|
|
||||||
"status": "completed|failed",
|
|
||||||
"executed_at": "ISO8601",
|
|
||||||
"executor": "code-developer|gemini|codex",
|
|
||||||
"files_modified": [
|
|
||||||
{ "path": "src/services/auth.ts", "action": "modified", "symbols_changed": ["AuthService.validate"] },
|
|
||||||
{ "path": "src/routes/login.ts", "action": "created", "symbols_changed": ["loginRoute"] }
|
|
||||||
],
|
|
||||||
"convergence_result": {
|
|
||||||
"criteria_met": ["Rate limiter middleware exists"],
|
|
||||||
"criteria_unmet": [],
|
|
||||||
"verification_output": "test output snippet..."
|
|
||||||
},
|
|
||||||
"error": null
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This file serves as the durable handoff between execute and sync — survives process interruptions.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4: Per-Batch Impact Verification
|
|
||||||
|
|
||||||
After each batch completes (unless `--skip-verify`):
|
|
||||||
|
|
||||||
### 4.1 Trace Changed Files
|
|
||||||
|
|
||||||
For each file modified in the batch:
|
|
||||||
```
|
|
||||||
changed_file → match to doc-index.technicalComponents[].codeLocations[].path
|
|
||||||
→ component_ids → featureIds → requirementIds
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.2 Scope Verification
|
|
||||||
|
|
||||||
Compare actual impact to planned impact:
|
|
||||||
|
|
||||||
```
|
|
||||||
Planned scope:
|
|
||||||
Features: [feat-auth]
|
|
||||||
Components: [tech-auth-service, tech-user-model]
|
|
||||||
|
|
||||||
Actual impact:
|
|
||||||
Features: [feat-auth] ← OK, within scope
|
|
||||||
Components: [tech-auth-service, tech-user-model, tech-email-service]
|
|
||||||
← WARNING: tech-email-service not in plan
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3 Flag Unexpected Impact
|
|
||||||
|
|
||||||
If changes affect features/components NOT in `plan.doc_context`:
|
|
||||||
- **Warning**: Display unexpected impact
|
|
||||||
- **No -y**: Ask user to confirm continuation
|
|
||||||
- **With -y**: Log warning, continue execution
|
|
||||||
|
|
||||||
### 4.4 Skip Conditions
|
|
||||||
|
|
||||||
Skip verification when:
|
|
||||||
- `--skip-verify` flag is set
|
|
||||||
- Only 1 batch (no intermediate verification needed for simple plans)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 4.5: Post-Execution Verify Gate
|
|
||||||
|
|
||||||
After all batches complete, before doc sync (unless `--skip-verify`):
|
|
||||||
|
|
||||||
### 4.5.1 Convergence Verification
|
|
||||||
|
|
||||||
For each completed task with `convergence.verification`:
|
|
||||||
```
|
|
||||||
Execute: {task.convergence.verification}
|
|
||||||
→ e.g., "npm test -- --grep rate-limit"
|
|
||||||
Record: pass/fail → update TASK-{id}.result.json.convergence_result
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.5.2 Build & Lint Check
|
|
||||||
|
|
||||||
```
|
|
||||||
Run project build command (if configured):
|
|
||||||
→ npm run build / tsc --noEmit / etc.
|
|
||||||
Run project lint command (if configured):
|
|
||||||
→ npm run lint / eslint src/ / etc.
|
|
||||||
```
|
|
||||||
|
|
||||||
If build or lint fails:
|
|
||||||
- **No -y**: Display errors, ask user: fix now / continue anyway / abort
|
|
||||||
- **With -y**: Log warning, continue (non-blocking)
|
|
||||||
|
|
||||||
### 4.5.3 Regression Test
|
|
||||||
|
|
||||||
```
|
|
||||||
Run project test suite:
|
|
||||||
→ npm test / pytest / etc.
|
|
||||||
Compare: test results before execution (baseline) vs after
|
|
||||||
```
|
|
||||||
|
|
||||||
If tests fail:
|
|
||||||
- **No -y**: Display failures, ask user: fix now / skip sync / abort
|
|
||||||
- **With -y**: Log failures as warning in execution results, continue
|
|
||||||
|
|
||||||
### 4.5.4 Verify Summary
|
|
||||||
|
|
||||||
```
|
|
||||||
Verify Gate Results:
|
|
||||||
Convergence: {passed}/{total} tasks verified
|
|
||||||
Build: pass|fail|skipped
|
|
||||||
Lint: pass|fail|skipped
|
|
||||||
Tests: {passed}/{total} ({new_failures} regressions)
|
|
||||||
|
|
||||||
Gate: PASS / WARN (continue with warnings) / FAIL (blocked)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.5.5 Persist Verify Manifest
|
|
||||||
|
|
||||||
Write `execution-manifest.json` to session folder:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"session_id": "{session-id}",
|
|
||||||
"plan_path": "planning/{slug}/plan.json",
|
|
||||||
"completed_at": "ISO8601",
|
|
||||||
"tasks": [
|
|
||||||
{
|
|
||||||
"task_id": "TASK-001",
|
|
||||||
"status": "completed",
|
|
||||||
"result_file": ".task/TASK-001.result.json"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"files_modified": [
|
|
||||||
{ "path": "src/services/auth.ts", "action": "modified", "task_id": "TASK-001" },
|
|
||||||
{ "path": "src/routes/login.ts", "action": "created", "task_id": "TASK-001" }
|
|
||||||
],
|
|
||||||
"verify": {
|
|
||||||
"convergence": { "passed": 2, "total": 2 },
|
|
||||||
"build": "pass",
|
|
||||||
"lint": "pass",
|
|
||||||
"tests": { "passed": 42, "total": 42, "regressions": 0 },
|
|
||||||
"gate": "PASS"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
This manifest is the **single source of truth** consumed by `ddd:sync --from-manifest`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 5: Post-Completion Doc Sync
|
|
||||||
|
|
||||||
After all batches complete (unless `--skip-sync`):
|
|
||||||
|
|
||||||
### 5.1 Auto-Trigger ddd:sync
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:sync [-y] --task-id {session-id} --from-manifest {session}/execution-manifest.json "{plan.summary}"
|
|
||||||
```
|
|
||||||
|
|
||||||
Note: `/ddd:sync` automatically creates a backup of `doc-index.json` before modifications.
|
|
||||||
|
|
||||||
When `--from-manifest` is provided, sync uses the **execution manifest** as its primary data source instead of git diff. This ensures:
|
|
||||||
- Precise file-level and symbol-level change tracking (from TASK-*.result.json)
|
|
||||||
- Task-to-file attribution (which task modified which file)
|
|
||||||
- Convergence verification results carried forward
|
|
||||||
- Survives process interruptions (manifest is persisted to disk)
|
|
||||||
|
|
||||||
Fallback: If manifest is unavailable (e.g., manual mode), sync falls back to git diff discovery.
|
|
||||||
|
|
||||||
### 5.2 Generate Action Log
|
|
||||||
|
|
||||||
Create action entry with:
|
|
||||||
- All tasks executed and their results
|
|
||||||
- Files modified across all batches
|
|
||||||
- Features and requirements addressed
|
|
||||||
|
|
||||||
### 5.3 Update Feature Status
|
|
||||||
|
|
||||||
Based on execution results:
|
|
||||||
- Requirements with verified convergence → update status
|
|
||||||
- Features with all requirements met → `status: "implemented"`
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Step 6: Summary & Follow-up
|
|
||||||
|
|
||||||
### 6.1 Execution Results
|
|
||||||
|
|
||||||
```
|
|
||||||
DDD Execute Complete
|
|
||||||
|
|
||||||
Tasks: {completed}/{total} ({failed} failed)
|
|
||||||
Files modified: {count}
|
|
||||||
Batches: {batch_count}
|
|
||||||
|
|
||||||
Doc-Index Changes:
|
|
||||||
Features updated: {list}
|
|
||||||
Components updated: {list}
|
|
||||||
New components registered: {list}
|
|
||||||
Requirements addressed: {list}
|
|
||||||
|
|
||||||
Convergence:
|
|
||||||
{for each task: task.id — criteria met: X/Y}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Follow-up Suggestions
|
|
||||||
|
|
||||||
Based on execution results, suggest:
|
|
||||||
- **New issues**: If unexpected scope expansion was detected
|
|
||||||
- **Additional tests**: If convergence criteria only partially met
|
|
||||||
- **Documentation gaps**: If new components were created without docs
|
|
||||||
- **Next tasks**: If plan had tasks marked as future/deferred
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm, auto-sync |
|
|
||||||
| `--skip-sync` | Skip post-completion ddd:sync (Step 5) |
|
|
||||||
| `--skip-verify` | Skip per-batch impact verification (Step 4) AND post-execution verify gate (Step 4.5) |
|
|
||||||
| `--plan <path>` | Explicit plan.json path |
|
|
||||||
| `--in-memory` | Accept executionContext from ddd:plan handoff |
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `/ddd:plan` output (plan.json + TASK-*.json), `doc-index.json`
|
|
||||||
- **Output to**: Updated `doc-index.json` (via ddd:sync), `TASK-*.result.json` (per-task), `execution-manifest.json` (session-level)
|
|
||||||
- **Schemas**: `plan-overview-ddd-schema.json` (input), `task-schema.json` + `task-ddd-extension-schema.json` (input), `doc-index.json` (enrichment)
|
|
||||||
- **Delegates to**: `/ddd:sync` for post-completion synchronization
|
|
||||||
@@ -1,212 +0,0 @@
|
|||||||
---
|
|
||||||
name: index-build
|
|
||||||
description: Build document index from spec-generator outputs + codebase mapping. Requires existing spec session. For projects without specs, use /ddd:scan instead.
|
|
||||||
argument-hint: "[-y|--yes] [-s|--spec <spec-session-id>] [--from-scratch]"
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-confirm all decisions, use inferred mappings, skip interactive review.
|
|
||||||
|
|
||||||
# DDD Index Build Command (/ddd:index-build)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
From **spec-generator outputs** (requirements, architecture, epics), construct the central document index and map spec entities to actual code locations.
|
|
||||||
|
|
||||||
```
|
|
||||||
Spec outputs (REQ, ADR, EPIC) + Codebase → doc-index.json
|
|
||||||
```
|
|
||||||
|
|
||||||
> **No spec?** Use `/ddd:scan` instead — it reverse-engineers the index from code alone.
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- At least one spec session in `.workflow/.doc-index/specs/` or `.workflow/.spec/`
|
|
||||||
- If no spec found → error with suggestion: "No spec session found. Run /ddd:scan for code-first indexing, or /spec-generator to create specs."
|
|
||||||
|
|
||||||
## Storage Location
|
|
||||||
|
|
||||||
```
|
|
||||||
.workflow/.doc-index/
|
|
||||||
├── doc-index.json ← Central index (primary output)
|
|
||||||
├── specs/ ← Spec-generator outputs
|
|
||||||
│ └── SPEC-{slug}-{date}/
|
|
||||||
├── feature-maps/ ← Feature documentation (from Epics)
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {feature-slug}.md
|
|
||||||
├── tech-registry/ ← Technical component docs (from code mapping)
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {component-slug}.md
|
|
||||||
└── action-logs/ ← Change history (initially empty)
|
|
||||||
└── _index.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 1: Discover & Parse Spec Sources
|
|
||||||
|
|
||||||
### 1.1 Locate Spec Session
|
|
||||||
|
|
||||||
```
|
|
||||||
IF --spec <id> provided:
|
|
||||||
Load from .workflow/.doc-index/specs/<id>/ OR .workflow/.spec/<id>/
|
|
||||||
ELSE:
|
|
||||||
Scan for all SPEC-* directories
|
|
||||||
IF multiple → present list, ask user to select (-y picks latest)
|
|
||||||
IF none → ERROR: "No spec session found. Use /ddd:scan or /spec-generator."
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Migrate Specs (if needed)
|
|
||||||
|
|
||||||
If spec in `.workflow/.spec/` but not in `.workflow/.doc-index/specs/`:
|
|
||||||
- Copy to `.workflow/.doc-index/specs/`
|
|
||||||
- Preserve original (backward compatibility)
|
|
||||||
|
|
||||||
### 1.3 Extract Structured Entities
|
|
||||||
|
|
||||||
| Source File | Extract To |
|
|
||||||
|------------|------------|
|
|
||||||
| `spec-config.json` | project name, domain, spec_type |
|
|
||||||
| `glossary.json` | → index glossary[] |
|
|
||||||
| `product-brief.md` | vision, goals |
|
|
||||||
| `requirements/REQ-*.md` | → index requirements[] (with MoSCoW priority) |
|
|
||||||
| `requirements/NFR-*.md` | → index requirements[] (non-functional) |
|
|
||||||
| `architecture/ADR-*.md` | → index architectureDecisions[] |
|
|
||||||
| `epics/EPIC-*.md` | → feature grouping seeds |
|
|
||||||
|
|
||||||
## Phase 2: Codebase Mapping
|
|
||||||
|
|
||||||
Map spec entities to actual code locations using Gemini:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Map codebase to specification entities for documentation indexing.
|
|
||||||
TASK:
|
|
||||||
• Scan the codebase and identify all major modules/components
|
|
||||||
• For each component: extract file paths, exported symbols (classes, functions, types)
|
|
||||||
• Match components to these specification entities by name/domain similarity:
|
|
||||||
Requirements: {REQ-001: desc, REQ-002: desc, ...extracted from Phase 1}
|
|
||||||
Architecture decisions: {ADR-001: title, ...extracted from Phase 1}
|
|
||||||
• Report unmatched components (exist in code but no spec counterpart)
|
|
||||||
• Report unmatched requirements (in spec but no code found)
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/*
|
|
||||||
EXPECTED: JSON: { components: [{ name, type, files, symbols, matched_req_ids, matched_adr_id, is_orphan }], unmatched_reqs: [REQ-NNN] }
|
|
||||||
CONSTRAINTS: Focus on source directories | Ignore node_modules, dist, build" --tool gemini --mode analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.1 Generate Component IDs & Link
|
|
||||||
|
|
||||||
For each discovered component:
|
|
||||||
- ID: `tech-{kebab-case-name}`
|
|
||||||
- Link to matched `REQ-NNN` and `ADR-NNN`
|
|
||||||
- Flag orphans for user review
|
|
||||||
|
|
||||||
## Phase 3: Build Feature Map (from Epics)
|
|
||||||
|
|
||||||
### 3.1 Epic → Feature Mapping
|
|
||||||
|
|
||||||
```
|
|
||||||
Each EPIC-NNN → one feat-{slug}
|
|
||||||
- id: feat-{slug} (from epic slug)
|
|
||||||
- name: from Epic name
|
|
||||||
- epicId: EPIC-NNN
|
|
||||||
- status: inferred from code mapping
|
|
||||||
- all requirements have matched components → "implemented"
|
|
||||||
- some matched → "in-progress"
|
|
||||||
- none matched → "planned"
|
|
||||||
- requirementIds: from Epic's stories → requirement links
|
|
||||||
- tags: from domain keywords
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Document Generation (delegated)
|
|
||||||
|
|
||||||
Feature-map and tech-registry document generation is handled by `/ddd:doc-generate` in Phase 5.
|
|
||||||
Phase 3 only builds the data structures (feature → requirement → component mappings) that doc-generate consumes.
|
|
||||||
|
|
||||||
## Phase 4: Assemble doc-index.json
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "1.0",
|
|
||||||
"project": "{project-name}",
|
|
||||||
"build_path": "spec-first",
|
|
||||||
"spec_session": "SPEC-{slug}-{date}",
|
|
||||||
"last_updated": "ISO8601",
|
|
||||||
"glossary": [
|
|
||||||
{ "id": "gloss-{slug}", "term": "Term", "definition": "...", "aliases": [], "category": "core|technical|business" }
|
|
||||||
],
|
|
||||||
"features": [
|
|
||||||
{ "id": "feat-{slug}", "name": "...", "epicId": "EPIC-NNN", "status": "...", "docPath": "feature-maps/{slug}.md", "requirementIds": ["REQ-NNN"], "tags": [] }
|
|
||||||
],
|
|
||||||
"requirements": [
|
|
||||||
{ "id": "REQ-NNN", "title": "...", "source": "spec", "priority": "Must|Should|Could|Won't", "sourcePath": "specs/SPEC-*/requirements/REQ-NNN-*.md", "techComponentIds": ["tech-{slug}"], "featureId": "feat-{slug}" }
|
|
||||||
],
|
|
||||||
"technicalComponents": [
|
|
||||||
{ "id": "tech-{slug}", "name": "...", "type": "...", "responsibility": "...", "adrId": "ADR-NNN|null", "docPath": "tech-registry/{slug}.md", "codeLocations": [{ "path": "...", "symbols": [], "lineRange": [0,0] }], "dependsOn": [], "featureIds": ["feat-{slug}"], "actionIds": [] }
|
|
||||||
],
|
|
||||||
"architectureDecisions": [
|
|
||||||
{ "id": "ADR-NNN", "title": "...", "source": "spec", "sourcePath": "specs/SPEC-*/architecture/ADR-NNN-*.md", "componentIds": ["tech-{slug}"] }
|
|
||||||
],
|
|
||||||
"actions": []
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Merge with Existing Code-First Index
|
|
||||||
|
|
||||||
If a code-first index exists (from prior `/ddd:scan`):
|
|
||||||
- Replace `IREQ-NNN` with matching `REQ-NNN` where content overlaps
|
|
||||||
- Keep `IREQ-NNN` without spec counterpart (mark `source: "legacy-inferred"`)
|
|
||||||
- Replace `IADR-NNN` with `ADR-NNN` where applicable
|
|
||||||
- Update `build_path` to `"spec-first"`
|
|
||||||
- Preserve existing `tech-*` components (update links only)
|
|
||||||
|
|
||||||
## Phase 5: Generate Documents
|
|
||||||
|
|
||||||
Delegate all document generation to `/ddd:doc-generate`:
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:doc-generate [-y]
|
|
||||||
```
|
|
||||||
|
|
||||||
This generates the complete document tree (Layer 3 → 2 → 1):
|
|
||||||
- `tech-registry/{slug}.md` — component docs from Phase 2 mapping (Layer 3)
|
|
||||||
- `feature-maps/{slug}.md` — feature docs from Phase 3 mapping (Layer 2)
|
|
||||||
- `_index.md`, `README.md`, `ARCHITECTURE.md`, `SCHEMA.md` — index/overview docs (Layer 1)
|
|
||||||
|
|
||||||
See `/ddd:doc-generate` for full details on generation strategy and flags.
|
|
||||||
|
|
||||||
## Phase 6: Coverage Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Index Build Report (spec-first)
|
|
||||||
|
|
||||||
Spec: {session-id}
|
|
||||||
Features: {N} (from {N} Epics)
|
|
||||||
Requirements: {N} (REQ: {n}, NFR: {n})
|
|
||||||
Components: {N} ({orphan} orphans without spec match)
|
|
||||||
ADRs: {N}
|
|
||||||
|
|
||||||
Mapping Coverage:
|
|
||||||
Requirements → Components: {%} ({unmapped} unmapped)
|
|
||||||
Components → Features: {%}
|
|
||||||
Epics → Features: 100%
|
|
||||||
|
|
||||||
Gaps:
|
|
||||||
- {N} requirements have no matching code component
|
|
||||||
- {N} code components are not linked to any requirement
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Skip all interactive prompts |
|
|
||||||
| `-s, --spec <id>` | Use specific spec session |
|
|
||||||
| `--from-scratch` | Delete existing index and rebuild |
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `spec-generator` outputs, codebase, existing `/ddd:scan` index
|
|
||||||
- **Delegates to**: `/ddd:doc-generate` (Phase 5, full document generation)
|
|
||||||
- **Output to**: `ddd:plan`, `ddd:sync`, `ddd:update`
|
|
||||||
- **Upgrades**: Can merge with prior code-first (`/ddd:scan`) index
|
|
||||||
@@ -1,611 +0,0 @@
|
|||||||
---
|
|
||||||
name: plan
|
|
||||||
description: Document-driven planning pipeline — queries doc-index, explores codebase with doc-aware angles, clarifies ambiguities, and produces unified plan.json + TASK-*.json artifacts with doc_context traceability.
|
|
||||||
argument-hint: "[-y|--yes] [--explore] [--skip-explore] [--skip-clarify] \"task description or feature keyword\""
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Skip clarification (Phase 3), auto-select ddd:execute (Phase 5), skip interactive refinement.
|
|
||||||
|
|
||||||
# DDD Plan Command (/ddd:plan)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Full planning pipeline for document-driven development. Unlike simple context lookup, this command:
|
|
||||||
1. **Queries** the doc-index for instant context (features, requirements, components, ADRs)
|
|
||||||
2. **Explores** the codebase with doc-index-informed angles (not generic presets)
|
|
||||||
3. **Clarifies** ambiguities from exploration results and doc-index gaps
|
|
||||||
4. **Plans** with unified schema output (plan.json + TASK-*.json with doc_context)
|
|
||||||
5. **Hands off** to ddd:execute or other execution engines
|
|
||||||
|
|
||||||
### Key Differentiation from lite-plan
|
|
||||||
- Phase 1 provides instant context from doc-index (no cold-start exploration)
|
|
||||||
- Exploration angles are doc-index-informed (not generic preset selection)
|
|
||||||
- Tasks carry doc_context for traceability (features → requirements → code)
|
|
||||||
- Architecture decisions (ADRs) automatically surface as constraints
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `doc-index.json` must exist at `.workflow/.doc-index/doc-index.json`
|
|
||||||
- If not found → suggest running `/ddd:index-build` or `/ddd:scan` first
|
|
||||||
|
|
||||||
## Session Folder
|
|
||||||
|
|
||||||
```
|
|
||||||
.workflow/.doc-index/planning/{task-slug}-{YYYY-MM-DD}/
|
|
||||||
├── exploration-{angle}.json # Per-angle exploration (Phase 2)
|
|
||||||
├── explorations-manifest.json # Exploration index
|
|
||||||
├── plan.json # Plan overview (Phase 4)
|
|
||||||
├── planning-context.md # Legacy context package (Phase 0+1 combined)
|
|
||||||
├── .process/
|
|
||||||
│ └── doc-context-package.json # Bundled doc_context (Phase 1.8)
|
|
||||||
└── .task/
|
|
||||||
├── TASK-001.json
|
|
||||||
└── TASK-002.json
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 0: Parse Task Intent (enhanced)
|
|
||||||
|
|
||||||
### 0.1 Extract Keywords
|
|
||||||
|
|
||||||
From the user's task description, extract:
|
|
||||||
- **Domain keywords**: feature names, module names, business terms
|
|
||||||
- **Technical keywords**: file paths, class names, function names
|
|
||||||
- **Action type**: feature | bugfix | refactor | optimization | migration
|
|
||||||
|
|
||||||
### 0.2 Glossary Match
|
|
||||||
|
|
||||||
Cross-reference extracted keywords against `doc-index.json.glossary[]`:
|
|
||||||
- Match terms and aliases
|
|
||||||
- Expand user's vocabulary with canonical terms
|
|
||||||
|
|
||||||
### 0.3 Classify Complexity
|
|
||||||
|
|
||||||
Assess task complexity based on:
|
|
||||||
- Number of features potentially affected (from keyword matching)
|
|
||||||
- Whether new components are needed or existing ones modified
|
|
||||||
- Cross-feature impact (single feature vs multiple)
|
|
||||||
|
|
||||||
| Signal | Complexity |
|
|
||||||
|--------|-----------|
|
|
||||||
| Single feature, existing components | Low |
|
|
||||||
| 1-2 features, some new components | Medium |
|
|
||||||
| 3+ features, new architecture needed | High |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 1: Doc-Index Query
|
|
||||||
|
|
||||||
### 1.0 Schema Version Check (TASK-006)
|
|
||||||
|
|
||||||
Before querying doc-index, verify schema compatibility:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const docIndex = JSON.parse(Read('.workflow/.doc-index/doc-index.json'));
|
|
||||||
const schemaVersion = docIndex.schema_version || '0.0'; // Default for legacy
|
|
||||||
|
|
||||||
if (schemaVersion !== '1.0') {
|
|
||||||
console.warn(`Schema version mismatch: found ${schemaVersion}, expected 1.0`);
|
|
||||||
console.warn('Consider running schema migration or regenerating doc-index with /ddd:scan');
|
|
||||||
// Continue with degraded functionality - may encounter missing fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Graceful degradation**: If version mismatch detected → log warning → continue with caution (some features may not work as expected).
|
|
||||||
|
|
||||||
### 1.1 Feature Search
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.features[] where:
|
|
||||||
- name CONTAINS keyword (fuzzy)
|
|
||||||
- tags INTERSECT keywords
|
|
||||||
- requirementIds link to matching requirements
|
|
||||||
→ Output: matched feature IDs + names
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Requirement Search
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.requirements[] where:
|
|
||||||
- title CONTAINS keyword
|
|
||||||
- id matches explicit REQ-NNN reference
|
|
||||||
- featureId matches found features
|
|
||||||
→ Output: matched requirement IDs + titles + priorities
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.3 Component Search
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.technicalComponents[] where:
|
|
||||||
- name CONTAINS keyword
|
|
||||||
- codeLocations[].path CONTAINS file path keyword
|
|
||||||
- codeLocations[].symbols CONTAINS symbol keyword
|
|
||||||
- featureIds INTERSECT found features
|
|
||||||
→ Output: matched component IDs + code locations
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.4 ADR Search
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.architectureDecisions[] where:
|
|
||||||
- componentIds INTERSECT found components
|
|
||||||
→ Output: matched ADR IDs + titles
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.5 Action History Search
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.actions[] where:
|
|
||||||
- related to found features or components
|
|
||||||
→ Output: recent actions with descriptions
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.6 Build Impact Map
|
|
||||||
|
|
||||||
Assemble all found references into a structured impact map:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"affected_features": ["feat-auth"],
|
|
||||||
"affected_requirements": ["REQ-001", "REQ-002"],
|
|
||||||
"affected_components": ["tech-auth-service", "tech-user-model"],
|
|
||||||
"architecture_constraints": ["ADR-001"],
|
|
||||||
"recent_actions": ["task-123"],
|
|
||||||
"complexity": "Medium"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Save as `planning-context.md` (legacy format for backward compatibility).
|
|
||||||
|
|
||||||
### Phase 1.7: Symbol Query (DeepWiki Bridge)
|
|
||||||
|
|
||||||
If DeepWiki is available (`deepwiki_feature_to_symbol_index` exists in doc-index.json):
|
|
||||||
|
|
||||||
1. Collect all `codeLocations[].path` from matched `technicalComponents[]`
|
|
||||||
2. Query DeepWiki: `POST /api/deepwiki/symbols-for-paths { paths: unique_paths }`
|
|
||||||
3. Build symbol_docs by component, sorted by type priority (class > function > method)
|
|
||||||
4. Populate `doc_context.symbol_docs[]` with Top-5 symbols per component
|
|
||||||
|
|
||||||
**Graceful degradation**: If DeepWiki unavailable → log warning → skip symbol injection → continue flow.
|
|
||||||
|
|
||||||
### Phase 1.8: Persist Doc Context Package
|
|
||||||
|
|
||||||
After building doc_context (including symbol_docs from Phase 1.7), persist it as a reusable context package:
|
|
||||||
|
|
||||||
1. Bundle doc_context into JSON structure:
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"affected_features": ["feat-auth"],
|
|
||||||
"affected_requirements": ["REQ-001", "REQ-002"],
|
|
||||||
"affected_components": ["tech-auth-service"],
|
|
||||||
"architecture_constraints": ["ADR-001"],
|
|
||||||
"index_path": ".workflow/.doc-index/doc-index.json",
|
|
||||||
"symbol_docs": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Write to session folder: `{sessionFolder}/.process/doc-context-package.json`
|
|
||||||
3. Store relative path for task.json population: `../.process/doc-context-package.json`
|
|
||||||
|
|
||||||
**Error handling**: If write fails → log warning → continue without context package (backward compatible).
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 2: Doc-Index-Guided Exploration (NEW)
|
|
||||||
|
|
||||||
Use Phase 1 results to **SELECT exploration angles intelligently**:
|
|
||||||
|
|
||||||
### 2.1 Angle Selection Logic
|
|
||||||
|
|
||||||
| Phase 1 Signal | Add Exploration Angle |
|
|
||||||
|----------------|----------------------|
|
|
||||||
| feat-auth or security-related ADR affected | `security` |
|
|
||||||
| Multiple features crossed (2+) | `integration-points` |
|
|
||||||
| New component needed (no matching tech-*) | `architecture` |
|
|
||||||
| Performance-related requirements | `performance` |
|
|
||||||
| Default (always included) | `patterns` + `dependencies` |
|
|
||||||
|
|
||||||
Select 1-4 angles total. More angles for higher complexity.
|
|
||||||
|
|
||||||
### 2.2 Skip & Trigger Conditions
|
|
||||||
|
|
||||||
| Complexity | Default Behavior | Override |
|
|
||||||
|-----------|-----------------|---------|
|
|
||||||
| **Low** | Auto-skip Phase 2 | `--explore` forces exploration |
|
|
||||||
| **Medium** | Ask user (unless `-y` → skip) | `--explore` forces, `--skip-explore` forces skip |
|
|
||||||
| **High** | Always run | `--skip-explore` forces skip |
|
|
||||||
|
|
||||||
Skip Phase 2 entirely when:
|
|
||||||
- Complexity is Low AND `--explore` not set
|
|
||||||
- OR `--skip-explore` flag is set
|
|
||||||
- OR `-y` flag AND complexity is Medium
|
|
||||||
|
|
||||||
### 2.3 Parallel Exploration
|
|
||||||
|
|
||||||
Launch 1-4 parallel `cli-explore-agent` runs:
|
|
||||||
|
|
||||||
```
|
|
||||||
For each selected angle:
|
|
||||||
Agent(subagent_type="cli-explore-agent", prompt="
|
|
||||||
Explore codebase for: {user task description}
|
|
||||||
Angle: {angle}
|
|
||||||
|
|
||||||
## Doc-Index Context (pre-loaded)
|
|
||||||
Features affected: {feature names + IDs}
|
|
||||||
Components: {component names + code locations}
|
|
||||||
Requirements: {requirement titles}
|
|
||||||
Architecture decisions: {ADR titles + decisions}
|
|
||||||
|
|
||||||
Focus exploration on {angle}-specific concerns.
|
|
||||||
Output: explore-json-schema format.
|
|
||||||
")
|
|
||||||
```
|
|
||||||
|
|
||||||
Each agent receives doc-index context (feature-maps, tech-registry docs) to avoid cold-start.
|
|
||||||
|
|
||||||
### 2.4 Save Exploration Results
|
|
||||||
|
|
||||||
- Each exploration → `exploration-{angle}.json` (explore-json-schema)
|
|
||||||
- Manifest → `explorations-manifest.json`:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"explorations": [
|
|
||||||
{ "angle": "patterns", "path": "exploration-patterns.json", "file_count": 12 },
|
|
||||||
{ "angle": "security", "path": "exploration-security.json", "file_count": 8 }
|
|
||||||
],
|
|
||||||
"total_files_discovered": 18,
|
|
||||||
"timestamp": "ISO8601"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 3: Clarification (NEW)
|
|
||||||
|
|
||||||
### 3.1 Aggregate Clarification Needs
|
|
||||||
|
|
||||||
Collect from three sources:
|
|
||||||
1. **Exploration results**: `clarification_needs[]` from each exploration JSON
|
|
||||||
2. **Doc-index gaps**: unmapped requirements, orphan components, missing feature coverage
|
|
||||||
3. **Conflicting constraints**: contradictory architecture decisions, requirement priority conflicts
|
|
||||||
|
|
||||||
### 3.2 Deduplicate & Batch
|
|
||||||
|
|
||||||
- Merge duplicate/similar questions across exploration angles
|
|
||||||
- Group into rounds (max 4 questions per AskUserQuestion call)
|
|
||||||
- Prioritize: blocking questions first, nice-to-have last
|
|
||||||
|
|
||||||
### 3.3 Skip Conditions
|
|
||||||
|
|
||||||
Skip Phase 3 when:
|
|
||||||
- `-y` flag is set
|
|
||||||
- `--skip-clarify` flag is set
|
|
||||||
- No clarification needs collected from any source
|
|
||||||
- Complexity is Low AND Phase 2 was skipped (no exploration results to aggregate)
|
|
||||||
|
|
||||||
### 3.4 Execute Clarification
|
|
||||||
|
|
||||||
```
|
|
||||||
AskUserQuestion(questions=[
|
|
||||||
{
|
|
||||||
question: "Which authentication strategy should the new endpoint use?",
|
|
||||||
header: "Auth strategy",
|
|
||||||
options: [
|
|
||||||
{ label: "JWT Bearer (Recommended)", description: "Consistent with ADR-001 and existing auth middleware" },
|
|
||||||
{ label: "API Key", description: "Simpler but inconsistent with current architecture" },
|
|
||||||
{ label: "OAuth2", description: "Most flexible but higher implementation cost" }
|
|
||||||
],
|
|
||||||
multiSelect: false
|
|
||||||
}
|
|
||||||
])
|
|
||||||
```
|
|
||||||
|
|
||||||
Feed answers back into Phase 4 as constraints.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 4: Task Planning (NEW — produces plan.json + TASK-*.json)
|
|
||||||
|
|
||||||
### 4.1 Planning Strategy Selection
|
|
||||||
|
|
||||||
| Complexity | Strategy |
|
|
||||||
|-----------|---------|
|
|
||||||
| Low | Direct Claude planning (inline) |
|
|
||||||
| Medium | cli-lite-planning-agent with doc-index context |
|
|
||||||
| High | cli-lite-planning-agent with full exploration + doc-index context |
|
|
||||||
|
|
||||||
### 4.2 Planning Input Assembly
|
|
||||||
|
|
||||||
Combine:
|
|
||||||
- User's original task description
|
|
||||||
- Phase 1 impact map (features, requirements, components, ADRs)
|
|
||||||
- Phase 2 exploration results (if executed)
|
|
||||||
- Phase 3 clarification answers (if collected)
|
|
||||||
- Relevant feature-map and tech-registry doc excerpts
|
|
||||||
|
|
||||||
### 4.3 Execute Planning
|
|
||||||
|
|
||||||
For **Low complexity** (direct):
|
|
||||||
```
|
|
||||||
Generate plan.json + TASK-*.json directly based on assembled context.
|
|
||||||
```
|
|
||||||
|
|
||||||
For **Medium/High complexity**:
|
|
||||||
```
|
|
||||||
Agent(subagent_type="cli-lite-planning-agent", prompt="
|
|
||||||
Task: {user task description}
|
|
||||||
|
|
||||||
## Doc-Index Impact Map
|
|
||||||
{Phase 1 results}
|
|
||||||
|
|
||||||
## Exploration Context
|
|
||||||
{Phase 2 results summary}
|
|
||||||
|
|
||||||
## Clarification Answers
|
|
||||||
{Phase 3 answers}
|
|
||||||
|
|
||||||
## Architecture Constraints
|
|
||||||
{ADR excerpts}
|
|
||||||
|
|
||||||
Generate plan following plan-overview-base-schema.
|
|
||||||
Generate tasks following task-schema.
|
|
||||||
Include doc_context in both plan.json and each TASK-*.json.
|
|
||||||
")
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.3.1 Populate Task Artifacts (TASK-002)
|
|
||||||
|
|
||||||
After task generation, enrich each TASK-*.json with artifacts[] field:
|
|
||||||
|
|
||||||
1. Load doc-index.json from `.workflow/.doc-index/doc-index.json`
|
|
||||||
2. For each task, extract feature_ids from task.doc_context
|
|
||||||
3. Filter doc-index features/requirements matching task scope:
|
|
||||||
- Match by feature_ids in task.doc_context.feature_ids
|
|
||||||
- Include linked requirements via requirementIds
|
|
||||||
- Include linked components via componentIds
|
|
||||||
4. Populate task.artifacts[] with filtered references:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"artifacts": [
|
|
||||||
{
|
|
||||||
"type": "feature_spec",
|
|
||||||
"source": "doc-index",
|
|
||||||
"path": ".workflow/.doc-index/feature-maps/auth.md",
|
|
||||||
"feature_id": "feat-auth",
|
|
||||||
"usage": "Reference for authentication requirements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "requirement",
|
|
||||||
"source": "doc-index",
|
|
||||||
"path": ".workflow/.doc-index/doc-index.json#requirements[0]",
|
|
||||||
"feature_id": "feat-auth",
|
|
||||||
"requirement_id": "REQ-001",
|
|
||||||
"usage": "Acceptance criteria source"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "component_doc",
|
|
||||||
"source": "doc-index",
|
|
||||||
"path": ".workflow/.doc-index/tech-registry/auth-service.md",
|
|
||||||
"component_id": "tech-auth-service",
|
|
||||||
"usage": "Implementation reference"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Loading pattern** (following brainstorm pattern from action-planning-agent.md:200-214):
|
|
||||||
- Load doc-index.json once for catalog
|
|
||||||
- Filter by task-relevant feature IDs (1-3 per task)
|
|
||||||
- Only include artifacts directly referenced in task scope
|
|
||||||
- Use relative paths from task file location
|
|
||||||
|
|
||||||
### 4.3.2 Populate Context Package Path (TASK-001)
|
|
||||||
|
|
||||||
Set context_package_path field in each TASK-*.json:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"context_package_path": "../.process/doc-context-package.json"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Relative path from `.task/TASK-*.json` to `.process/doc-context-package.json`.
|
|
||||||
|
|
||||||
### 4.3.3 Add Navigation Links Block (TASK-003)
|
|
||||||
|
|
||||||
Add links{} navigation block to each TASK-*.json for improved discoverability:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"links": {
|
|
||||||
"plan": "../plan.json",
|
|
||||||
"doc_index": "../../../doc-index.json",
|
|
||||||
"feature_maps": [
|
|
||||||
"../../../feature-maps/auth.md"
|
|
||||||
],
|
|
||||||
"related_tasks": [
|
|
||||||
"TASK-002.json",
|
|
||||||
"TASK-003.json"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Path computation**:
|
|
||||||
- `plan`: Relative path from `.task/TASK-*.json` to `plan.json` (sibling of .task/)
|
|
||||||
- `doc_index`: Relative path to `.workflow/.doc-index/doc-index.json`
|
|
||||||
- `feature_maps`: Paths to feature-map docs from task.doc_context.feature_docs
|
|
||||||
- `related_tasks`: Task IDs from task.depends_on or tasks sharing same feature_ids
|
|
||||||
|
|
||||||
**Backward compatibility**: links{} is optional field (task-schema allows additionalProperties).
|
|
||||||
|
|
||||||
### 4.4 Output Schema: plan.json
|
|
||||||
|
|
||||||
Follows `plan-overview-base-schema` with ddd-specific `doc_context` extension:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"summary": "...",
|
|
||||||
"approach": "...",
|
|
||||||
"task_ids": ["TASK-001", "TASK-002"],
|
|
||||||
"task_count": 2,
|
|
||||||
"complexity": "Medium",
|
|
||||||
"doc_context": {
|
|
||||||
"affected_features": ["feat-auth"],
|
|
||||||
"affected_requirements": ["REQ-001", "REQ-002"],
|
|
||||||
"affected_components": ["tech-auth-service"],
|
|
||||||
"architecture_constraints": ["ADR-001"],
|
|
||||||
"index_path": ".workflow/.doc-index/doc-index.json",
|
|
||||||
"symbol_docs": [
|
|
||||||
{
|
|
||||||
"symbol_urn": "deepwiki:symbol:<path>#L<start>-L<end>",
|
|
||||||
"name": "SymbolName",
|
|
||||||
"type": "class|function|method",
|
|
||||||
"doc_summary": "Generated documentation summary...",
|
|
||||||
"source_path": "src/path/to/file.ts",
|
|
||||||
"doc_path": ".deepwiki/file.md",
|
|
||||||
"freshness": "fresh|stale|unknown"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"_metadata": {
|
|
||||||
"timestamp": "ISO8601",
|
|
||||||
"source": "cli-lite-planning-agent",
|
|
||||||
"plan_type": "feature",
|
|
||||||
"schema_version": "2.0",
|
|
||||||
"exploration_angles": ["patterns", "security"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.5 Output Schema: TASK-*.json
|
|
||||||
|
|
||||||
Follows `task-schema` with ddd-specific `doc_context` extension:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "TASK-001",
|
|
||||||
"title": "Add rate limiting middleware",
|
|
||||||
"description": "...",
|
|
||||||
"depends_on": [],
|
|
||||||
"convergence": {
|
|
||||||
"criteria": ["Rate limiter middleware exists and is registered", "Tests pass"],
|
|
||||||
"verification": "npm test -- --grep rate-limit",
|
|
||||||
"definition_of_done": "API endpoints enforce rate limits per ADR-001 specifications"
|
|
||||||
},
|
|
||||||
"doc_context": {
|
|
||||||
"feature_ids": ["feat-auth"],
|
|
||||||
"requirement_ids": ["REQ-001"],
|
|
||||||
"component_ids": ["tech-auth-service"],
|
|
||||||
"adr_ids": ["ADR-001"],
|
|
||||||
"feature_docs": ["feature-maps/auth.md"],
|
|
||||||
"component_docs": ["tech-registry/auth-service.md"],
|
|
||||||
"symbol_docs": [
|
|
||||||
{
|
|
||||||
"symbol_urn": "deepwiki:symbol:<path>#L<start>-L<end>",
|
|
||||||
"name": "SymbolName",
|
|
||||||
"type": "class|function|method",
|
|
||||||
"doc_summary": "Generated documentation summary...",
|
|
||||||
"source_path": "src/path/to/file.ts",
|
|
||||||
"doc_path": ".deepwiki/file.md",
|
|
||||||
"freshness": "fresh|stale|unknown"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"files": [...],
|
|
||||||
"implementation": [...]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 4.6 Enrichment Rules
|
|
||||||
|
|
||||||
Each task is enriched with:
|
|
||||||
- `feature_ids`, `requirement_ids`, `component_ids`, `adr_ids` — traced from Phase 1
|
|
||||||
- Relevant feature-map and tech-registry doc paths
|
|
||||||
- Requirement acceptance criteria as convergence criteria source
|
|
||||||
- ADR decisions as implementation constraints
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 5: Confirmation & Handoff Selection
|
|
||||||
|
|
||||||
### 5.1 Display Plan Summary
|
|
||||||
|
|
||||||
Show:
|
|
||||||
- Plan overview (summary, approach, complexity)
|
|
||||||
- Task list with dependencies
|
|
||||||
- Doc-index impact: which features/requirements/components will be affected
|
|
||||||
- Estimated scope
|
|
||||||
|
|
||||||
### 5.2 Handoff Options
|
|
||||||
|
|
||||||
| Option | Description | When |
|
|
||||||
|--------|-------------|------|
|
|
||||||
| **ddd:execute** | Document-aware execution (recommended) | Default for ddd workflow |
|
|
||||||
| **lite-execute** | Standard execution (no doc awareness) | When doc traceability not needed |
|
|
||||||
| **direct** | Output context, manual work | User prefers manual coding |
|
|
||||||
| **stop** | Planning only, no execution | Research/analysis tasks |
|
|
||||||
|
|
||||||
### 5.3 Auto-Selection
|
|
||||||
|
|
||||||
With `-y`: auto-select `ddd:execute`.
|
|
||||||
|
|
||||||
Without `-y`: present options via AskUserQuestion.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Phase 6: Handoff
|
|
||||||
|
|
||||||
### 6.1 Build Execution Context
|
|
||||||
|
|
||||||
Build `executionContext` compatible with lite-execute format:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"plan_path": ".workflow/.doc-index/planning/{slug}/plan.json",
|
|
||||||
"task_dir": ".workflow/.doc-index/planning/{slug}/.task/",
|
|
||||||
"doc_index_path": ".workflow/.doc-index/doc-index.json",
|
|
||||||
"exploration_manifest": ".workflow/.doc-index/planning/{slug}/explorations-manifest.json",
|
|
||||||
"original_input": "user's task description"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6.2 Invoke Selected Engine
|
|
||||||
|
|
||||||
| Selection | Action |
|
|
||||||
|-----------|--------|
|
|
||||||
| `ddd:execute` | Invoke `/ddd:execute --in-memory` with executionContext |
|
|
||||||
| `lite-execute` | Invoke `/workflow:lite-execute` with plan.json path |
|
|
||||||
| `direct` | Display context package + file list for manual work |
|
|
||||||
| `stop` | Output plan summary, end here |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Skip clarification, auto-select ddd:execute |
|
|
||||||
| `--explore` | Force Phase 2 exploration even for Low complexity |
|
|
||||||
| `--skip-explore` | Skip Phase 2 (doc-index-guided exploration) |
|
|
||||||
| `--skip-clarify` | Skip Phase 3 (clarification) only |
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Primary**: plan.json + TASK-*.json in session folder
|
|
||||||
- **Secondary**: planning-context.md (legacy format)
|
|
||||||
- **Exploration**: exploration-{angle}.json files (if Phase 2 ran)
|
|
||||||
- **Console**: Plan summary with doc-index impact
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `doc-index.json` (built by `/ddd:index-build` or `/ddd:scan`)
|
|
||||||
- **Output to**: `/ddd:execute`, `/workflow:lite-execute`, `/ddd:sync` post-task
|
|
||||||
- **Schemas**: `plan-overview-ddd-schema.json` (plan output), `task-schema.json` + `task-ddd-extension-schema.json` (task output), `explore-json-schema.json`
|
|
||||||
- **Triggers**: Before any development task in ddd workflow
|
|
||||||
@@ -1,365 +0,0 @@
|
|||||||
---
|
|
||||||
name: scan
|
|
||||||
description: Scan existing codebase to build document index without specs. Analyzes code structure, infers features, discovers components, and reverse-engineers project knowledge graph.
|
|
||||||
argument-hint: "[-y|--yes] [--from-scratch] [--scope <dir>] \"optional project description\""
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-confirm feature groupings, component naming, skip interactive review.
|
|
||||||
|
|
||||||
# DDD Scan Command (/ddd:scan)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
For **existing projects without specifications**: analyze codebase to construct the document index by reverse-engineering project structure. This is the code-first entry point — no spec-generator required.
|
|
||||||
|
|
||||||
```
|
|
||||||
Codebase → Components → Features (inferred) → Requirements (inferred) → doc-index.json
|
|
||||||
```
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
- Existing project, no spec-generator outputs
|
|
||||||
- Want to start using doc-driven workflow on a legacy codebase
|
|
||||||
- Quick project mapping for onboarding or audit
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- A codebase must exist (src/, lib/, app/, or similar source directories)
|
|
||||||
- Git repository recommended (for action history seeding)
|
|
||||||
|
|
||||||
## Storage Location
|
|
||||||
|
|
||||||
```
|
|
||||||
.workflow/.doc-index/
|
|
||||||
├── doc-index.json ← Central index (primary output)
|
|
||||||
├── feature-maps/ ← Inferred feature documentation
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {feature-slug}.md
|
|
||||||
├── tech-registry/ ← Discovered component documentation
|
|
||||||
│ ├── _index.md
|
|
||||||
│ └── {component-slug}.md
|
|
||||||
└── action-logs/ ← Git history seeds
|
|
||||||
├── _index.md
|
|
||||||
└── {act-hash}.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 1: Project Structure Analysis
|
|
||||||
|
|
||||||
### 1.1 Framework & Stack Detection
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Analyze project structure, tech stack, and architecture for documentation indexing.
|
|
||||||
TASK:
|
|
||||||
• Detect language/framework from manifest files (package.json, go.mod, Cargo.toml, requirements.txt, etc.)
|
|
||||||
• Map directory structure: source dirs, test dirs, config dirs, entry points
|
|
||||||
• Identify architectural pattern: monolith, microservices, monorepo, library, CLI tool
|
|
||||||
• Detect key dependencies and their roles (ORM, HTTP framework, auth library, etc.)
|
|
||||||
• List all major source directories with brief purpose description
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/*
|
|
||||||
EXPECTED: JSON with: {
|
|
||||||
project_name, language, framework, architecture_pattern,
|
|
||||||
source_dirs: [{ path, purpose, file_count }],
|
|
||||||
dependencies: [{ name, role }],
|
|
||||||
entry_points: [{ path, description }]
|
|
||||||
}
|
|
||||||
CONSTRAINTS: Prioritize source directories | Ignore node_modules, dist, build, vendor" --tool gemini --mode analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Merge with project-tech.json
|
|
||||||
|
|
||||||
If `.workflow/project-tech.json` exists, merge to reduce redundant analysis.
|
|
||||||
|
|
||||||
## Phase 2: Component Discovery
|
|
||||||
|
|
||||||
### 2.1 Deep Module Scan
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Discover all significant code components/modules for documentation indexing.
|
|
||||||
TASK:
|
|
||||||
• For each source directory, identify distinct modules/components
|
|
||||||
• For each component extract:
|
|
||||||
- Name (class name, module name, or logical group)
|
|
||||||
- Type: service | controller | model | util | hook | route | config | middleware | component
|
|
||||||
- File paths (primary file + related files)
|
|
||||||
- Exported symbols (public API: classes, functions, types, constants)
|
|
||||||
- Internal dependencies: what other modules it imports from within the project
|
|
||||||
- Responsibility: one-line description of what it does
|
|
||||||
• Group small utility files under parent module when they share domain
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @{source_dirs from Phase 1}
|
|
||||||
EXPECTED: JSON array: [{ name, type, files, symbols, depends_on, responsibility }]
|
|
||||||
CONSTRAINTS: Focus on business logic | Min threshold: components with 2+ exports or clear domain purpose | Group utilities under parent domain" --tool gemini --mode analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Generate Component IDs
|
|
||||||
|
|
||||||
For each discovered component:
|
|
||||||
- ID: `tech-{kebab-case-name}` (e.g., `tech-auth-service`, `tech-user-model`)
|
|
||||||
- Validate uniqueness, append counter on collision
|
|
||||||
|
|
||||||
### 2.3 Build Dependency Graph
|
|
||||||
|
|
||||||
From `depends_on` fields, construct internal dependency edges:
|
|
||||||
```
|
|
||||||
tech-auth-service → tech-user-model
|
|
||||||
tech-auth-service → tech-jwt-util
|
|
||||||
tech-order-controller → tech-auth-service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 3: Feature Inference
|
|
||||||
|
|
||||||
**Key step: group components into logical features without formal specs.**
|
|
||||||
|
|
||||||
### 3.1 Inference Strategy (priority order)
|
|
||||||
|
|
||||||
```
|
|
||||||
Strategy 1 — Directory grouping:
|
|
||||||
src/auth/** → feat-auth
|
|
||||||
src/orders/** → feat-orders
|
|
||||||
src/payments/** → feat-payments
|
|
||||||
|
|
||||||
Strategy 2 — Route/endpoint grouping (web apps):
|
|
||||||
/api/users/* → feat-user-management
|
|
||||||
/api/orders/* → feat-order-management
|
|
||||||
|
|
||||||
Strategy 3 — Dependency clustering:
|
|
||||||
Components that heavily import each other → same feature
|
|
||||||
|
|
||||||
Strategy 4 — Domain keyword extraction:
|
|
||||||
Class names + file names → domain terms → feature names
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.2 Gemini Feature Synthesis
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ccw cli -p "PURPOSE: Infer high-level features from discovered code components. This project has no formal specification.
|
|
||||||
TASK:
|
|
||||||
Given these discovered components:
|
|
||||||
{component list from Phase 2: names, types, files, responsibilities, dependencies}
|
|
||||||
|
|
||||||
• Group them into logical features (3-10 features for a typical project)
|
|
||||||
• For each feature:
|
|
||||||
- name: human-readable (Chinese OK)
|
|
||||||
- component_ids: which components belong
|
|
||||||
- description: what the feature does (inferred from code)
|
|
||||||
- inferred_requirements: what this feature needs to accomplish (1-3 per feature)
|
|
||||||
- status: 'implemented' (code complete) or 'partial' (incomplete patterns)
|
|
||||||
- tags: search keywords
|
|
||||||
• Identify cross-cutting concerns (logging, auth middleware, error handling) as separate features
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: {component list JSON}
|
|
||||||
EXPECTED: JSON: { features: [{ name, description, component_ids, inferred_requirements: [{ id, title }], status, tags }] }
|
|
||||||
CONSTRAINTS: Every component must belong to at least 1 feature | Prefer fewer broad features over many narrow ones" --tool gemini --mode analysis
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.3 Interactive Feature Review (unless -y)
|
|
||||||
|
|
||||||
Present inferred features to user:
|
|
||||||
- Allow renaming, merging, splitting
|
|
||||||
- Allow reassigning components between features
|
|
||||||
- Confirm final feature list
|
|
||||||
|
|
||||||
## Phase 4: Implicit Requirement & Architecture Extraction
|
|
||||||
|
|
||||||
### 4.1 Inferred Requirements
|
|
||||||
|
|
||||||
For each feature, generate lightweight requirement entries from its components:
|
|
||||||
|
|
||||||
```
|
|
||||||
Feature: feat-auth (User Authentication)
|
|
||||||
→ IREQ-001: "Users can log in with email and password" (from LoginController)
|
|
||||||
→ IREQ-002: "JWT tokens for session management" (from AuthMiddleware + jwt dep)
|
|
||||||
→ IREQ-003: "Password reset via email" (from PasswordResetService)
|
|
||||||
```
|
|
||||||
|
|
||||||
**ID Convention**: `IREQ-NNN` — distinguishes inferred from formal `REQ-NNN`.
|
|
||||||
|
|
||||||
### 4.2 Inferred Architecture Decisions
|
|
||||||
|
|
||||||
Detect patterns from code + dependencies:
|
|
||||||
|
|
||||||
```
|
|
||||||
Express.js + JWT middleware → IADR-001: "REST API with JWT authentication"
|
|
||||||
Prisma ORM + PostgreSQL → IADR-002: "PostgreSQL via Prisma ORM"
|
|
||||||
React + Redux → IADR-003: "React frontend with Redux state"
|
|
||||||
```
|
|
||||||
|
|
||||||
**ID Convention**: `IADR-NNN` — distinguishes inferred from formal `ADR-NNN`.
|
|
||||||
|
|
||||||
### 4.3 Glossary Generation
|
|
||||||
|
|
||||||
Extract domain terms from:
|
|
||||||
- Class/function names (CamelCase → terms)
|
|
||||||
- Key business terms in comments and strings
|
|
||||||
- Framework-specific terminology
|
|
||||||
|
|
||||||
Write to `.workflow/.doc-index/glossary.json`.
|
|
||||||
|
|
||||||
## Phase 5: Git History Seeds
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git log --oneline --since="3 months ago" --no-merges --format="%H|%s|%ai" | head -30
|
|
||||||
```
|
|
||||||
|
|
||||||
For each significant commit:
|
|
||||||
- Match changed files to discovered components
|
|
||||||
- Create action entry with `type: "historical"`
|
|
||||||
|
|
||||||
## Phase 6: Assemble doc-index.json
|
|
||||||
|
|
||||||
Write the index with code-first markers:
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"version": "1.0",
|
|
||||||
"project": "{project-name}",
|
|
||||||
"build_path": "code-first",
|
|
||||||
"spec_session": null,
|
|
||||||
"last_updated": "ISO8601",
|
|
||||||
"glossary": [...],
|
|
||||||
"features": [{
|
|
||||||
"id": "feat-{slug}",
|
|
||||||
"name": "Feature Name",
|
|
||||||
"epicId": null,
|
|
||||||
"status": "implemented|partial",
|
|
||||||
"docPath": "feature-maps/{slug}.md",
|
|
||||||
"requirementIds": ["IREQ-NNN"],
|
|
||||||
"tags": ["tag"]
|
|
||||||
}],
|
|
||||||
"requirements": [{
|
|
||||||
"id": "IREQ-NNN",
|
|
||||||
"title": "Inferred requirement",
|
|
||||||
"source": "inferred",
|
|
||||||
"priority": "inferred",
|
|
||||||
"sourcePath": null,
|
|
||||||
"techComponentIds": ["tech-{slug}"],
|
|
||||||
"featureId": "feat-{slug}"
|
|
||||||
}],
|
|
||||||
"technicalComponents": [{
|
|
||||||
"id": "tech-{slug}",
|
|
||||||
"name": "ComponentName",
|
|
||||||
"type": "service|controller|model|...",
|
|
||||||
"responsibility": "One-line description",
|
|
||||||
"adrId": "IADR-NNN|null",
|
|
||||||
"docPath": "tech-registry/{slug}.md",
|
|
||||||
"codeLocations": [{ "path": "src/...", "symbols": [...] }],
|
|
||||||
"dependsOn": ["tech-{other}"],
|
|
||||||
"featureIds": ["feat-{slug}"],
|
|
||||||
"actionIds": []
|
|
||||||
}],
|
|
||||||
"architectureDecisions": [{
|
|
||||||
"id": "IADR-NNN",
|
|
||||||
"title": "Inferred decision",
|
|
||||||
"source": "inferred",
|
|
||||||
"sourcePath": null,
|
|
||||||
"componentIds": ["tech-{slug}"]
|
|
||||||
}],
|
|
||||||
"actions": [{
|
|
||||||
"id": "act-{short-hash}",
|
|
||||||
"description": "Commit message",
|
|
||||||
"type": "historical",
|
|
||||||
"status": "historical",
|
|
||||||
"affectedComponents": ["tech-{slug}"],
|
|
||||||
"relatedCommit": "full-hash",
|
|
||||||
"timestamp": "ISO8601"
|
|
||||||
}],
|
|
||||||
"freshness": {
|
|
||||||
"thresholds": { "warning": 0.3, "stale": 0.7 },
|
|
||||||
"weights": { "time": 0.1, "churn": 0.4, "symbol": 0.5 },
|
|
||||||
"time_decay_k": 0.05,
|
|
||||||
"auto_regenerate": false
|
|
||||||
},
|
|
||||||
"deepwiki_feature_to_symbol_index": {}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Phase 7: Build DeepWiki Feature-to-Symbol Index
|
|
||||||
|
|
||||||
If DeepWiki is available (`.codexlens/deepwiki_index.db` exists):
|
|
||||||
|
|
||||||
1. Collect all `codeLocations[].path` from `technicalComponents[]`
|
|
||||||
2. Query DeepWiki: `POST /api/deepwiki/symbols-for-paths { paths: [...] }`
|
|
||||||
3. Build `deepwiki_feature_to_symbol_index` by traversing:
|
|
||||||
`feature → requirementIds → techComponentIds → codeLocations → symbols`
|
|
||||||
|
|
||||||
```json
|
|
||||||
"deepwiki_feature_to_symbol_index": {
|
|
||||||
"feat-auth": [
|
|
||||||
"deepwiki:symbol:src/auth/jwt.ts#L30-L55",
|
|
||||||
"deepwiki:symbol:src/models/user.ts#L12-L40"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Symbol URN format**: `deepwiki:symbol:<file_path>#L<start>-L<end>`
|
|
||||||
|
|
||||||
**Graceful degradation**: If DeepWiki is unavailable, set `deepwiki_feature_to_symbol_index: {}` and log warning.
|
|
||||||
|
|
||||||
## Phase 8: Generate Documents
|
|
||||||
|
|
||||||
Delegate all document generation to `/ddd:doc-generate`:
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:doc-generate [-y]
|
|
||||||
```
|
|
||||||
|
|
||||||
This generates the complete document tree (Layer 3 → 2 → 1):
|
|
||||||
- `tech-registry/{slug}.md` — component docs (Layer 3)
|
|
||||||
- `feature-maps/{slug}.md` — feature docs (Layer 2)
|
|
||||||
- `_index.md`, `README.md`, `ARCHITECTURE.md`, `SCHEMA.md` — index/overview docs (Layer 1)
|
|
||||||
|
|
||||||
See `/ddd:doc-generate` for full details on generation strategy and flags.
|
|
||||||
|
|
||||||
## Phase 9: Validation & Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Scan Report
|
|
||||||
|
|
||||||
Project: {name} ({language}/{framework})
|
|
||||||
Architecture: {pattern}
|
|
||||||
Source dirs: {N}
|
|
||||||
|
|
||||||
Discovered:
|
|
||||||
Components: {N} ({by type breakdown})
|
|
||||||
Features: {N} (inferred)
|
|
||||||
Requirements: {N} (IREQ, inferred)
|
|
||||||
Architecture Decisions: {N} (IADR, inferred)
|
|
||||||
Historical Actions: {N} (from git)
|
|
||||||
|
|
||||||
Coverage:
|
|
||||||
Components → Features: {%}
|
|
||||||
Dependencies mapped: {%}
|
|
||||||
|
|
||||||
Recommendations:
|
|
||||||
- Run /spec-generator to formalize {N} inferred requirements
|
|
||||||
- {N} components have unclear responsibility — review tech-registry docs
|
|
||||||
- Use /ddd:plan to start planning tasks with this index
|
|
||||||
```
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm all decisions |
|
|
||||||
| `--from-scratch` | Delete existing index and rebuild |
|
|
||||||
| `--scope <dir>` | Limit scan to specific directory (e.g., `--scope src/auth`) |
|
|
||||||
|
|
||||||
## Upgrade Path: scan → spec
|
|
||||||
|
|
||||||
When a scanned project later runs `spec-generator` + `/ddd:index-build`:
|
|
||||||
- `/ddd:index-build` detects existing code-first index
|
|
||||||
- Merges: `IREQ-NNN` → `REQ-NNN`, `IADR-NNN` → `ADR-NNN` where content overlaps
|
|
||||||
- Updates `build_path` to `"spec-first"`
|
|
||||||
- Preserves all `tech-*` and `feat-*` entries (updates links only)
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: Codebase, git history, `project-tech.json`
|
|
||||||
- **Delegates to**: `/ddd:doc-generate` (Phase 8, full document generation)
|
|
||||||
- **Output to**: `ddd:plan`, `ddd:sync`, `ddd:update`, `ddd:index-build` (upgrade)
|
|
||||||
- **Standalone**: Can be used independently on any project
|
|
||||||
@@ -1,353 +0,0 @@
|
|||||||
---
|
|
||||||
name: sync
|
|
||||||
description: Post-task synchronization - update document index, generate action log, and refresh feature/component docs after completing a development task.
|
|
||||||
argument-hint: "[-y|--yes] [--dry-run] [--from-manifest <path>] [--task-id <id>] [--commit <hash>] \"task summary\""
|
|
||||||
allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-detect changes, auto-update all docs, skip review prompts.
|
|
||||||
|
|
||||||
# DDD Sync Command (/ddd:sync)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
After completing a development task, synchronize the document index with actual code changes:
|
|
||||||
1. **Analyze** what changed (git diff)
|
|
||||||
2. **Trace** which features/requirements/components are affected
|
|
||||||
3. **Update** index entries (status, code locations, links)
|
|
||||||
4. **Generate** action log entry
|
|
||||||
5. **Refresh** feature-map and tech-registry documents
|
|
||||||
|
|
||||||
## When to Use: sync vs update
|
|
||||||
|
|
||||||
| Scenario | Use |
|
|
||||||
|----------|-----|
|
|
||||||
| Task completed, ready to commit | **ddd:sync** — full post-task reconciliation |
|
|
||||||
| Mid-development, quick impact check | ddd:update |
|
|
||||||
| Pre-commit validation | ddd:update --check-only |
|
|
||||||
| Auto-triggered after ddd:execute | **ddd:sync** (automatic) |
|
|
||||||
| Periodic index refresh during refactoring | ddd:update |
|
|
||||||
|
|
||||||
**Rule of thumb**: `sync` = task boundary (done something), `update` = development pulse (doing something).
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `doc-index.json` must exist
|
|
||||||
- Git repository with committed or staged changes
|
|
||||||
|
|
||||||
## Phase 0: Consistency Validation
|
|
||||||
|
|
||||||
Before processing changes, verify that `doc-index.json` entries are consistent with actual code state.
|
|
||||||
|
|
||||||
### 0.1 Validate Code Locations
|
|
||||||
|
|
||||||
For each `technicalComponents[].codeLocations[]`:
|
|
||||||
- Verify file exists on disk
|
|
||||||
- If file was deleted/moved → flag for removal or update
|
|
||||||
- If file exists → verify listed `symbols[]` still exist (quick grep/AST check)
|
|
||||||
|
|
||||||
### 0.2 Validate Symbols
|
|
||||||
|
|
||||||
For components with `codeLocations[].symbols[]`:
|
|
||||||
- Check each symbol still exists in the referenced file
|
|
||||||
- Detect new exported symbols not yet tracked
|
|
||||||
- Report: `{N} stale symbols, {N} untracked symbols`
|
|
||||||
|
|
||||||
### 0.3 Validation Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Consistency Check:
|
|
||||||
Components validated: {N}
|
|
||||||
Files verified: {N}
|
|
||||||
Stale references: {N} (files missing or symbols removed)
|
|
||||||
Untracked symbols: {N} (new exports not in index)
|
|
||||||
```
|
|
||||||
|
|
||||||
If stale references found: warn and auto-fix during Phase 3 updates.
|
|
||||||
If `--dry-run`: report only, no fixes.
|
|
||||||
|
|
||||||
## Phase 1: Change Detection
|
|
||||||
|
|
||||||
### 1.0.1 Schema Version Check
|
|
||||||
|
|
||||||
Before processing changes, verify doc-index.json schema compatibility:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const docIndex = JSON.parse(Read('.workflow/.doc-index/doc-index.json'));
|
|
||||||
const schemaVersion = docIndex.schema_version || '0.0'; // Default for legacy
|
|
||||||
|
|
||||||
if (schemaVersion !== '1.0') {
|
|
||||||
console.warn(`Schema version mismatch: found ${schemaVersion}, expected 1.0`);
|
|
||||||
console.warn('Consider running schema migration or regenerating doc-index with /ddd:scan');
|
|
||||||
// Continue with degraded functionality - may encounter missing fields
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Graceful degradation**: If version mismatch detected → log warning → continue with caution (some features may not work as expected).
|
|
||||||
|
|
||||||
### 1.0 Data Source Selection
|
|
||||||
|
|
||||||
```
|
|
||||||
IF --from-manifest <path>:
|
|
||||||
Load execution-manifest.json
|
|
||||||
→ files_modified[] provides precise file list + action type + task attribution
|
|
||||||
→ TASK-*.result.json provides symbol-level changes + convergence results
|
|
||||||
→ Skip Phase 1.1/1.2 (already classified by execute)
|
|
||||||
→ Proceed directly to Phase 2 with manifest data
|
|
||||||
ELSE:
|
|
||||||
→ Fall through to Phase 1.1 (git-based discovery)
|
|
||||||
```
|
|
||||||
|
|
||||||
**`--from-manifest` advantages** (used automatically by ddd:execute):
|
|
||||||
- Precise file → task attribution (which task modified which file)
|
|
||||||
- Symbol-level change tracking (not just file-level)
|
|
||||||
- Convergence verification results carried forward to action-log
|
|
||||||
- Survives process interruptions (manifest is persisted to disk)
|
|
||||||
|
|
||||||
### 1.1 Identify Changes (git-based fallback)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# If --commit provided:
|
|
||||||
git diff --name-only {commit}^..{commit}
|
|
||||||
git diff --stat {commit}^..{commit}
|
|
||||||
|
|
||||||
# If --task-id provided, find related commits:
|
|
||||||
git log --oneline --grep="task-{id}" | head -10
|
|
||||||
|
|
||||||
# Otherwise: changes since last ddd:sync
|
|
||||||
git diff --name-only HEAD~1..HEAD
|
|
||||||
```
|
|
||||||
|
|
||||||
### 1.2 Classify Changes (git-based fallback)
|
|
||||||
|
|
||||||
For each changed file, determine:
|
|
||||||
- **Type**: added | modified | deleted | renamed
|
|
||||||
- **Category**: source | test | config | docs | other
|
|
||||||
- **Symbols affected**: parse diff for changed functions/classes (use Gemini if complex)
|
|
||||||
|
|
||||||
## Phase 2: Impact Tracing (Layer-Based, TASK-004)
|
|
||||||
|
|
||||||
**Strategy**: Trace impact through layers (files → components → features → indexes) following memory-manage pattern.
|
|
||||||
|
|
||||||
### 2.1 Match to Index
|
|
||||||
|
|
||||||
For each changed file path:
|
|
||||||
|
|
||||||
```
|
|
||||||
Search doc-index.json.technicalComponents[].codeLocations[].path
|
|
||||||
→ Find matching component IDs (Layer 3)
|
|
||||||
→ From components, find linked featureIds (Layer 2)
|
|
||||||
→ From features, find linked requirementIds (Layer 2)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Discover New Components
|
|
||||||
|
|
||||||
If changed files don't match any existing component:
|
|
||||||
- Flag as potential new component
|
|
||||||
- Ask user if it should be registered (or auto-register with `-y`)
|
|
||||||
|
|
||||||
### 2.3 Build Impact Report
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
## Impact Summary
|
|
||||||
|
|
||||||
### Changed Files (5)
|
|
||||||
- src/services/auth.ts (modified) → tech-auth-service → feat-auth
|
|
||||||
- src/models/user.ts (modified) → tech-user-model → feat-auth
|
|
||||||
- src/routes/login.ts (added) → NEW COMPONENT → feat-auth
|
|
||||||
- src/tests/auth.test.ts (modified) → [test file, skip]
|
|
||||||
- package.json (modified) → [config, skip]
|
|
||||||
|
|
||||||
### Affected Features
|
|
||||||
- feat-auth: User Authentication (2 components modified, 1 new)
|
|
||||||
|
|
||||||
### Affected Requirements
|
|
||||||
- REQ-001: Email login (implementation updated)
|
|
||||||
- REQ-002: JWT token generation (implementation updated)
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 2.4: DeepWiki Freshness Check
|
|
||||||
|
|
||||||
If DeepWiki integration is configured (`doc-index.json.freshness` exists):
|
|
||||||
|
|
||||||
### 2.4.1 Identify Modified Files
|
|
||||||
From `execution-manifest.json` or git diff, collect `files_modified[]` with `action == "modified"`.
|
|
||||||
|
|
||||||
### 2.4.2 Check Staleness
|
|
||||||
Query DeepWiki: `POST /api/deepwiki/stale-files { files: [{path, hash}] }`
|
|
||||||
|
|
||||||
For each stale file's symbols, calculate staleness score:
|
|
||||||
```
|
|
||||||
S(symbol) = min(1.0, w_t × T + w_c × C + w_s × M)
|
|
||||||
```
|
|
||||||
Where weights come from `doc-index.json.freshness.weights`.
|
|
||||||
|
|
||||||
### 2.4.3 Score Propagation (max aggregation)
|
|
||||||
```
|
|
||||||
S_file = max(S_symbol_1, S_symbol_2, ...)
|
|
||||||
S_component = max(S_file_1, S_file_2, ...)
|
|
||||||
S_feature = max(S_component_1, S_component_2, ...)
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.4.4 Status Assignment
|
|
||||||
Using thresholds from `doc-index.json.freshness.thresholds`:
|
|
||||||
- `fresh`: score in [0, warning_threshold)
|
|
||||||
- `warning`: score in [warning_threshold, stale_threshold)
|
|
||||||
- `stale`: score in [stale_threshold, 1.0]
|
|
||||||
|
|
||||||
### 2.4.5 Update Records
|
|
||||||
- Update `deepwiki_symbols.staleness_score` and `deepwiki_files.staleness_score` in DeepWiki SQLite
|
|
||||||
- Update `tech-registry/{slug}.md` YAML frontmatter with freshness block
|
|
||||||
- Update `feature-maps/{slug}.md` YAML frontmatter with freshness block
|
|
||||||
- Update `deepwiki_feature_to_symbol_index` in doc-index.json
|
|
||||||
|
|
||||||
### 2.4.6 Staleness Report
|
|
||||||
Add to action-log:
|
|
||||||
- Number of stale symbols/files/components
|
|
||||||
- Top-5 most stale items with scores
|
|
||||||
- Auto-regeneration candidates (if `auto_regenerate: true` and score >= stale threshold)
|
|
||||||
|
|
||||||
## Phase 3: Update Index
|
|
||||||
|
|
||||||
### 3.0 Dry-Run Gate
|
|
||||||
|
|
||||||
If `--dry-run` is set:
|
|
||||||
- Execute Phase 3 analysis (determine what would change)
|
|
||||||
- Display planned modifications as a preview report
|
|
||||||
- Skip all file writes (Phase 3.1-3.5 and Phase 4)
|
|
||||||
- Output: "Dry-run complete. Run without --dry-run to apply changes."
|
|
||||||
|
|
||||||
### 3.0.1 Backup Index
|
|
||||||
|
|
||||||
Before any modifications, create backup:
|
|
||||||
- Copy `doc-index.json` → `doc-index.json.bak`
|
|
||||||
- On failure: restore from `.bak` and report error
|
|
||||||
- On success: remove `.bak`
|
|
||||||
|
|
||||||
### 3.1 Update Technical Components
|
|
||||||
|
|
||||||
For each affected component in `doc-index.json`:
|
|
||||||
- Update `codeLocations` if file paths or line ranges changed
|
|
||||||
- Update `symbols` if new exports were added
|
|
||||||
- Add new `actionIds` entry
|
|
||||||
- **Auto-update `responsibility`**: If symbols changed (new methods/exports added or removed), re-infer responsibility from current symbols list using Gemini analysis. This prevents stale descriptions (e.g., responsibility still says "登录、注册" after adding logout support)
|
|
||||||
|
|
||||||
### 3.2 Register New Components
|
|
||||||
|
|
||||||
For newly discovered components:
|
|
||||||
- Generate `tech-{slug}` ID
|
|
||||||
- Create entry in `technicalComponents[]`
|
|
||||||
- Link to appropriate features
|
|
||||||
- Generate new `tech-registry/{slug}.md` document
|
|
||||||
|
|
||||||
### 3.3 Update Feature Status
|
|
||||||
|
|
||||||
For each affected feature:
|
|
||||||
- If all requirements now have mapped components → `status: "implemented"`
|
|
||||||
- If some requirements still unmapped → `status: "in-progress"`
|
|
||||||
|
|
||||||
### 3.4 Add Action Entry
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "task-{id}",
|
|
||||||
"description": "{task summary from user}",
|
|
||||||
"type": "feature|bugfix|refactor",
|
|
||||||
"status": "completed",
|
|
||||||
"affectedFeatures": ["feat-auth"],
|
|
||||||
"affectedComponents": ["tech-auth-service", "tech-user-model"],
|
|
||||||
"changedFiles": [
|
|
||||||
{ "path": "src/services/auth.ts", "action": "modified", "task_id": "TASK-001" },
|
|
||||||
{ "path": "src/models/user.ts", "action": "modified", "task_id": "TASK-001" }
|
|
||||||
],
|
|
||||||
"symbolsChanged": ["AuthService.validate", "UserModel.toJSON"],
|
|
||||||
"convergenceResults": {
|
|
||||||
"passed": 2,
|
|
||||||
"total": 2,
|
|
||||||
"details": ["Rate limiter middleware exists", "Config accepts per-route limits"]
|
|
||||||
},
|
|
||||||
"verifyGate": "PASS|WARN|FAIL|skipped",
|
|
||||||
"relatedCommit": "{commit hash}",
|
|
||||||
"manifestPath": "{execution-manifest.json path | null}",
|
|
||||||
"timestamp": "ISO8601"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3.5 Update Timestamp
|
|
||||||
|
|
||||||
Set `doc-index.json.last_updated` to current time.
|
|
||||||
|
|
||||||
## Phase 4: Refresh Documents & Action Log
|
|
||||||
|
|
||||||
### 4.1 Delegate Document Refresh to /ddd:doc-refresh
|
|
||||||
|
|
||||||
From Phase 2 impact tracing, collect affected component and feature IDs, then delegate:
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:doc-refresh [-y] --components {affected_component_ids} --features {affected_feature_ids}
|
|
||||||
```
|
|
||||||
|
|
||||||
This handles Layer 3 → 2 → 1 selective document refresh. See `/ddd:doc-refresh` for full details.
|
|
||||||
|
|
||||||
### 4.2 Generate Action Log Entry
|
|
||||||
|
|
||||||
Create `.workflow/.doc-index/action-logs/{task-id}.md`:
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
---
|
|
||||||
id: task-{id}
|
|
||||||
type: feature|bugfix|refactor
|
|
||||||
status: completed
|
|
||||||
features: [feat-auth]
|
|
||||||
components: [tech-auth-service, tech-user-model]
|
|
||||||
commit: {hash}
|
|
||||||
timestamp: ISO8601
|
|
||||||
---
|
|
||||||
|
|
||||||
# Task: {summary}
|
|
||||||
|
|
||||||
## Changes
|
|
||||||
| File | Type | Component |
|
|
||||||
|------|------|-----------|
|
|
||||||
| src/services/auth.ts | modified | tech-auth-service |
|
|
||||||
|
|
||||||
## Impact
|
|
||||||
- Features affected: feat-auth
|
|
||||||
- Requirements addressed: REQ-001, REQ-002
|
|
||||||
|
|
||||||
## Staleness (if DeepWiki freshness enabled)
|
|
||||||
| Item | Type | Score | Status |
|
|
||||||
|------|------|-------|--------|
|
|
||||||
| {symbol/file/component} | {type} | {score} | {fresh/warning/stale} |
|
|
||||||
|
|
||||||
## Notes
|
|
||||||
{any user-provided notes}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 5: Confirmation (unless -y)
|
|
||||||
|
|
||||||
Present update summary to user:
|
|
||||||
- Files updated in doc-index
|
|
||||||
- New documents created
|
|
||||||
- Status changes
|
|
||||||
- Ask for confirmation before writing
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm all updates |
|
|
||||||
| `--dry-run` | Preview all changes without modifying any files |
|
|
||||||
| `--from-manifest <path>` | Use execution-manifest.json as data source (auto-set by ddd:execute) |
|
|
||||||
| `--task-id <id>` | Associate with specific task ID |
|
|
||||||
| `--commit <hash>` | Analyze specific commit |
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: `execution-manifest.json` (preferred, from ddd:execute) OR Git history (fallback), `doc-index.json`, `/ddd:plan` output
|
|
||||||
- **Delegates to**: `/ddd:doc-refresh` (Phase 4.1, selective document refresh)
|
|
||||||
- **Output to**: Updated `doc-index.json`, feature-maps/, tech-registry/, action-logs/
|
|
||||||
- **Triggers**: After completing any development task
|
|
||||||
- **Data source priority**: `--from-manifest` > `--commit` > `--task-id` > git diff HEAD~1
|
|
||||||
@@ -1,160 +0,0 @@
|
|||||||
---
|
|
||||||
name: update
|
|
||||||
description: Incremental index update - detect code changes and trace impact to related features/requirements. Lightweight alternative to full sync.
|
|
||||||
argument-hint: "[-y|--yes] [--files <file1,file2,...>] [--staged] [--check-only]"
|
|
||||||
allowed-tools: TodoWrite(*), AskUserQuestion(*), Read(*), Grep(*), Glob(*), Bash(*), Edit(*), Write(*), mcp__ace-tool__search_context(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-update index without confirmation prompts.
|
|
||||||
|
|
||||||
# DDD Update Command (/ddd:update)
|
|
||||||
|
|
||||||
## Purpose
|
|
||||||
|
|
||||||
Lightweight incremental update: given a set of changed files, trace their impact through the document index and update affected entries. Unlike `/ddd:sync` (full post-task sync), this command focuses on keeping the index fresh during development.
|
|
||||||
|
|
||||||
## When to Use: update vs sync
|
|
||||||
|
|
||||||
| Scenario | Use |
|
|
||||||
|----------|-----|
|
|
||||||
| Quick impact check during development | **ddd:update** |
|
|
||||||
| Preview what sync would change | **ddd:update --check-only** |
|
|
||||||
| Task completed, full reconciliation | ddd:sync |
|
|
||||||
| Register new components + update all docs | ddd:sync |
|
|
||||||
|
|
||||||
**Rule of thumb**: `update` = lightweight pulse (during work), `sync` = full checkpoint (after work).
|
|
||||||
|
|
||||||
## Use Cases
|
|
||||||
|
|
||||||
1. **During development**: Quick check which docs are affected by current changes
|
|
||||||
2. **Pre-commit check**: Ensure index is up-to-date before committing
|
|
||||||
3. **Periodic refresh**: Update stale code locations after refactoring
|
|
||||||
|
|
||||||
## Prerequisite
|
|
||||||
|
|
||||||
- `doc-index.json` must exist at `.workflow/.doc-index/doc-index.json`
|
|
||||||
|
|
||||||
## Phase 1: Identify Changed Files
|
|
||||||
|
|
||||||
### Source Priority
|
|
||||||
|
|
||||||
```
|
|
||||||
1. --files <list> → Explicit file list
|
|
||||||
2. --staged → git diff --cached --name-only
|
|
||||||
3. (default) → git diff --name-only (unstaged changes)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Output
|
|
||||||
|
|
||||||
List of changed file paths with change type (added/modified/deleted/renamed).
|
|
||||||
|
|
||||||
## Phase 2: Trace Impact
|
|
||||||
|
|
||||||
### 2.1 Forward Lookup (Code → Components → Features)
|
|
||||||
|
|
||||||
For each changed file:
|
|
||||||
|
|
||||||
```
|
|
||||||
doc-index.json.technicalComponents[]
|
|
||||||
.codeLocations[].path MATCH changed_file
|
|
||||||
→ component_ids[]
|
|
||||||
|
|
||||||
doc-index.json.technicalComponents[component_ids]
|
|
||||||
.featureIds[]
|
|
||||||
→ feature_ids[]
|
|
||||||
|
|
||||||
doc-index.json.features[feature_ids]
|
|
||||||
.requirementIds[]
|
|
||||||
→ requirement_ids[]
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2.2 Orphan Detection
|
|
||||||
|
|
||||||
Files not matching any component → flag as:
|
|
||||||
- **Potential new component**: if in src/ directory
|
|
||||||
- **Ignorable**: if in test/, docs/, config/ directories
|
|
||||||
|
|
||||||
### 2.3 Impact Report
|
|
||||||
|
|
||||||
```
|
|
||||||
Impact Analysis for 3 changed files:
|
|
||||||
|
|
||||||
src/services/auth.ts (modified)
|
|
||||||
→ Component: tech-auth-service (AuthService)
|
|
||||||
→ Feature: feat-auth (User Authentication)
|
|
||||||
→ Requirements: REQ-001, REQ-002
|
|
||||||
|
|
||||||
src/middleware/rate-limit.ts (added)
|
|
||||||
→ No matching component (new file)
|
|
||||||
→ Suggested: Register as new component
|
|
||||||
|
|
||||||
src/utils/hash.ts (modified)
|
|
||||||
→ Component: tech-hash-util
|
|
||||||
→ Features: feat-auth, feat-password-reset
|
|
||||||
→ Requirements: REQ-001, REQ-005
|
|
||||||
```
|
|
||||||
|
|
||||||
## Phase 3: Update Index (unless --check-only)
|
|
||||||
|
|
||||||
### 3.1 Update Code Locations
|
|
||||||
|
|
||||||
For matched components:
|
|
||||||
- If file was renamed → update `codeLocations[].path`
|
|
||||||
- If file was deleted → remove code location entry
|
|
||||||
- If symbols changed → update `symbols` list (requires AST or Gemini analysis)
|
|
||||||
|
|
||||||
### 3.2 Register New Components (interactive unless -y)
|
|
||||||
|
|
||||||
For orphan files in src/:
|
|
||||||
- Prompt user for component name and type
|
|
||||||
- Or auto-generate with `-y`: derive name from file path
|
|
||||||
- Create `technicalComponents[]` entry
|
|
||||||
- Ask which feature it belongs to (or auto-link by directory structure)
|
|
||||||
|
|
||||||
### 3.3 Update Timestamps
|
|
||||||
|
|
||||||
- Update `technicalComponents[].docPath` last_updated in corresponding .md
|
|
||||||
- Update `doc-index.json.last_updated`
|
|
||||||
|
|
||||||
## Phase 4: Refresh Documents (if updates were made)
|
|
||||||
|
|
||||||
### 4.1 Delegate to /ddd:doc-refresh
|
|
||||||
|
|
||||||
From Phase 2 impact tracing, collect affected component and feature IDs, then delegate:
|
|
||||||
|
|
||||||
```
|
|
||||||
Invoke /ddd:doc-refresh [-y] --minimal --components {affected_component_ids} --features {affected_feature_ids}
|
|
||||||
```
|
|
||||||
|
|
||||||
The `--minimal` flag ensures only metadata/frontmatter is updated (code locations, timestamps), skipping full content regeneration. This keeps the update lightweight.
|
|
||||||
|
|
||||||
See `/ddd:doc-refresh` for full details.
|
|
||||||
|
|
||||||
### 4.2 Skip If --check-only
|
|
||||||
|
|
||||||
With `--check-only`, skip Phase 3 and Phase 4 entirely — only output the impact report.
|
|
||||||
|
|
||||||
## Flags
|
|
||||||
|
|
||||||
| Flag | Effect |
|
|
||||||
|------|--------|
|
|
||||||
| `-y, --yes` | Auto-confirm updates |
|
|
||||||
| `--files <list>` | Explicit comma-separated file list |
|
|
||||||
| `--staged` | Analyze staged (git cached) files |
|
|
||||||
| `--check-only` | Report impact without modifying index |
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Console**: Impact report showing affected features/requirements
|
|
||||||
- **Updated**: `doc-index.json` (if not --check-only)
|
|
||||||
- **Updated**: Affected tech-registry/ and feature-maps/ docs
|
|
||||||
|
|
||||||
## Integration Points
|
|
||||||
|
|
||||||
- **Input from**: Git working tree, `doc-index.json`
|
|
||||||
- **Delegates to**: `/ddd:doc-refresh` (Phase 4.1, incremental document refresh with --minimal)
|
|
||||||
- **Output to**: Updated `doc-index.json`, impact report
|
|
||||||
- **Triggers**: During development, pre-commit, or periodic refresh
|
|
||||||
- **Can chain to**: `/ddd:sync` for full post-task synchronization
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
---
|
|
||||||
name: add
|
|
||||||
description: Add IDAW tasks - manual creation or import from ccw issue
|
|
||||||
argument-hint: "[-y|--yes] [--from-issue <id>[,<id>,...]] \"description\" [--type <task_type>] [--priority <1-5>]"
|
|
||||||
allowed-tools: AskUserQuestion(*), Read(*), Bash(*), Write(*), Glob(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
# IDAW Add Command (/idaw:add)
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Skip clarification questions, create task with inferred details.
|
|
||||||
|
|
||||||
## IDAW Task Schema
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"id": "IDAW-001",
|
|
||||||
"title": "string",
|
|
||||||
"description": "string",
|
|
||||||
"status": "pending",
|
|
||||||
"priority": 2,
|
|
||||||
"task_type": null,
|
|
||||||
"skill_chain": null,
|
|
||||||
"context": {
|
|
||||||
"affected_files": [],
|
|
||||||
"acceptance_criteria": [],
|
|
||||||
"constraints": [],
|
|
||||||
"references": []
|
|
||||||
},
|
|
||||||
"source": {
|
|
||||||
"type": "manual|import-issue",
|
|
||||||
"issue_id": null,
|
|
||||||
"issue_snapshot": null
|
|
||||||
},
|
|
||||||
"execution": {
|
|
||||||
"session_id": null,
|
|
||||||
"started_at": null,
|
|
||||||
"completed_at": null,
|
|
||||||
"skill_results": [],
|
|
||||||
"git_commit": null,
|
|
||||||
"error": null
|
|
||||||
},
|
|
||||||
"created_at": "ISO",
|
|
||||||
"updated_at": "ISO"
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
**Valid task_type values**: `bugfix|bugfix-hotfix|feature|feature-complex|refactor|tdd|test|test-fix|review|docs`
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
### Phase 1: Parse Arguments
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const args = $ARGUMENTS;
|
|
||||||
const autoYes = /(-y|--yes)\b/.test(args);
|
|
||||||
const fromIssue = args.match(/--from-issue\s+([\w,-]+)/)?.[1];
|
|
||||||
const typeFlag = args.match(/--type\s+([\w-]+)/)?.[1];
|
|
||||||
const priorityFlag = args.match(/--priority\s+(\d)/)?.[1];
|
|
||||||
|
|
||||||
// Extract description: content inside quotes (preferred), or fallback to stripping flags
|
|
||||||
const quotedMatch = args.match(/(?:^|\s)["']([^"']+)["']/);
|
|
||||||
const description = quotedMatch
|
|
||||||
? quotedMatch[1].trim()
|
|
||||||
: args.replace(/(-y|--yes|--from-issue\s+[\w,-]+|--type\s+[\w-]+|--priority\s+\d)/g, '').trim();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Route — Import or Manual
|
|
||||||
|
|
||||||
```
|
|
||||||
--from-issue present?
|
|
||||||
├─ YES → Import Mode (Phase 3A)
|
|
||||||
└─ NO → Manual Mode (Phase 3B)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3A: Import Mode (from ccw issue)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const issueIds = fromIssue.split(',');
|
|
||||||
|
|
||||||
// Fetch all issues once (outside loop)
|
|
||||||
let issues = [];
|
|
||||||
try {
|
|
||||||
const issueJson = Bash(`ccw issue list --json`);
|
|
||||||
issues = JSON.parse(issueJson).issues || [];
|
|
||||||
} catch (e) {
|
|
||||||
console.log(`Error fetching CCW issues: ${e.message || e}`);
|
|
||||||
console.log('Ensure ccw is installed and issues exist. Use /issue:new to create issues first.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const issueId of issueIds) {
|
|
||||||
// 1. Find issue data
|
|
||||||
const issue = issues.find(i => i.id === issueId.trim());
|
|
||||||
if (!issue) {
|
|
||||||
console.log(`Warning: Issue ${issueId} not found, skipping`);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Check duplicate (same issue_id already imported)
|
|
||||||
const existing = Glob('.workflow/.idaw/tasks/IDAW-*.json');
|
|
||||||
for (const f of existing) {
|
|
||||||
const data = JSON.parse(Read(f));
|
|
||||||
if (data.source?.issue_id === issueId.trim()) {
|
|
||||||
console.log(`Warning: Issue ${issueId} already imported as ${data.id}, skipping`);
|
|
||||||
continue; // skip to next issue
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 3. Generate next IDAW ID
|
|
||||||
const nextId = generateNextId();
|
|
||||||
|
|
||||||
// 4. Map issue → IDAW task
|
|
||||||
const task = {
|
|
||||||
id: nextId,
|
|
||||||
title: issue.title,
|
|
||||||
description: issue.context || issue.title,
|
|
||||||
status: 'pending',
|
|
||||||
priority: parseInt(priorityFlag) || issue.priority || 3,
|
|
||||||
task_type: typeFlag || inferTaskType(issue.title, issue.context || ''),
|
|
||||||
skill_chain: null,
|
|
||||||
context: {
|
|
||||||
affected_files: issue.affected_components || [],
|
|
||||||
acceptance_criteria: [],
|
|
||||||
constraints: [],
|
|
||||||
references: issue.source_url ? [issue.source_url] : []
|
|
||||||
},
|
|
||||||
source: {
|
|
||||||
type: 'import-issue',
|
|
||||||
issue_id: issue.id,
|
|
||||||
issue_snapshot: {
|
|
||||||
id: issue.id,
|
|
||||||
title: issue.title,
|
|
||||||
status: issue.status,
|
|
||||||
context: issue.context,
|
|
||||||
priority: issue.priority,
|
|
||||||
created_at: issue.created_at
|
|
||||||
}
|
|
||||||
},
|
|
||||||
execution: {
|
|
||||||
session_id: null,
|
|
||||||
started_at: null,
|
|
||||||
completed_at: null,
|
|
||||||
skill_results: [],
|
|
||||||
git_commit: null,
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
// 5. Write task file
|
|
||||||
Bash('mkdir -p .workflow/.idaw/tasks');
|
|
||||||
Write(`.workflow/.idaw/tasks/${nextId}.json`, JSON.stringify(task, null, 2));
|
|
||||||
console.log(`Created ${nextId} from issue ${issueId}: ${issue.title}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3B: Manual Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// 1. Validate description
|
|
||||||
if (!description && !autoYes) {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: 'Please provide a task description:',
|
|
||||||
header: 'Task',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Provide description', description: 'What needs to be done?' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
// Use custom text from "Other"
|
|
||||||
description = answer.customText || '';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!description) {
|
|
||||||
console.log('Error: No description provided. Usage: /idaw:add "task description"');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 2. Generate next IDAW ID
|
|
||||||
const nextId = generateNextId();
|
|
||||||
|
|
||||||
// 3. Build title from first sentence
|
|
||||||
const title = description.split(/[.\n]/)[0].substring(0, 80).trim();
|
|
||||||
|
|
||||||
// 4. Determine task_type
|
|
||||||
const taskType = typeFlag || null; // null → inferred at run time
|
|
||||||
|
|
||||||
// 5. Create task
|
|
||||||
const task = {
|
|
||||||
id: nextId,
|
|
||||||
title: title,
|
|
||||||
description: description,
|
|
||||||
status: 'pending',
|
|
||||||
priority: parseInt(priorityFlag) || 3,
|
|
||||||
task_type: taskType,
|
|
||||||
skill_chain: null,
|
|
||||||
context: {
|
|
||||||
affected_files: [],
|
|
||||||
acceptance_criteria: [],
|
|
||||||
constraints: [],
|
|
||||||
references: []
|
|
||||||
},
|
|
||||||
source: {
|
|
||||||
type: 'manual',
|
|
||||||
issue_id: null,
|
|
||||||
issue_snapshot: null
|
|
||||||
},
|
|
||||||
execution: {
|
|
||||||
session_id: null,
|
|
||||||
started_at: null,
|
|
||||||
completed_at: null,
|
|
||||||
skill_results: [],
|
|
||||||
git_commit: null,
|
|
||||||
error: null
|
|
||||||
},
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
Bash('mkdir -p .workflow/.idaw/tasks');
|
|
||||||
Write(`.workflow/.idaw/tasks/${nextId}.json`, JSON.stringify(task, null, 2));
|
|
||||||
console.log(`Created ${nextId}: ${title}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### ID Generation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function generateNextId() {
|
|
||||||
const files = Glob('.workflow/.idaw/tasks/IDAW-*.json') || [];
|
|
||||||
if (files.length === 0) return 'IDAW-001';
|
|
||||||
|
|
||||||
const maxNum = files
|
|
||||||
.map(f => parseInt(f.match(/IDAW-(\d+)/)?.[1] || '0'))
|
|
||||||
.reduce((max, n) => Math.max(max, n), 0);
|
|
||||||
|
|
||||||
return `IDAW-${String(maxNum + 1).padStart(3, '0')}`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Task Type Inference (deferred — used at run time if task_type is null)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function inferTaskType(title, description) {
|
|
||||||
const text = `${title} ${description}`.toLowerCase();
|
|
||||||
if (/urgent|production|critical/.test(text) && /fix|bug/.test(text)) return 'bugfix-hotfix';
|
|
||||||
if (/refactor|重构|tech.*debt/.test(text)) return 'refactor';
|
|
||||||
if (/tdd|test-driven|test first/.test(text)) return 'tdd';
|
|
||||||
if (/test fail|fix test|failing test/.test(text)) return 'test-fix';
|
|
||||||
if (/generate test|写测试|add test/.test(text)) return 'test';
|
|
||||||
if (/review|code review/.test(text)) return 'review';
|
|
||||||
if (/docs|documentation|readme/.test(text)) return 'docs';
|
|
||||||
if (/fix|bug|error|crash|fail/.test(text)) return 'bugfix';
|
|
||||||
if (/complex|multi-module|architecture/.test(text)) return 'feature-complex';
|
|
||||||
return 'feature';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Manual creation
|
|
||||||
/idaw:add "Fix login timeout bug" --type bugfix --priority 2
|
|
||||||
/idaw:add "Add rate limiting to API endpoints" --priority 1
|
|
||||||
/idaw:add "Refactor auth module to use strategy pattern"
|
|
||||||
|
|
||||||
# Import from ccw issue
|
|
||||||
/idaw:add --from-issue ISS-20260128-001
|
|
||||||
/idaw:add --from-issue ISS-20260128-001,ISS-20260128-002 --priority 1
|
|
||||||
|
|
||||||
# Auto mode (skip clarification)
|
|
||||||
/idaw:add -y "Quick fix for typo in header"
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
```
|
|
||||||
Created IDAW-001: Fix login timeout bug
|
|
||||||
Type: bugfix | Priority: 2 | Source: manual
|
|
||||||
→ Next: /idaw:run or /idaw:status
|
|
||||||
```
|
|
||||||
@@ -1,442 +0,0 @@
|
|||||||
---
|
|
||||||
name: resume
|
|
||||||
description: Resume interrupted IDAW session from last checkpoint
|
|
||||||
argument-hint: "[-y|--yes] [session-id]"
|
|
||||||
allowed-tools: Skill(*), TodoWrite(*), AskUserQuestion(*), Read(*), Write(*), Bash(*), Glob(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
# IDAW Resume Command (/idaw:resume)
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Auto-skip interrupted task, continue with remaining.
|
|
||||||
|
|
||||||
## Skill Chain Mapping
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const SKILL_CHAIN_MAP = {
|
|
||||||
'bugfix': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'bugfix-hotfix': ['workflow-lite-plan'],
|
|
||||||
'feature': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'feature-complex': ['workflow-plan', 'workflow-execute', 'workflow-test-fix'],
|
|
||||||
'refactor': ['workflow:refactor-cycle'],
|
|
||||||
'tdd': ['workflow-tdd-plan', 'workflow-execute'],
|
|
||||||
'test': ['workflow-test-fix'],
|
|
||||||
'test-fix': ['workflow-test-fix'],
|
|
||||||
'review': ['review-cycle'],
|
|
||||||
'docs': ['workflow-lite-plan']
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Task Type Inference
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function inferTaskType(title, description) {
|
|
||||||
const text = `${title} ${description}`.toLowerCase();
|
|
||||||
if (/urgent|production|critical/.test(text) && /fix|bug/.test(text)) return 'bugfix-hotfix';
|
|
||||||
if (/refactor|重构|tech.*debt/.test(text)) return 'refactor';
|
|
||||||
if (/tdd|test-driven|test first/.test(text)) return 'tdd';
|
|
||||||
if (/test fail|fix test|failing test/.test(text)) return 'test-fix';
|
|
||||||
if (/generate test|写测试|add test/.test(text)) return 'test';
|
|
||||||
if (/review|code review/.test(text)) return 'review';
|
|
||||||
if (/docs|documentation|readme/.test(text)) return 'docs';
|
|
||||||
if (/fix|bug|error|crash|fail/.test(text)) return 'bugfix';
|
|
||||||
if (/complex|multi-module|architecture/.test(text)) return 'feature-complex';
|
|
||||||
return 'feature';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
### Phase 1: Find Resumable Session
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const args = $ARGUMENTS;
|
|
||||||
const autoYes = /(-y|--yes)/.test(args);
|
|
||||||
const targetSessionId = args.replace(/(-y|--yes)/g, '').trim();
|
|
||||||
|
|
||||||
let session = null;
|
|
||||||
let sessionDir = null;
|
|
||||||
|
|
||||||
if (targetSessionId) {
|
|
||||||
// Load specific session
|
|
||||||
sessionDir = `.workflow/.idaw/sessions/${targetSessionId}`;
|
|
||||||
try {
|
|
||||||
session = JSON.parse(Read(`${sessionDir}/session.json`));
|
|
||||||
} catch {
|
|
||||||
console.log(`Session "${targetSessionId}" not found.`);
|
|
||||||
console.log('Use /idaw:status to list sessions, or /idaw:run to start a new one.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Find most recent running session
|
|
||||||
const sessionFiles = Glob('.workflow/.idaw/sessions/IDA-*/session.json') || [];
|
|
||||||
|
|
||||||
for (const f of sessionFiles) {
|
|
||||||
try {
|
|
||||||
const s = JSON.parse(Read(f));
|
|
||||||
if (s.status === 'running') {
|
|
||||||
session = s;
|
|
||||||
sessionDir = f.replace(/\/session\.json$/, '').replace(/\\session\.json$/, '');
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// Skip malformed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!session) {
|
|
||||||
console.log('No running sessions found to resume.');
|
|
||||||
console.log('Use /idaw:run to start a new execution.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`Resuming session: ${session.session_id}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Handle Interrupted Task
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Find the task that was in_progress when interrupted
|
|
||||||
let currentTaskId = session.current_task;
|
|
||||||
let currentTask = null;
|
|
||||||
|
|
||||||
if (currentTaskId) {
|
|
||||||
try {
|
|
||||||
currentTask = JSON.parse(Read(`.workflow/.idaw/tasks/${currentTaskId}.json`));
|
|
||||||
} catch {
|
|
||||||
console.log(`Warning: Could not read task ${currentTaskId}`);
|
|
||||||
currentTaskId = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (currentTask && currentTask.status === 'in_progress') {
|
|
||||||
if (autoYes) {
|
|
||||||
// Auto: skip interrupted task
|
|
||||||
currentTask.status = 'skipped';
|
|
||||||
currentTask.execution.error = 'Skipped on resume (auto mode)';
|
|
||||||
currentTask.execution.completed_at = new Date().toISOString();
|
|
||||||
currentTask.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${currentTaskId}.json`, JSON.stringify(currentTask, null, 2));
|
|
||||||
session.skipped.push(currentTaskId);
|
|
||||||
console.log(`Skipped interrupted task: ${currentTaskId}`);
|
|
||||||
} else {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `Task ${currentTaskId} was interrupted: "${currentTask.title}". How to proceed?`,
|
|
||||||
header: 'Resume',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Retry', description: 'Reset to pending, re-execute from beginning' },
|
|
||||||
{ label: 'Skip', description: 'Mark as skipped, move to next task' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (answer.answers?.Resume === 'Skip') {
|
|
||||||
currentTask.status = 'skipped';
|
|
||||||
currentTask.execution.error = 'Skipped on resume (user choice)';
|
|
||||||
currentTask.execution.completed_at = new Date().toISOString();
|
|
||||||
currentTask.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${currentTaskId}.json`, JSON.stringify(currentTask, null, 2));
|
|
||||||
session.skipped.push(currentTaskId);
|
|
||||||
} else {
|
|
||||||
// Retry: reset to pending
|
|
||||||
currentTask.status = 'pending';
|
|
||||||
currentTask.execution.started_at = null;
|
|
||||||
currentTask.execution.completed_at = null;
|
|
||||||
currentTask.execution.skill_results = [];
|
|
||||||
currentTask.execution.error = null;
|
|
||||||
currentTask.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${currentTaskId}.json`, JSON.stringify(currentTask, null, 2));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Build Remaining Task Queue
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Collect remaining tasks (pending, or the retried current task)
|
|
||||||
const allTaskIds = session.tasks;
|
|
||||||
const completedSet = new Set([...session.completed, ...session.failed, ...session.skipped]);
|
|
||||||
|
|
||||||
const remainingTasks = [];
|
|
||||||
for (const taskId of allTaskIds) {
|
|
||||||
if (completedSet.has(taskId)) continue;
|
|
||||||
try {
|
|
||||||
const task = JSON.parse(Read(`.workflow/.idaw/tasks/${taskId}.json`));
|
|
||||||
if (task.status === 'pending') {
|
|
||||||
remainingTasks.push(task);
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
console.log(`Warning: Could not read task ${taskId}, skipping`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (remainingTasks.length === 0) {
|
|
||||||
console.log('No remaining tasks to execute. Session complete.');
|
|
||||||
session.status = 'completed';
|
|
||||||
session.current_task = null;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort: priority ASC, then ID ASC
|
|
||||||
remainingTasks.sort((a, b) => {
|
|
||||||
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
||||||
return a.id.localeCompare(b.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(`Remaining tasks: ${remainingTasks.length}`);
|
|
||||||
|
|
||||||
// Append resume marker to progress.md
|
|
||||||
const progressFile = `${sessionDir}/progress.md`;
|
|
||||||
try {
|
|
||||||
const currentProgress = Read(progressFile);
|
|
||||||
Write(progressFile, currentProgress + `\n---\n**Resumed**: ${new Date().toISOString()}\n\n`);
|
|
||||||
} catch {
|
|
||||||
Write(progressFile, `# IDAW Progress — ${session.session_id}\n\n---\n**Resumed**: ${new Date().toISOString()}\n\n`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update TodoWrite
|
|
||||||
TodoWrite({
|
|
||||||
todos: remainingTasks.map((t, i) => ({
|
|
||||||
content: `IDAW:[${i + 1}/${remainingTasks.length}] ${t.title}`,
|
|
||||||
status: i === 0 ? 'in_progress' : 'pending',
|
|
||||||
activeForm: `Executing ${t.title}`
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4-6: Execute Remaining (reuse run.md main loop)
|
|
||||||
|
|
||||||
Execute remaining tasks using the same Phase 4-6 logic from `/idaw:run`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Phase 4: Main Loop — identical to run.md Phase 4
|
|
||||||
for (let taskIdx = 0; taskIdx < remainingTasks.length; taskIdx++) {
|
|
||||||
const task = remainingTasks[taskIdx];
|
|
||||||
|
|
||||||
// Resolve skill chain
|
|
||||||
const resolvedType = task.task_type || inferTaskType(task.title, task.description);
|
|
||||||
const chain = task.skill_chain || SKILL_CHAIN_MAP[resolvedType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
|
|
||||||
// Update task → in_progress
|
|
||||||
task.status = 'in_progress';
|
|
||||||
task.task_type = resolvedType;
|
|
||||||
task.execution.started_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
session.current_task = task.id;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
console.log(`\n--- [${taskIdx + 1}/${remainingTasks.length}] ${task.id}: ${task.title} ---`);
|
|
||||||
console.log(`Chain: ${chain.join(' → ')}`);
|
|
||||||
|
|
||||||
// ━━━ Pre-Task CLI Context Analysis (for complex/bugfix tasks) ━━━
|
|
||||||
if (['bugfix', 'bugfix-hotfix', 'feature-complex'].includes(resolvedType)) {
|
|
||||||
console.log(` Pre-analysis: gathering context for ${resolvedType} task...`);
|
|
||||||
const affectedFiles = (task.context?.affected_files || []).join(', ');
|
|
||||||
const preAnalysisPrompt = `PURPOSE: Pre-analyze codebase context for IDAW task before execution.
|
|
||||||
TASK: • Understand current state of: ${affectedFiles || 'files related to: ' + task.title} • Identify dependencies and risk areas • Note existing patterns to follow
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/*
|
|
||||||
EXPECTED: Brief context summary (affected modules, dependencies, risk areas) in 3-5 bullet points
|
|
||||||
CONSTRAINTS: Keep concise | Focus on execution-relevant context`;
|
|
||||||
const preAnalysis = Bash(`ccw cli -p '${preAnalysisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis 2>&1 || echo "Pre-analysis skipped"`);
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: 'cli-pre-analysis',
|
|
||||||
status: 'completed',
|
|
||||||
context_summary: preAnalysis?.substring(0, 500),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute skill chain
|
|
||||||
let previousResult = null;
|
|
||||||
let taskFailed = false;
|
|
||||||
|
|
||||||
for (let skillIdx = 0; skillIdx < chain.length; skillIdx++) {
|
|
||||||
const skillName = chain[skillIdx];
|
|
||||||
const skillArgs = assembleSkillArgs(skillName, task, previousResult, autoYes, skillIdx === 0);
|
|
||||||
|
|
||||||
console.log(` [${skillIdx + 1}/${chain.length}] ${skillName}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = Skill({ skill: skillName, args: skillArgs });
|
|
||||||
previousResult = result;
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// ━━━ CLI-Assisted Error Recovery ━━━
|
|
||||||
console.log(` Diagnosing failure: ${skillName}...`);
|
|
||||||
const diagnosisPrompt = `PURPOSE: Diagnose why skill "${skillName}" failed during IDAW task execution.
|
|
||||||
TASK: • Analyze error: ${String(error).substring(0, 300)} • Check affected files: ${(task.context?.affected_files || []).join(', ') || 'unknown'} • Identify root cause • Suggest fix strategy
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/* | Memory: IDAW task ${task.id}: ${task.title}
|
|
||||||
EXPECTED: Root cause + actionable fix recommendation (1-2 sentences)
|
|
||||||
CONSTRAINTS: Focus on actionable diagnosis`;
|
|
||||||
const diagnosisResult = Bash(`ccw cli -p '${diagnosisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis --rule analysis-diagnose-bug-root-cause 2>&1 || echo "CLI diagnosis unavailable"`);
|
|
||||||
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: `cli-diagnosis:${skillName}`,
|
|
||||||
status: 'completed',
|
|
||||||
diagnosis: diagnosisResult?.substring(0, 500),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retry with diagnosis context
|
|
||||||
console.log(` Retry with diagnosis: ${skillName}`);
|
|
||||||
try {
|
|
||||||
const retryResult = Skill({ skill: skillName, args: skillArgs });
|
|
||||||
previousResult = retryResult;
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'completed-retry-with-diagnosis',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} catch (retryError) {
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'failed',
|
|
||||||
error: String(retryError).substring(0, 200),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoYes) {
|
|
||||||
taskFailed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `${skillName} failed after CLI diagnosis + retry: ${String(retryError).substring(0, 100)}`,
|
|
||||||
header: 'Error',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Skip task', description: 'Mark as failed, continue' },
|
|
||||||
{ label: 'Abort', description: 'Stop run' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (answer.answers?.Error === 'Abort') {
|
|
||||||
task.status = 'failed';
|
|
||||||
task.execution.error = String(retryError).substring(0, 200);
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
session.failed.push(task.id);
|
|
||||||
session.status = 'failed';
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
taskFailed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 5: Checkpoint
|
|
||||||
if (taskFailed) {
|
|
||||||
task.status = 'failed';
|
|
||||||
task.execution.error = 'Skill chain failed after retry';
|
|
||||||
task.execution.completed_at = new Date().toISOString();
|
|
||||||
session.failed.push(task.id);
|
|
||||||
} else {
|
|
||||||
// Git commit
|
|
||||||
const commitMsg = `feat(idaw): ${task.title} [${task.id}]`;
|
|
||||||
const diffCheck = Bash('git diff --stat HEAD 2>/dev/null || echo ""');
|
|
||||||
const untrackedCheck = Bash('git ls-files --others --exclude-standard 2>/dev/null || echo ""');
|
|
||||||
|
|
||||||
if (diffCheck?.trim() || untrackedCheck?.trim()) {
|
|
||||||
Bash('git add -A');
|
|
||||||
Bash(`git commit -m "$(cat <<'EOF'\n${commitMsg}\nEOF\n)"`);
|
|
||||||
const commitHash = Bash('git rev-parse --short HEAD 2>/dev/null')?.trim();
|
|
||||||
task.execution.git_commit = commitHash;
|
|
||||||
} else {
|
|
||||||
task.execution.git_commit = 'no-commit';
|
|
||||||
}
|
|
||||||
|
|
||||||
task.status = 'completed';
|
|
||||||
task.execution.completed_at = new Date().toISOString();
|
|
||||||
session.completed.push(task.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
task.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Append progress
|
|
||||||
const chain_str = chain.join(' → ');
|
|
||||||
const progressEntry = `## ${task.id} — ${task.title}\n- Status: ${task.status}\n- Chain: ${chain_str}\n- Commit: ${task.execution.git_commit || '-'}\n\n`;
|
|
||||||
const currentProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, currentProgress + progressEntry);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 6: Report
|
|
||||||
session.status = session.failed.length > 0 && session.completed.length === 0 ? 'failed' : 'completed';
|
|
||||||
session.current_task = null;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
const summary = `\n---\n## Summary (Resumed)\n- Completed: ${session.completed.length}\n- Failed: ${session.failed.length}\n- Skipped: ${session.skipped.length}\n`;
|
|
||||||
const finalProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, finalProgress + summary);
|
|
||||||
|
|
||||||
console.log('\n=== IDAW Resume Complete ===');
|
|
||||||
console.log(`Session: ${session.session_id}`);
|
|
||||||
console.log(`Completed: ${session.completed.length} | Failed: ${session.failed.length} | Skipped: ${session.skipped.length}`);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### assembleSkillArgs
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function assembleSkillArgs(skillName, task, previousResult, autoYes, isFirst) {
|
|
||||||
let args = '';
|
|
||||||
|
|
||||||
if (isFirst) {
|
|
||||||
// Sanitize for shell safety
|
|
||||||
const goal = `${task.title}\n${task.description}`
|
|
||||||
.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/"/g, '\\"')
|
|
||||||
.replace(/\$/g, '\\$')
|
|
||||||
.replace(/`/g, '\\`');
|
|
||||||
args = `"${goal}"`;
|
|
||||||
if (task.task_type === 'bugfix-hotfix') args += ' --hotfix';
|
|
||||||
} else if (previousResult?.session_id) {
|
|
||||||
args = `--session="${previousResult.session_id}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (autoYes && !args.includes('-y') && !args.includes('--yes')) {
|
|
||||||
args = args ? `${args} -y` : '-y';
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Resume most recent running session (interactive)
|
|
||||||
/idaw:resume
|
|
||||||
|
|
||||||
# Resume specific session
|
|
||||||
/idaw:resume IDA-auth-fix-20260301
|
|
||||||
|
|
||||||
# Resume with auto mode (skip interrupted, continue)
|
|
||||||
/idaw:resume -y
|
|
||||||
|
|
||||||
# Resume specific session with auto mode
|
|
||||||
/idaw:resume -y IDA-auth-fix-20260301
|
|
||||||
```
|
|
||||||
@@ -1,648 +0,0 @@
|
|||||||
---
|
|
||||||
name: run-coordinate
|
|
||||||
description: IDAW coordinator - execute task skill chains via external CLI with hook callbacks and git checkpoints
|
|
||||||
argument-hint: "[-y|--yes] [--task <id>[,<id>,...]] [--dry-run] [--tool <tool>]"
|
|
||||||
allowed-tools: Agent(*), AskUserQuestion(*), Read(*), Write(*), Bash(*), Glob(*), Grep(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
# IDAW Run Coordinate Command (/idaw:run-coordinate)
|
|
||||||
|
|
||||||
Coordinator variant of `/idaw:run`: external CLI execution with background tasks and hook callbacks.
|
|
||||||
|
|
||||||
**Execution Model**: `ccw cli -p "..." --tool <tool> --mode write` in background → hook callback → next step.
|
|
||||||
|
|
||||||
**vs `/idaw:run`**: Direct `Skill()` calls (blocking, main process) vs `ccw cli` (background, external process).
|
|
||||||
|
|
||||||
## When to Use
|
|
||||||
|
|
||||||
| Scenario | Use |
|
|
||||||
|----------|-----|
|
|
||||||
| Standard IDAW execution (main process) | `/idaw:run` |
|
|
||||||
| External CLI execution (background, hook-driven) | `/idaw:run-coordinate` |
|
|
||||||
| Need `claude` or `gemini` as execution tool | `/idaw:run-coordinate --tool claude` |
|
|
||||||
| Long-running tasks, avoid context window pressure | `/idaw:run-coordinate` |
|
|
||||||
|
|
||||||
## Skill Chain Mapping
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const SKILL_CHAIN_MAP = {
|
|
||||||
'bugfix': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'bugfix-hotfix': ['workflow-lite-plan'],
|
|
||||||
'feature': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'feature-complex': ['workflow-plan', 'workflow-execute', 'workflow-test-fix'],
|
|
||||||
'refactor': ['workflow:refactor-cycle'],
|
|
||||||
'tdd': ['workflow-tdd-plan', 'workflow-execute'],
|
|
||||||
'test': ['workflow-test-fix'],
|
|
||||||
'test-fix': ['workflow-test-fix'],
|
|
||||||
'review': ['review-cycle'],
|
|
||||||
'docs': ['workflow-lite-plan']
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Task Type Inference
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function inferTaskType(title, description) {
|
|
||||||
const text = `${title} ${description}`.toLowerCase();
|
|
||||||
if (/urgent|production|critical/.test(text) && /fix|bug/.test(text)) return 'bugfix-hotfix';
|
|
||||||
if (/refactor|重构|tech.*debt/.test(text)) return 'refactor';
|
|
||||||
if (/tdd|test-driven|test first/.test(text)) return 'tdd';
|
|
||||||
if (/test fail|fix test|failing test/.test(text)) return 'test-fix';
|
|
||||||
if (/generate test|写测试|add test/.test(text)) return 'test';
|
|
||||||
if (/review|code review/.test(text)) return 'review';
|
|
||||||
if (/docs|documentation|readme/.test(text)) return 'docs';
|
|
||||||
if (/fix|bug|error|crash|fail/.test(text)) return 'bugfix';
|
|
||||||
if (/complex|multi-module|architecture/.test(text)) return 'feature-complex';
|
|
||||||
return 'feature';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6-Phase Execution (Coordinator Model)
|
|
||||||
|
|
||||||
### Phase 1: Load Tasks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const args = $ARGUMENTS;
|
|
||||||
const autoYes = /(-y|--yes)/.test(args);
|
|
||||||
const dryRun = /--dry-run/.test(args);
|
|
||||||
const taskFilter = args.match(/--task\s+([\w,-]+)/)?.[1]?.split(',') || null;
|
|
||||||
const cliTool = args.match(/--tool\s+(\w+)/)?.[1] || 'claude';
|
|
||||||
|
|
||||||
// Load task files
|
|
||||||
const taskFiles = Glob('.workflow/.idaw/tasks/IDAW-*.json') || [];
|
|
||||||
|
|
||||||
if (taskFiles.length === 0) {
|
|
||||||
console.log('No IDAW tasks found. Use /idaw:add to create tasks.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse and filter
|
|
||||||
let tasks = taskFiles.map(f => JSON.parse(Read(f)));
|
|
||||||
|
|
||||||
if (taskFilter) {
|
|
||||||
tasks = tasks.filter(t => taskFilter.includes(t.id));
|
|
||||||
} else {
|
|
||||||
tasks = tasks.filter(t => t.status === 'pending');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tasks.length === 0) {
|
|
||||||
console.log('No pending tasks to execute. Use /idaw:add to add tasks or --task to specify IDs.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort: priority ASC (1=critical first), then ID ASC
|
|
||||||
tasks.sort((a, b) => {
|
|
||||||
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
||||||
return a.id.localeCompare(b.id);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Session Setup
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Generate session ID: IDA-{slug}-YYYYMMDD
|
|
||||||
const slug = tasks[0].title
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, '-')
|
|
||||||
.substring(0, 20)
|
|
||||||
.replace(/-$/, '');
|
|
||||||
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
||||||
let sessionId = `IDA-${slug}-${dateStr}`;
|
|
||||||
|
|
||||||
// Check collision
|
|
||||||
const existingSession = Glob(`.workflow/.idaw/sessions/${sessionId}/session.json`);
|
|
||||||
if (existingSession?.length > 0) {
|
|
||||||
sessionId = `${sessionId}-2`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionDir = `.workflow/.idaw/sessions/${sessionId}`;
|
|
||||||
Bash(`mkdir -p "${sessionDir}"`);
|
|
||||||
|
|
||||||
const session = {
|
|
||||||
session_id: sessionId,
|
|
||||||
mode: 'coordinate', // ★ Marks this as coordinator-mode session
|
|
||||||
cli_tool: cliTool,
|
|
||||||
status: 'running',
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
tasks: tasks.map(t => t.id),
|
|
||||||
current_task: null,
|
|
||||||
current_skill_index: 0,
|
|
||||||
completed: [],
|
|
||||||
failed: [],
|
|
||||||
skipped: [],
|
|
||||||
prompts_used: []
|
|
||||||
};
|
|
||||||
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Initialize progress.md
|
|
||||||
const progressHeader = `# IDAW Progress — ${sessionId} (coordinate mode)\nStarted: ${session.created_at}\nCLI Tool: ${cliTool}\n\n`;
|
|
||||||
Write(`${sessionDir}/progress.md`, progressHeader);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Startup Protocol
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Check for existing running sessions
|
|
||||||
const runningSessions = Glob('.workflow/.idaw/sessions/IDA-*/session.json')
|
|
||||||
?.map(f => { try { return JSON.parse(Read(f)); } catch { return null; } })
|
|
||||||
.filter(s => s && s.status === 'running' && s.session_id !== sessionId) || [];
|
|
||||||
|
|
||||||
if (runningSessions.length > 0 && !autoYes) {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `Found running session: ${runningSessions[0].session_id}. How to proceed?`,
|
|
||||||
header: 'Conflict',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Resume existing', description: 'Use /idaw:resume instead' },
|
|
||||||
{ label: 'Start fresh', description: 'Continue with new session' },
|
|
||||||
{ label: 'Abort', description: 'Cancel this run' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
if (answer.answers?.Conflict === 'Resume existing') {
|
|
||||||
console.log(`Use: /idaw:resume ${runningSessions[0].session_id}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (answer.answers?.Conflict === 'Abort') return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check git status
|
|
||||||
const gitStatus = Bash('git status --porcelain 2>/dev/null');
|
|
||||||
if (gitStatus?.trim() && !autoYes) {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: 'Working tree has uncommitted changes. How to proceed?',
|
|
||||||
header: 'Git',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Continue', description: 'Proceed with dirty tree' },
|
|
||||||
{ label: 'Stash', description: 'git stash before running' },
|
|
||||||
{ label: 'Abort', description: 'Stop and handle manually' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
if (answer.answers?.Git === 'Stash') Bash('git stash push -m "idaw-pre-run"');
|
|
||||||
if (answer.answers?.Git === 'Abort') return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dry run
|
|
||||||
if (dryRun) {
|
|
||||||
console.log(`# Dry Run — ${sessionId} (coordinate mode, tool: ${cliTool})\n`);
|
|
||||||
for (const task of tasks) {
|
|
||||||
const taskType = task.task_type || inferTaskType(task.title, task.description);
|
|
||||||
const chain = task.skill_chain || SKILL_CHAIN_MAP[taskType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
console.log(`## ${task.id}: ${task.title}`);
|
|
||||||
console.log(` Type: ${taskType} | Priority: ${task.priority}`);
|
|
||||||
console.log(` Chain: ${chain.join(' → ')}`);
|
|
||||||
console.log(` CLI: ccw cli --tool ${cliTool} --mode write\n`);
|
|
||||||
}
|
|
||||||
console.log(`Total: ${tasks.length} tasks`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Launch First Task (then wait for hook)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Start with the first task, first skill
|
|
||||||
const firstTask = tasks[0];
|
|
||||||
const resolvedType = firstTask.task_type || inferTaskType(firstTask.title, firstTask.description);
|
|
||||||
const chain = firstTask.skill_chain || SKILL_CHAIN_MAP[resolvedType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
|
|
||||||
// Update task → in_progress
|
|
||||||
firstTask.status = 'in_progress';
|
|
||||||
firstTask.task_type = resolvedType;
|
|
||||||
firstTask.execution.started_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${firstTask.id}.json`, JSON.stringify(firstAgent, null, 2));
|
|
||||||
|
|
||||||
// Update session
|
|
||||||
session.current_task = firstTask.id;
|
|
||||||
session.current_skill_index = 0;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// ━━━ Pre-Task CLI Context Analysis (for complex/bugfix tasks) ━━━
|
|
||||||
if (['bugfix', 'bugfix-hotfix', 'feature-complex'].includes(resolvedType)) {
|
|
||||||
console.log(`Pre-analysis: gathering context for ${resolvedType} task...`);
|
|
||||||
const affectedFiles = (firstTask.context?.affected_files || []).join(', ');
|
|
||||||
const preAnalysisPrompt = `PURPOSE: Pre-analyze codebase context for IDAW task.
|
|
||||||
TASK: • Understand current state of: ${affectedFiles || 'files related to: ' + firstTask.title} • Identify dependencies and risk areas
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/*
|
|
||||||
EXPECTED: Brief context summary in 3-5 bullet points
|
|
||||||
CONSTRAINTS: Keep concise`;
|
|
||||||
Bash(`ccw cli -p '${preAnalysisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis 2>&1 || echo "Pre-analysis skipped"`);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assemble prompt for first skill
|
|
||||||
const skillName = chain[0];
|
|
||||||
const prompt = assembleCliPrompt(skillName, firstAgent, null, autoYes);
|
|
||||||
|
|
||||||
session.prompts_used.push({
|
|
||||||
task_id: firstTask.id,
|
|
||||||
skill_index: 0,
|
|
||||||
skill: skillName,
|
|
||||||
prompt: prompt,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Launch via ccw cli in background
|
|
||||||
console.log(`[1/${tasks.length}] ${firstTask.id}: ${firstTask.title}`);
|
|
||||||
console.log(` Chain: ${chain.join(' → ')}`);
|
|
||||||
console.log(` Launching: ${skillName} via ccw cli --tool ${cliTool}`);
|
|
||||||
|
|
||||||
Bash(
|
|
||||||
`ccw cli -p "${escapeForShell(prompt)}" --tool ${cliTool} --mode write`,
|
|
||||||
{ run_in_background: true }
|
|
||||||
);
|
|
||||||
|
|
||||||
// ★ STOP HERE — wait for hook callback
|
|
||||||
// Hook callback will trigger handleStepCompletion() below
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 5: Hook Callback Handler (per-step completion)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Called by hook when background CLI completes
|
|
||||||
async function handleStepCompletion(sessionId, cliOutput) {
|
|
||||||
const sessionDir = `.workflow/.idaw/sessions/${sessionId}`;
|
|
||||||
const session = JSON.parse(Read(`${sessionDir}/session.json`));
|
|
||||||
|
|
||||||
const taskId = session.current_task;
|
|
||||||
const task = JSON.parse(Read(`.workflow/.idaw/tasks/${taskId}.json`));
|
|
||||||
|
|
||||||
const resolvedType = task.task_type || inferTaskType(task.title, task.description);
|
|
||||||
const chain = task.skill_chain || SKILL_CHAIN_MAP[resolvedType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
const skillIdx = session.current_skill_index;
|
|
||||||
const skillName = chain[skillIdx];
|
|
||||||
|
|
||||||
// Parse CLI output for session ID
|
|
||||||
const parsedOutput = parseCliOutput(cliOutput);
|
|
||||||
|
|
||||||
// Record skill result
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: parsedOutput.success ? 'completed' : 'failed',
|
|
||||||
session_id: parsedOutput.sessionId,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// ━━━ Handle failure with CLI diagnosis ━━━
|
|
||||||
if (!parsedOutput.success) {
|
|
||||||
console.log(` ${skillName} failed. Running CLI diagnosis...`);
|
|
||||||
const diagnosisPrompt = `PURPOSE: Diagnose why skill "${skillName}" failed during IDAW task.
|
|
||||||
TASK: • Analyze error output • Check affected files: ${(task.context?.affected_files || []).join(', ') || 'unknown'}
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/* | Memory: IDAW task ${task.id}: ${task.title}
|
|
||||||
EXPECTED: Root cause + fix recommendation
|
|
||||||
CONSTRAINTS: Actionable diagnosis`;
|
|
||||||
Bash(`ccw cli -p '${diagnosisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis --rule analysis-diagnose-bug-root-cause 2>&1 || true`);
|
|
||||||
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: `cli-diagnosis:${skillName}`,
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Retry once
|
|
||||||
console.log(` Retrying: ${skillName}`);
|
|
||||||
const retryPrompt = assembleCliPrompt(skillName, task, parsedOutput, true);
|
|
||||||
session.prompts_used.push({
|
|
||||||
task_id: taskId,
|
|
||||||
skill_index: skillIdx,
|
|
||||||
skill: `${skillName}-retry`,
|
|
||||||
prompt: retryPrompt,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
Write(`.workflow/.idaw/tasks/${taskId}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
Bash(
|
|
||||||
`ccw cli -p "${escapeForShell(retryPrompt)}" --tool ${session.cli_tool} --mode write`,
|
|
||||||
{ run_in_background: true }
|
|
||||||
);
|
|
||||||
return; // Wait for retry hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// ━━━ Skill succeeded — advance ━━━
|
|
||||||
const nextSkillIdx = skillIdx + 1;
|
|
||||||
|
|
||||||
if (nextSkillIdx < chain.length) {
|
|
||||||
// More skills in this task's chain → launch next skill
|
|
||||||
session.current_skill_index = nextSkillIdx;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
|
|
||||||
const nextSkill = chain[nextSkillIdx];
|
|
||||||
const nextPrompt = assembleCliPrompt(nextSkill, task, parsedOutput, true);
|
|
||||||
|
|
||||||
session.prompts_used.push({
|
|
||||||
task_id: taskId,
|
|
||||||
skill_index: nextSkillIdx,
|
|
||||||
skill: nextSkill,
|
|
||||||
prompt: nextPrompt,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
Write(`.workflow/.idaw/tasks/${taskId}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
console.log(` Next skill: ${nextSkill}`);
|
|
||||||
Bash(
|
|
||||||
`ccw cli -p "${escapeForShell(nextPrompt)}" --tool ${session.cli_tool} --mode write`,
|
|
||||||
{ run_in_background: true }
|
|
||||||
);
|
|
||||||
return; // Wait for next hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// ━━━ Task chain complete — git checkpoint ━━━
|
|
||||||
const commitMsg = `feat(idaw): ${task.title} [${task.id}]`;
|
|
||||||
const diffCheck = Bash('git diff --stat HEAD 2>/dev/null || echo ""');
|
|
||||||
const untrackedCheck = Bash('git ls-files --others --exclude-standard 2>/dev/null || echo ""');
|
|
||||||
|
|
||||||
if (diffCheck?.trim() || untrackedCheck?.trim()) {
|
|
||||||
Bash('git add -A');
|
|
||||||
Bash(`git commit -m "$(cat <<'EOF'\n${commitMsg}\nEOF\n)"`);
|
|
||||||
const commitHash = Bash('git rev-parse --short HEAD 2>/dev/null')?.trim();
|
|
||||||
task.execution.git_commit = commitHash;
|
|
||||||
} else {
|
|
||||||
task.execution.git_commit = 'no-commit';
|
|
||||||
}
|
|
||||||
|
|
||||||
task.status = 'completed';
|
|
||||||
task.execution.completed_at = new Date().toISOString();
|
|
||||||
task.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${taskId}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
session.completed.push(taskId);
|
|
||||||
|
|
||||||
// Append progress
|
|
||||||
const progressEntry = `## ${task.id} — ${task.title}\n` +
|
|
||||||
`- Status: completed\n` +
|
|
||||||
`- Type: ${task.task_type}\n` +
|
|
||||||
`- Chain: ${chain.join(' → ')}\n` +
|
|
||||||
`- Commit: ${task.execution.git_commit || '-'}\n` +
|
|
||||||
`- Mode: coordinate (${session.cli_tool})\n\n`;
|
|
||||||
const currentProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, currentProgress + progressEntry);
|
|
||||||
|
|
||||||
// ━━━ Advance to next task ━━━
|
|
||||||
const allTaskIds = session.tasks;
|
|
||||||
const completedSet = new Set([...session.completed, ...session.failed, ...session.skipped]);
|
|
||||||
const nextTaskId = allTaskIds.find(id => !completedSet.has(id));
|
|
||||||
|
|
||||||
if (nextTaskId) {
|
|
||||||
// Load next task
|
|
||||||
const nextTask = JSON.parse(Read(`.workflow/.idaw/tasks/${nextTaskId}.json`));
|
|
||||||
const nextType = nextTask.task_type || inferTaskType(nextTask.title, nextTask.description);
|
|
||||||
const nextChain = nextTask.skill_chain || SKILL_CHAIN_MAP[nextType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
|
|
||||||
nextTask.status = 'in_progress';
|
|
||||||
nextTask.task_type = nextType;
|
|
||||||
nextTask.execution.started_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${nextTaskId}.json`, JSON.stringify(nextAgent, null, 2));
|
|
||||||
|
|
||||||
session.current_task = nextTaskId;
|
|
||||||
session.current_skill_index = 0;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Pre-analysis for complex tasks
|
|
||||||
if (['bugfix', 'bugfix-hotfix', 'feature-complex'].includes(nextType)) {
|
|
||||||
const affectedFiles = (nextTask.context?.affected_files || []).join(', ');
|
|
||||||
Bash(`ccw cli -p 'PURPOSE: Pre-analyze context for ${nextTask.title}. TASK: Check ${affectedFiles || "related files"}. MODE: analysis. EXPECTED: 3-5 bullet points.' --tool gemini --mode analysis 2>&1 || true`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const nextSkillName = nextChain[0];
|
|
||||||
const nextPrompt = assembleCliPrompt(nextSkillName, nextAgent, null, true);
|
|
||||||
|
|
||||||
session.prompts_used.push({
|
|
||||||
task_id: nextTaskId,
|
|
||||||
skill_index: 0,
|
|
||||||
skill: nextSkillName,
|
|
||||||
prompt: nextPrompt,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
const taskNum = session.completed.length + 1;
|
|
||||||
const totalTasks = session.tasks.length;
|
|
||||||
console.log(`\n[${taskNum}/${totalTasks}] ${nextTaskId}: ${nextTask.title}`);
|
|
||||||
console.log(` Chain: ${nextChain.join(' → ')}`);
|
|
||||||
|
|
||||||
Bash(
|
|
||||||
`ccw cli -p "${escapeForShell(nextPrompt)}" --tool ${session.cli_tool} --mode write`,
|
|
||||||
{ run_in_background: true }
|
|
||||||
);
|
|
||||||
return; // Wait for hook
|
|
||||||
}
|
|
||||||
|
|
||||||
// ━━━ All tasks complete — Phase 6: Report ━━━
|
|
||||||
session.status = session.failed.length > 0 && session.completed.length === 0 ? 'failed' : 'completed';
|
|
||||||
session.current_task = null;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
const summary = `\n---\n## Summary (coordinate mode)\n` +
|
|
||||||
`- CLI Tool: ${session.cli_tool}\n` +
|
|
||||||
`- Completed: ${session.completed.length}\n` +
|
|
||||||
`- Failed: ${session.failed.length}\n` +
|
|
||||||
`- Skipped: ${session.skipped.length}\n` +
|
|
||||||
`- Total: ${session.tasks.length}\n`;
|
|
||||||
const finalProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, finalProgress + summary);
|
|
||||||
|
|
||||||
console.log('\n=== IDAW Coordinate Complete ===');
|
|
||||||
console.log(`Session: ${sessionId}`);
|
|
||||||
console.log(`Completed: ${session.completed.length}/${session.tasks.length}`);
|
|
||||||
if (session.failed.length > 0) console.log(`Failed: ${session.failed.join(', ')}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### assembleCliPrompt
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function assembleCliPrompt(skillName, task, previousResult, autoYes) {
|
|
||||||
let prompt = '';
|
|
||||||
const yFlag = autoYes ? ' -y' : '';
|
|
||||||
|
|
||||||
// Map skill to command invocation
|
|
||||||
if (skillName === 'workflow-lite-plan') {
|
|
||||||
const goal = sanitize(`${task.title}\n${task.description}`);
|
|
||||||
prompt = `/workflow-lite-plan${yFlag} "${goal}"`;
|
|
||||||
if (task.task_type === 'bugfix') prompt = `/workflow-lite-plan${yFlag} --bugfix "${goal}"`;
|
|
||||||
if (task.task_type === 'bugfix-hotfix') prompt = `/workflow-lite-plan${yFlag} --hotfix "${goal}"`;
|
|
||||||
|
|
||||||
} else if (skillName === 'workflow-plan') {
|
|
||||||
prompt = `/workflow-plan${yFlag} "${sanitize(task.title)}"`;
|
|
||||||
|
|
||||||
} else if (skillName === 'workflow-execute') {
|
|
||||||
if (previousResult?.sessionId) {
|
|
||||||
prompt = `/workflow-execute${yFlag} --resume-session="${previousResult.sessionId}"`;
|
|
||||||
} else {
|
|
||||||
prompt = `/workflow-execute${yFlag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (skillName === 'workflow-test-fix') {
|
|
||||||
if (previousResult?.sessionId) {
|
|
||||||
prompt = `/workflow-test-fix${yFlag} "${previousResult.sessionId}"`;
|
|
||||||
} else {
|
|
||||||
prompt = `/workflow-test-fix${yFlag} "${sanitize(task.title)}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else if (skillName === 'workflow-tdd-plan') {
|
|
||||||
prompt = `/workflow-tdd-plan${yFlag} "${sanitize(task.title)}"`;
|
|
||||||
|
|
||||||
} else if (skillName === 'workflow:refactor-cycle') {
|
|
||||||
prompt = `/workflow:refactor-cycle${yFlag} "${sanitize(task.title)}"`;
|
|
||||||
|
|
||||||
} else if (skillName === 'review-cycle') {
|
|
||||||
if (previousResult?.sessionId) {
|
|
||||||
prompt = `/review-cycle${yFlag} --session="${previousResult.sessionId}"`;
|
|
||||||
} else {
|
|
||||||
prompt = `/review-cycle${yFlag}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
// Generic fallback
|
|
||||||
prompt = `/${skillName}${yFlag} "${sanitize(task.title)}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Append task context
|
|
||||||
prompt += `\n\nTask: ${task.title}\nDescription: ${task.description}`;
|
|
||||||
if (task.context?.affected_files?.length > 0) {
|
|
||||||
prompt += `\nAffected files: ${task.context.affected_files.join(', ')}`;
|
|
||||||
}
|
|
||||||
if (task.context?.acceptance_criteria?.length > 0) {
|
|
||||||
prompt += `\nAcceptance criteria: ${task.context.acceptance_criteria.join('; ')}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return prompt;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### sanitize & escapeForShell
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function sanitize(text) {
|
|
||||||
return text
|
|
||||||
.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/"/g, '\\"')
|
|
||||||
.replace(/\$/g, '\\$')
|
|
||||||
.replace(/`/g, '\\`');
|
|
||||||
}
|
|
||||||
|
|
||||||
function escapeForShell(prompt) {
|
|
||||||
return prompt.replace(/'/g, "'\\''");
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### parseCliOutput
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function parseCliOutput(output) {
|
|
||||||
// Extract session ID from CLI output (e.g., WFS-xxx, session-xxx)
|
|
||||||
const sessionMatch = output.match(/(?:session|WFS|Session ID)[:\s]*([\w-]+)/i);
|
|
||||||
const success = !/(?:error|failed|fatal)/i.test(output) || /completed|success/i.test(output);
|
|
||||||
|
|
||||||
return {
|
|
||||||
success,
|
|
||||||
sessionId: sessionMatch?.[1] || null,
|
|
||||||
raw: output?.substring(0, 500)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI-Assisted Analysis
|
|
||||||
|
|
||||||
Same as `/idaw:run` — integrated at two points:
|
|
||||||
|
|
||||||
### Pre-Task Context Analysis
|
|
||||||
For `bugfix`, `bugfix-hotfix`, `feature-complex` tasks: auto-invoke `ccw cli --tool gemini --mode analysis` before launching skill chain.
|
|
||||||
|
|
||||||
### Error Recovery with CLI Diagnosis
|
|
||||||
When a skill's CLI execution fails: invoke diagnosis → retry once → if still fails, mark failed and advance.
|
|
||||||
|
|
||||||
```
|
|
||||||
Skill CLI fails → CLI diagnosis (gemini) → Retry CLI → Still fails → mark failed → next task
|
|
||||||
```
|
|
||||||
|
|
||||||
## State Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Phase 4: Launch first skill
|
|
||||||
↓
|
|
||||||
ccw cli --tool claude --mode write (background)
|
|
||||||
↓
|
|
||||||
★ STOP — wait for hook callback
|
|
||||||
↓
|
|
||||||
Phase 5: handleStepCompletion()
|
|
||||||
├─ Skill succeeded + more in chain → launch next skill → STOP
|
|
||||||
├─ Skill succeeded + chain complete → git checkpoint → next task → STOP
|
|
||||||
├─ Skill failed → CLI diagnosis → retry → STOP
|
|
||||||
└─ All tasks done → Phase 6: Report
|
|
||||||
```
|
|
||||||
|
|
||||||
## Session State (session.json)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"session_id": "IDA-fix-login-20260301",
|
|
||||||
"mode": "coordinate",
|
|
||||||
"cli_tool": "claude",
|
|
||||||
"status": "running|waiting|completed|failed",
|
|
||||||
"created_at": "ISO",
|
|
||||||
"updated_at": "ISO",
|
|
||||||
"tasks": ["IDAW-001", "IDAW-002"],
|
|
||||||
"current_task": "IDAW-001",
|
|
||||||
"current_skill_index": 0,
|
|
||||||
"completed": [],
|
|
||||||
"failed": [],
|
|
||||||
"skipped": [],
|
|
||||||
"prompts_used": [
|
|
||||||
{
|
|
||||||
"task_id": "IDAW-001",
|
|
||||||
"skill_index": 0,
|
|
||||||
"skill": "workflow-lite-plan",
|
|
||||||
"prompt": "/workflow-lite-plan -y \"Fix login timeout\"",
|
|
||||||
"timestamp": "ISO"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Differences from /idaw:run
|
|
||||||
|
|
||||||
| Aspect | /idaw:run | /idaw:run-coordinate |
|
|
||||||
|--------|-----------|---------------------|
|
|
||||||
| Execution | `Skill()` blocking in main process | `ccw cli` background + hook callback |
|
|
||||||
| Context window | Shared (each skill uses main context) | Isolated (each CLI gets fresh context) |
|
|
||||||
| Concurrency | Sequential blocking | Sequential non-blocking (hook-driven) |
|
|
||||||
| State tracking | session.json + task.json | session.json + task.json + prompts_used |
|
|
||||||
| Tool selection | N/A (Skill native) | `--tool claude\|gemini\|qwen` |
|
|
||||||
| Resume | Via `/idaw:resume` (same) | Via `/idaw:resume` (same, detects mode) |
|
|
||||||
| Best for | Short chains, interactive | Long chains, autonomous, context-heavy |
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Execute all pending tasks via claude CLI
|
|
||||||
/idaw:run-coordinate -y
|
|
||||||
|
|
||||||
# Use specific CLI tool
|
|
||||||
/idaw:run-coordinate -y --tool gemini
|
|
||||||
|
|
||||||
# Execute specific tasks
|
|
||||||
/idaw:run-coordinate --task IDAW-001,IDAW-003 --tool claude
|
|
||||||
|
|
||||||
# Dry run (show plan without executing)
|
|
||||||
/idaw:run-coordinate --dry-run
|
|
||||||
|
|
||||||
# Interactive mode
|
|
||||||
/idaw:run-coordinate
|
|
||||||
```
|
|
||||||
@@ -1,539 +0,0 @@
|
|||||||
---
|
|
||||||
name: run
|
|
||||||
description: IDAW orchestrator - execute task skill chains serially with git checkpoints
|
|
||||||
argument-hint: "[-y|--yes] [--task <id>[,<id>,...]] [--dry-run]"
|
|
||||||
allowed-tools: Skill(*), TodoWrite(*), AskUserQuestion(*), Read(*), Write(*), Bash(*), Glob(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
# IDAW Run Command (/idaw:run)
|
|
||||||
|
|
||||||
## Auto Mode
|
|
||||||
|
|
||||||
When `--yes` or `-y`: Skip all confirmations, auto-skip on failure, proceed with dirty git.
|
|
||||||
|
|
||||||
## Skill Chain Mapping
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const SKILL_CHAIN_MAP = {
|
|
||||||
'bugfix': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'bugfix-hotfix': ['workflow-lite-plan'],
|
|
||||||
'feature': ['workflow-lite-plan', 'workflow-test-fix'],
|
|
||||||
'feature-complex': ['workflow-plan', 'workflow-execute', 'workflow-test-fix'],
|
|
||||||
'refactor': ['workflow:refactor-cycle'],
|
|
||||||
'tdd': ['workflow-tdd-plan', 'workflow-execute'],
|
|
||||||
'test': ['workflow-test-fix'],
|
|
||||||
'test-fix': ['workflow-test-fix'],
|
|
||||||
'review': ['review-cycle'],
|
|
||||||
'docs': ['workflow-lite-plan']
|
|
||||||
};
|
|
||||||
```
|
|
||||||
|
|
||||||
## Task Type Inference
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function inferTaskType(title, description) {
|
|
||||||
const text = `${title} ${description}`.toLowerCase();
|
|
||||||
if (/urgent|production|critical/.test(text) && /fix|bug/.test(text)) return 'bugfix-hotfix';
|
|
||||||
if (/refactor|重构|tech.*debt/.test(text)) return 'refactor';
|
|
||||||
if (/tdd|test-driven|test first/.test(text)) return 'tdd';
|
|
||||||
if (/test fail|fix test|failing test/.test(text)) return 'test-fix';
|
|
||||||
if (/generate test|写测试|add test/.test(text)) return 'test';
|
|
||||||
if (/review|code review/.test(text)) return 'review';
|
|
||||||
if (/docs|documentation|readme/.test(text)) return 'docs';
|
|
||||||
if (/fix|bug|error|crash|fail/.test(text)) return 'bugfix';
|
|
||||||
if (/complex|multi-module|architecture/.test(text)) return 'feature-complex';
|
|
||||||
return 'feature';
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## 6-Phase Execution
|
|
||||||
|
|
||||||
### Phase 1: Load Tasks
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const args = $ARGUMENTS;
|
|
||||||
const autoYes = /(-y|--yes)/.test(args);
|
|
||||||
const dryRun = /--dry-run/.test(args);
|
|
||||||
const taskFilter = args.match(/--task\s+([\w,-]+)/)?.[1]?.split(',') || null;
|
|
||||||
|
|
||||||
// Load task files
|
|
||||||
const taskFiles = Glob('.workflow/.idaw/tasks/IDAW-*.json') || [];
|
|
||||||
|
|
||||||
if (taskFiles.length === 0) {
|
|
||||||
console.log('No IDAW tasks found. Use /idaw:add to create tasks.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse and filter
|
|
||||||
let tasks = taskFiles.map(f => JSON.parse(Read(f)));
|
|
||||||
|
|
||||||
if (taskFilter) {
|
|
||||||
tasks = tasks.filter(t => taskFilter.includes(t.id));
|
|
||||||
} else {
|
|
||||||
tasks = tasks.filter(t => t.status === 'pending');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tasks.length === 0) {
|
|
||||||
console.log('No pending tasks to execute. Use /idaw:add to add tasks or --task to specify IDs.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort: priority ASC (1=critical first), then ID ASC
|
|
||||||
tasks.sort((a, b) => {
|
|
||||||
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
||||||
return a.id.localeCompare(b.id);
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Session Setup
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Generate session ID: IDA-{slug}-YYYYMMDD
|
|
||||||
const slug = tasks[0].title
|
|
||||||
.toLowerCase()
|
|
||||||
.replace(/[^a-z0-9]+/g, '-')
|
|
||||||
.substring(0, 20)
|
|
||||||
.replace(/-$/, '');
|
|
||||||
const dateStr = new Date().toISOString().slice(0, 10).replace(/-/g, '');
|
|
||||||
let sessionId = `IDA-${slug}-${dateStr}`;
|
|
||||||
|
|
||||||
// Check collision
|
|
||||||
const existingSession = Glob(`.workflow/.idaw/sessions/${sessionId}/session.json`);
|
|
||||||
if (existingSession?.length > 0) {
|
|
||||||
sessionId = `${sessionId}-2`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const sessionDir = `.workflow/.idaw/sessions/${sessionId}`;
|
|
||||||
Bash(`mkdir -p "${sessionDir}"`);
|
|
||||||
|
|
||||||
const session = {
|
|
||||||
session_id: sessionId,
|
|
||||||
status: 'running',
|
|
||||||
created_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
tasks: tasks.map(t => t.id),
|
|
||||||
current_task: null,
|
|
||||||
completed: [],
|
|
||||||
failed: [],
|
|
||||||
skipped: []
|
|
||||||
};
|
|
||||||
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Initialize progress.md
|
|
||||||
const progressHeader = `# IDAW Progress — ${sessionId}\nStarted: ${session.created_at}\n\n`;
|
|
||||||
Write(`${sessionDir}/progress.md`, progressHeader);
|
|
||||||
|
|
||||||
// TodoWrite
|
|
||||||
TodoWrite({
|
|
||||||
todos: tasks.map((t, i) => ({
|
|
||||||
content: `IDAW:[${i + 1}/${tasks.length}] ${t.title}`,
|
|
||||||
status: i === 0 ? 'in_progress' : 'pending',
|
|
||||||
activeForm: `Executing ${t.title}`
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Startup Protocol
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Check for existing running sessions
|
|
||||||
const runningSessions = Glob('.workflow/.idaw/sessions/IDA-*/session.json')
|
|
||||||
?.map(f => JSON.parse(Read(f)))
|
|
||||||
.filter(s => s.status === 'running' && s.session_id !== sessionId) || [];
|
|
||||||
|
|
||||||
if (runningSessions.length > 0) {
|
|
||||||
if (!autoYes) {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `Found running session: ${runningSessions[0].session_id}. How to proceed?`,
|
|
||||||
header: 'Conflict',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Resume existing', description: 'Use /idaw:resume instead' },
|
|
||||||
{ label: 'Start fresh', description: 'Continue with new session' },
|
|
||||||
{ label: 'Abort', description: 'Cancel this run' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
if (answer.answers?.Conflict === 'Resume existing') {
|
|
||||||
console.log(`Use: /idaw:resume ${runningSessions[0].session_id}`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (answer.answers?.Conflict === 'Abort') return;
|
|
||||||
}
|
|
||||||
// autoYes or "Start fresh": proceed
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check git status
|
|
||||||
const gitStatus = Bash('git status --porcelain 2>/dev/null');
|
|
||||||
if (gitStatus?.trim()) {
|
|
||||||
if (!autoYes) {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: 'Working tree has uncommitted changes. How to proceed?',
|
|
||||||
header: 'Git',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Continue', description: 'Proceed with dirty tree' },
|
|
||||||
{ label: 'Stash', description: 'git stash before running' },
|
|
||||||
{ label: 'Abort', description: 'Stop and handle manually' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
if (answer.answers?.Git === 'Stash') {
|
|
||||||
Bash('git stash push -m "idaw-pre-run"');
|
|
||||||
}
|
|
||||||
if (answer.answers?.Git === 'Abort') return;
|
|
||||||
}
|
|
||||||
// autoYes: proceed silently
|
|
||||||
}
|
|
||||||
|
|
||||||
// Dry run: show plan and exit
|
|
||||||
if (dryRun) {
|
|
||||||
console.log(`# Dry Run — ${sessionId}\n`);
|
|
||||||
for (const task of tasks) {
|
|
||||||
const taskType = task.task_type || inferTaskType(task.title, task.description);
|
|
||||||
const chain = task.skill_chain || SKILL_CHAIN_MAP[taskType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
console.log(`## ${task.id}: ${task.title}`);
|
|
||||||
console.log(` Type: ${taskType} | Priority: ${task.priority}`);
|
|
||||||
console.log(` Chain: ${chain.join(' → ')}\n`);
|
|
||||||
}
|
|
||||||
console.log(`Total: ${tasks.length} tasks`);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: Main Loop (serial, one task at a time)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
for (let taskIdx = 0; taskIdx < tasks.length; taskIdx++) {
|
|
||||||
const task = tasks[taskIdx];
|
|
||||||
|
|
||||||
// Skip completed/failed/skipped
|
|
||||||
if (['completed', 'failed', 'skipped'].includes(task.status)) continue;
|
|
||||||
|
|
||||||
// Resolve skill chain
|
|
||||||
const resolvedType = task.task_type || inferTaskType(task.title, task.description);
|
|
||||||
const chain = task.skill_chain || SKILL_CHAIN_MAP[resolvedType] || SKILL_CHAIN_MAP['feature'];
|
|
||||||
|
|
||||||
// Update task status → in_progress
|
|
||||||
task.status = 'in_progress';
|
|
||||||
task.task_type = resolvedType; // persist inferred type
|
|
||||||
task.execution.started_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
// Update session
|
|
||||||
session.current_task = task.id;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
console.log(`\n--- [${taskIdx + 1}/${tasks.length}] ${task.id}: ${task.title} ---`);
|
|
||||||
console.log(`Chain: ${chain.join(' → ')}`);
|
|
||||||
|
|
||||||
// ━━━ Pre-Task CLI Context Analysis (for complex/bugfix tasks) ━━━
|
|
||||||
if (['bugfix', 'bugfix-hotfix', 'feature-complex'].includes(resolvedType)) {
|
|
||||||
console.log(` Pre-analysis: gathering context for ${resolvedType} task...`);
|
|
||||||
const affectedFiles = (task.context?.affected_files || []).join(', ');
|
|
||||||
const preAnalysisPrompt = `PURPOSE: Pre-analyze codebase context for IDAW task before execution.
|
|
||||||
TASK: • Understand current state of: ${affectedFiles || 'files related to: ' + task.title} • Identify dependencies and risk areas • Note existing patterns to follow
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/*
|
|
||||||
EXPECTED: Brief context summary (affected modules, dependencies, risk areas) in 3-5 bullet points
|
|
||||||
CONSTRAINTS: Keep concise | Focus on execution-relevant context`;
|
|
||||||
const preAnalysis = Bash(`ccw cli -p '${preAnalysisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis 2>&1 || echo "Pre-analysis skipped"`);
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: 'cli-pre-analysis',
|
|
||||||
status: 'completed',
|
|
||||||
context_summary: preAnalysis?.substring(0, 500),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execute each skill in chain
|
|
||||||
let previousResult = null;
|
|
||||||
let taskFailed = false;
|
|
||||||
|
|
||||||
for (let skillIdx = 0; skillIdx < chain.length; skillIdx++) {
|
|
||||||
const skillName = chain[skillIdx];
|
|
||||||
const skillArgs = assembleSkillArgs(skillName, task, previousResult, autoYes, skillIdx === 0);
|
|
||||||
|
|
||||||
console.log(` [${skillIdx + 1}/${chain.length}] ${skillName}`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
const result = Skill({ skill: skillName, args: skillArgs });
|
|
||||||
previousResult = result;
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'completed',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
// ━━━ CLI-Assisted Error Recovery ━━━
|
|
||||||
// Step 1: Invoke CLI diagnosis (auto-invoke trigger: self-repair fails)
|
|
||||||
console.log(` Diagnosing failure: ${skillName}...`);
|
|
||||||
const diagnosisPrompt = `PURPOSE: Diagnose why skill "${skillName}" failed during IDAW task execution.
|
|
||||||
TASK: • Analyze error: ${String(error).substring(0, 300)} • Check affected files: ${(task.context?.affected_files || []).join(', ') || 'unknown'} • Identify root cause • Suggest fix strategy
|
|
||||||
MODE: analysis
|
|
||||||
CONTEXT: @**/* | Memory: IDAW task ${task.id}: ${task.title}
|
|
||||||
EXPECTED: Root cause + actionable fix recommendation (1-2 sentences)
|
|
||||||
CONSTRAINTS: Focus on actionable diagnosis`;
|
|
||||||
const diagnosisResult = Bash(`ccw cli -p '${diagnosisPrompt.replace(/'/g, "'\\''")}' --tool gemini --mode analysis --rule analysis-diagnose-bug-root-cause 2>&1 || echo "CLI diagnosis unavailable"`);
|
|
||||||
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: `cli-diagnosis:${skillName}`,
|
|
||||||
status: 'completed',
|
|
||||||
diagnosis: diagnosisResult?.substring(0, 500),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
// Step 2: Retry with diagnosis context
|
|
||||||
console.log(` Retry with diagnosis: ${skillName}`);
|
|
||||||
try {
|
|
||||||
const retryResult = Skill({ skill: skillName, args: skillArgs });
|
|
||||||
previousResult = retryResult;
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'completed-retry-with-diagnosis',
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
} catch (retryError) {
|
|
||||||
// Step 3: Failed after CLI-assisted retry
|
|
||||||
task.execution.skill_results.push({
|
|
||||||
skill: skillName,
|
|
||||||
status: 'failed',
|
|
||||||
error: String(retryError).substring(0, 200),
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoYes) {
|
|
||||||
taskFailed = true;
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
const answer = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `${skillName} failed after CLI diagnosis + retry: ${String(retryError).substring(0, 100)}. How to proceed?`,
|
|
||||||
header: 'Error',
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: 'Skip task', description: 'Mark task as failed, continue to next' },
|
|
||||||
{ label: 'Abort', description: 'Stop entire run' }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
if (answer.answers?.Error === 'Abort') {
|
|
||||||
task.status = 'failed';
|
|
||||||
task.execution.error = String(retryError).substring(0, 200);
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
session.failed.push(task.id);
|
|
||||||
session.status = 'failed';
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
taskFailed = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Phase 5: Checkpoint (per task) — inline
|
|
||||||
if (taskFailed) {
|
|
||||||
task.status = 'failed';
|
|
||||||
task.execution.error = 'Skill chain failed after retry';
|
|
||||||
task.execution.completed_at = new Date().toISOString();
|
|
||||||
session.failed.push(task.id);
|
|
||||||
} else {
|
|
||||||
// Git commit checkpoint
|
|
||||||
const commitMsg = `feat(idaw): ${task.title} [${task.id}]`;
|
|
||||||
const diffCheck = Bash('git diff --stat HEAD 2>/dev/null || echo ""');
|
|
||||||
const untrackedCheck = Bash('git ls-files --others --exclude-standard 2>/dev/null || echo ""');
|
|
||||||
|
|
||||||
if (diffCheck?.trim() || untrackedCheck?.trim()) {
|
|
||||||
Bash('git add -A');
|
|
||||||
const commitResult = Bash(`git commit -m "$(cat <<'EOF'\n${commitMsg}\nEOF\n)"`);
|
|
||||||
const commitHash = Bash('git rev-parse --short HEAD 2>/dev/null')?.trim();
|
|
||||||
task.execution.git_commit = commitHash;
|
|
||||||
} else {
|
|
||||||
task.execution.git_commit = 'no-commit';
|
|
||||||
}
|
|
||||||
|
|
||||||
task.status = 'completed';
|
|
||||||
task.execution.completed_at = new Date().toISOString();
|
|
||||||
session.completed.push(task.id);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write task + session state
|
|
||||||
task.updated_at = new Date().toISOString();
|
|
||||||
Write(`.workflow/.idaw/tasks/${task.id}.json`, JSON.stringify(task, null, 2));
|
|
||||||
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Append to progress.md
|
|
||||||
const duration = task.execution.started_at && task.execution.completed_at
|
|
||||||
? formatDuration(new Date(task.execution.completed_at) - new Date(task.execution.started_at))
|
|
||||||
: 'unknown';
|
|
||||||
|
|
||||||
const progressEntry = `## ${task.id} — ${task.title}\n` +
|
|
||||||
`- Status: ${task.status}\n` +
|
|
||||||
`- Type: ${task.task_type}\n` +
|
|
||||||
`- Chain: ${chain.join(' → ')}\n` +
|
|
||||||
`- Commit: ${task.execution.git_commit || '-'}\n` +
|
|
||||||
`- Duration: ${duration}\n\n`;
|
|
||||||
|
|
||||||
const currentProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, currentProgress + progressEntry);
|
|
||||||
|
|
||||||
// Update TodoWrite
|
|
||||||
if (taskIdx + 1 < tasks.length) {
|
|
||||||
TodoWrite({
|
|
||||||
todos: tasks.map((t, i) => ({
|
|
||||||
content: `IDAW:[${i + 1}/${tasks.length}] ${t.title}`,
|
|
||||||
status: i < taskIdx + 1 ? 'completed' : (i === taskIdx + 1 ? 'in_progress' : 'pending'),
|
|
||||||
activeForm: `Executing ${t.title}`
|
|
||||||
}))
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 6: Report
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
session.status = session.failed.length > 0 && session.completed.length === 0 ? 'failed' : 'completed';
|
|
||||||
session.current_task = null;
|
|
||||||
session.updated_at = new Date().toISOString();
|
|
||||||
Write(`${sessionDir}/session.json`, JSON.stringify(session, null, 2));
|
|
||||||
|
|
||||||
// Final progress summary
|
|
||||||
const summary = `\n---\n## Summary\n` +
|
|
||||||
`- Completed: ${session.completed.length}\n` +
|
|
||||||
`- Failed: ${session.failed.length}\n` +
|
|
||||||
`- Skipped: ${session.skipped.length}\n` +
|
|
||||||
`- Total: ${tasks.length}\n`;
|
|
||||||
|
|
||||||
const finalProgress = Read(`${sessionDir}/progress.md`);
|
|
||||||
Write(`${sessionDir}/progress.md`, finalProgress + summary);
|
|
||||||
|
|
||||||
// Display report
|
|
||||||
console.log('\n=== IDAW Run Complete ===');
|
|
||||||
console.log(`Session: ${sessionId}`);
|
|
||||||
console.log(`Completed: ${session.completed.length}/${tasks.length}`);
|
|
||||||
if (session.failed.length > 0) console.log(`Failed: ${session.failed.join(', ')}`);
|
|
||||||
if (session.skipped.length > 0) console.log(`Skipped: ${session.skipped.join(', ')}`);
|
|
||||||
|
|
||||||
// List git commits
|
|
||||||
for (const taskId of session.completed) {
|
|
||||||
const t = JSON.parse(Read(`.workflow/.idaw/tasks/${taskId}.json`));
|
|
||||||
if (t.execution.git_commit && t.execution.git_commit !== 'no-commit') {
|
|
||||||
console.log(` ${t.execution.git_commit} ${t.title}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Helper Functions
|
|
||||||
|
|
||||||
### assembleSkillArgs
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function assembleSkillArgs(skillName, task, previousResult, autoYes, isFirst) {
|
|
||||||
let args = '';
|
|
||||||
|
|
||||||
if (isFirst) {
|
|
||||||
// First skill: pass task goal — sanitize for shell safety
|
|
||||||
const goal = `${task.title}\n${task.description}`
|
|
||||||
.replace(/\\/g, '\\\\')
|
|
||||||
.replace(/"/g, '\\"')
|
|
||||||
.replace(/\$/g, '\\$')
|
|
||||||
.replace(/`/g, '\\`');
|
|
||||||
args = `"${goal}"`;
|
|
||||||
|
|
||||||
// bugfix-hotfix: add --hotfix
|
|
||||||
if (task.task_type === 'bugfix-hotfix') {
|
|
||||||
args += ' --hotfix';
|
|
||||||
}
|
|
||||||
} else if (previousResult?.session_id) {
|
|
||||||
// Subsequent skills: chain session
|
|
||||||
args = `--session="${previousResult.session_id}"`;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Propagate -y
|
|
||||||
if (autoYes && !args.includes('-y') && !args.includes('--yes')) {
|
|
||||||
args = args ? `${args} -y` : '-y';
|
|
||||||
}
|
|
||||||
|
|
||||||
return args;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### formatDuration
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function formatDuration(ms) {
|
|
||||||
const seconds = Math.floor(ms / 1000);
|
|
||||||
const minutes = Math.floor(seconds / 60);
|
|
||||||
const remainingSeconds = seconds % 60;
|
|
||||||
if (minutes > 0) return `${minutes}m ${remainingSeconds}s`;
|
|
||||||
return `${seconds}s`;
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## CLI-Assisted Analysis
|
|
||||||
|
|
||||||
IDAW integrates `ccw cli` (Gemini) for intelligent analysis at two key points:
|
|
||||||
|
|
||||||
### Pre-Task Context Analysis
|
|
||||||
|
|
||||||
For `bugfix`, `bugfix-hotfix`, and `feature-complex` tasks, IDAW automatically invokes CLI analysis **before** executing the skill chain to gather codebase context:
|
|
||||||
|
|
||||||
```
|
|
||||||
Task starts → CLI pre-analysis (gemini) → Context gathered → Skill chain executes
|
|
||||||
```
|
|
||||||
|
|
||||||
- Identifies dependencies and risk areas
|
|
||||||
- Notes existing patterns to follow
|
|
||||||
- Results stored in `task.execution.skill_results` as `cli-pre-analysis`
|
|
||||||
|
|
||||||
### Error Recovery with CLI Diagnosis
|
|
||||||
|
|
||||||
When a skill fails, instead of blind retry, IDAW uses CLI-assisted diagnosis:
|
|
||||||
|
|
||||||
```
|
|
||||||
Skill fails → CLI diagnosis (gemini, analysis-diagnose-bug-root-cause)
|
|
||||||
→ Root cause identified → Retry with diagnosis context
|
|
||||||
→ Still fails → Skip (autoYes) or Ask user (interactive)
|
|
||||||
```
|
|
||||||
|
|
||||||
- Uses `--rule analysis-diagnose-bug-root-cause` template
|
|
||||||
- Diagnosis results stored in `task.execution.skill_results` as `cli-diagnosis:{skill}`
|
|
||||||
- Follows CLAUDE.md auto-invoke trigger pattern: "self-repair fails → invoke CLI analysis"
|
|
||||||
|
|
||||||
### Execution Flow (with CLI analysis)
|
|
||||||
|
|
||||||
```
|
|
||||||
Phase 4 Main Loop (per task):
|
|
||||||
├─ [bugfix/complex only] CLI pre-analysis → context summary
|
|
||||||
├─ Skill 1: execute
|
|
||||||
│ ├─ Success → next skill
|
|
||||||
│ └─ Failure → CLI diagnosis → retry → success/fail
|
|
||||||
├─ Skill 2: execute ...
|
|
||||||
└─ Phase 5: git checkpoint
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Execute all pending tasks
|
|
||||||
/idaw:run -y
|
|
||||||
|
|
||||||
# Execute specific tasks
|
|
||||||
/idaw:run --task IDAW-001,IDAW-003
|
|
||||||
|
|
||||||
# Dry run (show plan without executing)
|
|
||||||
/idaw:run --dry-run
|
|
||||||
|
|
||||||
# Interactive mode (confirm at each step)
|
|
||||||
/idaw:run
|
|
||||||
```
|
|
||||||
@@ -1,182 +0,0 @@
|
|||||||
---
|
|
||||||
name: status
|
|
||||||
description: View IDAW task and session progress
|
|
||||||
argument-hint: "[session-id]"
|
|
||||||
allowed-tools: Read(*), Glob(*), Bash(*)
|
|
||||||
---
|
|
||||||
|
|
||||||
# IDAW Status Command (/idaw:status)
|
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
Read-only command to view IDAW task queue and execution session progress.
|
|
||||||
|
|
||||||
## Implementation
|
|
||||||
|
|
||||||
### Phase 1: Determine View Mode
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const sessionId = $ARGUMENTS?.trim();
|
|
||||||
|
|
||||||
if (sessionId) {
|
|
||||||
// Specific session view
|
|
||||||
showSession(sessionId);
|
|
||||||
} else {
|
|
||||||
// Overview: pending tasks + latest session
|
|
||||||
showOverview();
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Show Overview
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function showOverview() {
|
|
||||||
// 1. Load all tasks
|
|
||||||
const taskFiles = Glob('.workflow/.idaw/tasks/IDAW-*.json') || [];
|
|
||||||
|
|
||||||
if (taskFiles.length === 0) {
|
|
||||||
console.log('No IDAW tasks found. Use /idaw:add to create tasks.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tasks = taskFiles.map(f => JSON.parse(Read(f)));
|
|
||||||
|
|
||||||
// 2. Group by status
|
|
||||||
const byStatus = {
|
|
||||||
pending: tasks.filter(t => t.status === 'pending'),
|
|
||||||
in_progress: tasks.filter(t => t.status === 'in_progress'),
|
|
||||||
completed: tasks.filter(t => t.status === 'completed'),
|
|
||||||
failed: tasks.filter(t => t.status === 'failed'),
|
|
||||||
skipped: tasks.filter(t => t.status === 'skipped')
|
|
||||||
};
|
|
||||||
|
|
||||||
// 3. Display task summary table
|
|
||||||
console.log('# IDAW Tasks\n');
|
|
||||||
console.log('| ID | Title | Type | Priority | Status |');
|
|
||||||
console.log('|----|-------|------|----------|--------|');
|
|
||||||
|
|
||||||
// Sort: priority ASC, then ID ASC
|
|
||||||
const sorted = [...tasks].sort((a, b) => {
|
|
||||||
if (a.priority !== b.priority) return a.priority - b.priority;
|
|
||||||
return a.id.localeCompare(b.id);
|
|
||||||
});
|
|
||||||
|
|
||||||
for (const t of sorted) {
|
|
||||||
const type = t.task_type || '(infer)';
|
|
||||||
console.log(`| ${t.id} | ${t.title.substring(0, 40)} | ${type} | ${t.priority} | ${t.status} |`);
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nTotal: ${tasks.length} | Pending: ${byStatus.pending.length} | Completed: ${byStatus.completed.length} | Failed: ${byStatus.failed.length}`);
|
|
||||||
|
|
||||||
// 4. Show latest session (if any)
|
|
||||||
const sessionDirs = Glob('.workflow/.idaw/sessions/IDA-*/session.json') || [];
|
|
||||||
if (sessionDirs.length > 0) {
|
|
||||||
// Sort by modification time (newest first) — Glob returns sorted by mtime
|
|
||||||
const latestSessionFile = sessionDirs[0];
|
|
||||||
const session = JSON.parse(Read(latestSessionFile));
|
|
||||||
console.log(`\n## Latest Session: ${session.session_id}`);
|
|
||||||
console.log(`Status: ${session.status} | Tasks: ${session.tasks?.length || 0}`);
|
|
||||||
console.log(`Completed: ${session.completed?.length || 0} | Failed: ${session.failed?.length || 0} | Skipped: ${session.skipped?.length || 0}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 3: Show Specific Session
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function showSession(sessionId) {
|
|
||||||
const sessionFile = `.workflow/.idaw/sessions/${sessionId}/session.json`;
|
|
||||||
const progressFile = `.workflow/.idaw/sessions/${sessionId}/progress.md`;
|
|
||||||
|
|
||||||
// Try reading session
|
|
||||||
try {
|
|
||||||
const session = JSON.parse(Read(sessionFile));
|
|
||||||
|
|
||||||
console.log(`# IDAW Session: ${session.session_id}\n`);
|
|
||||||
console.log(`Status: ${session.status}`);
|
|
||||||
console.log(`Created: ${session.created_at}`);
|
|
||||||
console.log(`Updated: ${session.updated_at}`);
|
|
||||||
console.log(`Current Task: ${session.current_task || 'none'}\n`);
|
|
||||||
|
|
||||||
// Task detail table
|
|
||||||
console.log('| ID | Title | Status | Commit |');
|
|
||||||
console.log('|----|-------|--------|--------|');
|
|
||||||
|
|
||||||
for (const taskId of session.tasks) {
|
|
||||||
const taskFile = `.workflow/.idaw/tasks/${taskId}.json`;
|
|
||||||
try {
|
|
||||||
const task = JSON.parse(Read(taskFile));
|
|
||||||
const commit = task.execution?.git_commit?.substring(0, 7) || '-';
|
|
||||||
console.log(`| ${task.id} | ${task.title.substring(0, 40)} | ${task.status} | ${commit} |`);
|
|
||||||
} catch {
|
|
||||||
console.log(`| ${taskId} | (file not found) | unknown | - |`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log(`\nCompleted: ${session.completed?.length || 0} | Failed: ${session.failed?.length || 0} | Skipped: ${session.skipped?.length || 0}`);
|
|
||||||
|
|
||||||
// Show progress.md if exists
|
|
||||||
try {
|
|
||||||
const progress = Read(progressFile);
|
|
||||||
console.log('\n---\n');
|
|
||||||
console.log(progress);
|
|
||||||
} catch {
|
|
||||||
// No progress file yet
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch {
|
|
||||||
// Session not found — try listing all sessions
|
|
||||||
console.log(`Session "${sessionId}" not found.\n`);
|
|
||||||
listSessions();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 4: List All Sessions
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function listSessions() {
|
|
||||||
const sessionFiles = Glob('.workflow/.idaw/sessions/IDA-*/session.json') || [];
|
|
||||||
|
|
||||||
if (sessionFiles.length === 0) {
|
|
||||||
console.log('No IDAW sessions found. Use /idaw:run to start execution.');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('# IDAW Sessions\n');
|
|
||||||
console.log('| Session ID | Status | Tasks | Completed | Failed |');
|
|
||||||
console.log('|------------|--------|-------|-----------|--------|');
|
|
||||||
|
|
||||||
for (const f of sessionFiles) {
|
|
||||||
try {
|
|
||||||
const session = JSON.parse(Read(f));
|
|
||||||
console.log(`| ${session.session_id} | ${session.status} | ${session.tasks?.length || 0} | ${session.completed?.length || 0} | ${session.failed?.length || 0} |`);
|
|
||||||
} catch {
|
|
||||||
// Skip malformed
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
console.log('\nUse /idaw:status <session-id> for details.');
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Examples
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Show overview (pending tasks + latest session)
|
|
||||||
/idaw:status
|
|
||||||
|
|
||||||
# Show specific session details
|
|
||||||
/idaw:status IDA-auth-fix-20260301
|
|
||||||
|
|
||||||
# Output example:
|
|
||||||
# IDAW Tasks
|
|
||||||
#
|
|
||||||
# | ID | Title | Type | Priority | Status |
|
|
||||||
# |----------|------------------------------------|--------|----------|-----------|
|
|
||||||
# | IDAW-001 | Fix auth token refresh | bugfix | 1 | completed |
|
|
||||||
# | IDAW-002 | Add rate limiting | feature| 2 | pending |
|
|
||||||
# | IDAW-003 | Refactor payment module | refact | 3 | pending |
|
|
||||||
#
|
|
||||||
# Total: 3 | Pending: 2 | Completed: 1 | Failed: 0
|
|
||||||
```
|
|
||||||
811
.claude/commands/workflow-tune.md
Normal file
811
.claude/commands/workflow-tune.md
Normal file
@@ -0,0 +1,811 @@
|
|||||||
|
---
|
||||||
|
name: workflow-tune
|
||||||
|
description: Workflow tuning - extract commands from reference docs or natural language, execute each via ccw cli --tool claude --mode write, then analyze artifacts via gemini. For testing how commands execute in Claude.
|
||||||
|
argument-hint: "<file-path> <intent> | \"step1 | step2 | step3\" | \"skill-a,skill-b\" | --file workflow.json [--depth quick|standard|deep] [-y|--yes] [--auto-fix]"
|
||||||
|
allowed-tools: Agent(*), AskUserQuestion(*), TaskCreate(*), TaskUpdate(*), TaskList(*), Read(*), Write(*), Edit(*), Bash(*), Glob(*), Grep(*)
|
||||||
|
---
|
||||||
|
|
||||||
|
# Workflow Tune
|
||||||
|
|
||||||
|
测试 Claude command/skill 的执行效果并优化。提取可执行命令,逐步通过 `ccw cli --tool claude` 执行,分析产物质量,生成优化建议。
|
||||||
|
|
||||||
|
## Tool Assignment
|
||||||
|
|
||||||
|
| Phase | Tool | Mode | Rule |
|
||||||
|
|-------|------|------|------|
|
||||||
|
| Execute | `claude` | `write` | `universal-rigorous-style` |
|
||||||
|
| Analyze | `gemini` | `analysis` | `analysis-review-code-quality` |
|
||||||
|
| Synthesize | `gemini` | `analysis` | `analysis-review-architecture` |
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
```
|
||||||
|
Input → Parse → GenTestTask → Confirm → Setup → [resolveCmd → readMeta → assemblePrompt → Execute → STOP → Analyze → STOP]×N → Synthesize → STOP → Report
|
||||||
|
↑ ↑
|
||||||
|
Claude 直接生成测试任务 prompt 中注入 test_task
|
||||||
|
(无需 CLI 调用) 作为命令的执行输入
|
||||||
|
```
|
||||||
|
|
||||||
|
## Input Formats
|
||||||
|
|
||||||
|
```
|
||||||
|
1. --file workflow.json → JSON definition
|
||||||
|
2. "cmd1 | cmd2 | cmd3" → pipe-separated commands
|
||||||
|
3. "skill-a,skill-b,skill-c" → comma-separated skills
|
||||||
|
4. natural language → semantic decomposition
|
||||||
|
4a: <file-path> <intent> → extract commands from reference doc via LLM
|
||||||
|
4b: <pure intent text> → intent-verb matching → ccw cli command assembly
|
||||||
|
```
|
||||||
|
|
||||||
|
**ANTI-PATTERN**: Steps like `{ command: "分析 Phase 管线" }` are WRONG — descriptions, not commands. Correct: `{ command: "/workflow-lite-plan analyze auth module" }` or `{ command: "ccw cli -p '...' --tool claude --mode write" }`
|
||||||
|
|
||||||
|
## Utility: Shell Escaping
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function escapeForShell(str) {
|
||||||
|
// Replace single quotes with escaped version, wrap in single quotes
|
||||||
|
return "'" + str.replace(/'/g, "'\\''") + "'";
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 1: Setup
|
||||||
|
|
||||||
|
### Step 1.1: Parse Input + Preference Collection
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const args = $ARGUMENTS.trim();
|
||||||
|
const autoYes = /\b(-y|--yes)\b/.test(args);
|
||||||
|
|
||||||
|
// Preference collection (skip if -y)
|
||||||
|
if (autoYes) {
|
||||||
|
workflowPreferences = { autoYes: true, analysisDepth: 'standard', autoFix: false };
|
||||||
|
} else {
|
||||||
|
const prefResponse = AskUserQuestion({
|
||||||
|
questions: [
|
||||||
|
{ question: "选择调优配置:", header: "Tune Config", multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Quick (轻量分析)", description: "每步简要检查" },
|
||||||
|
{ label: "Standard (标准分析) (Recommended)", description: "每步详细分析" },
|
||||||
|
{ label: "Deep (深度分析)", description: "深度审查含架构建议" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{ question: "是否自动应用优化建议?", header: "Auto Fix", multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "No (仅报告) (Recommended)", description: "只分析不修改" },
|
||||||
|
{ label: "Yes (自动应用)", description: "自动应用高优先级建议" }
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
});
|
||||||
|
const depthMap = { "Quick": "quick", "Standard": "standard", "Deep": "deep" };
|
||||||
|
const selectedDepth = Object.keys(depthMap).find(k => prefResponse["Tune Config"].startsWith(k)) || "Standard";
|
||||||
|
workflowPreferences = {
|
||||||
|
autoYes: false,
|
||||||
|
analysisDepth: depthMap[selectedDepth],
|
||||||
|
autoFix: prefResponse["Auto Fix"].startsWith("Yes")
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse --depth override
|
||||||
|
const depthMatch = args.match(/--depth\s+(quick|standard|deep)/);
|
||||||
|
if (depthMatch) workflowPreferences.analysisDepth = depthMatch[1];
|
||||||
|
|
||||||
|
// ── Format Detection ──
|
||||||
|
let steps = [], workflowName = 'unnamed-workflow', inputFormat = '';
|
||||||
|
let projectScenario = ''; // ★ 统一虚构项目场景,所有步骤共享(在 Step 1.1a 生成)
|
||||||
|
|
||||||
|
const fileMatch = args.match(/--file\s+"?([^\s"]+)"?/);
|
||||||
|
if (fileMatch) {
|
||||||
|
const wfDef = JSON.parse(Read(fileMatch[1]));
|
||||||
|
workflowName = wfDef.name || 'unnamed-workflow';
|
||||||
|
projectScenario = wfDef.project_scenario || wfDef.description || '';
|
||||||
|
steps = wfDef.steps;
|
||||||
|
inputFormat = 'json';
|
||||||
|
}
|
||||||
|
else if (args.includes('|')) {
|
||||||
|
const rawSteps = args.split(/(?:--context|--depth|-y|--yes|--auto-fix)\s+("[^"]*"|\S+)/)[0];
|
||||||
|
steps = rawSteps.split('|').map((cmd, i) => ({
|
||||||
|
name: `step-${i + 1}`,
|
||||||
|
command: cmd.trim(),
|
||||||
|
expected_artifacts: [], success_criteria: ''
|
||||||
|
}));
|
||||||
|
inputFormat = 'pipe';
|
||||||
|
}
|
||||||
|
else if (/^[\w-]+(,[\w-]+)+/.test(args.split(/\s/)[0])) {
|
||||||
|
const skillNames = args.match(/^([^\s]+)/)[1].split(',');
|
||||||
|
steps = skillNames.map(name => ({
|
||||||
|
name, command: `/${name}`,
|
||||||
|
expected_artifacts: [], success_criteria: ''
|
||||||
|
}));
|
||||||
|
inputFormat = 'skills';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
inputFormat = 'natural-language';
|
||||||
|
let naturalLanguageInput = args.replace(/--\w+\s+"[^"]*"/g, '').replace(/--\w+\s+\S+/g, '').replace(/-y|--yes/g, '').trim();
|
||||||
|
const filePathPattern = /(?:[A-Za-z]:[\\\/][^\s,;]+|\/[^\s,;]+\.(?:md|txt|json|yaml|yml|toml)|\.\/?[^\s,;]+\.(?:md|txt|json|yaml|yml|toml))/g;
|
||||||
|
const detectedPaths = naturalLanguageInput.match(filePathPattern) || [];
|
||||||
|
let referenceDocContent = null, referenceDocPath = null;
|
||||||
|
if (detectedPaths.length > 0) {
|
||||||
|
referenceDocPath = detectedPaths[0];
|
||||||
|
try {
|
||||||
|
referenceDocContent = Read(referenceDocPath);
|
||||||
|
naturalLanguageInput = naturalLanguageInput.replace(referenceDocPath, '').trim();
|
||||||
|
} catch (e) { referenceDocContent = null; }
|
||||||
|
}
|
||||||
|
// → Mode 4a/4b in Step 1.1b
|
||||||
|
}
|
||||||
|
|
||||||
|
// workflowContext 已移除 — 统一使用 projectScenario(在 Step 1.1a 生成)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.1a: Generate Test Task (测试任务直接生成)
|
||||||
|
|
||||||
|
> **核心概念**: 所有步骤共享一个**统一虚构项目场景**(如"在线书店网站"),每个命令根据自身能力获得该场景下的一个子任务。由当前 Claude 直接生成,不需要额外 CLI 调用。所有执行在独立沙箱目录中进行,不影响真实项目。
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ★ 测试任务直接生成 — 无需 CLI 调用
|
||||||
|
// 来源优先级:
|
||||||
|
// 1. JSON 定义中的 step.test_task 字段 (已有则跳过)
|
||||||
|
// 2. 当前 Claude 直接生成
|
||||||
|
|
||||||
|
const stepsNeedTask = steps.filter(s => !s.test_task);
|
||||||
|
|
||||||
|
if (stepsNeedTask.length > 0) {
|
||||||
|
// ── Step A: 生成统一项目场景 ──
|
||||||
|
// 根据命令链的整体复杂度,选一个虚构项目作为测试场景
|
||||||
|
// 场景必须:完全虚构、与当前工作空间无关、足够支撑所有步骤
|
||||||
|
//
|
||||||
|
// 场景池示例(根据步骤数量和类型选择合适规模):
|
||||||
|
// 1-2 步: 小型项目 — "命令行 TODO 工具" "Markdown 转 HTML 工具" "天气查询 CLI"
|
||||||
|
// 3-4 步: 中型项目 — "在线书店网站" "团队任务看板" "博客系统"
|
||||||
|
// 5+ 步: 大型项目 — "多租户 SaaS 平台" "电商系统" "在线教育平台"
|
||||||
|
|
||||||
|
projectScenario = /* Claude 从上述池中选择或自创一个场景 */;
|
||||||
|
// 例如: "在线书店网站 — 支持用户注册登录、书籍搜索浏览、购物车、订单管理、评论系统"
|
||||||
|
|
||||||
|
// ── Step B: 为每步生成子任务 ──
|
||||||
|
for (const step of stepsNeedTask) {
|
||||||
|
const cmdFile = resolveCommandFile(step.command);
|
||||||
|
const cmdMeta = readCommandMeta(cmdFile);
|
||||||
|
const cmdDesc = (cmdMeta?.description || step.command).toLowerCase();
|
||||||
|
|
||||||
|
// 根据命令类型分配场景下的子任务
|
||||||
|
// 每个子任务必须按以下模板生成:
|
||||||
|
//
|
||||||
|
// ┌─────────────────────────────────────────────────┐
|
||||||
|
// │ 项目: {projectScenario} │
|
||||||
|
// │ 任务: {具体子任务描述} │
|
||||||
|
// │ 功能点: │
|
||||||
|
// │ 1. {功能点1 — 具体到接口/组件/模块} │
|
||||||
|
// │ 2. {功能点2} │
|
||||||
|
// │ 3. {功能点3} │
|
||||||
|
// │ 技术约束: {语言/框架/架构要求} │
|
||||||
|
// │ 验收标准: │
|
||||||
|
// │ 1. {可验证的标准1} │
|
||||||
|
// │ 2. {可验证的标准2} │
|
||||||
|
// └─────────────────────────────────────────────────┘
|
||||||
|
//
|
||||||
|
// 命令类型 → 子任务映射:
|
||||||
|
// plan/design → 架构设计任务: "为{场景}设计技术架构,包含模块划分、数据模型、API 设计"
|
||||||
|
// implement → 功能实现任务: "实现{场景}的{某模块},包含{具体功能点}"
|
||||||
|
// analyze/review→ 代码分析任务: "先在沙箱创建{场景}的{某模块}示例代码,然后分析其质量"
|
||||||
|
// test → 测试任务: "为{场景}的{某模块}编写测试,覆盖{具体场景}"
|
||||||
|
// fix/debug → 修复任务: "先在沙箱创建含已知 bug 的代码,然后诊断修复"
|
||||||
|
// refactor → 重构任务: "先在沙箱创建可工作但需重构的代码,然后重构"
|
||||||
|
|
||||||
|
step.test_task = /* 按上述模板生成,必须包含:项目、任务、功能点、技术约束、验收标准 */;
|
||||||
|
step.acceptance_criteria = /* 从 test_task 中提取 2-4 条可验证标准 */;
|
||||||
|
step.complexity_level = /plan|design|architect/i.test(cmdDesc) ? 'high'
|
||||||
|
: /test|lint|format/i.test(cmdDesc) ? 'low' : 'medium';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**模拟示例** — 输入 `workflow-lite-plan,workflow-lite-execute`:
|
||||||
|
|
||||||
|
```
|
||||||
|
场景: 在线书店网站 — 支持用户注册登录、书籍搜索、购物车、订单管理
|
||||||
|
|
||||||
|
Step 1 (workflow-lite-plan → plan 类, high):
|
||||||
|
项目: 在线书店网站
|
||||||
|
任务: 为在线书店设计技术架构和实现计划
|
||||||
|
功能点:
|
||||||
|
1. 用户模块 — 注册、登录、个人信息管理
|
||||||
|
2. 书籍模块 — 搜索、分类浏览、详情页
|
||||||
|
3. 交易模块 — 购物车、下单、支付状态
|
||||||
|
4. 数据模型 — User, Book, Order, CartItem 表结构设计
|
||||||
|
技术约束: TypeScript + Express + SQLite, REST API
|
||||||
|
验收标准:
|
||||||
|
1. 输出包含模块划分和依赖关系
|
||||||
|
2. 包含数据模型定义
|
||||||
|
3. 包含 API 路由清单
|
||||||
|
4. 包含实现步骤分解
|
||||||
|
|
||||||
|
Step 2 (workflow-lite-execute → implement 类, medium):
|
||||||
|
项目: 在线书店网站
|
||||||
|
任务: 根据 Step 1 的计划,实现书籍搜索和浏览模块
|
||||||
|
功能点:
|
||||||
|
1. GET /api/books — 分页列表,支持按标题/作者搜索
|
||||||
|
2. GET /api/books/:id — 书籍详情
|
||||||
|
3. GET /api/categories — 分类列表
|
||||||
|
4. Book 数据模型 + seed 数据
|
||||||
|
技术约束: TypeScript + Express + SQLite, 沿用 Step 1 架构
|
||||||
|
验收标准:
|
||||||
|
1. API 可正常调用返回 JSON
|
||||||
|
2. 搜索支持模糊匹配
|
||||||
|
3. 包含至少 5 条 seed 数据
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.1b: Semantic Decomposition (Format 4 only)
|
||||||
|
|
||||||
|
#### Mode 4a: Reference Document → LLM Extraction
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (inputFormat === 'natural-language' && referenceDocContent) {
|
||||||
|
const extractPrompt = `PURPOSE: Extract ACTUAL EXECUTABLE COMMANDS from the reference document. The user wants to TEST these commands by running them.
|
||||||
|
|
||||||
|
USER INTENT: ${naturalLanguageInput}
|
||||||
|
REFERENCE DOCUMENT: ${referenceDocPath}
|
||||||
|
|
||||||
|
DOCUMENT CONTENT:
|
||||||
|
${referenceDocContent}
|
||||||
|
|
||||||
|
CRITICAL RULES:
|
||||||
|
- "command" field MUST be a real executable: slash command (/skill-name args), ccw cli call, or shell command
|
||||||
|
- CORRECT: { "command": "/workflow-lite-plan analyze auth module" }
|
||||||
|
- CORRECT: { "command": "ccw cli -p 'review code' --tool claude --mode write" }
|
||||||
|
- WRONG: { "command": "分析 Phase 管线" } ← DESCRIPTION, not command
|
||||||
|
- Default mode to "write"
|
||||||
|
|
||||||
|
EXPECTED OUTPUT (strict JSON):
|
||||||
|
{
|
||||||
|
"workflow_name": "<name>",
|
||||||
|
"project_scenario": "<虚构项目场景>",
|
||||||
|
"steps": [{ "name": "", "command": "<executable>", "expected_artifacts": [], "success_criteria": "" }]
|
||||||
|
}`;
|
||||||
|
|
||||||
|
Bash({
|
||||||
|
command: `ccw cli -p ${escapeForShell(extractPrompt)} --tool claude --mode write --rule universal-rigorous-style`,
|
||||||
|
run_in_background: true, timeout: 300000
|
||||||
|
});
|
||||||
|
// ■ STOP — wait for hook callback, parse JSON → steps[]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Mode 4b: Pure Intent → Command Assembly
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
if (inputFormat === 'natural-language' && !referenceDocContent) {
|
||||||
|
// Intent → rule mapping for ccw cli command generation
|
||||||
|
const intentMap = [
|
||||||
|
{ pattern: /分析|analyze|审查|inspect|scan/i, name: 'analyze', rule: 'analysis-analyze-code-patterns' },
|
||||||
|
{ pattern: /评审|review|code.?review/i, name: 'review', rule: 'analysis-review-code-quality' },
|
||||||
|
{ pattern: /诊断|debug|排查|diagnose/i, name: 'diagnose', rule: 'analysis-diagnose-bug-root-cause' },
|
||||||
|
{ pattern: /安全|security|漏洞/i, name: 'security-audit', rule: 'analysis-assess-security-risks' },
|
||||||
|
{ pattern: /性能|performance|perf/i, name: 'perf-analysis', rule: 'analysis-analyze-performance' },
|
||||||
|
{ pattern: /架构|architecture/i, name: 'arch-review', rule: 'analysis-review-architecture' },
|
||||||
|
{ pattern: /修复|fix|repair|解决/i, name: 'fix', rule: 'development-debug-runtime-issues' },
|
||||||
|
{ pattern: /实现|implement|开发|create|新增/i, name: 'implement', rule: 'development-implement-feature' },
|
||||||
|
{ pattern: /重构|refactor/i, name: 'refactor', rule: 'development-refactor-codebase' },
|
||||||
|
{ pattern: /测试|test/i, name: 'test', rule: 'development-generate-tests' },
|
||||||
|
{ pattern: /规划|plan|设计|design/i, name: 'plan', rule: 'planning-plan-architecture-design' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const segments = naturalLanguageInput
|
||||||
|
.split(/[,,;;、]|(?:然后|接着|之后|最后|再|并|and then|then|finally|next)\s*/i)
|
||||||
|
.map(s => s.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
// ★ 将意图文本转化为完整的 ccw cli 命令
|
||||||
|
steps = segments.map((segment, i) => {
|
||||||
|
const matched = intentMap.find(m => m.pattern.test(segment));
|
||||||
|
const rule = matched?.rule || 'universal-rigorous-style';
|
||||||
|
// 组装真正可执行的命令
|
||||||
|
const command = `ccw cli -p ${escapeForShell('PURPOSE: ' + segment + '\\nTASK: Execute based on intent\\nCONTEXT: @**/*')} --tool claude --mode write --rule ${rule}`;
|
||||||
|
return {
|
||||||
|
name: matched?.name || `step-${i + 1}`,
|
||||||
|
command,
|
||||||
|
original_intent: segment, // 保留原始意图用于分析
|
||||||
|
expected_artifacts: [], success_criteria: ''
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.1c: Execution Plan Confirmation
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function generateCommandDoc(steps, workflowName, projectScenario, analysisDepth) {
|
||||||
|
const stepTable = steps.map((s, i) => {
|
||||||
|
const cmdPreview = s.command.length > 60 ? s.command.substring(0, 57) + '...' : s.command;
|
||||||
|
const taskPreview = (s.test_task || '-').length > 40 ? s.test_task.substring(0, 37) + '...' : (s.test_task || '-');
|
||||||
|
return `| ${i + 1} | ${s.name} | \`${cmdPreview}\` | ${taskPreview} |`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
return `# Workflow Tune — Execution Plan\n\n**Workflow**: ${workflowName}\n**Test Project**: ${projectScenario}\n**Steps**: ${steps.length}\n**Depth**: ${analysisDepth}\n\n| # | Name | Command | Test Task |\n|---|------|---------|-----------|\n${stepTable}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
const commandDoc = generateCommandDoc(steps, workflowName, projectScenario, workflowPreferences.analysisDepth);
|
||||||
|
|
||||||
|
if (!workflowPreferences.autoYes) {
|
||||||
|
const confirmation = AskUserQuestion({
|
||||||
|
questions: [{
|
||||||
|
question: commandDoc + "\n\n确认执行以上 Workflow 调优计划?", header: "Confirm Execution", multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Execute (确认执行)", description: "按计划开始执行" },
|
||||||
|
{ label: "Cancel (取消)", description: "取消" }
|
||||||
|
]
|
||||||
|
}]
|
||||||
|
});
|
||||||
|
if (confirmation["Confirm Execution"].startsWith("Cancel")) return;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 1.2: (Merged into Step 1.1a)
|
||||||
|
|
||||||
|
> Test requirements (acceptance_criteria) are now generated together with test_task in Step 1.1a, avoiding an extra CLI call.
|
||||||
|
|
||||||
|
### Step 1.3: Create Workspace + Sandbox Project
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const ts = Date.now();
|
||||||
|
const workDir = `.workflow/.scratchpad/workflow-tune-${ts}`;
|
||||||
|
|
||||||
|
// ★ 创建独立沙箱项目目录 — 所有命令执行在此目录中,不影响真实项目
|
||||||
|
const sandboxDir = `${workDir}/sandbox`;
|
||||||
|
Bash(`mkdir -p "${workDir}/steps" "${sandboxDir}"`);
|
||||||
|
// 初始化沙箱为独立 git 仓库(部分命令依赖 git 环境)
|
||||||
|
Bash(`cd "${sandboxDir}" && git init && echo "# Sandbox Project" > README.md && git add . && git commit -m "init sandbox"`);
|
||||||
|
|
||||||
|
for (let i = 0; i < steps.length; i++) Bash(`mkdir -p "${workDir}/steps/step-${i + 1}/artifacts"`);
|
||||||
|
|
||||||
|
Write(`${workDir}/command-doc.md`, commandDoc);
|
||||||
|
|
||||||
|
const initialState = {
|
||||||
|
status: 'running', started_at: new Date().toISOString(),
|
||||||
|
workflow_name: workflowName, project_scenario: projectScenario,
|
||||||
|
analysis_depth: workflowPreferences.analysisDepth, auto_fix: workflowPreferences.autoFix,
|
||||||
|
sandbox_dir: sandboxDir, // ★ 独立沙箱项目目录
|
||||||
|
current_step: 0, // ★ State machine cursor
|
||||||
|
current_phase: 'execute', // 'execute' | 'analyze'
|
||||||
|
steps: steps.map((s, i) => ({
|
||||||
|
...s, index: i, status: 'pending',
|
||||||
|
test_task: s.test_task || '', // ★ 每步的测试任务
|
||||||
|
execution: null, analysis: null,
|
||||||
|
test_requirements: s.test_requirements || null
|
||||||
|
})),
|
||||||
|
gemini_session_id: null, // ★ Updated after each gemini callback
|
||||||
|
work_dir: workDir,
|
||||||
|
errors: [], error_count: 0, max_errors: 3
|
||||||
|
};
|
||||||
|
|
||||||
|
Write(`${workDir}/workflow-state.json`, JSON.stringify(initialState, null, 2));
|
||||||
|
Write(`${workDir}/process-log.md`, `# Process Log\n\n**Workflow**: ${workflowName}\n**Test Project**: ${projectScenario}\n**Steps**: ${steps.length}\n**Started**: ${new Date().toISOString()}\n\n---\n\n`);
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 2: Execute Step
|
||||||
|
|
||||||
|
### resolveCommandFile — Slash command → file path
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function resolveCommandFile(command) {
|
||||||
|
const cmdMatch = command.match(/^\/?([^\s]+)/);
|
||||||
|
if (!cmdMatch) return null;
|
||||||
|
const cmdName = cmdMatch[1];
|
||||||
|
const cmdPath = cmdName.replace(/:/g, '/');
|
||||||
|
|
||||||
|
const searchRoots = ['.claude', '~/.claude'];
|
||||||
|
|
||||||
|
for (const root of searchRoots) {
|
||||||
|
const candidates = [
|
||||||
|
`${root}/commands/${cmdPath}.md`,
|
||||||
|
`${root}/commands/${cmdPath}/index.md`,
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try { Read(candidate, { limit: 1 }); return candidate; } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const root of searchRoots) {
|
||||||
|
const candidates = [
|
||||||
|
`${root}/skills/${cmdName}/SKILL.md`,
|
||||||
|
`${root}/skills/${cmdPath.replace(/\//g, '-')}/SKILL.md`,
|
||||||
|
];
|
||||||
|
for (const candidate of candidates) {
|
||||||
|
try { Read(candidate, { limit: 1 }); return candidate; } catch {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### readCommandMeta — Read YAML frontmatter + body summary
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function readCommandMeta(filePath) {
|
||||||
|
if (!filePath) return null;
|
||||||
|
|
||||||
|
const content = Read(filePath);
|
||||||
|
const meta = { filePath, name: '', description: '', argumentHint: '', allowedTools: '', bodySummary: '' };
|
||||||
|
|
||||||
|
const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
|
||||||
|
if (yamlMatch) {
|
||||||
|
const yaml = yamlMatch[1];
|
||||||
|
const nameMatch = yaml.match(/^name:\s*(.+)$/m);
|
||||||
|
const descMatch = yaml.match(/^description:\s*(.+)$/m);
|
||||||
|
const hintMatch = yaml.match(/^argument-hint:\s*"?(.+?)"?\s*$/m);
|
||||||
|
const toolsMatch = yaml.match(/^allowed-tools:\s*(.+)$/m);
|
||||||
|
|
||||||
|
if (nameMatch) meta.name = nameMatch[1].trim();
|
||||||
|
if (descMatch) meta.description = descMatch[1].trim();
|
||||||
|
if (hintMatch) meta.argumentHint = hintMatch[1].trim();
|
||||||
|
if (toolsMatch) meta.allowedTools = toolsMatch[1].trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const bodyStart = content.indexOf('---', content.indexOf('---') + 3);
|
||||||
|
if (bodyStart !== -1) {
|
||||||
|
const body = content.substring(bodyStart + 3).trim();
|
||||||
|
meta.bodySummary = body.split('\n').slice(0, 30).join('\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
return meta;
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### assembleStepPrompt — Build execution prompt from command metadata
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function assembleStepPrompt(step, stepIdx, state) {
|
||||||
|
// ── 1. Resolve command file + metadata ──
|
||||||
|
const isSlashCmd = step.command.startsWith('/');
|
||||||
|
const cmdFile = isSlashCmd ? resolveCommandFile(step.command) : null;
|
||||||
|
const cmdMeta = readCommandMeta(cmdFile);
|
||||||
|
const cmdArgs = isSlashCmd ? step.command.replace(/^\/?[^\s]+\s*/, '').trim() : '';
|
||||||
|
|
||||||
|
// ── 2. Prior/next step context ──
|
||||||
|
const prevStep = stepIdx > 0 ? state.steps[stepIdx - 1] : null;
|
||||||
|
const nextStep = stepIdx < state.steps.length - 1 ? state.steps[stepIdx + 1] : null;
|
||||||
|
|
||||||
|
const priorContext = prevStep
|
||||||
|
? `PRIOR STEP: "${prevStep.name}" — ${prevStep.command}\n Status: ${prevStep.status} | Artifacts: ${prevStep.execution?.artifact_count || 0}`
|
||||||
|
: 'PRIOR STEP: None (first step)';
|
||||||
|
|
||||||
|
const nextContext = nextStep
|
||||||
|
? `NEXT STEP: "${nextStep.name}" — ${nextStep.command}\n Ensure output is consumable by next step`
|
||||||
|
: 'NEXT STEP: None (last step)';
|
||||||
|
|
||||||
|
// ── 3. Acceptance criteria (from test_task generation) ──
|
||||||
|
const criteria = step.acceptance_criteria || [];
|
||||||
|
const testReqSection = criteria.length > 0
|
||||||
|
? `ACCEPTANCE CRITERIA:\n${criteria.map((c, i) => ` ${i + 1}. ${c}`).join('\n')}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// ── 4. Test task — the concrete scenario to drive execution ──
|
||||||
|
const testTask = step.test_task || '';
|
||||||
|
const testTaskSection = testTask
|
||||||
|
? `TEST TASK (用此任务驱动命令执行):\n ${testTask}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
// ── 5. Build prompt based on whether command has metadata ──
|
||||||
|
if (cmdMeta) {
|
||||||
|
// Slash command with resolved file — rich context prompt
|
||||||
|
return `PURPOSE: Execute workflow step "${step.name}" (${stepIdx + 1}/${state.steps.length}).
|
||||||
|
|
||||||
|
COMMAND DEFINITION:
|
||||||
|
Name: ${cmdMeta.name}
|
||||||
|
Description: ${cmdMeta.description}
|
||||||
|
Argument Format: ${cmdMeta.argumentHint || 'none'}
|
||||||
|
Allowed Tools: ${cmdMeta.allowedTools || 'default'}
|
||||||
|
Source: ${cmdMeta.filePath}
|
||||||
|
|
||||||
|
COMMAND TO EXECUTE: ${step.command}
|
||||||
|
ARGUMENTS: ${cmdArgs || '(no arguments)'}
|
||||||
|
|
||||||
|
${testTaskSection}
|
||||||
|
|
||||||
|
COMMAND REFERENCE (first 30 lines):
|
||||||
|
${cmdMeta.bodySummary}
|
||||||
|
|
||||||
|
PROJECT: ${state.project_scenario}
|
||||||
|
SANDBOX PROJECT: ${state.sandbox_dir}
|
||||||
|
OUTPUT DIR: ${state.work_dir}/steps/step-${stepIdx + 1}
|
||||||
|
|
||||||
|
${priorContext}
|
||||||
|
${nextContext}
|
||||||
|
${testReqSection}
|
||||||
|
|
||||||
|
TASK: Execute the command as described in COMMAND DEFINITION, using TEST TASK as the input/scenario. Use the COMMAND REFERENCE to understand expected behavior. All work happens in the SANDBOX PROJECT directory (an isolated empty project, NOT the real workspace). Auto-confirm all prompts.
|
||||||
|
CONSTRAINTS: Stay scoped to this step only. Follow the command's own execution flow. The TEST TASK is the real work — treat it as the $ARGUMENTS input to the command. Do NOT read/modify files outside SANDBOX PROJECT.`;
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Shell command, ccw cli command, or unresolved command
|
||||||
|
return `PURPOSE: Execute workflow step "${step.name}" (${stepIdx + 1}/${state.steps.length}).
|
||||||
|
COMMAND: ${step.command}
|
||||||
|
${testTaskSection}
|
||||||
|
PROJECT: ${state.project_scenario}
|
||||||
|
SANDBOX PROJECT: ${state.sandbox_dir}
|
||||||
|
OUTPUT DIR: ${state.work_dir}/steps/step-${stepIdx + 1}
|
||||||
|
|
||||||
|
${priorContext}
|
||||||
|
${nextContext}
|
||||||
|
${testReqSection}
|
||||||
|
|
||||||
|
TASK: Execute the COMMAND above with TEST TASK as the input scenario. All work happens in the SANDBOX PROJECT directory (an isolated empty project). Auto-confirm all prompts.
|
||||||
|
CONSTRAINTS: Stay scoped to this step only. The TEST TASK is the real work to execute. Do NOT read/modify files outside SANDBOX PROJECT.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step Execution
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const stepIdx = state.current_step;
|
||||||
|
const step = state.steps[stepIdx];
|
||||||
|
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;
|
||||||
|
|
||||||
|
// Pre-execution: snapshot sandbox directory files
|
||||||
|
const preFiles = Bash(`find "${state.sandbox_dir}" -type f 2>/dev/null | sort`).stdout.trim();
|
||||||
|
Write(`${stepDir}/pre-exec-snapshot.txt`, preFiles || '(empty)');
|
||||||
|
|
||||||
|
const startTime = Date.now();
|
||||||
|
const prompt = assembleStepPrompt(step, stepIdx, state);
|
||||||
|
|
||||||
|
// ★ All steps execute via ccw cli --tool claude --mode write
|
||||||
|
// ★ --cd 指向沙箱目录(独立项目),不影响真实工作空间
|
||||||
|
Bash({
|
||||||
|
command: `ccw cli -p ${escapeForShell(prompt)} --tool claude --mode write --rule universal-rigorous-style --cd "${state.sandbox_dir}"`,
|
||||||
|
run_in_background: true, timeout: 600000
|
||||||
|
});
|
||||||
|
// ■ STOP — wait for hook callback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post-Execute Callback Handler
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ★ This runs after receiving the ccw cli callback
|
||||||
|
|
||||||
|
const duration = Date.now() - startTime;
|
||||||
|
|
||||||
|
// Collect artifacts by scanning sandbox (not git diff — sandbox is an independent project)
|
||||||
|
const postFiles = Bash(`find "${state.sandbox_dir}" -type f -newer "${stepDir}/pre-exec-snapshot.txt" 2>/dev/null | sort`).stdout.trim();
|
||||||
|
const newArtifacts = postFiles ? postFiles.split('\n').filter(f => !f.endsWith('.git/')) : [];
|
||||||
|
|
||||||
|
const artifactManifest = {
|
||||||
|
step: step.name, step_index: stepIdx,
|
||||||
|
success: true, duration_ms: duration,
|
||||||
|
artifacts: newArtifacts.map(f => ({
|
||||||
|
path: f,
|
||||||
|
type: f.endsWith('.md') ? 'markdown' : f.endsWith('.json') ? 'json' : 'other'
|
||||||
|
})),
|
||||||
|
collected_at: new Date().toISOString()
|
||||||
|
};
|
||||||
|
Write(`${stepDir}/artifacts-manifest.json`, JSON.stringify(artifactManifest, null, 2));
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
state.steps[stepIdx].status = 'executed';
|
||||||
|
state.steps[stepIdx].execution = {
|
||||||
|
success: true, duration_ms: duration,
|
||||||
|
artifact_count: newArtifacts.length
|
||||||
|
};
|
||||||
|
state.current_phase = 'analyze';
|
||||||
|
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
|
// → Proceed to Phase 3 for this step
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 3: Analyze Step (per step, via gemini)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const manifest = JSON.parse(Read(`${stepDir}/artifacts-manifest.json`));
|
||||||
|
|
||||||
|
// Build artifact content for analysis
|
||||||
|
let artifactSummary = '';
|
||||||
|
if (state.analysis_depth === 'quick') {
|
||||||
|
artifactSummary = manifest.artifacts.map(a => `- ${a.path} (${a.type})`).join('\n');
|
||||||
|
} else {
|
||||||
|
const maxLines = state.analysis_depth === 'deep' ? 300 : 150;
|
||||||
|
artifactSummary = manifest.artifacts.map(a => {
|
||||||
|
try { return `--- ${a.path} ---\n${Read(a.path, { limit: maxLines })}`; }
|
||||||
|
catch { return `--- ${a.path} --- [unreadable]`; }
|
||||||
|
}).join('\n\n');
|
||||||
|
}
|
||||||
|
|
||||||
|
const criteria = step.acceptance_criteria || [];
|
||||||
|
const testTaskDesc = step.test_task ? `TEST TASK: ${step.test_task}` : '';
|
||||||
|
const criteriaSection = criteria.length > 0
|
||||||
|
? `ACCEPTANCE CRITERIA:\n${criteria.map((c, i) => ` ${i + 1}. ${c}`).join('\n')}`
|
||||||
|
: '';
|
||||||
|
|
||||||
|
const analysisPrompt = `PURPOSE: Evaluate execution quality of step "${step.name}" (${stepIdx + 1}/${state.steps.length}).
|
||||||
|
WORKFLOW: ${state.workflow_name} — ${state.project_scenario}
|
||||||
|
COMMAND: ${step.command}
|
||||||
|
${testTaskDesc}
|
||||||
|
${criteriaSection}
|
||||||
|
EXECUTION: Duration ${step.execution.duration_ms}ms | Artifacts: ${manifest.artifacts.length}
|
||||||
|
ARTIFACTS:\n${artifactSummary}
|
||||||
|
EXPECTED OUTPUT (strict JSON):
|
||||||
|
{ "quality_score": <0-100>, "requirement_match": { "pass": <bool>, "criteria_met": [], "criteria_missed": [], "fail_signals_detected": [] }, "execution_assessment": { "success": <bool>, "completeness": "", "notes": "" }, "artifact_assessment": { "count": <n>, "quality": "", "key_outputs": [], "missing_outputs": [] }, "issues": [{ "severity": "critical|high|medium|low", "description": "", "suggestion": "" }], "optimization_opportunities": [{ "area": "", "description": "", "impact": "high|medium|low" }], "step_summary": "" }`;
|
||||||
|
|
||||||
|
let cliCommand = `ccw cli -p ${escapeForShell(analysisPrompt)} --tool gemini --mode analysis --rule analysis-review-code-quality`;
|
||||||
|
if (state.gemini_session_id) cliCommand += ` --resume ${state.gemini_session_id}`;
|
||||||
|
Bash({ command: cliCommand, run_in_background: true, timeout: 300000 });
|
||||||
|
// ■ STOP — wait for hook callback
|
||||||
|
```
|
||||||
|
|
||||||
|
### Post-Analyze Callback Handler
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// ★ Parse analysis result JSON from callback
|
||||||
|
const analysisResult = /* parsed from callback output */;
|
||||||
|
|
||||||
|
// ★ Capture gemini session ID for resume chain
|
||||||
|
// Session ID is in stderr: [CCW_EXEC_ID=gem-xxxxxx-xxxx]
|
||||||
|
state.gemini_session_id = /* captured from callback exec_id */;
|
||||||
|
|
||||||
|
Write(`${stepDir}/step-${stepIdx + 1}-analysis.json`, JSON.stringify(analysisResult, null, 2));
|
||||||
|
|
||||||
|
// Update state
|
||||||
|
state.steps[stepIdx].analysis = {
|
||||||
|
quality_score: analysisResult.quality_score,
|
||||||
|
requirement_pass: analysisResult.requirement_match?.pass,
|
||||||
|
issue_count: (analysisResult.issues || []).length
|
||||||
|
};
|
||||||
|
state.steps[stepIdx].status = 'completed';
|
||||||
|
|
||||||
|
// Append to process log
|
||||||
|
const logEntry = `## Step ${stepIdx + 1}: ${step.name}\n- Score: ${analysisResult.quality_score}/100\n- Req: ${analysisResult.requirement_match?.pass ? 'PASS' : 'FAIL'}\n- Issues: ${(analysisResult.issues || []).length}\n- Summary: ${analysisResult.step_summary}\n\n`;
|
||||||
|
Edit(`${state.work_dir}/process-log.md`, /* append logEntry */);
|
||||||
|
|
||||||
|
// ★ Advance state machine
|
||||||
|
state.current_step = stepIdx + 1;
|
||||||
|
state.current_phase = 'execute';
|
||||||
|
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
|
// ★ Decision: advance or synthesize
|
||||||
|
if (state.current_step < state.steps.length) {
|
||||||
|
// → Back to Phase 2 for next step
|
||||||
|
} else {
|
||||||
|
// → Phase 4: Synthesize
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step Loop — State Machine
|
||||||
|
|
||||||
|
```
|
||||||
|
NOT a sync for-loop. Each step follows this state machine:
|
||||||
|
|
||||||
|
┌─────────────────────────────────────────────────────┐
|
||||||
|
│ state.current_step = N, state.current_phase = X │
|
||||||
|
├─────────────────────────────────────────────────────┤
|
||||||
|
│ phase='execute' → Phase 2 → ccw cli claude → STOP │
|
||||||
|
│ callback → collect artifacts → phase='analyze' │
|
||||||
|
│ phase='analyze' → Phase 3 → ccw cli gemini → STOP │
|
||||||
|
│ callback → save analysis → current_step++ │
|
||||||
|
│ if current_step < total → phase='execute' (loop) │
|
||||||
|
│ else → Phase 4 (synthesize) │
|
||||||
|
└─────────────────────────────────────────────────────┘
|
||||||
|
|
||||||
|
Error handling:
|
||||||
|
- Execute timeout → retry once, then mark failed, advance
|
||||||
|
- Analyze failure → retry without --resume, then skip analysis
|
||||||
|
- 3+ consecutive errors → terminate, jump to Phase 5 partial report
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 4: Synthesize (via gemini)
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const stepAnalyses = state.steps.map((step, i) => {
|
||||||
|
try { return { step: step.name, content: Read(`${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.json`) }; }
|
||||||
|
catch { return { step: step.name, content: '[Not available]' }; }
|
||||||
|
});
|
||||||
|
|
||||||
|
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
||||||
|
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
||||||
|
|
||||||
|
const synthesisPrompt = `PURPOSE: Synthesize all step analyses into holistic workflow assessment with actionable optimization plan.
|
||||||
|
WORKFLOW: ${state.workflow_name} — ${state.project_scenario}
|
||||||
|
Steps: ${state.steps.length} | Avg Quality: ${avgScore}/100
|
||||||
|
STEP ANALYSES:\n${stepAnalyses.map(a => `### ${a.step}\n${a.content}`).join('\n\n---\n\n')}
|
||||||
|
Evaluate: coherence across steps, handoff quality, redundancy, bottlenecks.
|
||||||
|
EXPECTED OUTPUT (strict JSON):
|
||||||
|
{ "workflow_score": <0-100>, "coherence": { "score": <0-100>, "assessment": "", "gaps": [] }, "bottlenecks": [{ "step": "", "issue": "", "suggestion": "" }], "per_step_improvements": [{ "step": "", "priority": "high|medium|low", "action": "" }], "workflow_improvements": [{ "area": "", "description": "", "impact": "high|medium|low" }], "summary": "" }`;
|
||||||
|
|
||||||
|
let cliCommand = `ccw cli -p ${escapeForShell(synthesisPrompt)} --tool gemini --mode analysis --rule analysis-review-architecture`;
|
||||||
|
if (state.gemini_session_id) cliCommand += ` --resume ${state.gemini_session_id}`;
|
||||||
|
Bash({ command: cliCommand, run_in_background: true, timeout: 300000 });
|
||||||
|
// ■ STOP — wait for hook callback → parse JSON, write synthesis.json, update state
|
||||||
|
```
|
||||||
|
|
||||||
|
## Phase 5: Report
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const synthesis = JSON.parse(Read(`${state.work_dir}/synthesis.json`));
|
||||||
|
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
||||||
|
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
||||||
|
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);
|
||||||
|
|
||||||
|
const stepTable = state.steps.map((s, i) => {
|
||||||
|
const reqStr = s.analysis?.requirement_pass === true ? 'PASS' : s.analysis?.requirement_pass === false ? 'FAIL' : '-';
|
||||||
|
return `| ${i + 1} | ${s.name} | ${s.execution?.success ? 'OK' : 'FAIL'} | ${reqStr} | ${s.analysis?.quality_score || '-'} | ${s.analysis?.issue_count || 0} |`;
|
||||||
|
}).join('\n');
|
||||||
|
|
||||||
|
const improvements = (synthesis.per_step_improvements || [])
|
||||||
|
.filter(imp => imp.priority === 'high')
|
||||||
|
.map(imp => `- **${imp.step}**: ${imp.action}`)
|
||||||
|
.join('\n');
|
||||||
|
|
||||||
|
const report = `# Workflow Tune Report
|
||||||
|
|
||||||
|
| Field | Value |
|
||||||
|
|---|---|
|
||||||
|
| Workflow | ${state.workflow_name} |
|
||||||
|
| Test Project | ${state.project_scenario} |
|
||||||
|
| Workflow Score | ${synthesis.workflow_score || avgScore}/100 |
|
||||||
|
| Avg Step Score | ${avgScore}/100 |
|
||||||
|
| Total Issues | ${totalIssues} |
|
||||||
|
| Coherence | ${synthesis.coherence?.score || '-'}/100 |
|
||||||
|
|
||||||
|
## Step Results
|
||||||
|
|
||||||
|
| # | Step | Exec | Req | Quality | Issues |
|
||||||
|
|---|------|------|-----|---------|--------|
|
||||||
|
${stepTable}
|
||||||
|
|
||||||
|
## High Priority Improvements
|
||||||
|
|
||||||
|
${improvements || 'None'}
|
||||||
|
|
||||||
|
## Workflow-Level Improvements
|
||||||
|
|
||||||
|
${(synthesis.workflow_improvements || []).map(w => `- **${w.area}** (${w.impact}): ${w.description}`).join('\n') || 'None'}
|
||||||
|
|
||||||
|
## Bottlenecks
|
||||||
|
|
||||||
|
${(synthesis.bottlenecks || []).map(b => `- **${b.step}**: ${b.issue} → ${b.suggestion}`).join('\n') || 'None'}
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
|
||||||
|
${synthesis.summary || 'N/A'}
|
||||||
|
`;
|
||||||
|
|
||||||
|
Write(`${state.work_dir}/final-report.md`, report);
|
||||||
|
state.status = 'completed';
|
||||||
|
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
||||||
|
|
||||||
|
// Output report to user
|
||||||
|
```
|
||||||
|
|
||||||
|
## Resume Chain
|
||||||
|
|
||||||
|
```
|
||||||
|
Step 1 Execute → ccw cli claude --mode write --rule universal-rigorous-style --cd step-1/ → STOP → callback → artifacts
|
||||||
|
Step 1 Analyze → ccw cli gemini --mode analysis --rule analysis-review-code-quality → STOP → callback → gemini_session_id = exec_id
|
||||||
|
Step 2 Execute → ccw cli claude --mode write --rule universal-rigorous-style --cd step-2/ → STOP → callback → artifacts
|
||||||
|
Step 2 Analyze → ccw cli gemini --mode analysis --resume gemini_session_id → STOP → callback → gemini_session_id = exec_id
|
||||||
|
...
|
||||||
|
Synthesize → ccw cli gemini --mode analysis --resume gemini_session_id → STOP → callback → synthesis
|
||||||
|
Report → local generation (no CLI call)
|
||||||
|
```
|
||||||
|
|
||||||
|
## Error Handling
|
||||||
|
|
||||||
|
| Phase | Error | Recovery |
|
||||||
|
|-------|-------|----------|
|
||||||
|
| Execute | CLI timeout | Retry once, then mark step failed and advance |
|
||||||
|
| Execute | Command not found | Skip step, note in process-log |
|
||||||
|
| Analyze | CLI fails | Retry without --resume, then skip analysis |
|
||||||
|
| Synthesize | CLI fails | Generate report from step analyses only |
|
||||||
|
| Any | 3+ consecutive errors | Terminate, produce partial report |
|
||||||
|
|
||||||
|
## Core Rules
|
||||||
|
|
||||||
|
1. **STOP After Each CLI Call**: Every `ccw cli` call runs in background — STOP output immediately, wait for hook callback
|
||||||
|
2. **State Machine**: Advance via `current_step` + `current_phase`, never use sync loops for async operations
|
||||||
|
3. **Test Task Drives Execution**: 每个命令必须有 test_task(完整需求说明),作为命令的 $ARGUMENTS 输入。test_task 由当前 Claude 直接根据命令链复杂度生成,不需要额外 CLI 调用
|
||||||
|
4. **All Execution via claude**: `ccw cli --tool claude --mode write --rule universal-rigorous-style`
|
||||||
|
5. **All Analysis via gemini**: `ccw cli --tool gemini --mode analysis`, chained via `--resume`
|
||||||
|
6. **Session Capture**: After each gemini callback, capture exec_id → `gemini_session_id` for resume chain
|
||||||
|
7. **Sandbox Isolation**: 所有命令在独立沙箱目录(`sandbox/`)中执行,使用虚构测试任务,不影响真实项目
|
||||||
|
8. **Artifact Collection**: Scan sandbox filesystem (not git diff), compare pre/post snapshots
|
||||||
|
9. **Prompt Assembly**: Every step goes through `assembleStepPrompt()` — resolves command file, reads YAML metadata, injects test_task, builds rich context
|
||||||
|
10. **Auto-Confirm**: All prompts auto-confirmed, no blocking interactions during execution
|
||||||
@@ -10,11 +10,11 @@ allowed-tools: TodoWrite(*), Agent(*), AskUserQuestion(*), Read(*), Grep(*), Glo
|
|||||||
When `--yes` or `-y`: Auto-confirm exploration decisions, use recommended analysis angles.
|
When `--yes` or `-y`: Auto-confirm exploration decisions, use recommended analysis angles.
|
||||||
|
|
||||||
<purpose>
|
<purpose>
|
||||||
Interactive collaborative analysis workflow combining codebase exploration (cli-explore-agent) with CLI-assisted analysis (Gemini/Codex). Produces a documented discussion timeline with evolving understanding, decision trails, and actionable conclusions.
|
Interactive collaborative analysis workflow combining codebase exploration (cli-explore-agent), external research (workflow-research-agent), and CLI-assisted analysis (Gemini/Codex). Produces a documented discussion timeline with evolving understanding, decision trails, and actionable conclusions.
|
||||||
|
|
||||||
Invoked when user needs deep, multi-perspective analysis of a topic or codebase question — e.g., architecture review, implementation analysis, concept exploration, or decision evaluation.
|
Invoked when user needs deep, multi-perspective analysis of a topic or codebase question — e.g., architecture review, implementation analysis, concept exploration, or decision evaluation.
|
||||||
|
|
||||||
Produces: `discussion.md` (evolving analysis document with TOC, rounds, narrative synthesis), `explorations.json`/`perspectives.json` (structured findings), `conclusions.json` (final synthesis with recommendations). All artifacts stored in `.workflow/.analysis/{session-id}/`.
|
Produces: `discussion.md` (evolving analysis document with TOC, rounds, narrative synthesis), `explorations.json`/`perspectives.json` (structured findings), `research.json` (external research findings), `conclusions.json` (final synthesis with recommendations). All artifacts stored in `.workflow/.analysis/{session-id}/`.
|
||||||
</purpose>
|
</purpose>
|
||||||
|
|
||||||
<conventions>
|
<conventions>
|
||||||
@@ -78,10 +78,11 @@ All `AskUserQuestion` calls MUST comply:
|
|||||||
|-------|----------|-------------|
|
|-------|----------|-------------|
|
||||||
| 1 | `discussion.md` | Initialized with TOC, Current Understanding block, timeline, metadata |
|
| 1 | `discussion.md` | Initialized with TOC, Current Understanding block, timeline, metadata |
|
||||||
| 1 | Session variables | Dimensions, focus areas, analysis depth |
|
| 1 | Session variables | Dimensions, focus areas, analysis depth |
|
||||||
| 2 | `exploration-codebase.json` | Single codebase context from cli-explore-agent |
|
| 2 | `exploration-codebase.json` | Shared Layer 1 discovery (files, modules, patterns) — always created |
|
||||||
| 2 | `explorations/*.json` | Multi-perspective codebase explorations (parallel, up to 4) |
|
| 2 | `explorations/*.json` | Per-perspective Layer 2-3 deep-dives (multi-perspective only, max 4) |
|
||||||
| 2 | `explorations.json` | Single perspective aggregated findings |
|
| 2 | `research.json` | External research findings (best practices, API details, known issues) — from workflow-research-agent |
|
||||||
| 2 | `perspectives.json` | Multi-perspective findings (up to 4) with synthesis |
|
| 2 | `explorations.json` | Single perspective aggregated findings (Layer 1 + CLI analysis + research) |
|
||||||
|
| 2 | `perspectives.json` | Multi-perspective findings (Layer 1 shared + per-perspective deep-dives + research) with synthesis |
|
||||||
| 2 | Updated `discussion.md` | Round 1 + Initial Intent Coverage Check + Current Understanding replaced |
|
| 2 | Updated `discussion.md` | Round 1 + Initial Intent Coverage Check + Current Understanding replaced |
|
||||||
| 3 | Updated `discussion.md` | Round 2-N: feedback, insights, narrative synthesis; TOC + Current Understanding updated each round |
|
| 3 | Updated `discussion.md` | Round 2-N: feedback, insights, narrative synthesis; TOC + Current Understanding updated each round |
|
||||||
| 4 | `conclusions.json` | Final synthesis with recommendations (incl. steps[] + review_status) |
|
| 4 | `conclusions.json` | Final synthesis with recommendations (incl. steps[] + review_status) |
|
||||||
@@ -141,98 +142,179 @@ All `AskUserQuestion` calls MUST comply:
|
|||||||
<step name="cli_exploration">
|
<step name="cli_exploration">
|
||||||
**Phase 2: Codebase exploration FIRST, then CLI analysis.**
|
**Phase 2: Codebase exploration FIRST, then CLI analysis.**
|
||||||
|
|
||||||
**Step 1: Codebase Exploration** (cli-explore-agent, parallel up to 6)
|
**Step 1: Codebase Exploration** (cli-explore-agent, 1 shared + N perspective-specific)
|
||||||
|
|
||||||
- **Single**: General codebase analysis -> `{sessionFolder}/exploration-codebase.json`
|
Two-phase approach to avoid redundant file discovery:
|
||||||
- **Multi-perspective**: Parallel per-perspective -> `{sessionFolder}/explorations/{perspective}.json`
|
|
||||||
- **Common tasks**: `ccw tool exec get_modules_by_depth '{}'`, keyword searches, read `.workflow/project-tech.json`
|
**Phase A — Shared Discovery** (1 agent, always runs):
|
||||||
|
One cli-explore-agent performs Layer 1 (breadth) for ALL perspectives -> `{sessionFolder}/exploration-codebase.json`
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Template for cli-explore-agent (single or per-perspective)
|
// Shared Layer 1 discovery — runs ONCE regardless of perspective count
|
||||||
Agent({
|
Agent({
|
||||||
subagent_type: "cli-explore-agent",
|
subagent_type: "cli-explore-agent",
|
||||||
run_in_background: false,
|
run_in_background: false,
|
||||||
description: `Explore codebase: ${topicSlug}`,
|
description: `Discover codebase: ${topicSlug}`,
|
||||||
prompt: `
|
prompt: `
|
||||||
## Analysis Context
|
## Analysis Context
|
||||||
Topic: ${topic_or_question}
|
Topic: ${topic_or_question}
|
||||||
Dimensions: ${dimensions.join(', ')}
|
Dimensions: ${dimensions.join(', ')}
|
||||||
// For multi-perspective, add: Perspective: ${perspective.name} - ${perspective.focus}
|
|
||||||
Session: ${sessionFolder}
|
Session: ${sessionFolder}
|
||||||
|
|
||||||
## MANDATORY FIRST STEPS
|
## MANDATORY FIRST STEPS
|
||||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||||
2. Read: .workflow/project-tech.json (if exists)
|
2. Read: .workflow/project-tech.json (if exists)
|
||||||
|
|
||||||
## Layered Exploration (MUST follow all 3 layers)
|
## Layer 1 — Module Discovery (Breadth ONLY)
|
||||||
|
- Search by topic keywords across ALL dimensions: ${dimensions.join(', ')}
|
||||||
### Layer 1 — Module Discovery (Breadth)
|
- Identify ALL relevant files, map module boundaries and entry points
|
||||||
- Search by topic keywords, identify ALL relevant files
|
- Categorize files by dimension/perspective relevance
|
||||||
- Map module boundaries and entry points -> relevant_files[] with annotations
|
- Output: relevant_files[] with annotations + dimension tags, initial patterns[]
|
||||||
|
|
||||||
### Layer 2 — Structure Tracing (Depth)
|
|
||||||
- Top 3-5 key files: trace call chains 2-3 levels deep
|
|
||||||
- Identify data flow paths and dependencies -> call_chains[], data_flows[]
|
|
||||||
|
|
||||||
### Layer 3 — Code Anchor Extraction (Detail)
|
|
||||||
- Each key finding: extract code snippet (20-50 lines) with file:line
|
|
||||||
- Annotate WHY this matters -> code_anchors[]
|
|
||||||
|
|
||||||
## Output
|
## Output
|
||||||
Write to: ${sessionFolder}/exploration-codebase.json
|
Write to: ${sessionFolder}/exploration-codebase.json
|
||||||
// Multi-perspective: ${sessionFolder}/explorations/${perspective.name}.json
|
Schema: {relevant_files: [{path, annotation, dimensions[]}], patterns[], module_map: {}, questions_for_user, _metadata}
|
||||||
|
|
||||||
Schema: {relevant_files, patterns, key_findings, code_anchors: [{file, lines, snippet, significance}], call_chains: [{entry, chain, files}], questions_for_user, _metadata}
|
|
||||||
`
|
`
|
||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 2: CLI Analysis** (AFTER exploration)
|
**Phase A2 — External Research** (parallel with Phase A, runs when topic involves technologies/patterns/APIs):
|
||||||
|
|
||||||
- **Single**: Comprehensive CLI analysis with exploration context
|
Determine if external research would add value — skip for purely internal codebase questions (e.g., "how does module X work"), run for topics involving technology choices, best practices, architecture patterns, API usage, or comparison with industry standards.
|
||||||
- **Multi (up to 4)**: Parallel CLI calls per perspective
|
|
||||||
|
```javascript
|
||||||
|
// External research — runs in PARALLEL with Phase A codebase exploration
|
||||||
|
// Skip if topic is purely internal codebase navigation
|
||||||
|
const needsResearch = dimensions.some(d =>
|
||||||
|
['architecture', 'comparison', 'decision', 'performance', 'security'].includes(d)
|
||||||
|
) || topic_or_question.match(/best practice|pattern|vs|compare|approach|standard|library|framework/i)
|
||||||
|
|
||||||
|
if (needsResearch) {
|
||||||
|
Agent({
|
||||||
|
subagent_type: "workflow-research-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Research: ${topicSlug}`,
|
||||||
|
prompt: `
|
||||||
|
## Research Objective
|
||||||
|
Topic: ${topic_or_question}
|
||||||
|
Mode: detail-verification
|
||||||
|
Dimensions: ${dimensions.join(', ')}
|
||||||
|
|
||||||
|
## Focus
|
||||||
|
${dimensions.includes('architecture') ? '- Architecture patterns and best practices for this domain' : ''}
|
||||||
|
${dimensions.includes('performance') ? '- Performance benchmarks and optimization patterns' : ''}
|
||||||
|
${dimensions.includes('security') ? '- Security best practices and known vulnerabilities' : ''}
|
||||||
|
${dimensions.includes('comparison') ? '- Technology comparison and trade-off analysis' : ''}
|
||||||
|
${dimensions.includes('decision') ? '- Decision frameworks and industry recommendations' : ''}
|
||||||
|
- Verify assumptions about technologies/patterns involved
|
||||||
|
- Known issues and pitfalls in this area
|
||||||
|
- Recommended approaches with evidence
|
||||||
|
|
||||||
|
## Codebase Context (from Phase A if available)
|
||||||
|
Tech stack: ${techStack || 'detect from project files'}
|
||||||
|
Key patterns observed: ${sharedDiscovery?.patterns?.join(', ') || 'pending Phase A results'}
|
||||||
|
|
||||||
|
## Output
|
||||||
|
Return structured markdown per your output format.
|
||||||
|
Do NOT write files.
|
||||||
|
`
|
||||||
|
})
|
||||||
|
// Parse research agent output → save to ${sessionFolder}/research.json
|
||||||
|
// Schema: {topic, mode, findings[], best_practices[], alternatives[], pitfalls[], sources[], _metadata}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase B — Perspective Deep-Dive** (parallel, only for multi-perspective, max 4):
|
||||||
|
Each perspective agent receives shared Layer 1 results, performs only Layer 2-3 on its relevant subset.
|
||||||
|
Skip if single-perspective (single mode proceeds directly to Step 2 CLI analysis with Layer 1 results).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// Per-perspective Layer 2-3 — receives shared discovery, avoids re-scanning
|
||||||
|
// Only runs in multi-perspective mode
|
||||||
|
const sharedDiscovery = readJSON(`${sessionFolder}/exploration-codebase.json`)
|
||||||
|
const perspectiveFiles = sharedDiscovery.relevant_files
|
||||||
|
.filter(f => f.dimensions.includes(perspective.dimension))
|
||||||
|
|
||||||
|
selectedPerspectives.forEach(perspective => {
|
||||||
|
Agent({
|
||||||
|
subagent_type: "cli-explore-agent",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Deep-dive: ${perspective.name}`,
|
||||||
|
prompt: `
|
||||||
|
## Analysis Context
|
||||||
|
Topic: ${topic_or_question}
|
||||||
|
Perspective: ${perspective.name} - ${perspective.focus}
|
||||||
|
Session: ${sessionFolder}
|
||||||
|
|
||||||
|
## SHARED DISCOVERY (Layer 1 already completed — DO NOT re-scan)
|
||||||
|
Relevant files for this perspective:
|
||||||
|
${perspectiveFiles.map(f => `- ${f.path}: ${f.annotation}`).join('\n')}
|
||||||
|
Patterns found: ${sharedDiscovery.patterns.join(', ')}
|
||||||
|
|
||||||
|
## Layer 2 — Structure Tracing (Depth)
|
||||||
|
- From the relevant files above, pick top 3-5 key files for this perspective
|
||||||
|
- Trace call chains 2-3 levels deep
|
||||||
|
- Identify data flow paths and dependencies -> call_chains[], data_flows[]
|
||||||
|
|
||||||
|
## Layer 3 — Code Anchor Extraction (Detail)
|
||||||
|
- Each key finding: extract code snippet (20-50 lines) with file:line
|
||||||
|
- Annotate WHY this matters for ${perspective.name} -> code_anchors[]
|
||||||
|
|
||||||
|
## Output
|
||||||
|
Write to: ${sessionFolder}/explorations/${perspective.name}.json
|
||||||
|
Schema: {perspective, relevant_files, key_findings, code_anchors: [{file, lines, snippet, significance}], call_chains: [{entry, chain, files}], questions_for_user, _metadata}
|
||||||
|
`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**Step 2: CLI Deep Analysis** (AFTER exploration, single-perspective ONLY)
|
||||||
|
|
||||||
|
- **Single-perspective**: CLI does Layer 2-3 depth analysis (explore agent only did Layer 1)
|
||||||
|
- **Multi-perspective**: SKIP this step — perspective agents in Step 1 Phase B already did Layer 2-3
|
||||||
- Execution: `Bash` with `run_in_background: true`
|
- Execution: `Bash` with `run_in_background: true`
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Build shared exploration context for CLI prompts
|
// ONLY for single-perspective mode — multi-perspective already has deep-dive agents
|
||||||
const explorationContext = `
|
if (selectedPerspectives.length <= 1) {
|
||||||
PRIOR EXPLORATION CONTEXT:
|
const sharedDiscovery = readJSON(`${sessionFolder}/exploration-codebase.json`)
|
||||||
- Key files: ${explorationResults.relevant_files.slice(0,5).map(f => f.path).join(', ')}
|
const explorationContext = `
|
||||||
- Patterns: ${explorationResults.patterns.slice(0,3).join(', ')}
|
PRIOR EXPLORATION (Layer 1 discovery):
|
||||||
- Findings: ${explorationResults.key_findings.slice(0,3).join(', ')}
|
- Key files: ${sharedDiscovery.relevant_files.slice(0,8).map(f => `${f.path} (${f.annotation})`).join(', ')}
|
||||||
- Code anchors:
|
- Patterns: ${sharedDiscovery.patterns.slice(0,5).join(', ')}
|
||||||
${(explorationResults.code_anchors || []).slice(0,5).map(a => ` [${a.file}:${a.lines}] ${a.significance}\n \`\`\`\n ${a.snippet}\n \`\`\``).join('\n')}
|
- Module map: ${JSON.stringify(sharedDiscovery.module_map || {})}`
|
||||||
- Call chains: ${(explorationResults.call_chains || []).slice(0,3).map(c => `${c.entry} -> ${c.chain.join(' -> ')}`).join('; ')}`
|
|
||||||
|
|
||||||
// Single perspective (for multi: loop selectedPerspectives with perspective.purpose/tasks/constraints)
|
Bash({
|
||||||
Bash({
|
command: `ccw cli -p "
|
||||||
command: `ccw cli -p "
|
PURPOSE: Deep analysis of '${topic_or_question}' — build on prior file discovery
|
||||||
PURPOSE: Analyze '${topic_or_question}' from ${dimensions.join(', ')} perspectives
|
Success: Actionable insights with code evidence (anchors + call chains)
|
||||||
Success: Actionable insights with clear reasoning
|
|
||||||
|
|
||||||
${explorationContext}
|
${explorationContext}
|
||||||
|
|
||||||
TASK:
|
TASK:
|
||||||
- Build on exploration findings — reference specific code anchors
|
- From discovered files, trace call chains 2-3 levels deep for top 3-5 key files
|
||||||
- Analyze common patterns and anti-patterns with code evidence
|
- Extract code snippets (20-50 lines) for each key finding with file:line
|
||||||
- Highlight potential issues/opportunities with file:line references
|
- Identify patterns, anti-patterns, and potential issues with evidence
|
||||||
- Generate discussion points for user clarification
|
- Generate discussion points for user clarification
|
||||||
|
|
||||||
MODE: analysis
|
MODE: analysis
|
||||||
CONTEXT: @**/* | Topic: ${topic_or_question}
|
CONTEXT: @**/* | Topic: ${topic_or_question}
|
||||||
EXPECTED: Structured analysis with sections, insights tied to evidence, questions, recommendations
|
EXPECTED: Structured analysis with: key_findings[], code_anchors[{file,lines,snippet,significance}], call_chains[{entry,chain,files}], discussion_points[]
|
||||||
CONSTRAINTS: Focus on ${dimensions.join(', ')}
|
CONSTRAINTS: Focus on ${dimensions.join(', ')} | Do NOT re-discover files — use provided file list
|
||||||
" --tool gemini --mode analysis`,
|
" --tool gemini --mode analysis`,
|
||||||
run_in_background: true
|
run_in_background: true
|
||||||
})
|
})
|
||||||
// STOP: Wait for hook callback before continuing
|
// STOP: Wait for hook callback before continuing
|
||||||
// Multi-perspective: Same pattern per perspective with perspective.purpose/tasks/constraints/tool
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
**Step 3: Aggregate Findings**
|
**Step 3: Aggregate Findings**
|
||||||
- Consolidate explorations + CLI results
|
- Consolidate explorations + CLI results + research findings (if research.json exists)
|
||||||
|
- Merge research best_practices[] and pitfalls[] into discussion points
|
||||||
|
- Cross-reference: flag gaps where codebase patterns diverge from research best practices
|
||||||
- Multi: Extract synthesis (convergent themes, conflicting views, unique contributions)
|
- Multi: Extract synthesis (convergent themes, conflicting views, unique contributions)
|
||||||
- Write to `explorations.json` (single) or `perspectives.json` (multi)
|
- Write to `explorations.json` (single) or `perspectives.json` (multi)
|
||||||
|
- If research.json exists, add `external_research` section to explorations/perspectives with: key findings, best practices, codebase gaps
|
||||||
|
|
||||||
**Step 4: Update discussion.md** — Append Round 1 with sources, key findings, discussion points, open questions
|
**Step 4: Update discussion.md** — Append Round 1 with sources, key findings, discussion points, open questions
|
||||||
|
|
||||||
@@ -243,18 +325,36 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
|
|||||||
- Present to user at beginning of Phase 3: "初始探索完成后,以下意图的覆盖情况:[list]。接下来的讨论将重点关注未覆盖的部分。"
|
- Present to user at beginning of Phase 3: "初始探索完成后,以下意图的覆盖情况:[list]。接下来的讨论将重点关注未覆盖的部分。"
|
||||||
- Purpose: Early course correction — catch drift before spending multiple interactive rounds
|
- Purpose: Early course correction — catch drift before spending multiple interactive rounds
|
||||||
|
|
||||||
**explorations.json Schema** (single):
|
**exploration-codebase.json Schema** (shared Layer 1):
|
||||||
|
- `session_id`, `timestamp`, `topic`, `dimensions[]`
|
||||||
|
- `relevant_files[]`: {path, annotation, dimensions[]}
|
||||||
|
- `patterns[]`, `module_map`: {}
|
||||||
|
- `questions_for_user[]`, `_metadata`
|
||||||
|
|
||||||
|
**research.json Schema** (external research findings):
|
||||||
|
- `topic`, `mode` (detail-verification|api-research|design-research), `timestamp`
|
||||||
|
- `findings[]`: {finding, detail, confidence, source_url}
|
||||||
|
- `best_practices[]`: {practice, rationale, source}
|
||||||
|
- `alternatives[]`: {option, pros, cons, verdict}
|
||||||
|
- `pitfalls[]`: {issue, mitigation, source}
|
||||||
|
- `codebase_gaps[]`: {gap, current_approach, recommended_approach}
|
||||||
|
- `sources[]`: {title, url, key_takeaway}
|
||||||
|
- `_metadata`: {queries_executed, results_found}
|
||||||
|
|
||||||
|
**explorations.json Schema** (single — Layer 1 + CLI analysis + research merged):
|
||||||
- `session_id`, `timestamp`, `topic`, `dimensions[]`
|
- `session_id`, `timestamp`, `topic`, `dimensions[]`
|
||||||
- `sources[]`: {type, file/summary}
|
- `sources[]`: {type, file/summary}
|
||||||
- `key_findings[]`, `code_anchors[]`: {file, lines, snippet, significance}
|
- `key_findings[]`, `code_anchors[]`: {file, lines, snippet, significance}
|
||||||
- `call_chains[]`: {entry, chain, files}
|
- `call_chains[]`: {entry, chain, files}
|
||||||
- `discussion_points[]`, `open_questions[]`
|
- `discussion_points[]`, `open_questions[]`
|
||||||
- `technical_solutions[]`: {round, solution, problem, rationale, alternatives, status: proposed|validated|rejected, evidence_refs[], next_action}
|
- `technical_solutions[]`: {round, solution, problem, rationale, alternatives, status: proposed|validated|rejected, evidence_refs[], next_action}
|
||||||
|
- `external_research`: {findings[], best_practices[], codebase_gaps[], sources[]} — merged from research.json if available
|
||||||
|
|
||||||
**perspectives.json Schema** (multi — extends explorations.json):
|
**perspectives.json Schema** (multi — Layer 1 shared + per-perspective Layer 2-3 + research):
|
||||||
- `perspectives[]`: [{name, tool, findings, insights, questions}]
|
- `shared_discovery`: {relevant_files[], patterns[], module_map}
|
||||||
|
- `perspectives[]`: [{name, tool, findings, insights, questions, code_anchors[], call_chains[]}]
|
||||||
|
- `external_research`: {findings[], best_practices[], codebase_gaps[], sources[]} — merged from research.json if available
|
||||||
- `synthesis`: {convergent_themes, conflicting_views, unique_contributions}
|
- `synthesis`: {convergent_themes, conflicting_views, unique_contributions}
|
||||||
- code_anchors/call_chains include `perspective` field
|
|
||||||
|
|
||||||
| Condition | Action |
|
| Condition | Action |
|
||||||
|-----------|--------|
|
|-----------|--------|
|
||||||
@@ -270,6 +370,22 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
|
|||||||
|
|
||||||
**Guideline**: Delegate complex tasks to agents (cli-explore-agent) or CLI calls. Avoid direct analysis in main process.
|
**Guideline**: Delegate complex tasks to agents (cli-explore-agent) or CLI calls. Avoid direct analysis in main process.
|
||||||
|
|
||||||
|
**Cumulative Context Rule**: Every agent/CLI call in Phase 3 MUST include a summary of ALL prior exploration results to avoid re-discovering known information. Build `priorContext` before each call:
|
||||||
|
```javascript
|
||||||
|
// Build cumulative context from all prior explorations (Phase 2 + previous rounds)
|
||||||
|
const allFindings = readJSON(`${sessionFolder}/explorations.json`) // or perspectives.json
|
||||||
|
const priorContext = `
|
||||||
|
## KNOWN FINDINGS (DO NOT re-discover)
|
||||||
|
- Established files: ${allFindings.sources.map(s => s.file).join(', ')}
|
||||||
|
- Key findings: ${allFindings.key_findings.join('; ')}
|
||||||
|
- Code anchors: ${allFindings.code_anchors.slice(0,5).map(a => `${a.file}:${a.lines}`).join(', ')}
|
||||||
|
- Call chains: ${allFindings.call_chains.slice(0,3).map(c => c.entry).join(', ')}
|
||||||
|
- Open questions: ${allFindings.open_questions.join('; ')}
|
||||||
|
|
||||||
|
## NEW TASK: Focus ONLY on unexplored areas below.
|
||||||
|
`
|
||||||
|
```
|
||||||
|
|
||||||
**Loop** (max 5 rounds):
|
**Loop** (max 5 rounds):
|
||||||
|
|
||||||
1. **Current Understanding Summary** (Round >= 2, BEFORE presenting new findings):
|
1. **Current Understanding Summary** (Round >= 2, BEFORE presenting new findings):
|
||||||
@@ -280,8 +396,8 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
|
|||||||
|
|
||||||
3. **Gather Feedback** (AskUserQuestion, single-select, header: "分析反馈"):
|
3. **Gather Feedback** (AskUserQuestion, single-select, header: "分析反馈"):
|
||||||
- **继续深入**: Direction correct — deepen automatically or user specifies direction (combines agree+deepen and agree+suggest)
|
- **继续深入**: Direction correct — deepen automatically or user specifies direction (combines agree+deepen and agree+suggest)
|
||||||
|
- **外部研究**: Need external research on specific technology/pattern/best practice (spawns workflow-research-agent)
|
||||||
- **调整方向**: Different focus or specific questions to address
|
- **调整方向**: Different focus or specific questions to address
|
||||||
- **补充信息**: User has additional context, constraints, or corrections to provide
|
|
||||||
- **分析完成**: Sufficient -> exit to Phase 4
|
- **分析完成**: Sufficient -> exit to Phase 4
|
||||||
|
|
||||||
4. **Process Response** (always record user choice + impact to discussion.md):
|
4. **Process Response** (always record user choice + impact to discussion.md):
|
||||||
@@ -293,9 +409,14 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
|
|||||||
- **"Other" is auto-provided** by AskUserQuestion — covers user-specified custom direction (no need for separate "suggest next step" option)
|
- **"Other" is auto-provided** by AskUserQuestion — covers user-specified custom direction (no need for separate "suggest next step" option)
|
||||||
- Execute selected direction -> merge new code_anchors/call_chains -> record confirmed assumptions + deepen angle
|
- Execute selected direction -> merge new code_anchors/call_chains -> record confirmed assumptions + deepen angle
|
||||||
|
|
||||||
**调整方向** -> AskUserQuestion (header: "新方向", user selects or provides custom via "Other") -> new CLI exploration -> Record Decision (old vs new direction, reason, impact)
|
**外部研究** -> Spawn workflow-research-agent for targeted research:
|
||||||
|
- AskUserQuestion (header: "研究主题", freetext via "Other"): What specific technology/pattern/approach needs external research?
|
||||||
|
- Spawn research agent with topic + current codebase context (from explorations.json)
|
||||||
|
- Merge research findings into explorations.json `external_research` section
|
||||||
|
- Update research.json with new findings (append, don't overwrite)
|
||||||
|
- Record research findings as Key Findings in discussion.md
|
||||||
|
|
||||||
**补充信息** -> Capture user input, integrate into context, answer questions via CLI/analysis if needed -> Record corrections/additions + updated understanding
|
**调整方向** -> AskUserQuestion (header: "新方向", user selects or provides custom via "Other") -> new CLI exploration -> Record Decision (old vs new direction, reason, impact)
|
||||||
|
|
||||||
**分析完成** -> Exit loop -> Record why concluding
|
**分析完成** -> Exit loop -> Record why concluding
|
||||||
|
|
||||||
@@ -525,6 +646,8 @@ ${implScope.map((item, i) => `${i+1}. **${item.objective}** [${item.priority}]
|
|||||||
| E005 | error | No relevant findings from exploration — broaden search, ask user for clarification | cli_exploration |
|
| E005 | error | No relevant findings from exploration — broaden search, ask user for clarification | cli_exploration |
|
||||||
| E006 | warning | Session folder conflict — append timestamp suffix | session_init |
|
| E006 | warning | Session folder conflict — append timestamp suffix | session_init |
|
||||||
| E007 | error | Gemini unavailable — fallback to Codex or manual analysis | cli_exploration |
|
| E007 | error | Gemini unavailable — fallback to Codex or manual analysis | cli_exploration |
|
||||||
|
| E008 | warning | Research agent WebSearch failed — continue with codebase-only analysis, note limitation | cli_exploration |
|
||||||
|
| E009 | warning | Research findings conflict with codebase patterns — flag as codebase_gaps for user review | cli_exploration |
|
||||||
|
|
||||||
</error_codes>
|
</error_codes>
|
||||||
|
|
||||||
@@ -534,6 +657,8 @@ ${implScope.map((item, i) => `${i+1}. **${item.objective}** [${item.priority}]
|
|||||||
- [ ] Dimensions identified and user preferences captured (Phase 1)
|
- [ ] Dimensions identified and user preferences captured (Phase 1)
|
||||||
- [ ] discussion.md initialized with TOC, Current Understanding, metadata
|
- [ ] discussion.md initialized with TOC, Current Understanding, metadata
|
||||||
- [ ] Codebase exploration completed with code_anchors and call_chains (Phase 2)
|
- [ ] Codebase exploration completed with code_anchors and call_chains (Phase 2)
|
||||||
|
- [ ] External research executed if topic warrants it (architecture/comparison/decision/performance/security dimensions)
|
||||||
|
- [ ] Research findings merged into explorations/perspectives with codebase_gaps flagged
|
||||||
- [ ] CLI analysis executed and findings aggregated
|
- [ ] CLI analysis executed and findings aggregated
|
||||||
- [ ] Initial Intent Coverage Check appended to discussion.md
|
- [ ] Initial Intent Coverage Check appended to discussion.md
|
||||||
- [ ] Interactive discussion rounds documented with narrative synthesis (Phase 3)
|
- [ ] Interactive discussion rounds documented with narrative synthesis (Phase 3)
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ function selectExecutionOptions() {
|
|||||||
const autoYes = workflowPreferences?.autoYes ?? false
|
const autoYes = workflowPreferences?.autoYes ?? false
|
||||||
|
|
||||||
if (autoYes) {
|
if (autoYes) {
|
||||||
return { execution_method: "Auto", code_review_tool: "Skip" }
|
return { execution_method: "Auto", code_review_tool: "Skip", convergence_review_tool: "Skip" }
|
||||||
}
|
}
|
||||||
|
|
||||||
return AskUserQuestion({
|
return AskUserQuestion({
|
||||||
@@ -90,14 +90,25 @@ function selectExecutionOptions() {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Review tool for test-review phase?",
|
question: "Code review after execution? (runs here in lite-execute)",
|
||||||
header: "Review Tool (passed to lite-test-review)",
|
header: "Code Review",
|
||||||
multiSelect: false,
|
multiSelect: false,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Agent Review", description: "Agent review in test-review (default)" },
|
{ label: "Gemini Review", description: "Gemini CLI: git diff quality review" },
|
||||||
{ label: "Gemini Review", description: "Gemini CLI in test-review" },
|
{ label: "Codex Review", description: "Codex CLI: git-aware code review (--mode review)" },
|
||||||
{ label: "Codex Review", description: "Codex CLI in test-review" },
|
{ label: "Agent Review", description: "@code-reviewer agent" },
|
||||||
{ label: "Skip", description: "Skip review in test-review" }
|
{ label: "Skip", description: "No code review" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Convergence review in test-review phase?",
|
||||||
|
header: "Convergence Review",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Agent", description: "Agent: verify convergence criteria" },
|
||||||
|
{ label: "Gemini", description: "Gemini CLI: convergence verification" },
|
||||||
|
{ label: "Codex", description: "Codex CLI: convergence verification" },
|
||||||
|
{ label: "Skip", description: "Skip convergence review, run tests only" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -117,7 +128,8 @@ if (executionContext) {
|
|||||||
console.log(`
|
console.log(`
|
||||||
Execution Strategy (from lite-plan):
|
Execution Strategy (from lite-plan):
|
||||||
Method: ${executionContext.executionMethod}
|
Method: ${executionContext.executionMethod}
|
||||||
Review: ${executionContext.codeReviewTool}
|
Code Review: ${executionContext.codeReviewTool}
|
||||||
|
Convergence Review: ${executionContext.convergenceReviewTool}
|
||||||
Tasks: ${getTasks(executionContext.planObject).length}
|
Tasks: ${getTasks(executionContext.planObject).length}
|
||||||
Complexity: ${executionContext.planObject.complexity}
|
Complexity: ${executionContext.planObject.complexity}
|
||||||
${executionContext.executorAssignments ? ` Assignments: ${JSON.stringify(executionContext.executorAssignments)}` : ''}
|
${executionContext.executorAssignments ? ` Assignments: ${JSON.stringify(executionContext.executorAssignments)}` : ''}
|
||||||
@@ -367,18 +379,97 @@ ${(t.test?.success_metrics || []).length > 0 ? `**Success metrics**: ${t.test.su
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Step 4: Chain to Test Review & Post-Completion
|
### Step 4: Code Review
|
||||||
|
|
||||||
> **Note**: Spec sync (session:sync) is handled by lite-test-review's TR-Phase 5, not here. This avoids duplicate sync and ensures test fix changes are also captured.
|
**Skip if**: `codeReviewTool === 'Skip'`
|
||||||
|
|
||||||
**Map review tool**: Convert lite-execute's `codeReviewTool` to test-review tool name.
|
**Resolve review tool**: From `executionContext.codeReviewTool` (Mode 1) or `userSelection.code_review_tool` (Mode 2/3).
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
function mapReviewTool(codeReviewTool) {
|
const codeReviewTool = executionContext?.codeReviewTool || userSelection?.code_review_tool || 'Skip'
|
||||||
|
const resolvedTool = (() => {
|
||||||
if (!codeReviewTool || codeReviewTool === 'Skip') return 'skip'
|
if (!codeReviewTool || codeReviewTool === 'Skip') return 'skip'
|
||||||
if (/gemini/i.test(codeReviewTool)) return 'gemini'
|
if (/gemini/i.test(codeReviewTool)) return 'gemini'
|
||||||
if (/codex/i.test(codeReviewTool)) return 'codex'
|
if (/codex/i.test(codeReviewTool)) return 'codex'
|
||||||
return 'agent'
|
return 'agent'
|
||||||
|
})()
|
||||||
|
|
||||||
|
if (resolvedTool === 'skip') {
|
||||||
|
console.log('[Code Review] Skipped')
|
||||||
|
} else {
|
||||||
|
// proceed with review
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Agent Code Review** (resolvedTool === 'agent'):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
Agent({
|
||||||
|
subagent_type: "code-reviewer",
|
||||||
|
run_in_background: false,
|
||||||
|
description: `Code review: ${planObject.summary}`,
|
||||||
|
prompt: `## Code Review — Post-Execution Quality Check
|
||||||
|
|
||||||
|
**Goal**: ${originalUserInput}
|
||||||
|
**Plan Summary**: ${planObject.summary}
|
||||||
|
|
||||||
|
### Changed Files
|
||||||
|
Run \`git diff --name-only HEAD~${getTasks(planObject).length}..HEAD\` to identify changes.
|
||||||
|
|
||||||
|
### Review Focus
|
||||||
|
1. **Code quality**: Readability, naming, structure, dead code
|
||||||
|
2. **Correctness**: Logic errors, off-by-one, null handling, edge cases
|
||||||
|
3. **Patterns**: Consistency with existing codebase conventions
|
||||||
|
4. **Security**: Injection, XSS, auth bypass, secrets exposure
|
||||||
|
5. **Performance**: Unnecessary loops, N+1 queries, missing indexes
|
||||||
|
|
||||||
|
### Instructions
|
||||||
|
1. Run git diff to see actual changes
|
||||||
|
2. Read changed files for full context
|
||||||
|
3. For each issue found: severity (Critical/High/Medium/Low) + file:line + description + fix suggestion
|
||||||
|
4. Return structured review: issues[], summary, overall verdict (PASS/WARN/FAIL)`
|
||||||
|
})
|
||||||
|
```
|
||||||
|
|
||||||
|
**CLI Code Review — Codex** (resolvedTool === 'codex'):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const reviewId = `${sessionId}-code-review`
|
||||||
|
Bash(`ccw cli -p "Review code changes for quality, correctness, security, and pattern compliance. Focus: ${planObject.summary}" --tool codex --mode review --id ${reviewId}`, { run_in_background: true })
|
||||||
|
// STOP - wait for hook callback
|
||||||
|
```
|
||||||
|
|
||||||
|
**CLI Code Review — Gemini** (resolvedTool === 'gemini'):
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
const reviewId = `${sessionId}-code-review`
|
||||||
|
Bash(`ccw cli -p "PURPOSE: Post-execution code quality review for: ${planObject.summary}
|
||||||
|
TASK: • Run git diff to identify all changes • Review each changed file for quality, correctness, security • Check pattern compliance with existing codebase • Identify potential bugs, edge cases, performance issues
|
||||||
|
MODE: analysis
|
||||||
|
CONTEXT: @**/* | Memory: lite-execute completed, reviewing code quality
|
||||||
|
EXPECTED: Per-file review with severity levels (Critical/High/Medium/Low), file:line references, fix suggestions, overall verdict
|
||||||
|
CONSTRAINTS: Read-only | Focus on code quality not convergence" --tool gemini --mode analysis --rule analysis-review-code-quality --id ${reviewId}`, { run_in_background: true })
|
||||||
|
// STOP - wait for hook callback
|
||||||
|
```
|
||||||
|
|
||||||
|
**Write review artifact** (if session folder exists):
|
||||||
|
```javascript
|
||||||
|
if (executionContext?.session?.folder) {
|
||||||
|
Write(`${executionContext.session.folder}/code-review.md`, codeReviewOutput)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Step 5: Chain to Test Review & Post-Completion
|
||||||
|
|
||||||
|
**Resolve convergence review tool**: From `executionContext.convergenceReviewTool` (Mode 1) or `userSelection.convergence_review_tool` (Mode 2/3).
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
function resolveConvergenceTool(ctx, selection) {
|
||||||
|
const raw = ctx?.convergenceReviewTool || selection?.convergence_review_tool || 'skip'
|
||||||
|
if (!raw || raw === 'Skip') return 'skip'
|
||||||
|
if (/gemini/i.test(raw)) return 'gemini'
|
||||||
|
if (/codex/i.test(raw)) return 'codex'
|
||||||
|
return 'agent'
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -389,7 +480,7 @@ testReviewContext = {
|
|||||||
planObject: planObject,
|
planObject: planObject,
|
||||||
taskFiles: executionContext?.taskFiles
|
taskFiles: executionContext?.taskFiles
|
||||||
|| getTasks(planObject).map(t => ({ id: t.id, path: `${executionContext?.session?.folder}/.task/${t.id}.json` })),
|
|| getTasks(planObject).map(t => ({ id: t.id, path: `${executionContext?.session?.folder}/.task/${t.id}.json` })),
|
||||||
reviewTool: mapReviewTool(executionContext?.codeReviewTool),
|
convergenceReviewTool: resolveConvergenceTool(executionContext, userSelection),
|
||||||
executionResults: previousExecutionResults,
|
executionResults: previousExecutionResults,
|
||||||
originalUserInput: originalUserInput,
|
originalUserInput: originalUserInput,
|
||||||
session: executionContext?.session || {
|
session: executionContext?.session || {
|
||||||
@@ -442,7 +533,8 @@ Skill("lite-test-review")
|
|||||||
explorationManifest: {...} | null,
|
explorationManifest: {...} | null,
|
||||||
clarificationContext: {...} | null,
|
clarificationContext: {...} | null,
|
||||||
executionMethod: "Agent" | "Codex" | "Auto",
|
executionMethod: "Agent" | "Codex" | "Auto",
|
||||||
codeReviewTool: "Skip" | "Gemini Review" | "Agent Review" | string,
|
codeReviewTool: "Skip" | "Gemini Review" | "Codex Review" | "Agent Review",
|
||||||
|
convergenceReviewTool: "Skip" | "Agent" | "Gemini" | "Codex",
|
||||||
originalUserInput: string,
|
originalUserInput: string,
|
||||||
executorAssignments: { // per-task override, priority over executionMethod
|
executorAssignments: { // per-task override, priority over executionMethod
|
||||||
[taskId]: { executor: "gemini" | "codex" | "agent", reason: string }
|
[taskId]: { executor: "gemini" | "codex" | "agent", reason: string }
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ Produces exploration results, a structured plan (plan.json), independent task fi
|
|||||||
|
|
||||||
**Output Directory**: `.workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/`
|
**Output Directory**: `.workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/`
|
||||||
|
|
||||||
**Agent Usage**: Low → Direct Claude planning (no agent) | Medium/High → `cli-lite-planning-agent`
|
**Agent Usage**: All complexities → `cli-lite-planning-agent`
|
||||||
|
|
||||||
**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`
|
**Schema Reference**: `~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`
|
||||||
|
|
||||||
@@ -51,7 +51,7 @@ Produces exploration results, a structured plan (plan.json), independent task fi
|
|||||||
| LP-0 | Initialize workflowPreferences | autoYes, forceExplore |
|
| LP-0 | Initialize workflowPreferences | autoYes, forceExplore |
|
||||||
| LP-1 | Complexity assessment → parallel cli-explore-agents (1-4) | exploration-*.json + manifest |
|
| LP-1 | Complexity assessment → parallel cli-explore-agents (1-4) | exploration-*.json + manifest |
|
||||||
| LP-2 | Aggregate + dedup clarification_needs → multi-round AskUserQuestion | clarificationContext (in-memory) |
|
| LP-2 | Aggregate + dedup clarification_needs → multi-round AskUserQuestion | clarificationContext (in-memory) |
|
||||||
| LP-3 | Low: Direct Claude planning / Medium+High: cli-lite-planning-agent | plan.json + .task/TASK-*.json |
|
| LP-3 | cli-lite-planning-agent | plan.json + .task/TASK-*.json |
|
||||||
| LP-4 | Display plan → AskUserQuestion (Confirm + Execution + Review) | userSelection |
|
| LP-4 | Display plan → AskUserQuestion (Confirm + Execution + Review) | userSelection |
|
||||||
| LP-5 | Build executionContext → Skill("lite-execute") | handoff (Mode 1) |
|
| LP-5 | Build executionContext → Skill("lite-execute") | handoff (Mode 1) |
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ bash(`mkdir -p ${sessionFolder} && test -d ${sessionFolder} && echo "SUCCESS: ${
|
|||||||
TodoWrite({ todos: [
|
TodoWrite({ todos: [
|
||||||
{ content: `LP-Phase 1: Exploration [${complexity}] ${selectedAngles.length} angles`, status: "in_progress", activeForm: `Exploring: ${selectedAngles.join(', ')}` },
|
{ content: `LP-Phase 1: Exploration [${complexity}] ${selectedAngles.length} angles`, status: "in_progress", activeForm: `Exploring: ${selectedAngles.join(', ')}` },
|
||||||
{ content: "LP-Phase 2: Clarification", status: "pending" },
|
{ content: "LP-Phase 2: Clarification", status: "pending" },
|
||||||
{ content: `LP-Phase 3: Planning [${planningStrategy}]`, status: "pending" },
|
{ content: "LP-Phase 3: Planning [cli-lite-planning-agent]", status: "pending" },
|
||||||
{ content: "LP-Phase 4: Confirmation", status: "pending" },
|
{ content: "LP-Phase 4: Confirmation", status: "pending" },
|
||||||
{ content: "LP-Phase 5: Execution", status: "pending" }
|
{ content: "LP-Phase 5: Execution", status: "pending" }
|
||||||
]})
|
]})
|
||||||
@@ -154,12 +154,7 @@ function selectAngles(taskDescription, count) {
|
|||||||
|
|
||||||
const selectedAngles = selectAngles(task_description, complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1))
|
const selectedAngles = selectAngles(task_description, complexity === 'High' ? 4 : (complexity === 'Medium' ? 3 : 1))
|
||||||
|
|
||||||
// Direct Claude planning ONLY for: Low + no prior analysis + single angle
|
console.log(`Exploration Plan: ${complexity} | ${selectedAngles.join(', ')} | cli-lite-planning-agent`)
|
||||||
const planningStrategy = (
|
|
||||||
complexity === 'Low' && !hasPriorAnalysis && selectedAngles.length <= 1
|
|
||||||
) ? 'Direct Claude Planning' : 'cli-lite-planning-agent'
|
|
||||||
|
|
||||||
console.log(`Exploration Plan: ${complexity} | ${selectedAngles.join(', ')} | ${planningStrategy}`)
|
|
||||||
```
|
```
|
||||||
|
|
||||||
**Launch Parallel Explorations**:
|
**Launch Parallel Explorations**:
|
||||||
@@ -328,56 +323,7 @@ taskFiles.forEach(taskPath => {
|
|||||||
})
|
})
|
||||||
```
|
```
|
||||||
|
|
||||||
**Low Complexity** — Direct planning by Claude:
|
**Invoke cli-lite-planning-agent**:
|
||||||
```javascript
|
|
||||||
const schema = Bash(`cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json`)
|
|
||||||
|
|
||||||
const manifest = file_exists(`${sessionFolder}/explorations-manifest.json`)
|
|
||||||
? JSON.parse(Read(`${sessionFolder}/explorations-manifest.json`))
|
|
||||||
: { explorations: [] }
|
|
||||||
manifest.explorations.forEach(exp => {
|
|
||||||
console.log(`\n### Exploration: ${exp.angle}\n${Read(exp.path)}`)
|
|
||||||
})
|
|
||||||
|
|
||||||
// When handoffSpec exists, use it as primary planning input
|
|
||||||
// implementation_scope[].acceptance_criteria -> convergence.criteria
|
|
||||||
// implementation_scope[].target_files -> files[]
|
|
||||||
// implementation_scope[].objective -> task title/description
|
|
||||||
if (handoffSpec) {
|
|
||||||
console.log(`\n### Handoff Spec from ${handoffSpec.source}`)
|
|
||||||
console.log(`Scope items: ${handoffSpec.implementation_scope.length}`)
|
|
||||||
handoffSpec.implementation_scope.forEach((item, i) => {
|
|
||||||
console.log(` ${i+1}. ${item.objective} [${item.priority}] — Done when: ${item.acceptance_criteria.join('; ')}`)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generate tasks — MUST incorporate exploration insights OR handoff spec
|
|
||||||
// When handoffSpec: map implementation_scope[] → tasks[] (1:1 or group by context)
|
|
||||||
// Field names: convergence.criteria (not acceptance), files[].change (not modification_points), test (not verification)
|
|
||||||
const tasks = [
|
|
||||||
{
|
|
||||||
id: "TASK-001", title: "...", description: "...", depends_on: [],
|
|
||||||
convergence: { criteria: ["..."] }, // From handoffSpec: item.acceptance_criteria
|
|
||||||
files: [{ path: "...", change: "..." }], // From handoffSpec: item.target_files + item.change_summary
|
|
||||||
implementation: ["..."], test: "..."
|
|
||||||
}
|
|
||||||
]
|
|
||||||
|
|
||||||
const taskDir = `${sessionFolder}/.task`
|
|
||||||
Bash(`mkdir -p "${taskDir}"`)
|
|
||||||
tasks.forEach(task => Write(`${taskDir}/${task.id}.json`, JSON.stringify(task, null, 2)))
|
|
||||||
|
|
||||||
const plan = {
|
|
||||||
summary: "...", approach: "...",
|
|
||||||
task_ids: tasks.map(t => t.id), task_count: tasks.length,
|
|
||||||
complexity: "Low", estimated_time: "...", recommended_execution: "Agent",
|
|
||||||
_metadata: { timestamp: getUtc8ISOString(), source: "direct-planning", planning_mode: "direct", plan_type: "feature" }
|
|
||||||
}
|
|
||||||
Write(`${sessionFolder}/plan.json`, JSON.stringify(plan, null, 2))
|
|
||||||
// MUST continue to LP-Phase 4 — DO NOT execute code here
|
|
||||||
```
|
|
||||||
|
|
||||||
**Medium/High Complexity** — Invoke cli-lite-planning-agent:
|
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
Task(
|
Task(
|
||||||
@@ -492,8 +438,8 @@ ${tasks.map((t, i) => `${i+1}. ${t.title} (${t.scope || t.files?.[0]?.path || ''
|
|||||||
let userSelection
|
let userSelection
|
||||||
|
|
||||||
if (workflowPreferences.autoYes) {
|
if (workflowPreferences.autoYes) {
|
||||||
console.log(`[Auto] Allow & Execute | Auto | Skip`)
|
console.log(`[Auto] Allow & Execute | Auto | Skip + Skip`)
|
||||||
userSelection = { confirmation: "Allow", execution_method: "Auto", code_review_tool: "Skip" }
|
userSelection = { confirmation: "Allow", execution_method: "Auto", code_review_tool: "Skip", convergence_review_tool: "Skip" }
|
||||||
} else {
|
} else {
|
||||||
// "Other" in Execution allows specifying CLI tools from ~/.claude/cli-tools.json
|
// "Other" in Execution allows specifying CLI tools from ~/.claude/cli-tools.json
|
||||||
userSelection = AskUserQuestion({
|
userSelection = AskUserQuestion({
|
||||||
@@ -519,14 +465,25 @@ if (workflowPreferences.autoYes) {
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Code review after execution?",
|
question: "Code review after execution? (runs in lite-execute)",
|
||||||
header: "Review",
|
header: "Code Review",
|
||||||
multiSelect: false,
|
multiSelect: false,
|
||||||
options: [
|
options: [
|
||||||
{ label: "Gemini Review", description: "Gemini CLI review" },
|
{ label: "Gemini Review", description: "Gemini CLI: git diff quality review" },
|
||||||
{ label: "Codex Review", description: "Git-aware review (prompt OR --uncommitted)" },
|
{ label: "Codex Review", description: "Codex CLI: git-aware code review (--mode review)" },
|
||||||
{ label: "Agent Review", description: "@code-reviewer agent" },
|
{ label: "Agent Review", description: "@code-reviewer agent" },
|
||||||
{ label: "Skip", description: "No review" }
|
{ label: "Skip", description: "No code review" }
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
question: "Convergence review in test-review phase?",
|
||||||
|
header: "Convergence Review",
|
||||||
|
multiSelect: false,
|
||||||
|
options: [
|
||||||
|
{ label: "Agent", description: "Agent: verify convergence criteria against implementation" },
|
||||||
|
{ label: "Gemini", description: "Gemini CLI: convergence verification" },
|
||||||
|
{ label: "Codex", description: "Codex CLI: convergence verification" },
|
||||||
|
{ label: "Skip", description: "Skip convergence review, run tests only" }
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -534,7 +491,7 @@ if (workflowPreferences.autoYes) {
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
// TodoWrite: Phase 4 → completed `[${userSelection.execution_method} + ${userSelection.code_review_tool}]`, Phase 5 → in_progress
|
// TodoWrite: Phase 4 → completed `[${userSelection.execution_method} | CR:${userSelection.code_review_tool} | CVR:${userSelection.convergence_review_tool}]`, Phase 5 → in_progress
|
||||||
|
|
||||||
## 10. LP-Phase 5: Handoff to Execution
|
## 10. LP-Phase 5: Handoff to Execution
|
||||||
|
|
||||||
@@ -561,6 +518,7 @@ executionContext = {
|
|||||||
clarificationContext: clarificationContext || null,
|
clarificationContext: clarificationContext || null,
|
||||||
executionMethod: userSelection.execution_method,
|
executionMethod: userSelection.execution_method,
|
||||||
codeReviewTool: userSelection.code_review_tool,
|
codeReviewTool: userSelection.code_review_tool,
|
||||||
|
convergenceReviewTool: userSelection.convergence_review_tool,
|
||||||
originalUserInput: task_description,
|
originalUserInput: task_description,
|
||||||
executorAssignments: executorAssignments, // { taskId: { executor, reason } } — overrides executionMethod
|
executorAssignments: executorAssignments, // { taskId: { executor, reason } } — overrides executionMethod
|
||||||
session: {
|
session: {
|
||||||
@@ -588,7 +546,7 @@ TodoWrite({ todos: [
|
|||||||
{ content: "LP-Phase 1: Exploration", status: "completed" },
|
{ content: "LP-Phase 1: Exploration", status: "completed" },
|
||||||
{ content: "LP-Phase 2: Clarification", status: "completed" },
|
{ content: "LP-Phase 2: Clarification", status: "completed" },
|
||||||
{ content: "LP-Phase 3: Planning", status: "completed" },
|
{ content: "LP-Phase 3: Planning", status: "completed" },
|
||||||
{ content: `LP-Phase 4: Confirmed [${userSelection.execution_method}]`, status: "completed" },
|
{ content: `LP-Phase 4: Confirmed [${userSelection.execution_method} | CR:${userSelection.code_review_tool} | CVR:${userSelection.convergence_review_tool}]`, status: "completed" },
|
||||||
{ content: `LP-Phase 5: Handoff → lite-execute`, status: "completed" },
|
{ content: `LP-Phase 5: Handoff → lite-execute`, status: "completed" },
|
||||||
{ content: `LE-Phase 1: Task Loading [${taskCount} tasks]`, status: "in_progress", activeForm: "Loading tasks" }
|
{ content: `LE-Phase 1: Task Loading [${taskCount} tasks]`, status: "in_progress", activeForm: "Loading tasks" }
|
||||||
]})
|
]})
|
||||||
@@ -605,6 +563,7 @@ Skill("lite-execute")
|
|||||||
├── explorations-manifest.json # Exploration index
|
├── explorations-manifest.json # Exploration index
|
||||||
├── planning-context.md # Evidence paths + understanding
|
├── planning-context.md # Evidence paths + understanding
|
||||||
├── plan.json # Plan overview (task_ids[])
|
├── plan.json # Plan overview (task_ids[])
|
||||||
|
├── code-review.md # Generated by lite-execute Step 4
|
||||||
├── test-checklist.json # Generated by lite-test-review
|
├── test-checklist.json # Generated by lite-test-review
|
||||||
├── test-review.md # Generated by lite-test-review
|
├── test-review.md # Generated by lite-test-review
|
||||||
└── .task/
|
└── .task/
|
||||||
@@ -618,9 +577,12 @@ Skill("lite-execute")
|
|||||||
```
|
```
|
||||||
lite-plan (LP-Phase 1-5)
|
lite-plan (LP-Phase 1-5)
|
||||||
└─ Skill("lite-execute") ← executionContext (global)
|
└─ Skill("lite-execute") ← executionContext (global)
|
||||||
├─ Step 1-4: Execute + Review
|
├─ Step 1-3: Task Execution
|
||||||
|
├─ Step 4: Code Review (quality/correctness/security)
|
||||||
└─ Step 5: Skill("lite-test-review") ← testReviewContext (global)
|
└─ Step 5: Skill("lite-test-review") ← testReviewContext (global)
|
||||||
├─ TR-Phase 1-4: Test + Fix
|
├─ TR-Phase 1: Detect test framework
|
||||||
|
├─ TR-Phase 2: Convergence verification (plan criteria)
|
||||||
|
├─ TR-Phase 3-4: Run tests + Auto-fix
|
||||||
└─ TR-Phase 5: Report + Sync specs
|
└─ TR-Phase 5: Report + Sync specs
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -629,7 +591,7 @@ lite-plan (LP-Phase 1-5)
|
|||||||
| Error | Resolution |
|
| Error | Resolution |
|
||||||
|-------|------------|
|
|-------|------------|
|
||||||
| Exploration agent failure | Skip exploration, continue with task description only |
|
| Exploration agent failure | Skip exploration, continue with task description only |
|
||||||
| Planning agent failure | Fallback to direct planning by Claude |
|
| Planning agent failure | Retry with reduced complexity or suggest breaking task |
|
||||||
| Clarification timeout | Use exploration findings as-is |
|
| Clarification timeout | Use exploration findings as-is |
|
||||||
| Confirmation timeout | Save context, display resume instructions |
|
| Confirmation timeout | Save context, display resume instructions |
|
||||||
| Modify loop > 3 times | Suggest breaking task or using /workflow-plan |
|
| Modify loop > 3 times | Suggest breaking task or using /workflow-plan |
|
||||||
@@ -649,7 +611,7 @@ Auto mode authorizes the complete plan-and-execute workflow with a single confir
|
|||||||
- [ ] Parallel exploration agents launched with run_in_background=false
|
- [ ] Parallel exploration agents launched with run_in_background=false
|
||||||
- [ ] Explorations manifest built from auto-discovered files
|
- [ ] Explorations manifest built from auto-discovered files
|
||||||
- [ ] Clarification needs aggregated, deduped, and presented in batches of 4
|
- [ ] Clarification needs aggregated, deduped, and presented in batches of 4
|
||||||
- [ ] Plan generated via direct Claude (Low) or cli-lite-planning-agent (Medium/High)
|
- [ ] Plan generated via cli-lite-planning-agent
|
||||||
- [ ] Plan output as two-layer: plan.json (task_ids[]) + .task/TASK-*.json
|
- [ ] Plan output as two-layer: plan.json (task_ids[]) + .task/TASK-*.json
|
||||||
- [ ] User confirmation collected (or auto-approved in auto mode)
|
- [ ] User confirmation collected (or auto-approved in auto mode)
|
||||||
- [ ] executionContext fully built with all artifacts and session references
|
- [ ] executionContext fully built with all artifacts and session references
|
||||||
|
|||||||
@@ -31,31 +31,31 @@ Test review and fix engine for lite-execute chain or standalone invocation.
|
|||||||
|
|
||||||
**Input Source**: `testReviewContext` global variable set by lite-execute Step 4
|
**Input Source**: `testReviewContext` global variable set by lite-execute Step 4
|
||||||
|
|
||||||
**Behavior**: Skip session discovery, inherit reviewTool from execution chain, proceed directly to TR-Phase 1.
|
**Behavior**: Skip session discovery, inherit convergenceReviewTool from execution chain, proceed directly to TR-Phase 1.
|
||||||
|
|
||||||
> **Note**: lite-execute Step 4 is the chain gate. Mode 1 invocation means execution is complete — proceed with test review.
|
> **Note**: lite-execute Step 5 is the chain gate. Mode 1 invocation means execution + code review are complete — proceed with convergence verification + tests.
|
||||||
|
|
||||||
### Mode 2: Standalone
|
### Mode 2: Standalone
|
||||||
|
|
||||||
**Trigger**: User calls with session path or `--last`
|
**Trigger**: User calls with session path or `--last`
|
||||||
|
|
||||||
**Behavior**: Discover session → load plan + tasks → `reviewTool = 'agent'` → proceed to TR-Phase 1.
|
**Behavior**: Discover session → load plan + tasks → `convergenceReviewTool = 'agent'` → proceed to TR-Phase 1.
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
let sessionPath, plan, taskFiles, reviewTool
|
let sessionPath, plan, taskFiles, convergenceReviewTool
|
||||||
|
|
||||||
if (testReviewContext) {
|
if (testReviewContext) {
|
||||||
// Mode 1: from lite-execute chain
|
// Mode 1: from lite-execute chain
|
||||||
sessionPath = testReviewContext.session.folder
|
sessionPath = testReviewContext.session.folder
|
||||||
plan = testReviewContext.planObject
|
plan = testReviewContext.planObject
|
||||||
taskFiles = testReviewContext.taskFiles.map(tf => JSON.parse(Read(tf.path)))
|
taskFiles = testReviewContext.taskFiles.map(tf => JSON.parse(Read(tf.path)))
|
||||||
reviewTool = testReviewContext.reviewTool || 'agent'
|
convergenceReviewTool = testReviewContext.convergenceReviewTool || 'agent'
|
||||||
} else {
|
} else {
|
||||||
// Mode 2: standalone — find last session or use provided path
|
// Mode 2: standalone — find last session or use provided path
|
||||||
sessionPath = resolveSessionPath($ARGUMENTS) // Glob('.workflow/.lite-plan/*/plan.json'), take last
|
sessionPath = resolveSessionPath($ARGUMENTS) // Glob('.workflow/.lite-plan/*/plan.json'), take last
|
||||||
plan = JSON.parse(Read(`${sessionPath}/plan.json`))
|
plan = JSON.parse(Read(`${sessionPath}/plan.json`))
|
||||||
taskFiles = plan.task_ids.map(id => JSON.parse(Read(`${sessionPath}/.task/${id}.json`)))
|
taskFiles = plan.task_ids.map(id => JSON.parse(Read(`${sessionPath}/.task/${id}.json`)))
|
||||||
reviewTool = 'agent'
|
convergenceReviewTool = 'agent'
|
||||||
}
|
}
|
||||||
|
|
||||||
const skipFix = $ARGUMENTS?.includes('--skip-fix') || false
|
const skipFix = $ARGUMENTS?.includes('--skip-fix') || false
|
||||||
@@ -66,7 +66,7 @@ const skipFix = $ARGUMENTS?.includes('--skip-fix') || false
|
|||||||
| Phase | Core Action | Output |
|
| Phase | Core Action | Output |
|
||||||
|-------|-------------|--------|
|
|-------|-------------|--------|
|
||||||
| TR-Phase 1 | Detect test framework + gather changes | testConfig, changedFiles |
|
| TR-Phase 1 | Detect test framework + gather changes | testConfig, changedFiles |
|
||||||
| TR-Phase 2 | Review implementation against convergence criteria | reviewResults[] |
|
| TR-Phase 2 | Convergence verification against plan criteria | reviewResults[] |
|
||||||
| TR-Phase 3 | Run tests + generate checklist | test-checklist.json |
|
| TR-Phase 3 | Run tests + generate checklist | test-checklist.json |
|
||||||
| TR-Phase 4 | Auto-fix failures (iterative, max 3 rounds) | Fixed code + updated checklist |
|
| TR-Phase 4 | Auto-fix failures (iterative, max 3 rounds) | Fixed code + updated checklist |
|
||||||
| TR-Phase 5 | Output report + chain to session:sync | test-review.md |
|
| TR-Phase 5 | Output report + chain to session:sync | test-review.md |
|
||||||
@@ -93,32 +93,45 @@ Output: `testConfig = { command, framework, type }` + `changedFiles[]`
|
|||||||
|
|
||||||
// TodoWrite: Phase 1 → completed, Phase 2 → in_progress
|
// TodoWrite: Phase 1 → completed, Phase 2 → in_progress
|
||||||
|
|
||||||
## TR-Phase 2: Review Implementation Against Plan
|
## TR-Phase 2: Convergence Verification
|
||||||
|
|
||||||
**Skip if**: `reviewTool === 'skip'` — set all tasks to PASS, proceed to Phase 3.
|
**Skip if**: `convergenceReviewTool === 'skip'` — set all tasks to PASS, proceed to Phase 3.
|
||||||
|
|
||||||
For each task, verify convergence criteria and identify test gaps.
|
Verify each task's convergence criteria are met in the implementation and identify test gaps.
|
||||||
|
|
||||||
**Agent Review** (reviewTool === 'agent', default):
|
**Agent Convergence Review** (convergenceReviewTool === 'agent', default):
|
||||||
|
|
||||||
For each task in taskFiles:
|
For each task in taskFiles:
|
||||||
1. Extract `convergence.criteria[]` and `test` requirements
|
1. Extract `convergence.criteria[]` from the task
|
||||||
2. Find changed files matching `task.files[].path` against `changedFiles`
|
2. Match `task.files[].path` against `changedFiles` to find actually-changed files
|
||||||
3. Read matched files, evaluate each criterion against implementation
|
3. Read each matched file, verify each convergence criterion with file:line evidence
|
||||||
4. Check test coverage: if `task.test.unit` exists but no test files in changedFiles → mark as test gap
|
4. Check test coverage gaps:
|
||||||
5. Same for `task.test.integration`
|
- If `task.test.unit` defined but no matching test files in changedFiles → mark as test gap
|
||||||
6. Build `reviewResult = { taskId, title, criteria_met[], criteria_unmet[], test_gaps[], files_reviewed[] }`
|
- If `task.test.integration` defined but no integration test in changedFiles → mark as test gap
|
||||||
|
5. Build `reviewResult = { taskId, title, criteria_met[], criteria_unmet[], test_gaps[], files_reviewed[] }`
|
||||||
|
|
||||||
**CLI Review** (reviewTool === 'gemini' or 'codex'):
|
**Verdict logic**:
|
||||||
|
- PASS = all `convergence.criteria` met + no test gaps
|
||||||
|
- PARTIAL = some criteria met OR has test gaps
|
||||||
|
- FAIL = no criteria met
|
||||||
|
|
||||||
|
**CLI Convergence Review** (convergenceReviewTool === 'gemini' or 'codex'):
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
const reviewId = `${sessionId}-tr-review`
|
const reviewId = `${sessionId}-convergence`
|
||||||
Bash(`ccw cli -p "PURPOSE: Post-execution test review — verify convergence criteria met and identify test gaps
|
const taskCriteria = taskFiles.map(t => `${t.id}: [${(t.convergence?.criteria || []).join(' | ')}]`).join('\n')
|
||||||
TASK: • Read plan.json and .task/*.json convergence criteria • For each criterion, check implementation in changed files • Identify missing unit/integration tests • List unmet criteria with file:line evidence
|
Bash(`ccw cli -p "PURPOSE: Convergence verification — check each task's completion criteria against actual implementation
|
||||||
|
TASK: • For each task below, verify every convergence criterion is satisfied in the changed files • Mark each criterion as MET (with file:line evidence) or UNMET (with what's missing) • Identify test coverage gaps (planned tests not found in changes)
|
||||||
|
|
||||||
|
TASK CRITERIA:
|
||||||
|
${taskCriteria}
|
||||||
|
|
||||||
|
CHANGED FILES: ${changedFiles.join(', ')}
|
||||||
|
|
||||||
MODE: analysis
|
MODE: analysis
|
||||||
CONTEXT: @${sessionPath}/plan.json @${sessionPath}/.task/*.json @**/* | Memory: lite-execute completed, reviewing convergence
|
CONTEXT: @${sessionPath}/plan.json @${sessionPath}/.task/*.json @**/* | Memory: lite-execute completed
|
||||||
EXPECTED: Per-task verdict table (PASS/PARTIAL/FAIL) + unmet criteria list + test gap list
|
EXPECTED: Per-task verdict (PASS/PARTIAL/FAIL) with per-criterion evidence + test gap list
|
||||||
CONSTRAINTS: Read-only | Focus on convergence verification" --tool ${reviewTool} --mode analysis --id ${reviewId}`, { run_in_background: true })
|
CONSTRAINTS: Read-only | Focus strictly on convergence criteria verification, NOT code quality (code review already done in lite-execute)" --tool ${convergenceReviewTool} --mode analysis --id ${reviewId}`, { run_in_background: true })
|
||||||
// STOP - wait for hook callback, then parse CLI output into reviewResults format
|
// STOP - wait for hook callback, then parse CLI output into reviewResults format
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -207,13 +220,13 @@ Skill({ skill: "workflow:session:sync", args: `-y "Test review: ${testChecklist.
|
|||||||
|
|
||||||
## Data Structures
|
## Data Structures
|
||||||
|
|
||||||
### testReviewContext (Input - Mode 1, set by lite-execute)
|
### testReviewContext (Input - Mode 1, set by lite-execute Step 5)
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
planObject: { /* same as executionContext.planObject */ },
|
planObject: { /* same as executionContext.planObject */ },
|
||||||
taskFiles: [{ id: string, path: string }],
|
taskFiles: [{ id: string, path: string }],
|
||||||
reviewTool: "skip" | "agent" | "gemini" | "codex",
|
convergenceReviewTool: "skip" | "agent" | "gemini" | "codex",
|
||||||
executionResults: [...],
|
executionResults: [...],
|
||||||
originalUserInput: string,
|
originalUserInput: string,
|
||||||
session: {
|
session: {
|
||||||
|
|||||||
@@ -1,495 +0,0 @@
|
|||||||
---
|
|
||||||
name: workflow-tune
|
|
||||||
description: Workflow tuning skill for multi-command/skill pipelines. Executes each step sequentially, inspects artifacts after each command, analyzes quality via ccw cli resume, builds process documentation, and generates optimization suggestions. Triggers on "workflow tune", "tune workflow", "workflow optimization".
|
|
||||||
allowed-tools: Skill, Agent, AskUserQuestion, TaskCreate, TaskUpdate, TaskList, Read, Write, Edit, Bash, Glob, Grep
|
|
||||||
---
|
|
||||||
|
|
||||||
# Workflow Tune
|
|
||||||
|
|
||||||
Tune multi-step workflows composed of commands or skills. Execute each step, inspect artifacts, analyze via ccw cli resume, build process documentation, and produce actionable optimization suggestions.
|
|
||||||
|
|
||||||
## Architecture Overview
|
|
||||||
|
|
||||||
```
|
|
||||||
┌──────────────────────────────────────────────────────────────────────┐
|
|
||||||
│ Workflow Tune Orchestrator (SKILL.md) │
|
|
||||||
│ → Parse → Decompose → Confirm → Setup → Step Loop → Synthesize │
|
|
||||||
└──────────────────────┬───────────────────────────────────────────────┘
|
|
||||||
│
|
|
||||||
┌───────────────────┼───────────────────────────────────┐
|
|
||||||
↓ ↓ ↓
|
|
||||||
┌──────────┐ ┌─────────────────────────────┐ ┌──────────────┐
|
|
||||||
│ Phase 1 │ │ Step Loop (2→3 per step) │ │ Phase 4 + 5 │
|
|
||||||
│ Setup │ │ ┌─────┐ ┌─────┐ │ │ Synthesize + │
|
|
||||||
│ │──→│ │ P2 │→ │ P3 │ │────→│ Report │
|
|
||||||
│ Parse + │ │ │Exec │ │Anal │ │ │ │
|
|
||||||
│ Decomp + │ │ └─────┘ └─────┘ │ └──────────────┘
|
|
||||||
│ Confirm │ │ ↑ │ next step │
|
|
||||||
└──────────┘ │ └───────┘ │
|
|
||||||
└─────────────────────────────┘
|
|
||||||
|
|
||||||
Phase 1 Detail:
|
|
||||||
Input → [Format 1-3: direct parse] ──→ Command Doc → Confirm → Init
|
|
||||||
→ [Format 4: natural lang] ──→ Semantic Decompose → Command Doc → Confirm → Init
|
|
||||||
```
|
|
||||||
|
|
||||||
## Key Design Principles
|
|
||||||
|
|
||||||
1. **Test-First Evaluation**: Auto-generate per-step acceptance criteria before execution — judge results against concrete requirements, not vague quality
|
|
||||||
2. **Step-by-Step Execution**: Each workflow step executes independently, artifacts inspected before proceeding
|
|
||||||
3. **Resume-Based Analysis**: Uses ccw cli `--resume` to maintain analysis context across steps
|
|
||||||
4. **Process Documentation**: Running `process-log.md` accumulates observations per step
|
|
||||||
5. **Two-Tool Pipeline**: Claude/target tool (execute) + Gemini (analyze) = complementary perspectives
|
|
||||||
6. **Pure Orchestrator**: SKILL.md coordinates only — execution detail in phase files
|
|
||||||
7. **Progressive Phase Loading**: Phase docs read only when that phase executes
|
|
||||||
|
|
||||||
## Interactive Preference Collection
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ★ Auto mode detection
|
|
||||||
const autoYes = /\b(-y|--yes)\b/.test($ARGUMENTS)
|
|
||||||
|
|
||||||
if (autoYes) {
|
|
||||||
workflowPreferences = {
|
|
||||||
autoYes: true,
|
|
||||||
analysisDepth: 'standard',
|
|
||||||
autoFix: false
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
const prefResponse = AskUserQuestion({
|
|
||||||
questions: [
|
|
||||||
{
|
|
||||||
question: "选择 Workflow 调优配置:",
|
|
||||||
header: "Tune Config",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Quick (轻量分析)", description: "每步简要检查,快速产出建议" },
|
|
||||||
{ label: "Standard (标准分析) (Recommended)", description: "每步详细分析,完整过程文档" },
|
|
||||||
{ label: "Deep (深度分析)", description: "每步深度审查,含性能和架构建议" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
question: "是否自动应用优化建议?",
|
|
||||||
header: "Auto Fix",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "No (仅生成报告) (Recommended)", description: "只分析,不修改" },
|
|
||||||
{ label: "Yes (自动应用)", description: "分析后自动应用高优先级建议" }
|
|
||||||
]
|
|
||||||
}
|
|
||||||
]
|
|
||||||
})
|
|
||||||
|
|
||||||
const depthMap = {
|
|
||||||
"Quick": "quick",
|
|
||||||
"Standard": "standard",
|
|
||||||
"Deep": "deep"
|
|
||||||
}
|
|
||||||
const selectedDepth = Object.keys(depthMap).find(k =>
|
|
||||||
prefResponse["Tune Config"].startsWith(k)
|
|
||||||
) || "Standard"
|
|
||||||
|
|
||||||
workflowPreferences = {
|
|
||||||
autoYes: false,
|
|
||||||
analysisDepth: depthMap[selectedDepth],
|
|
||||||
autoFix: prefResponse["Auto Fix"].startsWith("Yes")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Input Processing
|
|
||||||
|
|
||||||
```
|
|
||||||
$ARGUMENTS → Parse:
|
|
||||||
├─ Workflow definition: one of:
|
|
||||||
│ ├─ Format 1: Inline steps — "step1 | step2 | step3" (pipe-separated commands)
|
|
||||||
│ ├─ Format 2: Skill names — "skill-a,skill-b,skill-c" (comma-separated)
|
|
||||||
│ ├─ Format 3: File path — "--file workflow.json" (JSON definition)
|
|
||||||
│ └─ Format 4: Natural language — free-text description, auto-decomposed into steps
|
|
||||||
├─ Test context: --context "description of what the workflow should achieve"
|
|
||||||
└─ Flags: --depth quick|standard|deep, -y/--yes, --auto-fix
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format Detection Priority
|
|
||||||
|
|
||||||
```
|
|
||||||
1. --file flag present → Format 3 (JSON)
|
|
||||||
2. Contains pipe "|" → Format 1 (inline commands)
|
|
||||||
3. Matches skill-name pattern → Format 2 (comma-separated skills)
|
|
||||||
4. Everything else → Format 4 (natural language → semantic decomposition)
|
|
||||||
4a. Contains file path? → Read file as reference doc, extract workflow steps via LLM
|
|
||||||
4b. Pure intent text → Direct intent-verb matching (intentMap)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Format 4: Semantic Decomposition (Natural Language)
|
|
||||||
|
|
||||||
When input is free-text (e.g., "分析 src 目录代码质量,做代码评审,然后修复高优先级问题"), the orchestrator:
|
|
||||||
|
|
||||||
#### 4a. Reference Document Mode (input contains file path)
|
|
||||||
|
|
||||||
When the input contains a file path (e.g., `d:\maestro2\guide\command-usage-guide.md 提取核心工作流`), the orchestrator:
|
|
||||||
|
|
||||||
1. **Detect File Path**: Extract file path from input via path pattern matching
|
|
||||||
2. **Read Reference Doc**: Read the file content as workflow reference material
|
|
||||||
3. **Extract Workflow via LLM**: Use Gemini to analyze the document + user intent, extract executable workflow steps
|
|
||||||
4. **Step Chain Generation**: LLM returns structured step chain with commands, tools, and execution order
|
|
||||||
5. **Command Doc + Confirmation**: Same as 4b below
|
|
||||||
|
|
||||||
#### 4b. Pure Intent Mode (no file path)
|
|
||||||
|
|
||||||
1. **Semantic Parse**: Identify intent verbs and targets → map to available skills/commands
|
|
||||||
2. **Step Chain Generation**: Produce ordered step chain with tool/mode selection
|
|
||||||
3. **Command Doc**: Generate formatted execution plan document
|
|
||||||
4. **User Confirmation**: Display plan, ask user to confirm/edit before execution
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ★ 4a: If input contains file path → read file, extract workflow via LLM
|
|
||||||
// Detect paths: Windows (D:\path\file.md), Unix (/path/file.md), relative (./file.md)
|
|
||||||
// Read file content → send to Gemini with user intent → get executable step chain
|
|
||||||
// See phases/01-setup.md Step 1.1b Mode 4a
|
|
||||||
|
|
||||||
// ★ 4b: Pure intent text → regex-based intent-to-tool mapping
|
|
||||||
const intentMap = {
|
|
||||||
'分析|analyze|审查|inspect|scan': { tool: 'gemini', mode: 'analysis', rule: 'analysis-analyze-code-patterns' },
|
|
||||||
'评审|review|code review': { tool: 'gemini', mode: 'analysis', rule: 'analysis-review-code-quality' },
|
|
||||||
// ... (full map in phases/01-setup.md Step 1.1b Mode 4b)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Match input segments to intents, produce step chain
|
|
||||||
// See phases/01-setup.md Step 1.1b for full algorithm
|
|
||||||
```
|
|
||||||
|
|
||||||
### Workflow JSON Format (when using --file)
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"name": "my-workflow",
|
|
||||||
"description": "What this workflow achieves",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"name": "step-1-name",
|
|
||||||
"type": "skill|command|ccw-cli",
|
|
||||||
"command": "/skill-name args" | "ccw cli -p '...' --tool gemini --mode analysis",
|
|
||||||
"expected_artifacts": ["output.md", "report.json"],
|
|
||||||
"success_criteria": "description of what success looks like"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Inline Step Parsing
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Pipe-separated: each segment is a command
|
|
||||||
// "ccw cli -p 'analyze' --tool gemini --mode analysis | /review-code src/ | ccw cli -p 'fix' --tool claude --mode write"
|
|
||||||
const steps = input.split('|').map((cmd, i) => ({
|
|
||||||
name: `step-${i + 1}`,
|
|
||||||
type: cmd.trim().startsWith('/') ? 'skill' : 'command',
|
|
||||||
command: cmd.trim(),
|
|
||||||
expected_artifacts: [],
|
|
||||||
success_criteria: ''
|
|
||||||
}));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Pre-Execution Confirmation
|
|
||||||
|
|
||||||
After parsing (all formats) or decomposition (Format 4), generate a **Command Document** and ask for user confirmation before executing.
|
|
||||||
|
|
||||||
### Command Document Format
|
|
||||||
|
|
||||||
```markdown
|
|
||||||
# Workflow Tune — Execution Plan
|
|
||||||
|
|
||||||
**Workflow**: {name}
|
|
||||||
**Goal**: {context}
|
|
||||||
**Steps**: {count}
|
|
||||||
**Analysis Depth**: {depth}
|
|
||||||
|
|
||||||
## Step Chain
|
|
||||||
|
|
||||||
| # | Name | Type | Command | Tool | Mode |
|
|
||||||
|---|------|------|---------|------|------|
|
|
||||||
| 1 | {name} | {type} | {command} | {tool} | {mode} |
|
|
||||||
| 2 | ... | ... | ... | ... | ... |
|
|
||||||
|
|
||||||
## Execution Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
Step 1: {name}
|
|
||||||
→ Command: {command}
|
|
||||||
→ Expected: {artifacts}
|
|
||||||
→ Feeds into: Step 2
|
|
||||||
↓
|
|
||||||
Step 2: {name}
|
|
||||||
→ Command: {command}
|
|
||||||
→ Expected: {artifacts}
|
|
||||||
→ Feeds into: Step 3
|
|
||||||
↓
|
|
||||||
...
|
|
||||||
```
|
|
||||||
|
|
||||||
## Estimated Scope
|
|
||||||
|
|
||||||
- Total CLI calls: {N} (execute) + {N} (analyze) + 1 (synthesize)
|
|
||||||
- Analysis tool: gemini (--resume chain)
|
|
||||||
- Process documentation: process-log.md (accumulated)
|
|
||||||
```
|
|
||||||
|
|
||||||
### Confirmation Flow
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ★ Skip confirmation only if -y/--yes flag
|
|
||||||
if (!workflowPreferences.autoYes) {
|
|
||||||
// Display command document to user
|
|
||||||
// Output the formatted plan (direct text output, NOT a file)
|
|
||||||
|
|
||||||
const confirmation = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "确认执行以上 Workflow 调优计划?",
|
|
||||||
header: "Confirm Execution",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Execute (确认执行)", description: "按计划开始执行" },
|
|
||||||
{ label: "Edit steps (修改步骤)", description: "我想调整某些步骤" },
|
|
||||||
{ label: "Cancel (取消)", description: "取消本次调优" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (confirmation["Confirm Execution"].startsWith("Cancel")) {
|
|
||||||
// Abort workflow
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (confirmation["Confirm Execution"].startsWith("Edit")) {
|
|
||||||
// Ask user for modifications
|
|
||||||
const editResponse = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "请描述要修改的内容(如:删除步骤2、步骤3改用codex、在步骤1后加入安全扫描):",
|
|
||||||
header: "Edit Steps"
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
// Apply user edits to steps[] → re-display command doc → re-confirm
|
|
||||||
// (recursive confirmation loop until Execute or Cancel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Execution Flow
|
|
||||||
|
|
||||||
> **COMPACT DIRECTIVE**: Context compression MUST check TaskUpdate phase status.
|
|
||||||
> The phase currently marked `in_progress` is the active execution phase — preserve its FULL content.
|
|
||||||
> Only compress phases marked `completed` or `pending`.
|
|
||||||
|
|
||||||
### Phase 1: Setup (one-time)
|
|
||||||
|
|
||||||
Read and execute: `Ref: phases/01-setup.md`
|
|
||||||
|
|
||||||
- Parse workflow steps from input (Format 1-3: direct parse, Format 4: semantic decomposition)
|
|
||||||
- Generate Command Document (formatted execution plan)
|
|
||||||
- **User Confirmation**: Display plan, wait for confirm/edit/cancel
|
|
||||||
- **Generate Test Requirements**: Auto-create per-step acceptance criteria via Gemini (expected outputs, content signals, quality thresholds, pass/fail criteria, handoff contracts)
|
|
||||||
- Create workspace at `.workflow/.scratchpad/workflow-tune-{ts}/`
|
|
||||||
- Initialize workflow-state.json (with test_requirements per step)
|
|
||||||
- Create process-log.md template
|
|
||||||
|
|
||||||
Output: `workDir`, `steps[]` (with test_requirements), `workflowContext`, `commandDoc`, initialized state
|
|
||||||
|
|
||||||
### Step Loop (Phase 2 + Phase 3, per step)
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Track analysis session ID for resume chain
|
|
||||||
let analysisSessionId = null;
|
|
||||||
|
|
||||||
for (let stepIdx = 0; stepIdx < state.steps.length; stepIdx++) {
|
|
||||||
const step = state.steps[stepIdx];
|
|
||||||
|
|
||||||
TaskUpdate(stepLoopTask, {
|
|
||||||
subject: `Step ${stepIdx + 1}/${state.steps.length}: ${step.name}`,
|
|
||||||
status: 'in_progress'
|
|
||||||
});
|
|
||||||
|
|
||||||
// === Phase 2: Execute Step ===
|
|
||||||
// Read: phases/02-step-execute.md
|
|
||||||
// Execute command/skill → collect artifacts
|
|
||||||
// Write step-{N}-artifacts-manifest.json
|
|
||||||
|
|
||||||
// === Phase 3: Analyze Step ===
|
|
||||||
// Read: phases/03-step-analyze.md
|
|
||||||
// Inspect artifacts → ccw cli gemini --resume analysisSessionId
|
|
||||||
// Write step-{N}-analysis.md → append to process-log.md
|
|
||||||
// Update analysisSessionId for next step's resume
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
state.steps[stepIdx].status = 'completed';
|
|
||||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Phase 2: Execute Step (per step)
|
|
||||||
|
|
||||||
Read and execute: `Ref: phases/02-step-execute.md`
|
|
||||||
|
|
||||||
- Create step working directory
|
|
||||||
- Execute command/skill via ccw cli or Skill tool
|
|
||||||
- Collect output artifacts
|
|
||||||
- Write artifacts manifest
|
|
||||||
|
|
||||||
### Phase 3: Analyze Step (per step)
|
|
||||||
|
|
||||||
Read and execute: `Ref: phases/03-step-analyze.md`
|
|
||||||
|
|
||||||
- Inspect step artifacts (file list, content summary, quality signals)
|
|
||||||
- **Compare actual output against test requirements** (pass_criteria, content_signals, fail_signals, handoff_contract)
|
|
||||||
- Build analysis prompt with step context + test requirements + previous process log
|
|
||||||
- Execute: `ccw cli --tool gemini --mode analysis [--resume sessionId]`
|
|
||||||
- Parse analysis → write step-{N}-analysis.md (with requirement match: PASS/FAIL)
|
|
||||||
- Append findings to process-log.md
|
|
||||||
- Return analysis session ID for resume chain
|
|
||||||
|
|
||||||
### Phase 4: Synthesize (one-time)
|
|
||||||
|
|
||||||
Read and execute: `Ref: phases/04-synthesize.md`
|
|
||||||
|
|
||||||
- Read complete process-log.md + all step analyses
|
|
||||||
- Build synthesis prompt with full workflow context
|
|
||||||
- Execute: `ccw cli --tool gemini --mode analysis --resume analysisSessionId`
|
|
||||||
- Generate cross-step optimization insights
|
|
||||||
- Write synthesis.md
|
|
||||||
|
|
||||||
### Phase 5: Optimization Report (one-time)
|
|
||||||
|
|
||||||
Read and execute: `Ref: phases/05-optimize-report.md`
|
|
||||||
|
|
||||||
- Aggregate all analyses and synthesis
|
|
||||||
- Generate structured optimization report
|
|
||||||
- Optionally apply high-priority fixes (if autoFix enabled)
|
|
||||||
- Write final-report.md
|
|
||||||
- Display summary to user
|
|
||||||
|
|
||||||
**Phase Reference Documents**:
|
|
||||||
|
|
||||||
| Phase | Document | Purpose | Compact |
|
|
||||||
|-------|----------|---------|---------|
|
|
||||||
| 1 | [phases/01-setup.md](phases/01-setup.md) | Initialize workspace and state | TaskUpdate driven |
|
|
||||||
| 2 | [phases/02-step-execute.md](phases/02-step-execute.md) | Execute workflow step | TaskUpdate driven |
|
|
||||||
| 3 | [phases/03-step-analyze.md](phases/03-step-analyze.md) | Analyze step artifacts | TaskUpdate driven + resume |
|
|
||||||
| 4 | [phases/04-synthesize.md](phases/04-synthesize.md) | Cross-step synthesis | TaskUpdate driven + resume |
|
|
||||||
| 5 | [phases/05-optimize-report.md](phases/05-optimize-report.md) | Generate final report | TaskUpdate driven |
|
|
||||||
|
|
||||||
## Data Flow
|
|
||||||
|
|
||||||
```
|
|
||||||
User Input (workflow steps / natural language + context)
|
|
||||||
↓
|
|
||||||
Phase 1: Setup
|
|
||||||
├─ [Format 1-3] Direct parse → steps[]
|
|
||||||
├─ [Format 4a] File path detected → Read doc → LLM extract → steps[]
|
|
||||||
├─ [Format 4b] Pure intent text → Regex intent matching → steps[]
|
|
||||||
↓
|
|
||||||
Command Document (formatted plan)
|
|
||||||
↓
|
|
||||||
User Confirmation (Execute / Edit / Cancel)
|
|
||||||
↓ (Execute confirmed)
|
|
||||||
↓
|
|
||||||
Generate Test Requirements (Gemini) → per-step acceptance criteria
|
|
||||||
↓ workDir, steps[] (with test_requirements), workflow-state.json, process-log.md
|
|
||||||
↓
|
|
||||||
┌─→ Phase 2: Execute Step N (ccw cli / Skill)
|
|
||||||
│ ↓ step-N/ artifacts
|
|
||||||
│ ↓
|
|
||||||
│ Phase 3: Analyze Step N (ccw cli gemini --resume)
|
|
||||||
│ ↓ step-N-analysis.md, process-log.md updated
|
|
||||||
│ ↓ analysisSessionId carried forward
|
|
||||||
│ ↓
|
|
||||||
│ [More steps?]─── YES ──→ next step (Phase 2)
|
|
||||||
│ ↓ NO
|
|
||||||
│ ↓
|
|
||||||
└───┘
|
|
||||||
↓
|
|
||||||
Phase 4: Synthesize (ccw cli gemini --resume)
|
|
||||||
↓ synthesis.md
|
|
||||||
↓
|
|
||||||
Phase 5: Report
|
|
||||||
↓ final-report.md + optional auto-fix
|
|
||||||
↓
|
|
||||||
Done
|
|
||||||
```
|
|
||||||
|
|
||||||
## TaskUpdate Pattern
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Initial state
|
|
||||||
TaskCreate({ subject: "Phase 1: Setup workspace", activeForm: "Parsing workflow" })
|
|
||||||
TaskCreate({ subject: "Step Loop", activeForm: "Executing steps" })
|
|
||||||
TaskCreate({ subject: "Phase 4-5: Synthesize & Report", activeForm: "Pending" })
|
|
||||||
|
|
||||||
// Per-step tracking
|
|
||||||
for (const step of state.steps) {
|
|
||||||
TaskCreate({
|
|
||||||
subject: `Step: ${step.name}`,
|
|
||||||
activeForm: `Pending`,
|
|
||||||
description: `${step.type}: ${step.command}`
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// During step execution
|
|
||||||
TaskUpdate(stepTask, {
|
|
||||||
subject: `Step: ${step.name} — Executing`,
|
|
||||||
activeForm: `Running ${step.command}`
|
|
||||||
})
|
|
||||||
|
|
||||||
// After step analysis
|
|
||||||
TaskUpdate(stepTask, {
|
|
||||||
subject: `Step: ${step.name} — Analyzed`,
|
|
||||||
activeForm: `Quality: ${stepQuality} | Issues: ${issueCount}`,
|
|
||||||
status: 'completed'
|
|
||||||
})
|
|
||||||
|
|
||||||
// Final
|
|
||||||
TaskUpdate(synthesisTask, {
|
|
||||||
subject: `Synthesis & Report (${state.steps.length} steps, ${totalIssues} issues)`,
|
|
||||||
status: 'completed'
|
|
||||||
})
|
|
||||||
```
|
|
||||||
|
|
||||||
## Resume Chain Strategy
|
|
||||||
|
|
||||||
```
|
|
||||||
Step 1 Execute → artifacts
|
|
||||||
Step 1 Analyze → ccw cli gemini --mode analysis → sessionId_1
|
|
||||||
Step 2 Execute → artifacts
|
|
||||||
Step 2 Analyze → ccw cli gemini --mode analysis --resume sessionId_1 → sessionId_2
|
|
||||||
...
|
|
||||||
Step N Analyze → sessionId_N
|
|
||||||
Synthesize → ccw cli gemini --mode analysis --resume sessionId_N → final
|
|
||||||
```
|
|
||||||
|
|
||||||
Each analysis step resumes the previous session, maintaining full context of:
|
|
||||||
- All prior step observations
|
|
||||||
- Accumulated quality patterns
|
|
||||||
- Cross-step dependency insights
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Phase | Error | Recovery |
|
|
||||||
|-------|-------|----------|
|
|
||||||
| 2: Execute | CLI timeout/crash | Retry once, then record failure and continue to next step |
|
|
||||||
| 2: Execute | Skill not found | Skip step, note in process-log |
|
|
||||||
| 3: Analyze | CLI fails | Retry without --resume, start fresh session |
|
|
||||||
| 3: Analyze | Resume session not found | Start fresh analysis session |
|
|
||||||
| 4: Synthesize | CLI fails | Generate report from individual step analyses only |
|
|
||||||
| Any | 3+ consecutive errors | Terminate with partial report |
|
|
||||||
|
|
||||||
**Error Budget**: Each step gets 1 retry. 3 consecutive failures triggers early termination.
|
|
||||||
|
|
||||||
## Core Rules
|
|
||||||
|
|
||||||
1. **Start Immediately**: First action is preference collection → Phase 1 setup
|
|
||||||
2. **Progressive Loading**: Read phase doc ONLY when that phase is about to execute
|
|
||||||
3. **Inspect Before Proceed**: Always check step artifacts before moving to next step
|
|
||||||
4. **Background CLI**: ccw cli runs in background, wait for hook callback before proceeding
|
|
||||||
5. **Resume Chain**: Maintain analysis session continuity via --resume
|
|
||||||
6. **Process Documentation**: Every step observation goes into process-log.md
|
|
||||||
7. **Single State Source**: `workflow-state.json` is the only source of truth
|
|
||||||
8. **DO NOT STOP**: Continuous execution until all steps processed
|
|
||||||
@@ -1,670 +0,0 @@
|
|||||||
# Phase 1: Setup
|
|
||||||
|
|
||||||
Initialize workspace, parse workflow definition, semantic decomposition for natural language, generate command document, user confirmation, create state and process log.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Parse workflow steps from user input (Format 1-3: direct parse, Format 4: semantic decomposition)
|
|
||||||
- Generate Command Document (formatted execution plan)
|
|
||||||
- User confirmation: Execute / Edit steps / Cancel
|
|
||||||
- Validate step commands/skill paths
|
|
||||||
- Create isolated workspace directory
|
|
||||||
- Initialize workflow-state.json and process-log.md
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 1.1: Parse Input
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const args = $ARGUMENTS.trim();
|
|
||||||
|
|
||||||
// Detect input format
|
|
||||||
let steps = [];
|
|
||||||
let workflowName = 'unnamed-workflow';
|
|
||||||
let workflowContext = '';
|
|
||||||
|
|
||||||
// Format 1: JSON file (--file path)
|
|
||||||
const fileMatch = args.match(/--file\s+"?([^\s"]+)"?/);
|
|
||||||
if (fileMatch) {
|
|
||||||
const wfDef = JSON.parse(Read(fileMatch[1]));
|
|
||||||
workflowName = wfDef.name || 'unnamed-workflow';
|
|
||||||
workflowContext = wfDef.description || '';
|
|
||||||
steps = wfDef.steps;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format 2: Pipe-separated commands ("cmd1 | cmd2 | cmd3")
|
|
||||||
else if (args.includes('|')) {
|
|
||||||
const rawSteps = args.split(/(?:--context|--depth|-y|--yes|--auto-fix)\s+("[^"]*"|\S+)/)[0];
|
|
||||||
steps = rawSteps.split('|').map((cmd, i) => ({
|
|
||||||
name: `step-${i + 1}`,
|
|
||||||
type: cmd.trim().startsWith('/') ? 'skill'
|
|
||||||
: cmd.trim().startsWith('ccw cli') ? 'ccw-cli'
|
|
||||||
: 'command',
|
|
||||||
command: cmd.trim(),
|
|
||||||
expected_artifacts: [],
|
|
||||||
success_criteria: ''
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format 3: Comma-separated skill names (matches pattern: word,word or word-word,word-word)
|
|
||||||
else if (/^[\w-]+(,[\w-]+)+/.test(args.split(/\s/)[0])) {
|
|
||||||
const skillPart = args.match(/^([^\s]+)/);
|
|
||||||
const skillNames = skillPart ? skillPart[1].split(',') : [];
|
|
||||||
steps = skillNames.map((name, i) => {
|
|
||||||
const skillPath = name.startsWith('.claude/') ? name : `.claude/skills/${name}`;
|
|
||||||
return {
|
|
||||||
name: name.replace('.claude/skills/', ''),
|
|
||||||
type: 'skill',
|
|
||||||
command: `/${name.replace('.claude/skills/', '')}`,
|
|
||||||
skill_path: skillPath,
|
|
||||||
expected_artifacts: [],
|
|
||||||
success_criteria: ''
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Format 4: Natural language → semantic decomposition
|
|
||||||
else {
|
|
||||||
inputFormat = 'natural-language';
|
|
||||||
naturalLanguageInput = args.replace(/--\w+\s+"[^"]*"/g, '').replace(/--\w+\s+\S+/g, '').replace(/-y|--yes/g, '').trim();
|
|
||||||
|
|
||||||
// ★ 4a: Detect file paths in input (Windows absolute, Unix absolute, or relative paths)
|
|
||||||
const filePathPattern = /(?:[A-Za-z]:[\\\/][^\s,;,;、]+|\/[^\s,;,;、]+\.(?:md|txt|json|yaml|yml|toml)|\.\/?[^\s,;,;、]+\.(?:md|txt|json|yaml|yml|toml))/g;
|
|
||||||
const detectedPaths = naturalLanguageInput.match(filePathPattern) || [];
|
|
||||||
referenceDocContent = null;
|
|
||||||
referenceDocPath = null;
|
|
||||||
|
|
||||||
if (detectedPaths.length > 0) {
|
|
||||||
// Read first detected file as reference document
|
|
||||||
referenceDocPath = detectedPaths[0];
|
|
||||||
try {
|
|
||||||
referenceDocContent = Read(referenceDocPath);
|
|
||||||
// Remove file path from natural language input to get pure user intent
|
|
||||||
naturalLanguageInput = naturalLanguageInput.replace(referenceDocPath, '').trim();
|
|
||||||
} catch (e) {
|
|
||||||
// File not readable — fall through to 4b (pure intent mode)
|
|
||||||
referenceDocContent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Steps will be populated in Step 1.1b
|
|
||||||
steps = [];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse --context
|
|
||||||
const contextMatch = args.match(/--context\s+"([^"]+)"/);
|
|
||||||
workflowContext = contextMatch ? contextMatch[1] : workflowContext;
|
|
||||||
|
|
||||||
// Parse --depth
|
|
||||||
const depthMatch = args.match(/--depth\s+(quick|standard|deep)/);
|
|
||||||
if (depthMatch) {
|
|
||||||
workflowPreferences.analysisDepth = depthMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// If no context provided, ask user
|
|
||||||
if (!workflowContext) {
|
|
||||||
const response = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "请描述这个 workflow 的目标和预期效果:",
|
|
||||||
header: "Workflow Context",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "General quality check", description: "通用质量检查,评估步骤间衔接" },
|
|
||||||
{ label: "Custom description", description: "自定义描述 workflow 目标" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
workflowContext = response["Workflow Context"];
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.1b: Semantic Decomposition (Format 4 only)
|
|
||||||
|
|
||||||
> Skip this step if `inputFormat !== 'natural-language'`.
|
|
||||||
|
|
||||||
Two sub-modes based on whether a reference document was detected in Step 1.1:
|
|
||||||
|
|
||||||
#### Mode 4a: Reference Document → LLM Extraction
|
|
||||||
|
|
||||||
When `referenceDocContent` is available, use Gemini to extract executable workflow steps from the document, guided by the user's intent text.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (inputFormat === 'natural-language' && referenceDocContent) {
|
|
||||||
// ★ 4a: Extract workflow steps from reference document via LLM
|
|
||||||
const extractPrompt = `PURPOSE: Extract an executable workflow step chain from the reference document below, guided by the user's intent.
|
|
||||||
|
|
||||||
USER INTENT: ${naturalLanguageInput}
|
|
||||||
REFERENCE DOCUMENT PATH: ${referenceDocPath}
|
|
||||||
|
|
||||||
REFERENCE DOCUMENT CONTENT:
|
|
||||||
${referenceDocContent}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
1. Read the document and identify the workflow/process it describes (commands, steps, phases, procedures)
|
|
||||||
2. Filter by user intent — only extract the steps the user wants to test/tune
|
|
||||||
3. For each step, determine:
|
|
||||||
- The actual command to execute (shell command, CLI invocation, or skill name)
|
|
||||||
- The execution order and dependencies
|
|
||||||
- What tool to use (gemini/claude/codex/qwen) and mode (default: write)
|
|
||||||
4. Generate a step chain that can be directly executed
|
|
||||||
|
|
||||||
IMPORTANT:
|
|
||||||
- Extract REAL executable commands from the document, not analysis tasks about the document
|
|
||||||
- The user wants to RUN these workflow steps, not analyze the document itself
|
|
||||||
- If the document describes CLI commands like "maestro init", "maestro plan", etc., those are the steps to extract
|
|
||||||
- Preserve the original command syntax from the document
|
|
||||||
- Map each command to appropriate tool/mode for ccw cli execution, OR mark as 'command' type for direct shell execution
|
|
||||||
- Default mode to "write" — almost all steps produce output artifacts (files, reports, configs), even analysis steps need write permission to save results
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"workflow_name": "<descriptive name>",
|
|
||||||
"workflow_context": "<what this workflow achieves>",
|
|
||||||
"steps": [
|
|
||||||
{
|
|
||||||
"name": "<step name>",
|
|
||||||
"type": "command|ccw-cli|skill",
|
|
||||||
"command": "<the actual command to execute>",
|
|
||||||
"tool": "<gemini|claude|codex|qwen or null for shell commands>",
|
|
||||||
"mode": "<write (default) | analysis (read-only, rare) | null for shell commands>",
|
|
||||||
"rule": "<rule template or null>",
|
|
||||||
"original_text": "<source text from document>",
|
|
||||||
"expected_artifacts": ["<expected output files>"],
|
|
||||||
"success_criteria": "<what success looks like>"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Output ONLY valid JSON. Extract executable steps, NOT document analysis tasks.`;
|
|
||||||
|
|
||||||
Bash({
|
|
||||||
command: `ccw cli -p "${escapeForShell(extractPrompt)}" --tool gemini --mode analysis --rule universal-rigorous-style`,
|
|
||||||
run_in_background: true,
|
|
||||||
timeout: 300000
|
|
||||||
});
|
|
||||||
|
|
||||||
// STOP — wait for hook callback
|
|
||||||
// After callback: parse JSON response into steps[]
|
|
||||||
|
|
||||||
const extractOutput = /* CLI output from callback */;
|
|
||||||
const extractJsonMatch = extractOutput.match(/\{[\s\S]*\}/);
|
|
||||||
|
|
||||||
if (extractJsonMatch) {
|
|
||||||
try {
|
|
||||||
const extracted = JSON.parse(extractJsonMatch[0]);
|
|
||||||
workflowName = extracted.workflow_name || 'doc-workflow';
|
|
||||||
workflowContext = extracted.workflow_context || naturalLanguageInput;
|
|
||||||
steps = (extracted.steps || []).map((s, i) => ({
|
|
||||||
name: s.name || `step-${i + 1}`,
|
|
||||||
type: s.type || 'command',
|
|
||||||
command: s.command,
|
|
||||||
tool: s.tool || null,
|
|
||||||
mode: s.mode || null,
|
|
||||||
rule: s.rule || null,
|
|
||||||
original_text: s.original_text || '',
|
|
||||||
expected_artifacts: s.expected_artifacts || [],
|
|
||||||
success_criteria: s.success_criteria || ''
|
|
||||||
}));
|
|
||||||
} catch (e) {
|
|
||||||
// JSON parse failed — fall through to 4b intent matching
|
|
||||||
referenceDocContent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (steps.length === 0) {
|
|
||||||
// Extraction produced no steps — fall through to 4b
|
|
||||||
referenceDocContent = null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
#### Mode 4b: Pure Intent → Regex Matching
|
|
||||||
|
|
||||||
When no reference document, or when 4a extraction failed, decompose by intent-verb matching.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (inputFormat === 'natural-language' && !referenceDocContent) {
|
|
||||||
// Intent-to-tool mapping (regex patterns → tool config)
|
|
||||||
const intentMap = [
|
|
||||||
{ pattern: /分析|analyze|审查|inspect|scan/i, name: 'analyze', tool: 'gemini', mode: 'analysis', rule: 'analysis-analyze-code-patterns' },
|
|
||||||
{ pattern: /评审|review|code.?review/i, name: 'review', tool: 'gemini', mode: 'analysis', rule: 'analysis-review-code-quality' },
|
|
||||||
{ pattern: /诊断|debug|排查|diagnose/i, name: 'diagnose', tool: 'gemini', mode: 'analysis', rule: 'analysis-diagnose-bug-root-cause' },
|
|
||||||
{ pattern: /安全|security|漏洞|vulnerability/i, name: 'security-audit', tool: 'gemini', mode: 'analysis', rule: 'analysis-assess-security-risks' },
|
|
||||||
{ pattern: /性能|performance|perf/i, name: 'perf-analysis', tool: 'gemini', mode: 'analysis', rule: 'analysis-analyze-performance' },
|
|
||||||
{ pattern: /架构|architecture/i, name: 'arch-review', tool: 'gemini', mode: 'analysis', rule: 'analysis-review-architecture' },
|
|
||||||
{ pattern: /修复|fix|repair|解决/i, name: 'fix', tool: 'claude', mode: 'write', rule: 'development-debug-runtime-issues' },
|
|
||||||
{ pattern: /实现|implement|开发|create|新增/i, name: 'implement', tool: 'claude', mode: 'write', rule: 'development-implement-feature' },
|
|
||||||
{ pattern: /重构|refactor/i, name: 'refactor', tool: 'claude', mode: 'write', rule: 'development-refactor-codebase' },
|
|
||||||
{ pattern: /测试|test|generate.?test/i, name: 'test', tool: 'claude', mode: 'write', rule: 'development-generate-tests' },
|
|
||||||
{ pattern: /规划|plan|设计|design/i, name: 'plan', tool: 'gemini', mode: 'analysis', rule: 'planning-plan-architecture-design' },
|
|
||||||
{ pattern: /拆解|breakdown|分解/i, name: 'breakdown', tool: 'gemini', mode: 'analysis', rule: 'planning-breakdown-task-steps' },
|
|
||||||
];
|
|
||||||
|
|
||||||
// Segment input by Chinese/English delimiters: 、,,;然后/接着/最后/之后 etc.
|
|
||||||
const segments = naturalLanguageInput
|
|
||||||
.split(/[,,;;、]|(?:然后|接着|之后|最后|再|并|and then|then|finally|next)\s*/i)
|
|
||||||
.map(s => s.trim())
|
|
||||||
.filter(Boolean);
|
|
||||||
|
|
||||||
// Match each segment to an intent (with ambiguity resolution)
|
|
||||||
steps = segments.map((segment, i) => {
|
|
||||||
const allMatches = intentMap.filter(m => m.pattern.test(segment));
|
|
||||||
let matched = allMatches[0] || null;
|
|
||||||
|
|
||||||
// ★ Ambiguity resolution: if multiple intents match, ask user
|
|
||||||
if (allMatches.length > 1) {
|
|
||||||
const disambig = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `"${segment}" 匹配到多个意图,请选择最符合的:`,
|
|
||||||
header: `Disambiguate Step ${i + 1}`,
|
|
||||||
multiSelect: false,
|
|
||||||
options: allMatches.map(m => ({
|
|
||||||
label: m.name,
|
|
||||||
description: `Tool: ${m.tool}, Mode: ${m.mode}, Rule: ${m.rule}`
|
|
||||||
}))
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
const chosen = disambig[`Disambiguate Step ${i + 1}`];
|
|
||||||
matched = allMatches.find(m => m.name === chosen) || allMatches[0];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (matched) {
|
|
||||||
// Extract target scope from segment (e.g., "分析 src 目录" → scope = "src")
|
|
||||||
const scopeMatch = segment.match(/(?:目录|文件|模块|directory|file|module)?\s*[::]?\s*(\S+)/);
|
|
||||||
const scope = scopeMatch ? scopeMatch[1].replace(/[的地得]$/, '') : '**/*';
|
|
||||||
|
|
||||||
return {
|
|
||||||
name: `${matched.name}`,
|
|
||||||
type: 'ccw-cli',
|
|
||||||
command: `ccw cli -p "${segment}" --tool ${matched.tool} --mode ${matched.mode} --rule ${matched.rule}`,
|
|
||||||
tool: matched.tool,
|
|
||||||
mode: matched.mode,
|
|
||||||
rule: matched.rule,
|
|
||||||
original_text: segment,
|
|
||||||
expected_artifacts: [],
|
|
||||||
success_criteria: ''
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
// Unmatched segment → generic analysis step
|
|
||||||
return {
|
|
||||||
name: `step-${i + 1}`,
|
|
||||||
type: 'ccw-cli',
|
|
||||||
command: `ccw cli -p "${segment}" --tool gemini --mode analysis`,
|
|
||||||
tool: 'gemini',
|
|
||||||
mode: 'analysis',
|
|
||||||
rule: 'universal-rigorous-style',
|
|
||||||
original_text: segment,
|
|
||||||
expected_artifacts: [],
|
|
||||||
success_criteria: ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Deduplicate: if same intent name appears twice, suffix with index
|
|
||||||
const nameCount = {};
|
|
||||||
steps.forEach(s => {
|
|
||||||
nameCount[s.name] = (nameCount[s.name] || 0) + 1;
|
|
||||||
if (nameCount[s.name] > 1) {
|
|
||||||
s.name = `${s.name}-${nameCount[s.name]}`;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Common: set workflow context and name for Format 4
|
|
||||||
if (inputFormat === 'natural-language') {
|
|
||||||
if (!workflowContext) {
|
|
||||||
workflowContext = naturalLanguageInput;
|
|
||||||
}
|
|
||||||
if (!workflowName || workflowName === 'unnamed-workflow') {
|
|
||||||
workflowName = referenceDocPath ? 'doc-workflow' : 'nl-workflow';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.1c: Generate Command Document
|
|
||||||
|
|
||||||
Generate a formatted execution plan for user review. This runs for ALL input formats, not just Format 4.
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function generateCommandDoc(steps, workflowName, workflowContext, analysisDepth) {
|
|
||||||
const stepTable = steps.map((s, i) => {
|
|
||||||
const tool = s.tool || (s.type === 'skill' ? '-' : 'claude');
|
|
||||||
const mode = s.mode || (s.type === 'skill' ? '-' : 'write');
|
|
||||||
const cmdPreview = s.command.length > 60 ? s.command.substring(0, 57) + '...' : s.command;
|
|
||||||
return `| ${i + 1} | ${s.name} | ${s.type} | \`${cmdPreview}\` | ${tool} | ${mode} |`;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
const flowDiagram = steps.map((s, i) => {
|
|
||||||
const arrow = i < steps.length - 1 ? '\n ↓' : '';
|
|
||||||
const feedsInto = i < steps.length - 1 ? `Feeds into: Step ${i + 2} (${steps[i + 1].name})` : 'Final step';
|
|
||||||
const originalText = s.original_text ? `\n Source: "${s.original_text}"` : '';
|
|
||||||
return `Step ${i + 1}: ${s.name}
|
|
||||||
Command: ${s.command}
|
|
||||||
Type: ${s.type} | Tool: ${s.tool || '-'} | Mode: ${s.mode || '-'}${originalText}
|
|
||||||
${feedsInto}${arrow}`;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
const totalCli = steps.filter(s => s.type === 'ccw-cli').length;
|
|
||||||
const totalSkill = steps.filter(s => s.type === 'skill').length;
|
|
||||||
const totalCmd = steps.filter(s => s.type === 'command').length;
|
|
||||||
|
|
||||||
return `# Workflow Tune — Execution Plan
|
|
||||||
|
|
||||||
**Workflow**: ${workflowName}
|
|
||||||
**Goal**: ${workflowContext}
|
|
||||||
**Steps**: ${steps.length}
|
|
||||||
**Analysis Depth**: ${analysisDepth}
|
|
||||||
|
|
||||||
## Step Chain
|
|
||||||
|
|
||||||
| # | Name | Type | Command | Tool | Mode |
|
|
||||||
|---|------|------|---------|------|------|
|
|
||||||
${stepTable}
|
|
||||||
|
|
||||||
## Execution Flow
|
|
||||||
|
|
||||||
\`\`\`
|
|
||||||
${flowDiagram}
|
|
||||||
\`\`\`
|
|
||||||
|
|
||||||
## Estimated Scope
|
|
||||||
|
|
||||||
- CLI execute calls: ${totalCli}
|
|
||||||
- Skill invocations: ${totalSkill}
|
|
||||||
- Shell commands: ${totalCmd}
|
|
||||||
- Analysis calls (gemini --resume chain): ${steps.length} (per-step) + 1 (synthesis)
|
|
||||||
- Process documentation: process-log.md (accumulated)
|
|
||||||
- Final output: final-report.md with optimization recommendations
|
|
||||||
`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const commandDoc = generateCommandDoc(steps, workflowName, workflowContext, workflowPreferences.analysisDepth);
|
|
||||||
|
|
||||||
// Output command document to user (direct text output)
|
|
||||||
// The orchestrator displays this as formatted text before confirmation
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.1d: Pre-Execution Confirmation
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// ★ Skip confirmation if -y/--yes auto mode
|
|
||||||
if (!workflowPreferences.autoYes) {
|
|
||||||
// Display commandDoc to user as formatted text output
|
|
||||||
// Then ask for confirmation
|
|
||||||
|
|
||||||
const confirmation = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "确认执行以上 Workflow 调优计划?",
|
|
||||||
header: "Confirm Execution",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Execute (确认执行)", description: "按计划开始执行所有步骤" },
|
|
||||||
{ label: "Edit steps (修改步骤)", description: "调整步骤顺序、增删步骤、更换工具" },
|
|
||||||
{ label: "Cancel (取消)", description: "取消本次调优" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
const choice = confirmation["Confirm Execution"];
|
|
||||||
|
|
||||||
if (choice.startsWith("Cancel")) {
|
|
||||||
// Abort: no workspace created, no state written
|
|
||||||
// Output: "Workflow tune cancelled."
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (choice.startsWith("Edit")) {
|
|
||||||
// Enter edit loop: ask user what to change, apply, re-display, re-confirm
|
|
||||||
let editing = true;
|
|
||||||
while (editing) {
|
|
||||||
const editResponse = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "请描述要修改的内容:\n" +
|
|
||||||
" - 删除步骤: '删除步骤2' 或 'remove step 2'\n" +
|
|
||||||
" - 添加步骤: '在步骤1后加入安全扫描' 或 'add security scan after step 1'\n" +
|
|
||||||
" - 修改工具: '步骤3改用codex' 或 'step 3 use codex'\n" +
|
|
||||||
" - 调换顺序: '步骤2和步骤3互换' 或 'swap step 2 and 3'\n" +
|
|
||||||
" - 修改命令: '步骤1命令改为 ccw cli -p \"...\" --tool gemini'",
|
|
||||||
header: "Edit Steps"
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
const editText = editResponse["Edit Steps"];
|
|
||||||
|
|
||||||
// Apply edits to steps[] based on user instruction
|
|
||||||
// The orchestrator interprets the edit instruction and modifies steps:
|
|
||||||
//
|
|
||||||
// Delete: filter out the specified step, re-index
|
|
||||||
// Add: insert new step at specified position
|
|
||||||
// Modify tool: update the step's tool/mode/command
|
|
||||||
// Swap: exchange positions of two steps
|
|
||||||
// Modify command: replace command string
|
|
||||||
//
|
|
||||||
// After applying edits, re-generate command doc and re-display
|
|
||||||
|
|
||||||
const updatedCommandDoc = generateCommandDoc(steps, workflowName, workflowContext, workflowPreferences.analysisDepth);
|
|
||||||
// Display updatedCommandDoc to user
|
|
||||||
|
|
||||||
const reconfirm = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: "修改后的计划如上,是否确认?",
|
|
||||||
header: "Confirm Execution",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Execute (确认执行)", description: "按修改后的计划执行" },
|
|
||||||
{ label: "Edit more (继续修改)", description: "还需要调整" },
|
|
||||||
{ label: "Cancel (取消)", description: "取消本次调优" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
const reChoice = reconfirm["Confirm Execution"];
|
|
||||||
if (reChoice.startsWith("Execute")) {
|
|
||||||
editing = false;
|
|
||||||
} else if (reChoice.startsWith("Cancel")) {
|
|
||||||
return; // Abort
|
|
||||||
}
|
|
||||||
// else: continue editing loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// choice === "Execute" → proceed to workspace creation
|
|
||||||
}
|
|
||||||
|
|
||||||
// Save command doc for reference
|
|
||||||
// Will be written to workspace after Step 1.3
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.2: Validate Steps
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
for (const step of steps) {
|
|
||||||
if (step.type === 'skill' && step.skill_path) {
|
|
||||||
const skillFiles = Glob(`${step.skill_path}/SKILL.md`);
|
|
||||||
if (skillFiles.length === 0) {
|
|
||||||
step.validation = 'warning';
|
|
||||||
step.validation_msg = `Skill not found: ${step.skill_path}`;
|
|
||||||
} else {
|
|
||||||
step.validation = 'ok';
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// Command-type steps: basic validation (non-empty)
|
|
||||||
step.validation = step.command && step.command.trim() ? 'ok' : 'invalid';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const invalidSteps = steps.filter(s => s.validation === 'invalid');
|
|
||||||
if (invalidSteps.length > 0) {
|
|
||||||
throw new Error(`Invalid steps: ${invalidSteps.map(s => s.name).join(', ')}`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.2b: Generate Test Requirements (Acceptance Criteria)
|
|
||||||
|
|
||||||
> 调优的前提:为每一步生成跟任务匹配的验收标准。没有预期基准,就无法判断命令执行是否达标。
|
|
||||||
|
|
||||||
用 Gemini 根据 step command + workflow context + 上下游关系,自动推断每步的验收标准。
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Build step chain description for context
|
|
||||||
const stepChainDesc = steps.map((s, i) =>
|
|
||||||
`Step ${i + 1}: ${s.name} (${s.type}) — ${s.command}`
|
|
||||||
).join('\n');
|
|
||||||
|
|
||||||
const reqGenPrompt = `PURPOSE: Generate concrete acceptance criteria (test requirements) for each step in a workflow pipeline. These criteria will be used to objectively judge whether each step's execution succeeded or failed.
|
|
||||||
|
|
||||||
WORKFLOW:
|
|
||||||
Name: ${workflowName}
|
|
||||||
Goal: ${workflowContext}
|
|
||||||
|
|
||||||
STEP CHAIN:
|
|
||||||
${stepChainDesc}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
For each step, generate:
|
|
||||||
1. **expected_outputs** — what files/artifacts should be produced (specific filenames or patterns)
|
|
||||||
2. **content_signals** — what content patterns indicate success (keywords, structures, data shapes)
|
|
||||||
3. **quality_thresholds** — minimum quality bar (e.g., "no empty files", "JSON must be parseable", "must contain at least N items")
|
|
||||||
4. **pass_criteria** — 1-2 sentence description of what "pass" looks like for this step
|
|
||||||
5. **fail_signals** — what patterns indicate failure (error messages, empty output, wrong format)
|
|
||||||
6. **handoff_contract** — what this step must provide for the next step to work (data format, required fields)
|
|
||||||
|
|
||||||
CONTEXT RULES:
|
|
||||||
- Infer from the command what the step is supposed to do
|
|
||||||
- Consider workflow goal when judging what "good enough" means
|
|
||||||
- Each step's handoff_contract should match what the next step needs as input
|
|
||||||
- Be specific: "report.md with ## Summary section" not "a report file"
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"step_requirements": [
|
|
||||||
{
|
|
||||||
"step_index": 0,
|
|
||||||
"step_name": "<name>",
|
|
||||||
"expected_outputs": ["<file or pattern>"],
|
|
||||||
"content_signals": ["<keyword or pattern that indicates success>"],
|
|
||||||
"quality_thresholds": ["<minimum bar>"],
|
|
||||||
"pass_criteria": "<what pass looks like>",
|
|
||||||
"fail_signals": ["<pattern that indicates failure>"],
|
|
||||||
"handoff_contract": "<what next step needs from this step>"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Be specific to each command, output ONLY JSON`;
|
|
||||||
|
|
||||||
Bash({
|
|
||||||
command: `ccw cli -p "${escapeForShell(reqGenPrompt)}" --tool gemini --mode analysis --rule universal-rigorous-style`,
|
|
||||||
run_in_background: true,
|
|
||||||
timeout: 300000
|
|
||||||
});
|
|
||||||
|
|
||||||
// STOP — wait for hook callback
|
|
||||||
// After callback: parse JSON, attach requirements to each step
|
|
||||||
|
|
||||||
const reqOutput = /* CLI output from callback */;
|
|
||||||
const reqJsonMatch = reqOutput.match(/\{[\s\S]*\}/);
|
|
||||||
|
|
||||||
if (reqJsonMatch) {
|
|
||||||
try {
|
|
||||||
const reqData = JSON.parse(reqJsonMatch[0]);
|
|
||||||
(reqData.step_requirements || []).forEach(req => {
|
|
||||||
const idx = req.step_index;
|
|
||||||
if (idx >= 0 && idx < steps.length) {
|
|
||||||
steps[idx].test_requirements = {
|
|
||||||
expected_outputs: req.expected_outputs || [],
|
|
||||||
content_signals: req.content_signals || [],
|
|
||||||
quality_thresholds: req.quality_thresholds || [],
|
|
||||||
pass_criteria: req.pass_criteria || '',
|
|
||||||
fail_signals: req.fail_signals || [],
|
|
||||||
handoff_contract: req.handoff_contract || ''
|
|
||||||
};
|
|
||||||
}
|
|
||||||
});
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback: proceed without generated requirements
|
|
||||||
// Steps will use any manually provided success_criteria
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Capture session ID for resume chain start
|
|
||||||
const reqSessionMatch = reqOutput.match(/\[CCW_EXEC_ID=([^\]]+)\]/);
|
|
||||||
const reqSessionId = reqSessionMatch ? reqSessionMatch[1] : null;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.3: Create Workspace
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const ts = Date.now();
|
|
||||||
const workDir = `.workflow/.scratchpad/workflow-tune-${ts}`;
|
|
||||||
|
|
||||||
Bash(`mkdir -p "${workDir}/steps"`);
|
|
||||||
|
|
||||||
// Create per-step directories
|
|
||||||
for (let i = 0; i < steps.length; i++) {
|
|
||||||
Bash(`mkdir -p "${workDir}/steps/step-${i + 1}/artifacts"`);
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.3b: Save Command Document
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Save confirmed command doc to workspace for reference
|
|
||||||
Write(`${workDir}/command-doc.md`, commandDoc);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.4: Initialize State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const initialState = {
|
|
||||||
status: 'running',
|
|
||||||
started_at: new Date().toISOString(),
|
|
||||||
updated_at: new Date().toISOString(),
|
|
||||||
workflow_name: workflowName,
|
|
||||||
workflow_context: workflowContext,
|
|
||||||
analysis_depth: workflowPreferences.analysisDepth,
|
|
||||||
auto_fix: workflowPreferences.autoFix,
|
|
||||||
steps: steps.map((s, i) => ({
|
|
||||||
...s,
|
|
||||||
index: i,
|
|
||||||
status: 'pending',
|
|
||||||
execution: null,
|
|
||||||
analysis: null,
|
|
||||||
test_requirements: s.test_requirements || null // from Step 1.2b
|
|
||||||
})),
|
|
||||||
analysis_session_id: reqSessionId || null, // resume chain starts from requirements generation
|
|
||||||
process_log_entries: [],
|
|
||||||
synthesis: null,
|
|
||||||
errors: [],
|
|
||||||
error_count: 0,
|
|
||||||
max_errors: 3,
|
|
||||||
work_dir: workDir
|
|
||||||
};
|
|
||||||
|
|
||||||
Write(`${workDir}/workflow-state.json`, JSON.stringify(initialState, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 1.5: Initialize Process Log
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const processLog = `# Workflow Tune Process Log
|
|
||||||
|
|
||||||
**Workflow**: ${workflowName}
|
|
||||||
**Context**: ${workflowContext}
|
|
||||||
**Steps**: ${steps.length}
|
|
||||||
**Analysis Depth**: ${workflowPreferences.analysisDepth}
|
|
||||||
**Started**: ${new Date().toISOString()}
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
`;
|
|
||||||
|
|
||||||
Write(`${workDir}/process-log.md`, processLog);
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Variables**: `workDir`, `steps[]`, `workflowContext`, `commandDoc`, initialized state
|
|
||||||
- **Files**: `workflow-state.json`, `process-log.md`, `command-doc.md`, per-step directories
|
|
||||||
- **User Confirmation**: Execution plan confirmed (or cancelled → abort)
|
|
||||||
- **TaskUpdate**: Mark Phase 1 completed, start Step Loop
|
|
||||||
@@ -1,197 +0,0 @@
|
|||||||
# Phase 2: Execute Step
|
|
||||||
|
|
||||||
> **COMPACT SENTINEL [Phase 2: Execute Step]**
|
|
||||||
> This phase contains 4 execution steps (Step 2.1 -- 2.4).
|
|
||||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
|
||||||
> Recovery: `Read("phases/02-step-execute.md")`
|
|
||||||
|
|
||||||
Execute a single workflow step and collect its output artifacts.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Determine step execution method (skill invoke / ccw cli / shell command)
|
|
||||||
- Execute step with appropriate tool
|
|
||||||
- Collect output artifacts into step directory
|
|
||||||
- Write artifacts manifest
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 2.1: Prepare Step Directory
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const stepIdx = currentStepIndex; // from orchestrator loop
|
|
||||||
const step = state.steps[stepIdx];
|
|
||||||
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;
|
|
||||||
const artifactsDir = `${stepDir}/artifacts`;
|
|
||||||
|
|
||||||
// Capture pre-execution state (git status, file timestamps)
|
|
||||||
const preGitStatus = Bash('git status --porcelain 2>/dev/null || echo "not a git repo"').stdout;
|
|
||||||
|
|
||||||
// ★ Warn if dirty git working directory (first step only)
|
|
||||||
if (stepIdx === 0 && preGitStatus.trim() && preGitStatus.trim() !== 'not a git repo') {
|
|
||||||
const dirtyLines = preGitStatus.trim().split('\n').length;
|
|
||||||
// Log warning — artifact collection via git diff may be unreliable
|
|
||||||
// This is informational; does not block execution
|
|
||||||
console.warn(`⚠ Dirty git working directory detected (${dirtyLines} changed files). Artifact collection via git diff may include pre-existing changes.`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const preExecSnapshot = {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
git_status: preGitStatus,
|
|
||||||
working_files: Glob('**/*.{ts,js,md,json}').slice(0, 50) // sample
|
|
||||||
};
|
|
||||||
Write(`${stepDir}/pre-exec-snapshot.json`, JSON.stringify(preExecSnapshot, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.2: Execute by Step Type
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
let executionResult = { success: false, method: '', output: '', duration: 0 };
|
|
||||||
const startTime = Date.now();
|
|
||||||
|
|
||||||
switch (step.type) {
|
|
||||||
case 'skill': {
|
|
||||||
// Skill invocation — use Skill tool
|
|
||||||
// Extract skill name and arguments from command
|
|
||||||
const skillCmd = step.command.replace(/^\//, '');
|
|
||||||
const [skillName, ...skillArgs] = skillCmd.split(/\s+/);
|
|
||||||
|
|
||||||
// Execute skill (this runs synchronously within current context)
|
|
||||||
// Note: Skill execution produces artifacts in the working directory
|
|
||||||
// We capture changes by comparing pre/post state
|
|
||||||
Skill({
|
|
||||||
name: skillName,
|
|
||||||
arguments: skillArgs.join(' ')
|
|
||||||
});
|
|
||||||
|
|
||||||
executionResult.method = 'skill';
|
|
||||||
executionResult.success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'ccw-cli': {
|
|
||||||
// Direct ccw cli command
|
|
||||||
const cliCommand = step.command;
|
|
||||||
|
|
||||||
Bash({
|
|
||||||
command: cliCommand,
|
|
||||||
run_in_background: true,
|
|
||||||
timeout: 600000 // 10 minutes
|
|
||||||
});
|
|
||||||
|
|
||||||
// STOP — wait for hook callback
|
|
||||||
// After callback:
|
|
||||||
executionResult.method = 'ccw-cli';
|
|
||||||
executionResult.success = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
case 'command': {
|
|
||||||
// Generic shell command
|
|
||||||
const result = Bash({
|
|
||||||
command: step.command,
|
|
||||||
timeout: 300000 // 5 minutes
|
|
||||||
});
|
|
||||||
|
|
||||||
executionResult.method = 'command';
|
|
||||||
executionResult.output = result.stdout || '';
|
|
||||||
executionResult.success = result.exitCode === 0;
|
|
||||||
|
|
||||||
// Save command output
|
|
||||||
if (executionResult.output) {
|
|
||||||
Write(`${artifactsDir}/command-output.txt`, executionResult.output);
|
|
||||||
}
|
|
||||||
if (result.stderr) {
|
|
||||||
Write(`${artifactsDir}/command-stderr.txt`, result.stderr);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
executionResult.duration = Date.now() - startTime;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.3: Collect Artifacts
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Capture post-execution state
|
|
||||||
const postExecSnapshot = {
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
git_status: Bash('git status --porcelain 2>/dev/null || echo "not a git repo"').stdout,
|
|
||||||
working_files: Glob('**/*.{ts,js,md,json}').slice(0, 50)
|
|
||||||
};
|
|
||||||
|
|
||||||
// Detect changed/new files by comparing snapshots
|
|
||||||
const preFiles = new Set(preExecSnapshot.working_files);
|
|
||||||
const newOrChanged = postExecSnapshot.working_files.filter(f => !preFiles.has(f));
|
|
||||||
|
|
||||||
// Also check git diff for modified files
|
|
||||||
const gitDiff = Bash('git diff --name-only 2>/dev/null || true').stdout.trim().split('\n').filter(Boolean);
|
|
||||||
|
|
||||||
// Collect all artifacts (new files + git-changed files + declared expected_artifacts)
|
|
||||||
const declaredArtifacts = (step.expected_artifacts || []).filter(f => {
|
|
||||||
// Verify declared artifacts actually exist
|
|
||||||
const exists = Glob(f);
|
|
||||||
return exists.length > 0;
|
|
||||||
}).flatMap(f => Glob(f));
|
|
||||||
|
|
||||||
const allArtifacts = [...new Set([...newOrChanged, ...gitDiff, ...declaredArtifacts])];
|
|
||||||
|
|
||||||
// Copy detected artifacts to step artifacts dir (or record references)
|
|
||||||
const artifactManifest = {
|
|
||||||
step: step.name,
|
|
||||||
step_index: stepIdx,
|
|
||||||
execution_method: executionResult.method,
|
|
||||||
success: executionResult.success,
|
|
||||||
duration_ms: executionResult.duration,
|
|
||||||
artifacts: allArtifacts.map(f => ({
|
|
||||||
path: f,
|
|
||||||
type: f.endsWith('.md') ? 'markdown' : f.endsWith('.json') ? 'json' : 'other',
|
|
||||||
size: 'unknown' // Can be filled by stat if needed
|
|
||||||
})),
|
|
||||||
// For skill type: also check .workflow/.scratchpad for generated files
|
|
||||||
scratchpad_files: step.type === 'skill'
|
|
||||||
? Glob('.workflow/.scratchpad/**/*').filter(f => {
|
|
||||||
// Only include files created after step started
|
|
||||||
return true; // Heuristic: include recent scratchpad files
|
|
||||||
}).slice(0, 20)
|
|
||||||
: [],
|
|
||||||
collected_at: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
Write(`${stepDir}/artifacts-manifest.json`, JSON.stringify(artifactManifest, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 2.4: Update State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
state.steps[stepIdx].status = 'executed';
|
|
||||||
state.steps[stepIdx].execution = {
|
|
||||||
method: executionResult.method,
|
|
||||||
success: executionResult.success,
|
|
||||||
duration_ms: executionResult.duration,
|
|
||||||
artifacts_dir: artifactsDir,
|
|
||||||
manifest_path: `${stepDir}/artifacts-manifest.json`,
|
|
||||||
artifact_count: artifactManifest.artifacts.length,
|
|
||||||
started_at: preExecSnapshot.timestamp,
|
|
||||||
completed_at: new Date().toISOString()
|
|
||||||
};
|
|
||||||
|
|
||||||
state.updated_at = new Date().toISOString();
|
|
||||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| Skill not found | Record failure, set success=false, continue to Phase 3 |
|
|
||||||
| CLI timeout (10min) | Retry once with shorter timeout, then record failure |
|
|
||||||
| Command exit non-zero | Record stderr, set success=false, continue to Phase 3 |
|
|
||||||
| No artifacts detected | Continue to Phase 3 — analysis evaluates step definition quality |
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Files**: `pre-exec-snapshot.json`, `artifacts-manifest.json`, `artifacts/` (if command type)
|
|
||||||
- **State**: `steps[stepIdx].execution` updated
|
|
||||||
- **Next**: Phase 3 (Analyze Step)
|
|
||||||
@@ -1,386 +0,0 @@
|
|||||||
# Phase 3: Analyze Step
|
|
||||||
|
|
||||||
> **COMPACT SENTINEL [Phase 3: Analyze Step]**
|
|
||||||
> This phase contains 5 execution steps (Step 3.1 -- 3.5).
|
|
||||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
|
||||||
> Recovery: `Read("phases/03-step-analyze.md")`
|
|
||||||
|
|
||||||
Analyze a completed step's artifacts and quality using `ccw cli --tool gemini --mode analysis`. Uses `--resume` to maintain context across step analyses, building a continuous analysis chain.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Inspect step artifacts (file list, content, quality signals)
|
|
||||||
- Build analysis prompt with step context + prior process log
|
|
||||||
- Execute via ccw cli Gemini with resume chain
|
|
||||||
- Parse analysis results → write step-{N}-analysis.md
|
|
||||||
- Append findings to process-log.md
|
|
||||||
- Return updated session ID for resume chain
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 3.1: Inspect Artifacts
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const stepIdx = currentStepIndex;
|
|
||||||
const step = state.steps[stepIdx];
|
|
||||||
const stepDir = `${state.work_dir}/steps/step-${stepIdx + 1}`;
|
|
||||||
|
|
||||||
// Read artifacts manifest
|
|
||||||
const manifest = JSON.parse(Read(`${stepDir}/artifacts-manifest.json`));
|
|
||||||
|
|
||||||
// Build artifact summary based on analysis depth
|
|
||||||
let artifactSummary = '';
|
|
||||||
|
|
||||||
if (state.analysis_depth === 'quick') {
|
|
||||||
// Quick: just file list and sizes
|
|
||||||
artifactSummary = `Artifacts (${manifest.artifacts.length} files):\n` +
|
|
||||||
manifest.artifacts.map(a => `- ${a.path} (${a.type})`).join('\n');
|
|
||||||
} else {
|
|
||||||
// Standard/Deep: include file content summaries
|
|
||||||
artifactSummary = manifest.artifacts.map(a => {
|
|
||||||
const maxLines = state.analysis_depth === 'deep' ? 300 : 150;
|
|
||||||
try {
|
|
||||||
const content = Read(a.path, { limit: maxLines });
|
|
||||||
return `--- ${a.path} (${a.type}) ---\n${content}`;
|
|
||||||
} catch {
|
|
||||||
return `--- ${a.path} --- [unreadable]`;
|
|
||||||
}
|
|
||||||
}).join('\n\n');
|
|
||||||
|
|
||||||
// Deep: also include scratchpad files
|
|
||||||
if (state.analysis_depth === 'deep' && manifest.scratchpad_files?.length > 0) {
|
|
||||||
artifactSummary += '\n\n--- Scratchpad Files ---\n' +
|
|
||||||
manifest.scratchpad_files.slice(0, 5).map(f => {
|
|
||||||
const content = Read(f, { limit: 100 });
|
|
||||||
return `--- ${f} ---\n${content}`;
|
|
||||||
}).join('\n\n');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Execution result summary
|
|
||||||
const execSummary = `Execution: ${step.execution.method} | ` +
|
|
||||||
`Success: ${step.execution.success} | ` +
|
|
||||||
`Duration: ${step.execution.duration_ms}ms | ` +
|
|
||||||
`Artifacts: ${manifest.artifacts.length} files`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3.2: Build Prior Context
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Build accumulated process log context for this analysis
|
|
||||||
const priorProcessLog = Read(`${state.work_dir}/process-log.md`);
|
|
||||||
|
|
||||||
// Build step chain context (what came before, what comes after)
|
|
||||||
const stepChainContext = state.steps.map((s, i) => {
|
|
||||||
const status = i < stepIdx ? 'completed' : i === stepIdx ? 'CURRENT' : 'pending';
|
|
||||||
const score = s.analysis?.quality_score || '-';
|
|
||||||
return `${i + 1}. [${status}] ${s.name} (${s.type}) — Quality: ${score}`;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
// Previous step handoff context (if not first step)
|
|
||||||
let handoffContext = '';
|
|
||||||
if (stepIdx > 0) {
|
|
||||||
const prevStep = state.steps[stepIdx - 1];
|
|
||||||
const prevAnalysis = prevStep.analysis;
|
|
||||||
if (prevAnalysis) {
|
|
||||||
handoffContext = `PREVIOUS STEP OUTPUT SUMMARY:
|
|
||||||
Step "${prevStep.name}" produced ${prevStep.execution?.artifact_count || 0} artifacts.
|
|
||||||
Quality: ${prevAnalysis.quality_score}/100
|
|
||||||
Key outputs: ${prevAnalysis.key_outputs?.join(', ') || 'unknown'}
|
|
||||||
Handoff notes: ${prevAnalysis.handoff_notes || 'none'}`;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3.3: Construct Analysis Prompt
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Ref: templates/step-analysis-prompt.md
|
|
||||||
|
|
||||||
const depthInstructions = {
|
|
||||||
quick: 'Provide brief assessment (3-5 bullet points). Focus on: execution success, output completeness, obvious issues.',
|
|
||||||
standard: 'Provide detailed assessment. Cover: execution quality, output completeness, artifact quality, step-to-step handoff readiness, potential issues.',
|
|
||||||
deep: 'Provide exhaustive assessment. Cover: execution quality, output completeness and correctness, artifact quality and structure, step-to-step handoff integrity, error handling, performance signals, architecture implications, edge cases.'
|
|
||||||
};
|
|
||||||
|
|
||||||
// ★ Build test requirements section (the evaluation baseline)
|
|
||||||
const testReqs = step.test_requirements;
|
|
||||||
let testReqSection = '';
|
|
||||||
if (testReqs) {
|
|
||||||
testReqSection = `
|
|
||||||
TEST REQUIREMENTS (Acceptance Criteria — use these as the PRIMARY evaluation baseline):
|
|
||||||
Pass Criteria: ${testReqs.pass_criteria}
|
|
||||||
Expected Outputs: ${(testReqs.expected_outputs || []).join(', ') || 'not specified'}
|
|
||||||
Content Signals (patterns that indicate success): ${(testReqs.content_signals || []).join(', ') || 'not specified'}
|
|
||||||
Quality Thresholds: ${(testReqs.quality_thresholds || []).join(', ') || 'not specified'}
|
|
||||||
Fail Signals (patterns that indicate failure): ${(testReqs.fail_signals || []).join(', ') || 'not specified'}
|
|
||||||
Handoff Contract (what next step needs): ${testReqs.handoff_contract || 'not specified'}
|
|
||||||
|
|
||||||
IMPORTANT: Score quality_score based on how well the actual output matches these test requirements.
|
|
||||||
- 90-100: All pass_criteria met, all expected_outputs present, content_signals found, no fail_signals
|
|
||||||
- 70-89: Most criteria met, minor gaps
|
|
||||||
- 50-69: Partial match, significant gaps
|
|
||||||
- 0-49: Fail — fail_signals present or pass_criteria not met`;
|
|
||||||
} else {
|
|
||||||
testReqSection = `
|
|
||||||
NOTE: No pre-generated test requirements for this step. Evaluate based on general quality signals and workflow context.`;
|
|
||||||
}
|
|
||||||
|
|
||||||
const analysisPrompt = `PURPOSE: Evaluate workflow step "${step.name}" (step ${stepIdx + 1}/${state.steps.length}) against its acceptance criteria. Judge whether the command execution met the pre-defined test requirements.
|
|
||||||
|
|
||||||
WORKFLOW CONTEXT:
|
|
||||||
Name: ${state.workflow_name}
|
|
||||||
Goal: ${state.workflow_context}
|
|
||||||
Step Chain:
|
|
||||||
${stepChainContext}
|
|
||||||
|
|
||||||
CURRENT STEP:
|
|
||||||
Name: ${step.name}
|
|
||||||
Type: ${step.type}
|
|
||||||
Command: ${step.command}
|
|
||||||
${step.success_criteria ? `Success Criteria: ${step.success_criteria}` : ''}
|
|
||||||
${testReqSection}
|
|
||||||
|
|
||||||
EXECUTION RESULT:
|
|
||||||
${execSummary}
|
|
||||||
|
|
||||||
${handoffContext}
|
|
||||||
|
|
||||||
STEP ARTIFACTS:
|
|
||||||
${artifactSummary}
|
|
||||||
|
|
||||||
ANALYSIS DEPTH: ${state.analysis_depth}
|
|
||||||
${depthInstructions[state.analysis_depth]}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
1. **Requirement Matching**: Compare actual output against test requirements (pass_criteria, expected_outputs, content_signals)
|
|
||||||
2. **Fail Signal Detection**: Check for any fail_signals in the output
|
|
||||||
3. **Handoff Contract Verification**: Does the output satisfy handoff_contract for the next step?
|
|
||||||
4. **Gap Analysis**: What's missing between actual output and requirements?
|
|
||||||
5. **Quality Score**: Rate 0-100 based on requirement fulfillment (NOT general quality)
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"quality_score": <0-100>,
|
|
||||||
"requirement_match": {
|
|
||||||
"pass": <true|false>,
|
|
||||||
"criteria_met": ["<which pass_criteria were satisfied>"],
|
|
||||||
"criteria_missed": ["<which pass_criteria were NOT satisfied>"],
|
|
||||||
"expected_outputs_found": ["<expected files that exist>"],
|
|
||||||
"expected_outputs_missing": ["<expected files that are absent>"],
|
|
||||||
"content_signals_found": ["<success patterns detected in output>"],
|
|
||||||
"content_signals_missing": ["<success patterns NOT found>"],
|
|
||||||
"fail_signals_detected": ["<failure patterns found, if any>"]
|
|
||||||
},
|
|
||||||
"execution_assessment": {
|
|
||||||
"success": <true|false>,
|
|
||||||
"completeness": "<complete|partial|failed>",
|
|
||||||
"notes": "<brief assessment>"
|
|
||||||
},
|
|
||||||
"artifact_assessment": {
|
|
||||||
"count": <number>,
|
|
||||||
"quality": "<high|medium|low>",
|
|
||||||
"key_outputs": ["<main output 1>", "<main output 2>"],
|
|
||||||
"missing_outputs": ["<expected but missing>"]
|
|
||||||
},
|
|
||||||
"handoff_assessment": {
|
|
||||||
"ready": <true|false>,
|
|
||||||
"contract_satisfied": <true|false|null>,
|
|
||||||
"next_step_compatible": <true|false|null>,
|
|
||||||
"handoff_notes": "<what next step should know>"
|
|
||||||
},
|
|
||||||
"issues": [
|
|
||||||
{ "severity": "high|medium|low", "description": "<issue>", "suggestion": "<fix>" }
|
|
||||||
],
|
|
||||||
"optimization_opportunities": [
|
|
||||||
{ "area": "<area>", "description": "<opportunity>", "impact": "high|medium|low" }
|
|
||||||
],
|
|
||||||
"step_summary": "<1-2 sentence summary for process log>"
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Be specific, reference artifact content where possible, score against requirements not general quality, output ONLY JSON`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3.4: Execute via ccw cli Gemini with Resume
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function escapeForShell(str) {
|
|
||||||
return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build CLI command with optional resume
|
|
||||||
let cliCommand = `ccw cli -p "${escapeForShell(analysisPrompt)}" --tool gemini --mode analysis`;
|
|
||||||
|
|
||||||
// Resume from previous step's analysis session (maintains context chain)
|
|
||||||
if (state.analysis_session_id) {
|
|
||||||
cliCommand += ` --resume ${state.analysis_session_id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bash({
|
|
||||||
command: cliCommand,
|
|
||||||
run_in_background: true,
|
|
||||||
timeout: 300000 // 5 minutes
|
|
||||||
});
|
|
||||||
|
|
||||||
// STOP — wait for hook callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 3.5: Parse Results and Update Process Log
|
|
||||||
|
|
||||||
After CLI completes:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const rawOutput = /* CLI output from callback */;
|
|
||||||
|
|
||||||
// Extract session ID from CLI output for resume chain
|
|
||||||
const sessionIdMatch = rawOutput.match(/\[CCW_EXEC_ID=([^\]]+)\]/);
|
|
||||||
if (sessionIdMatch) {
|
|
||||||
state.analysis_session_id = sessionIdMatch[1];
|
|
||||||
}
|
|
||||||
|
|
||||||
// Parse JSON
|
|
||||||
const jsonMatch = rawOutput.match(/\{[\s\S]*\}/);
|
|
||||||
let analysis;
|
|
||||||
|
|
||||||
if (jsonMatch) {
|
|
||||||
try {
|
|
||||||
analysis = JSON.parse(jsonMatch[0]);
|
|
||||||
} catch (e) {
|
|
||||||
// Fallback: extract score heuristically
|
|
||||||
const scoreMatch = rawOutput.match(/"quality_score"\s*:\s*(\d+)/);
|
|
||||||
analysis = {
|
|
||||||
quality_score: scoreMatch ? parseInt(scoreMatch[1]) : 50,
|
|
||||||
execution_assessment: { success: step.execution.success, completeness: 'unknown', notes: 'Parse failed' },
|
|
||||||
artifact_assessment: { count: manifest.artifacts.length, quality: 'unknown', key_outputs: [], missing_outputs: [] },
|
|
||||||
handoff_assessment: { ready: true, next_step_compatible: null, handoff_notes: '' },
|
|
||||||
issues: [{ severity: 'low', description: 'Analysis output parsing failed', suggestion: 'Review raw output' }],
|
|
||||||
optimization_opportunities: [],
|
|
||||||
step_summary: 'Analysis parsing failed — raw output saved for manual review'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
analysis = {
|
|
||||||
quality_score: 50,
|
|
||||||
step_summary: 'No structured analysis output received'
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write step analysis file
|
|
||||||
const reqMatch = analysis.requirement_match;
|
|
||||||
const reqMatchSection = reqMatch ? `
|
|
||||||
## Requirement Match — ${reqMatch.pass ? 'PASS ✓' : 'FAIL ✗'}
|
|
||||||
|
|
||||||
### Criteria Met
|
|
||||||
${(reqMatch.criteria_met || []).map(c => `- ✓ ${c}`).join('\n') || '- None'}
|
|
||||||
|
|
||||||
### Criteria Missed
|
|
||||||
${(reqMatch.criteria_missed || []).map(c => `- ✗ ${c}`).join('\n') || '- None'}
|
|
||||||
|
|
||||||
### Expected Outputs
|
|
||||||
- Found: ${(reqMatch.expected_outputs_found || []).join(', ') || 'None'}
|
|
||||||
- Missing: ${(reqMatch.expected_outputs_missing || []).join(', ') || 'None'}
|
|
||||||
|
|
||||||
### Content Signals
|
|
||||||
- Detected: ${(reqMatch.content_signals_found || []).join(', ') || 'None'}
|
|
||||||
- Missing: ${(reqMatch.content_signals_missing || []).join(', ') || 'None'}
|
|
||||||
|
|
||||||
### Fail Signals
|
|
||||||
${(reqMatch.fail_signals_detected || []).length > 0
|
|
||||||
? (reqMatch.fail_signals_detected || []).map(f => `- ⚠ ${f}`).join('\n')
|
|
||||||
: '- None detected'}
|
|
||||||
` : '';
|
|
||||||
|
|
||||||
const stepAnalysisReport = `# Step ${stepIdx + 1} Analysis: ${step.name}
|
|
||||||
|
|
||||||
**Quality Score**: ${analysis.quality_score}/100
|
|
||||||
**Requirement Match**: ${reqMatch ? (reqMatch.pass ? 'PASS' : 'FAIL') : 'N/A (no test requirements)'}
|
|
||||||
**Date**: ${new Date().toISOString()}
|
|
||||||
${reqMatchSection}
|
|
||||||
## Execution
|
|
||||||
- Success: ${analysis.execution_assessment?.success}
|
|
||||||
- Completeness: ${analysis.execution_assessment?.completeness}
|
|
||||||
- Notes: ${analysis.execution_assessment?.notes}
|
|
||||||
|
|
||||||
## Artifacts
|
|
||||||
- Count: ${analysis.artifact_assessment?.count}
|
|
||||||
- Quality: ${analysis.artifact_assessment?.quality}
|
|
||||||
- Key Outputs: ${analysis.artifact_assessment?.key_outputs?.join(', ') || 'N/A'}
|
|
||||||
- Missing: ${analysis.artifact_assessment?.missing_outputs?.join(', ') || 'None'}
|
|
||||||
|
|
||||||
## Handoff Readiness
|
|
||||||
- Ready: ${analysis.handoff_assessment?.ready}
|
|
||||||
- Contract Satisfied: ${analysis.handoff_assessment?.contract_satisfied}
|
|
||||||
- Next Step Compatible: ${analysis.handoff_assessment?.next_step_compatible}
|
|
||||||
- Notes: ${analysis.handoff_assessment?.handoff_notes}
|
|
||||||
|
|
||||||
## Issues
|
|
||||||
${(analysis.issues || []).map(i => `- [${i.severity}] ${i.description} → ${i.suggestion}`).join('\n') || 'None'}
|
|
||||||
|
|
||||||
## Optimization Opportunities
|
|
||||||
${(analysis.optimization_opportunities || []).map(o => `- [${o.impact}] ${o.area}: ${o.description}`).join('\n') || 'None'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Write(`${stepDir}/step-${stepIdx + 1}-analysis.md`, stepAnalysisReport);
|
|
||||||
|
|
||||||
// Append to process log
|
|
||||||
const reqPassStr = reqMatch ? (reqMatch.pass ? 'PASS' : 'FAIL') : 'N/A';
|
|
||||||
const processLogEntry = `
|
|
||||||
## Step ${stepIdx + 1}: ${step.name} — Score: ${analysis.quality_score}/100 | Req: ${reqPassStr}
|
|
||||||
|
|
||||||
**Command**: \`${step.command}\`
|
|
||||||
**Result**: ${analysis.execution_assessment?.completeness || 'unknown'} | ${analysis.artifact_assessment?.count || 0} artifacts
|
|
||||||
**Requirement Match**: ${reqPassStr}${reqMatch ? ` — Met: ${(reqMatch.criteria_met || []).length}, Missed: ${(reqMatch.criteria_missed || []).length}, Fail Signals: ${(reqMatch.fail_signals_detected || []).length}` : ''}
|
|
||||||
**Summary**: ${analysis.step_summary || 'No summary'}
|
|
||||||
**Issues**: ${(analysis.issues || []).filter(i => i.severity === 'high').map(i => i.description).join('; ') || 'None critical'}
|
|
||||||
**Handoff**: ${analysis.handoff_assessment?.contract_satisfied ? 'Contract satisfied' : analysis.handoff_assessment?.handoff_notes || 'Ready'}
|
|
||||||
|
|
||||||
---
|
|
||||||
`;
|
|
||||||
|
|
||||||
// Append to process-log.md
|
|
||||||
const currentLog = Read(`${state.work_dir}/process-log.md`);
|
|
||||||
Write(`${state.work_dir}/process-log.md`, currentLog + processLogEntry);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
state.steps[stepIdx].analysis = {
|
|
||||||
quality_score: analysis.quality_score,
|
|
||||||
requirement_pass: reqMatch?.pass ?? null,
|
|
||||||
criteria_met_count: (reqMatch?.criteria_met || []).length,
|
|
||||||
criteria_missed_count: (reqMatch?.criteria_missed || []).length,
|
|
||||||
fail_signals_count: (reqMatch?.fail_signals_detected || []).length,
|
|
||||||
key_outputs: analysis.artifact_assessment?.key_outputs || [],
|
|
||||||
handoff_notes: analysis.handoff_assessment?.handoff_notes || '',
|
|
||||||
contract_satisfied: analysis.handoff_assessment?.contract_satisfied ?? null,
|
|
||||||
issue_count: (analysis.issues || []).length,
|
|
||||||
high_issues: (analysis.issues || []).filter(i => i.severity === 'high').length,
|
|
||||||
optimization_count: (analysis.optimization_opportunities || []).length,
|
|
||||||
analysis_file: `${stepDir}/step-${stepIdx + 1}-analysis.md`
|
|
||||||
};
|
|
||||||
state.steps[stepIdx].status = 'analyzed';
|
|
||||||
|
|
||||||
state.process_log_entries.push({
|
|
||||||
step_index: stepIdx,
|
|
||||||
step_name: step.name,
|
|
||||||
quality_score: analysis.quality_score,
|
|
||||||
summary: analysis.step_summary,
|
|
||||||
timestamp: new Date().toISOString()
|
|
||||||
});
|
|
||||||
|
|
||||||
state.updated_at = new Date().toISOString();
|
|
||||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| CLI timeout | Retry once without --resume (fresh session) |
|
|
||||||
| Resume session not found | Start fresh analysis session, continue |
|
|
||||||
| JSON parse fails | Extract score heuristically, save raw output |
|
|
||||||
| No output | Default score 50, minimal process log entry |
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Files**: `step-{N}-analysis.md`, updated `process-log.md`
|
|
||||||
- **State**: `steps[stepIdx].analysis` updated, `analysis_session_id` updated
|
|
||||||
- **Next**: Phase 2 for next step, or Phase 4 (Synthesize) if all steps done
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
# Phase 4: Synthesize
|
|
||||||
|
|
||||||
> **COMPACT SENTINEL [Phase 4: Synthesize]**
|
|
||||||
> This phase contains 4 execution steps (Step 4.1 -- 4.4).
|
|
||||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
|
||||||
> Recovery: `Read("phases/04-synthesize.md")`
|
|
||||||
|
|
||||||
Synthesize all step analyses into cross-step insights. Evaluates the workflow as a whole: step ordering, handoff quality, redundancy, bottlenecks, and overall coherence.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Read complete process-log.md and all step analyses
|
|
||||||
- Build synthesis prompt with full workflow context
|
|
||||||
- Execute via ccw cli Gemini with resume chain
|
|
||||||
- Generate cross-step optimization insights
|
|
||||||
- Write synthesis.md
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 4.1: Gather All Analyses
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Read process log
|
|
||||||
const processLog = Read(`${state.work_dir}/process-log.md`);
|
|
||||||
|
|
||||||
// Read all step analysis files
|
|
||||||
const stepAnalyses = state.steps.map((step, i) => {
|
|
||||||
const analysisFile = `${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.md`;
|
|
||||||
try {
|
|
||||||
return { step: step.name, index: i, content: Read(analysisFile) };
|
|
||||||
} catch {
|
|
||||||
return { step: step.name, index: i, content: '[Analysis not available]' };
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Build score summary
|
|
||||||
const scoreSummary = state.steps.map((s, i) =>
|
|
||||||
`Step ${i + 1} (${s.name}): ${s.analysis?.quality_score || '-'}/100 | Issues: ${s.analysis?.issue_count || 0} (${s.analysis?.high_issues || 0} high)`
|
|
||||||
).join('\n');
|
|
||||||
|
|
||||||
// Compute aggregate stats
|
|
||||||
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
|
||||||
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
|
||||||
const minScore = scores.length > 0 ? Math.min(...scores) : 0;
|
|
||||||
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);
|
|
||||||
const totalHighIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.high_issues || 0), 0);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4.2: Construct Synthesis Prompt
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Ref: templates/synthesis-prompt.md
|
|
||||||
|
|
||||||
const synthesisPrompt = `PURPOSE: Synthesize all step analyses into a holistic workflow optimization assessment. Evaluate cross-step concerns: ordering, handoff quality, redundancy, bottlenecks, and overall workflow coherence.
|
|
||||||
|
|
||||||
WORKFLOW OVERVIEW:
|
|
||||||
Name: ${state.workflow_name}
|
|
||||||
Goal: ${state.workflow_context}
|
|
||||||
Steps: ${state.steps.length}
|
|
||||||
Average Quality: ${avgScore}/100
|
|
||||||
Weakest Step: ${minScore}/100
|
|
||||||
Total Issues: ${totalIssues} (${totalHighIssues} high severity)
|
|
||||||
|
|
||||||
SCORE SUMMARY:
|
|
||||||
${scoreSummary}
|
|
||||||
|
|
||||||
COMPLETE PROCESS LOG:
|
|
||||||
${processLog}
|
|
||||||
|
|
||||||
DETAILED STEP ANALYSES:
|
|
||||||
${stepAnalyses.map(a => `### ${a.step} (Step ${a.index + 1})\n${a.content}`).join('\n\n---\n\n')}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
1. **Workflow Coherence**: Do steps form a logical sequence? Any missing steps?
|
|
||||||
2. **Handoff Quality**: Are step outputs well-consumed by subsequent steps? Data format mismatches?
|
|
||||||
3. **Redundancy Detection**: Do any steps duplicate work? Overlapping concerns?
|
|
||||||
4. **Bottleneck Identification**: Which steps are bottlenecks (lowest quality, longest duration)?
|
|
||||||
5. **Step Ordering**: Would reordering steps improve outcomes?
|
|
||||||
6. **Missing Steps**: Are there gaps in the pipeline that need additional steps?
|
|
||||||
7. **Per-Step Optimization**: Top 3 improvements per underperforming step
|
|
||||||
8. **Workflow-Level Optimization**: Structural changes to the overall pipeline
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"workflow_score": <0-100>,
|
|
||||||
"coherence": {
|
|
||||||
"score": <0-100>,
|
|
||||||
"assessment": "<logical flow evaluation>",
|
|
||||||
"gaps": ["<missing step or transition>"]
|
|
||||||
},
|
|
||||||
"handoff_quality": {
|
|
||||||
"score": <0-100>,
|
|
||||||
"issues": [
|
|
||||||
{ "from_step": "<step name>", "to_step": "<step name>", "issue": "<description>", "fix": "<suggestion>" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"redundancy": {
|
|
||||||
"found": <true|false>,
|
|
||||||
"items": [
|
|
||||||
{ "steps": ["<step1>", "<step2>"], "description": "<what overlaps>", "recommendation": "<merge or remove>" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bottlenecks": [
|
|
||||||
{ "step": "<step name>", "reason": "<why it's a bottleneck>", "impact": "high|medium|low", "fix": "<suggestion>" }
|
|
||||||
],
|
|
||||||
"ordering_suggestions": [
|
|
||||||
{ "current": "<current order description>", "proposed": "<new order>", "rationale": "<why>" }
|
|
||||||
],
|
|
||||||
"per_step_improvements": [
|
|
||||||
{ "step": "<step name>", "improvements": [
|
|
||||||
{ "priority": "high|medium|low", "description": "<what to change>", "rationale": "<why>" }
|
|
||||||
]}
|
|
||||||
],
|
|
||||||
"workflow_improvements": [
|
|
||||||
{ "priority": "high|medium|low", "category": "structure|handoff|performance|quality", "description": "<change>", "rationale": "<why>", "affected_steps": ["<step names>"] }
|
|
||||||
],
|
|
||||||
"summary": "<2-3 sentence executive summary of workflow health and top recommendations>"
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Be specific, reference step names and artifact details, output ONLY JSON`;
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4.3: Execute via ccw cli Gemini with Resume
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
function escapeForShell(str) {
|
|
||||||
return str.replace(/"/g, '\\"').replace(/\$/g, '\\$').replace(/`/g, '\\`');
|
|
||||||
}
|
|
||||||
|
|
||||||
let cliCommand = `ccw cli -p "${escapeForShell(synthesisPrompt)}" --tool gemini --mode analysis`;
|
|
||||||
|
|
||||||
// Resume from the last step's analysis session
|
|
||||||
if (state.analysis_session_id) {
|
|
||||||
cliCommand += ` --resume ${state.analysis_session_id}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
Bash({
|
|
||||||
command: cliCommand,
|
|
||||||
run_in_background: true,
|
|
||||||
timeout: 300000
|
|
||||||
});
|
|
||||||
|
|
||||||
// STOP — wait for hook callback
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 4.4: Parse Results and Write Synthesis
|
|
||||||
|
|
||||||
After CLI completes:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const rawOutput = /* CLI output from callback */;
|
|
||||||
const jsonMatch = rawOutput.match(/\{[\s\S]*\}/);
|
|
||||||
let synthesis;
|
|
||||||
|
|
||||||
if (jsonMatch) {
|
|
||||||
try {
|
|
||||||
synthesis = JSON.parse(jsonMatch[0]);
|
|
||||||
} catch {
|
|
||||||
synthesis = {
|
|
||||||
workflow_score: avgScore,
|
|
||||||
summary: 'Synthesis parsing failed — individual step analyses available',
|
|
||||||
workflow_improvements: [],
|
|
||||||
per_step_improvements: [],
|
|
||||||
bottlenecks: [],
|
|
||||||
handoff_quality: { score: 0, issues: [] },
|
|
||||||
coherence: { score: 0, assessment: 'Parse error' },
|
|
||||||
redundancy: { found: false, items: [] },
|
|
||||||
ordering_suggestions: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
synthesis = {
|
|
||||||
workflow_score: avgScore,
|
|
||||||
summary: 'No synthesis output received',
|
|
||||||
workflow_improvements: [],
|
|
||||||
per_step_improvements: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write synthesis report
|
|
||||||
const synthesisReport = `# Workflow Synthesis
|
|
||||||
|
|
||||||
**Workflow Score**: ${synthesis.workflow_score}/100
|
|
||||||
**Date**: ${new Date().toISOString()}
|
|
||||||
|
|
||||||
## Executive Summary
|
|
||||||
${synthesis.summary}
|
|
||||||
|
|
||||||
## Coherence (${synthesis.coherence?.score || '-'}/100)
|
|
||||||
${synthesis.coherence?.assessment || 'N/A'}
|
|
||||||
${(synthesis.coherence?.gaps || []).length > 0 ? '\n### Gaps\n' + synthesis.coherence.gaps.map(g => `- ${g}`).join('\n') : ''}
|
|
||||||
|
|
||||||
## Handoff Quality (${synthesis.handoff_quality?.score || '-'}/100)
|
|
||||||
${(synthesis.handoff_quality?.issues || []).map(i =>
|
|
||||||
`- **${i.from_step} → ${i.to_step}**: ${i.issue}\n Fix: ${i.fix}`
|
|
||||||
).join('\n') || 'No handoff issues'}
|
|
||||||
|
|
||||||
## Redundancy
|
|
||||||
${synthesis.redundancy?.found ? (synthesis.redundancy.items || []).map(r =>
|
|
||||||
`- Steps ${r.steps.join(', ')}: ${r.description} → ${r.recommendation}`
|
|
||||||
).join('\n') : 'No redundancy detected'}
|
|
||||||
|
|
||||||
## Bottlenecks
|
|
||||||
${(synthesis.bottlenecks || []).map(b =>
|
|
||||||
`- **${b.step}** [${b.impact}]: ${b.reason}\n Fix: ${b.fix}`
|
|
||||||
).join('\n') || 'No bottlenecks'}
|
|
||||||
|
|
||||||
## Ordering Suggestions
|
|
||||||
${(synthesis.ordering_suggestions || []).map(o =>
|
|
||||||
`- Current: ${o.current}\n Proposed: ${o.proposed}\n Rationale: ${o.rationale}`
|
|
||||||
).join('\n') || 'Current ordering is optimal'}
|
|
||||||
|
|
||||||
## Per-Step Improvements
|
|
||||||
${(synthesis.per_step_improvements || []).map(s =>
|
|
||||||
`### ${s.step}\n` + (s.improvements || []).map(i =>
|
|
||||||
`- [${i.priority}] ${i.description} — ${i.rationale}`
|
|
||||||
).join('\n')
|
|
||||||
).join('\n\n') || 'No per-step improvements'}
|
|
||||||
|
|
||||||
## Workflow-Level Improvements
|
|
||||||
${(synthesis.workflow_improvements || []).map((w, i) =>
|
|
||||||
`### ${i + 1}. [${w.priority}] ${w.description}\n- Category: ${w.category}\n- Rationale: ${w.rationale}\n- Affected: ${(w.affected_steps || []).join(', ')}`
|
|
||||||
).join('\n\n') || 'No workflow-level improvements'}
|
|
||||||
`;
|
|
||||||
|
|
||||||
Write(`${state.work_dir}/synthesis.md`, synthesisReport);
|
|
||||||
|
|
||||||
// Update state
|
|
||||||
state.synthesis = {
|
|
||||||
workflow_score: synthesis.workflow_score,
|
|
||||||
summary: synthesis.summary,
|
|
||||||
improvement_count: (synthesis.workflow_improvements || []).length +
|
|
||||||
(synthesis.per_step_improvements || []).reduce((sum, s) => sum + (s.improvements || []).length, 0),
|
|
||||||
high_priority_count: (synthesis.workflow_improvements || []).filter(w => w.priority === 'high').length,
|
|
||||||
bottleneck_count: (synthesis.bottlenecks || []).length,
|
|
||||||
handoff_issue_count: (synthesis.handoff_quality?.issues || []).length,
|
|
||||||
synthesis_file: `${state.work_dir}/synthesis.md`,
|
|
||||||
raw_data: synthesis
|
|
||||||
};
|
|
||||||
|
|
||||||
state.updated_at = new Date().toISOString();
|
|
||||||
Write(`${state.work_dir}/workflow-state.json`, JSON.stringify(state, null, 2));
|
|
||||||
```
|
|
||||||
|
|
||||||
## Error Handling
|
|
||||||
|
|
||||||
| Error | Recovery |
|
|
||||||
|-------|----------|
|
|
||||||
| CLI timeout | Generate synthesis from individual step analyses only (no cross-step) |
|
|
||||||
| Resume fails | Start fresh analysis session |
|
|
||||||
| JSON parse fails | Use step-level data to construct minimal synthesis |
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Files**: `synthesis.md`
|
|
||||||
- **State**: `state.synthesis` updated
|
|
||||||
- **Next**: Phase 5 (Optimization Report)
|
|
||||||
@@ -1,246 +0,0 @@
|
|||||||
# Phase 5: Optimization Report
|
|
||||||
|
|
||||||
> **COMPACT SENTINEL [Phase 5: Report]**
|
|
||||||
> This phase contains 4 execution steps (Step 5.1 -- 5.4).
|
|
||||||
> If you can read this sentinel but cannot find the full Step protocol below, context has been compressed.
|
|
||||||
> Recovery: `Read("phases/05-optimize-report.md")`
|
|
||||||
|
|
||||||
Generate the final optimization report and optionally apply high-priority fixes.
|
|
||||||
|
|
||||||
## Objective
|
|
||||||
|
|
||||||
- Read complete state, process log, synthesis
|
|
||||||
- Generate structured final report
|
|
||||||
- Optionally apply auto-fix (if enabled)
|
|
||||||
- Write final-report.md
|
|
||||||
- Display summary to user
|
|
||||||
|
|
||||||
## Execution
|
|
||||||
|
|
||||||
### Step 5.1: Read Complete State
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
const state = JSON.parse(Read(`${state.work_dir}/workflow-state.json`));
|
|
||||||
const processLog = Read(`${state.work_dir}/process-log.md`);
|
|
||||||
const synthesis = state.synthesis;
|
|
||||||
state.status = 'completed';
|
|
||||||
state.updated_at = new Date().toISOString();
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5.2: Generate Report
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
// Compute stats
|
|
||||||
const scores = state.steps.map(s => s.analysis?.quality_score).filter(Boolean);
|
|
||||||
const avgScore = scores.length > 0 ? Math.round(scores.reduce((a, b) => a + b, 0) / scores.length) : 0;
|
|
||||||
const minStep = state.steps.reduce((min, s) =>
|
|
||||||
(s.analysis?.quality_score || 100) < (min.analysis?.quality_score || 100) ? s : min
|
|
||||||
, state.steps[0]);
|
|
||||||
|
|
||||||
const totalIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.issue_count || 0), 0);
|
|
||||||
const totalHighIssues = state.steps.reduce((sum, s) => sum + (s.analysis?.high_issues || 0), 0);
|
|
||||||
|
|
||||||
// Step quality table (with requirement match)
|
|
||||||
const stepTable = state.steps.map((s, i) => {
|
|
||||||
const reqPass = s.analysis?.requirement_pass;
|
|
||||||
const reqStr = reqPass === true ? 'PASS' : reqPass === false ? 'FAIL' : '-';
|
|
||||||
return `| ${i + 1} | ${s.name} | ${s.type} | ${s.execution?.success ? 'OK' : 'FAIL'} | ${reqStr} | ${s.analysis?.quality_score || '-'} | ${s.analysis?.issue_count || 0} | ${s.analysis?.high_issues || 0} |`;
|
|
||||||
}).join('\n');
|
|
||||||
|
|
||||||
// Collect all improvements (workflow-level + per-step)
|
|
||||||
const allImprovements = [];
|
|
||||||
if (synthesis?.raw_data?.workflow_improvements) {
|
|
||||||
synthesis.raw_data.workflow_improvements.forEach(w => {
|
|
||||||
allImprovements.push({
|
|
||||||
scope: 'workflow',
|
|
||||||
priority: w.priority,
|
|
||||||
description: w.description,
|
|
||||||
rationale: w.rationale,
|
|
||||||
category: w.category,
|
|
||||||
affected: w.affected_steps || []
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (synthesis?.raw_data?.per_step_improvements) {
|
|
||||||
synthesis.raw_data.per_step_improvements.forEach(s => {
|
|
||||||
(s.improvements || []).forEach(imp => {
|
|
||||||
allImprovements.push({
|
|
||||||
scope: s.step,
|
|
||||||
priority: imp.priority,
|
|
||||||
description: imp.description,
|
|
||||||
rationale: imp.rationale,
|
|
||||||
category: 'step',
|
|
||||||
affected: [s.step]
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort by priority
|
|
||||||
const priorityOrder = { high: 0, medium: 1, low: 2 };
|
|
||||||
allImprovements.sort((a, b) => (priorityOrder[a.priority] || 2) - (priorityOrder[b.priority] || 2));
|
|
||||||
|
|
||||||
const report = `# Workflow Tune — Final Report
|
|
||||||
|
|
||||||
## Summary
|
|
||||||
|
|
||||||
| Field | Value |
|
|
||||||
|-------|-------|
|
|
||||||
| **Workflow** | ${state.workflow_name} |
|
|
||||||
| **Goal** | ${state.workflow_context} |
|
|
||||||
| **Steps** | ${state.steps.length} |
|
|
||||||
| **Workflow Score** | ${synthesis?.workflow_score || avgScore}/100 |
|
|
||||||
| **Average Step Quality** | ${avgScore}/100 |
|
|
||||||
| **Weakest Step** | ${minStep.name} (${minStep.analysis?.quality_score || '-'}/100) |
|
|
||||||
| **Total Issues** | ${totalIssues} (${totalHighIssues} high severity) |
|
|
||||||
| **Analysis Depth** | ${state.analysis_depth} |
|
|
||||||
| **Started** | ${state.started_at} |
|
|
||||||
| **Completed** | ${state.updated_at} |
|
|
||||||
|
|
||||||
## Step Quality Matrix
|
|
||||||
|
|
||||||
| # | Step | Type | Exec | Req Match | Quality | Issues | High |
|
|
||||||
|---|------|------|------|-----------|---------|--------|------|
|
|
||||||
${stepTable}
|
|
||||||
|
|
||||||
## Workflow Flow Assessment
|
|
||||||
|
|
||||||
### Coherence: ${synthesis?.raw_data?.coherence?.score || '-'}/100
|
|
||||||
${synthesis?.raw_data?.coherence?.assessment || 'Not evaluated'}
|
|
||||||
|
|
||||||
### Handoff Quality: ${synthesis?.raw_data?.handoff_quality?.score || '-'}/100
|
|
||||||
${(synthesis?.raw_data?.handoff_quality?.issues || []).map(i =>
|
|
||||||
`- **${i.from_step} → ${i.to_step}**: ${i.issue}`
|
|
||||||
).join('\n') || 'No handoff issues'}
|
|
||||||
|
|
||||||
### Bottlenecks
|
|
||||||
${(synthesis?.raw_data?.bottlenecks || []).map(b =>
|
|
||||||
`- **${b.step}** [${b.impact}]: ${b.reason}`
|
|
||||||
).join('\n') || 'No bottlenecks identified'}
|
|
||||||
|
|
||||||
## Optimization Recommendations
|
|
||||||
|
|
||||||
### Priority: HIGH
|
|
||||||
${allImprovements.filter(i => i.priority === 'high').map((i, idx) =>
|
|
||||||
`${idx + 1}. **[${i.scope}]** ${i.description}\n - Rationale: ${i.rationale}\n - Affected: ${i.affected.join(', ')}`
|
|
||||||
).join('\n') || 'None'}
|
|
||||||
|
|
||||||
### Priority: MEDIUM
|
|
||||||
${allImprovements.filter(i => i.priority === 'medium').map((i, idx) =>
|
|
||||||
`${idx + 1}. **[${i.scope}]** ${i.description}\n - Rationale: ${i.rationale}`
|
|
||||||
).join('\n') || 'None'}
|
|
||||||
|
|
||||||
### Priority: LOW
|
|
||||||
${allImprovements.filter(i => i.priority === 'low').map((i, idx) =>
|
|
||||||
`${idx + 1}. **[${i.scope}]** ${i.description}`
|
|
||||||
).join('\n') || 'None'}
|
|
||||||
|
|
||||||
## Process Documentation
|
|
||||||
|
|
||||||
Full process log: \`${state.work_dir}/process-log.md\`
|
|
||||||
Synthesis: \`${state.work_dir}/synthesis.md\`
|
|
||||||
|
|
||||||
### Per-Step Analysis Files
|
|
||||||
|
|
||||||
| Step | Analysis File |
|
|
||||||
|------|---------------|
|
|
||||||
${state.steps.map((s, i) =>
|
|
||||||
`| ${s.name} | \`${state.work_dir}/steps/step-${i + 1}/step-${i + 1}-analysis.md\` |`
|
|
||||||
).join('\n')}
|
|
||||||
|
|
||||||
## Artifact Locations
|
|
||||||
|
|
||||||
| Path | Description |
|
|
||||||
|------|-------------|
|
|
||||||
| \`${state.work_dir}/workflow-state.json\` | Complete state |
|
|
||||||
| \`${state.work_dir}/process-log.md\` | Accumulated process log |
|
|
||||||
| \`${state.work_dir}/synthesis.md\` | Cross-step synthesis |
|
|
||||||
| \`${state.work_dir}/final-report.md\` | This report |
|
|
||||||
`;
|
|
||||||
|
|
||||||
Write(`${state.work_dir}/final-report.md`, report);
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5.3: Optional Auto-Fix
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (state.auto_fix && allImprovements.filter(i => i.priority === 'high').length > 0) {
|
|
||||||
const highPriorityFixes = allImprovements.filter(i => i.priority === 'high');
|
|
||||||
|
|
||||||
// ★ Safety: confirm with user before applying auto-fixes
|
|
||||||
const fixList = highPriorityFixes.map((f, i) =>
|
|
||||||
`${i + 1}. [${f.scope}] ${f.description}\n Affected: ${f.affected.join(', ')}`
|
|
||||||
).join('\n');
|
|
||||||
|
|
||||||
const autoFixConfirm = AskUserQuestion({
|
|
||||||
questions: [{
|
|
||||||
question: `以下 ${highPriorityFixes.length} 项高优先级优化将被自动应用:\n\n${fixList}\n\n确认应用?`,
|
|
||||||
header: "Auto-Fix Confirmation",
|
|
||||||
multiSelect: false,
|
|
||||||
options: [
|
|
||||||
{ label: "Apply (应用)", description: "自动应用以上高优先级修复" },
|
|
||||||
{ label: "Skip (跳过)", description: "跳过自动修复,仅保留报告" }
|
|
||||||
]
|
|
||||||
}]
|
|
||||||
});
|
|
||||||
|
|
||||||
if (autoFixConfirm["Auto-Fix Confirmation"].startsWith("Skip")) {
|
|
||||||
// Skip auto-fix, just log it
|
|
||||||
state.auto_fix_skipped = true;
|
|
||||||
} else {
|
|
||||||
|
|
||||||
Agent({
|
|
||||||
subagent_type: 'general-purpose',
|
|
||||||
run_in_background: false,
|
|
||||||
description: 'Apply high-priority workflow optimizations',
|
|
||||||
prompt: `## Task: Apply High-Priority Workflow Optimizations
|
|
||||||
|
|
||||||
You are applying the top optimization suggestions from a workflow analysis.
|
|
||||||
|
|
||||||
## Improvements to Apply (HIGH priority only)
|
|
||||||
${highPriorityFixes.map((f, i) =>
|
|
||||||
`${i + 1}. [${f.scope}] ${f.description}\n Rationale: ${f.rationale}\n Affected: ${f.affected.join(', ')}`
|
|
||||||
).join('\n')}
|
|
||||||
|
|
||||||
## Workflow Steps
|
|
||||||
${state.steps.map((s, i) => `${i + 1}. ${s.name} (${s.type}): ${s.command}`).join('\n')}
|
|
||||||
|
|
||||||
## Rules
|
|
||||||
1. Read each affected file BEFORE modifying
|
|
||||||
2. Apply ONLY the high-priority suggestions
|
|
||||||
3. Preserve existing code style
|
|
||||||
4. Write a changes summary to: ${state.work_dir}/auto-fix-changes.md
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
} // end Apply branch
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Step 5.4: Display Summary
|
|
||||||
|
|
||||||
Output to user:
|
|
||||||
|
|
||||||
```
|
|
||||||
Workflow Tune Complete!
|
|
||||||
|
|
||||||
Workflow: {name}
|
|
||||||
Steps: {count}
|
|
||||||
Workflow Score: {score}/100
|
|
||||||
Average Step Quality: {avgScore}/100
|
|
||||||
Weakest Step: {name} ({score}/100)
|
|
||||||
|
|
||||||
Step Scores: {step1}={score1} → {step2}={score2} → ... → {stepN}={scoreN}
|
|
||||||
|
|
||||||
Issues: {total} ({high} high priority)
|
|
||||||
Improvements: {count} ({highCount} high priority)
|
|
||||||
|
|
||||||
Full report: {workDir}/final-report.md
|
|
||||||
Process log: {workDir}/process-log.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Output
|
|
||||||
|
|
||||||
- **Files**: `final-report.md`, optionally `auto-fix-changes.md`
|
|
||||||
- **State**: `status = completed`
|
|
||||||
- **Next**: Workflow complete. Return control to user.
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
# Workflow Evaluation Criteria
|
|
||||||
|
|
||||||
Workflow 调优评估标准,由 Phase 03 (Analyze Step) 和 Phase 04 (Synthesize) 引用。
|
|
||||||
|
|
||||||
## Per-Step Dimensions
|
|
||||||
|
|
||||||
| Dimension | Description |
|
|
||||||
|-----------|-------------|
|
|
||||||
| Execution Success | 命令是否成功执行,退出码是否正确 |
|
|
||||||
| Output Completeness | 产物是否齐全,预期文件是否生成 |
|
|
||||||
| Artifact Quality | 产物内容质量 — 非空、格式正确、内容有意义 |
|
|
||||||
| Handoff Readiness | 产物是否满足下一步的输入要求,格式兼容性 |
|
|
||||||
|
|
||||||
## Per-Step Scoring Guide
|
|
||||||
|
|
||||||
| Range | Level | Description |
|
|
||||||
|-------|-------|-------------|
|
|
||||||
| 90-100 | Excellent | 执行完美,产物高质量,下游可直接消费 |
|
|
||||||
| 80-89 | Good | 执行成功,产物基本完整,微调即可衔接 |
|
|
||||||
| 70-79 | Adequate | 执行成功但产物有缺失或质量一般 |
|
|
||||||
| 60-69 | Needs Work | 部分失败或产物质量差,衔接困难 |
|
|
||||||
| 0-59 | Poor | 执行失败或产物无法使用 |
|
|
||||||
|
|
||||||
## Workflow-Level Dimensions
|
|
||||||
|
|
||||||
| Dimension | Description |
|
|
||||||
|-----------|-------------|
|
|
||||||
| Coherence | 步骤间的逻辑顺序是否合理,是否形成完整流程 |
|
|
||||||
| Handoff Quality | 步骤间的数据传递是否顺畅,格式是否匹配 |
|
|
||||||
| Redundancy | 是否存在步骤间的工作重叠或重复 |
|
|
||||||
| Efficiency | 整体流程是否高效,有无不必要的步骤 |
|
|
||||||
| Completeness | 是否覆盖所有必要环节,有无遗漏 |
|
|
||||||
|
|
||||||
## Analysis Depth Profiles
|
|
||||||
|
|
||||||
### Quick
|
|
||||||
- 每步 3-5 要点
|
|
||||||
- 关注: 执行成功、产出完整、明显问题
|
|
||||||
- 跨步骤: 基本衔接检查
|
|
||||||
|
|
||||||
### Standard
|
|
||||||
- 每步详细评估
|
|
||||||
- 关注: 执行质量、产出完整性、产物质量、衔接就绪度、潜在问题
|
|
||||||
- 跨步骤: 衔接质量、冗余检测、瓶颈识别
|
|
||||||
|
|
||||||
### Deep
|
|
||||||
- 每步深度审查
|
|
||||||
- 关注: 执行质量、产出正确性、结构质量、衔接完整性、错误处理、性能信号、架构影响、边界情况
|
|
||||||
- 跨步骤: 全面流程优化、重排建议、缺失步骤检测、架构改进
|
|
||||||
|
|
||||||
## Issue Severity Guide
|
|
||||||
|
|
||||||
| Severity | Description | Example |
|
|
||||||
|----------|-------------|---------|
|
|
||||||
| High | 阻断流程或导致错误结果 | 步骤执行失败、产物格式不兼容、关键数据丢失 |
|
|
||||||
| Medium | 影响质量但不阻断 | 产物不完整、衔接需手动调整、冗余步骤 |
|
|
||||||
| Low | 可改进但不影响功能 | 输出格式不一致、可优化的步骤顺序 |
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
# Step Analysis Prompt Template
|
|
||||||
|
|
||||||
Phase 03 使用此模板构造 ccw cli 提示词,让 Gemini 分析单个步骤的执行结果和产物质量。
|
|
||||||
|
|
||||||
## Template
|
|
||||||
|
|
||||||
```
|
|
||||||
PURPOSE: Analyze the output of workflow step "${stepName}" (step ${stepIndex}/${totalSteps}) to assess quality, identify issues, and evaluate handoff readiness for the next step.
|
|
||||||
|
|
||||||
WORKFLOW CONTEXT:
|
|
||||||
Name: ${workflowName}
|
|
||||||
Goal: ${workflowContext}
|
|
||||||
Step Chain:
|
|
||||||
${stepChainContext}
|
|
||||||
|
|
||||||
CURRENT STEP:
|
|
||||||
Name: ${stepName}
|
|
||||||
Type: ${stepType}
|
|
||||||
Command: ${stepCommand}
|
|
||||||
${successCriteria}
|
|
||||||
|
|
||||||
EXECUTION RESULT:
|
|
||||||
${execSummary}
|
|
||||||
|
|
||||||
${handoffContext}
|
|
||||||
|
|
||||||
STEP ARTIFACTS:
|
|
||||||
${artifactSummary}
|
|
||||||
|
|
||||||
ANALYSIS DEPTH: ${analysisDepth}
|
|
||||||
${depthInstructions}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
1. Assess step execution quality (did it succeed? complete output?)
|
|
||||||
2. Evaluate artifact quality (content correctness, completeness, format)
|
|
||||||
3. Check handoff readiness (can the next step consume this output?)
|
|
||||||
4. Identify issues, risks, or optimization opportunities
|
|
||||||
5. Rate overall step quality 0-100
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"quality_score": <0-100>,
|
|
||||||
"execution_assessment": {
|
|
||||||
"success": <true|false>,
|
|
||||||
"completeness": "<complete|partial|failed>",
|
|
||||||
"notes": "<brief assessment>"
|
|
||||||
},
|
|
||||||
"artifact_assessment": {
|
|
||||||
"count": <number>,
|
|
||||||
"quality": "<high|medium|low>",
|
|
||||||
"key_outputs": ["<main output 1>", "<main output 2>"],
|
|
||||||
"missing_outputs": ["<expected but missing>"]
|
|
||||||
},
|
|
||||||
"handoff_assessment": {
|
|
||||||
"ready": <true|false>,
|
|
||||||
"next_step_compatible": <true|false|null>,
|
|
||||||
"handoff_notes": "<what next step should know>"
|
|
||||||
},
|
|
||||||
"issues": [
|
|
||||||
{ "severity": "high|medium|low", "description": "<issue>", "suggestion": "<fix>" }
|
|
||||||
],
|
|
||||||
"optimization_opportunities": [
|
|
||||||
{ "area": "<area>", "description": "<opportunity>", "impact": "high|medium|low" }
|
|
||||||
],
|
|
||||||
"step_summary": "<1-2 sentence summary for process log>"
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Be specific, reference artifact content where possible, output ONLY JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variable Substitution
|
|
||||||
|
|
||||||
| Variable | Source | Description |
|
|
||||||
|----------|--------|-------------|
|
|
||||||
| `${stepName}` | workflow-state.json | 当前步骤名称 |
|
|
||||||
| `${stepIndex}` | orchestrator loop | 当前步骤序号 (1-based) |
|
|
||||||
| `${totalSteps}` | workflow-state.json | 总步骤数 |
|
|
||||||
| `${workflowName}` | workflow-state.json | Workflow 名称 |
|
|
||||||
| `${workflowContext}` | workflow-state.json | Workflow 目标描述 |
|
|
||||||
| `${stepChainContext}` | Phase 03 builds | 所有步骤状态概览 |
|
|
||||||
| `${stepType}` | workflow-state.json | 步骤类型 (skill/ccw-cli/command) |
|
|
||||||
| `${stepCommand}` | workflow-state.json | 步骤命令 |
|
|
||||||
| `${successCriteria}` | workflow-state.json | 成功标准 (如有) |
|
|
||||||
| `${execSummary}` | Phase 03 builds | 执行结果摘要 |
|
|
||||||
| `${handoffContext}` | Phase 03 builds | 上一步的产出摘要 (非首步) |
|
|
||||||
| `${artifactSummary}` | Phase 03 builds | 产物内容摘要 |
|
|
||||||
| `${analysisDepth}` | workflow-state.json | 分析深度 (quick/standard/deep) |
|
|
||||||
| `${depthInstructions}` | Phase 03 maps | 对应深度的分析指令 |
|
|
||||||
@@ -1,90 +0,0 @@
|
|||||||
# Synthesis Prompt Template
|
|
||||||
|
|
||||||
Phase 04 使用此模板构造 ccw cli 提示词,让 Gemini 综合所有步骤分析,产出跨步骤优化建议。
|
|
||||||
|
|
||||||
## Template
|
|
||||||
|
|
||||||
```
|
|
||||||
PURPOSE: Synthesize all step analyses into a holistic workflow optimization assessment. Evaluate cross-step concerns: ordering, handoff quality, redundancy, bottlenecks, and overall workflow coherence.
|
|
||||||
|
|
||||||
WORKFLOW OVERVIEW:
|
|
||||||
Name: ${workflowName}
|
|
||||||
Goal: ${workflowContext}
|
|
||||||
Steps: ${stepCount}
|
|
||||||
Average Quality: ${avgScore}/100
|
|
||||||
Weakest Step: ${minScore}/100
|
|
||||||
Total Issues: ${totalIssues} (${totalHighIssues} high severity)
|
|
||||||
|
|
||||||
SCORE SUMMARY:
|
|
||||||
${scoreSummary}
|
|
||||||
|
|
||||||
COMPLETE PROCESS LOG:
|
|
||||||
${processLog}
|
|
||||||
|
|
||||||
DETAILED STEP ANALYSES:
|
|
||||||
${stepAnalyses}
|
|
||||||
|
|
||||||
TASK:
|
|
||||||
1. **Workflow Coherence**: Do steps form a logical sequence? Any missing steps?
|
|
||||||
2. **Handoff Quality**: Are step outputs well-consumed by subsequent steps? Data format mismatches?
|
|
||||||
3. **Redundancy Detection**: Do any steps duplicate work? Overlapping concerns?
|
|
||||||
4. **Bottleneck Identification**: Which steps are bottlenecks (lowest quality, longest duration)?
|
|
||||||
5. **Step Ordering**: Would reordering steps improve outcomes?
|
|
||||||
6. **Missing Steps**: Are there gaps in the pipeline that need additional steps?
|
|
||||||
7. **Per-Step Optimization**: Top 3 improvements per underperforming step
|
|
||||||
8. **Workflow-Level Optimization**: Structural changes to the overall pipeline
|
|
||||||
|
|
||||||
EXPECTED OUTPUT (strict JSON, no markdown):
|
|
||||||
{
|
|
||||||
"workflow_score": <0-100>,
|
|
||||||
"coherence": {
|
|
||||||
"score": <0-100>,
|
|
||||||
"assessment": "<logical flow evaluation>",
|
|
||||||
"gaps": ["<missing step or transition>"]
|
|
||||||
},
|
|
||||||
"handoff_quality": {
|
|
||||||
"score": <0-100>,
|
|
||||||
"issues": [
|
|
||||||
{ "from_step": "<step name>", "to_step": "<step name>", "issue": "<description>", "fix": "<suggestion>" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"redundancy": {
|
|
||||||
"found": <true|false>,
|
|
||||||
"items": [
|
|
||||||
{ "steps": ["<step1>", "<step2>"], "description": "<what overlaps>", "recommendation": "<merge or remove>" }
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"bottlenecks": [
|
|
||||||
{ "step": "<step name>", "reason": "<why it's a bottleneck>", "impact": "high|medium|low", "fix": "<suggestion>" }
|
|
||||||
],
|
|
||||||
"ordering_suggestions": [
|
|
||||||
{ "current": "<current order description>", "proposed": "<new order>", "rationale": "<why>" }
|
|
||||||
],
|
|
||||||
"per_step_improvements": [
|
|
||||||
{ "step": "<step name>", "improvements": [
|
|
||||||
{ "priority": "high|medium|low", "description": "<what to change>", "rationale": "<why>" }
|
|
||||||
]}
|
|
||||||
],
|
|
||||||
"workflow_improvements": [
|
|
||||||
{ "priority": "high|medium|low", "category": "structure|handoff|performance|quality", "description": "<change>", "rationale": "<why>", "affected_steps": ["<step names>"] }
|
|
||||||
],
|
|
||||||
"summary": "<2-3 sentence executive summary of workflow health and top recommendations>"
|
|
||||||
}
|
|
||||||
|
|
||||||
CONSTRAINTS: Be specific, reference step names and artifact details, output ONLY JSON
|
|
||||||
```
|
|
||||||
|
|
||||||
## Variable Substitution
|
|
||||||
|
|
||||||
| Variable | Source | Description |
|
|
||||||
|----------|--------|-------------|
|
|
||||||
| `${workflowName}` | workflow-state.json | Workflow 名称 |
|
|
||||||
| `${workflowContext}` | workflow-state.json | Workflow 目标 |
|
|
||||||
| `${stepCount}` | workflow-state.json | 总步骤数 |
|
|
||||||
| `${avgScore}` | Phase 04 computes | 所有步骤平均分 |
|
|
||||||
| `${minScore}` | Phase 04 computes | 最低步骤分 |
|
|
||||||
| `${totalIssues}` | Phase 04 computes | 总问题数 |
|
|
||||||
| `${totalHighIssues}` | Phase 04 computes | 高优先级问题数 |
|
|
||||||
| `${scoreSummary}` | Phase 04 builds | 每步分数一行 |
|
|
||||||
| `${processLog}` | process-log.md | 完整过程日志 |
|
|
||||||
| `${stepAnalyses}` | Phase 04 reads | 所有 step-N-analysis.md 内容 |
|
|
||||||
3
.gitignore
vendored
3
.gitignore
vendored
@@ -136,6 +136,9 @@ ccw/.tmp-ccw-auth-home/
|
|||||||
.claude/skills_lib/team-skill-designer/templates/role-template.md
|
.claude/skills_lib/team-skill-designer/templates/role-template.md
|
||||||
.claude/skills_lib/team-skill-designer/templates/skill-router-template.md
|
.claude/skills_lib/team-skill-designer/templates/skill-router-template.md
|
||||||
|
|
||||||
|
# Workflow tune command (local only)
|
||||||
|
.claude/commands/workflow-tune.md
|
||||||
|
|
||||||
# Skills library (local only)
|
# Skills library (local only)
|
||||||
.claude/skills_lib/
|
.claude/skills_lib/
|
||||||
|
|
||||||
|
|||||||
@@ -85,3 +85,4 @@ codex-lens/.workflow/
|
|||||||
|
|
||||||
# Claude skills (development only)
|
# Claude skills (development only)
|
||||||
.claude/skills/team-designer/
|
.claude/skills/team-designer/
|
||||||
|
.claude/commands/workflow-tune.md
|
||||||
|
|||||||
Binary file not shown.
|
Before Width: | Height: | Size: 229 KiB After Width: | Height: | Size: 171 KiB |
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "claude-code-workflow",
|
"name": "claude-code-workflow",
|
||||||
"version": "7.2.11",
|
"version": "7.2.13",
|
||||||
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
"description": "JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"main": "ccw/dist/index.js",
|
"main": "ccw/dist/index.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user