feat: add spec-setup command for project initialization and interactive configuration

- Introduced a new command `spec-setup` to initialize project-level state.
- Generates `.workflow/project-tech.json` and `.ccw/specs/*.md` files.
- Implements a multi-round interactive questionnaire for configuring project guidelines.
- Supports flags for regeneration, skipping specs, and resetting existing content.
- Integrates analysis via `cli-explore-agent` for comprehensive project understanding.
- Provides detailed execution process and error handling for various scenarios.
This commit is contained in:
catlog22
2026-03-06 16:49:35 +08:00
parent f2d4364c69
commit a9469a5e3b
25 changed files with 3472 additions and 1819 deletions

View File

@@ -923,8 +923,9 @@ Task: <description>
**Cycle Workflows**: workflow:integration-test-cycle, workflow:refactor-cycle
**Execution**: workflow:unified-execute-with-file
**Design**: workflow:ui-design:*
**Session Management**: workflow:session:start, workflow:session:resume, workflow:session:complete, workflow:session:solidify, workflow:session:list, workflow:session:sync
**Utility**: workflow:clean, workflow:init, workflow:init-guidelines, workflow:status
**Session Management**: workflow:session:start, workflow:session:resume, workflow:session:complete, workflow:session:list, workflow:session:sync
**Utility**: workflow:clean, workflow:spec:setup, workflow:status
**Spec Management**: workflow:spec:setup, workflow:spec:add
**Issue Workflow**: issue:discover, issue:discover-by-prompt, issue:plan, issue:queue, issue:execute, issue:convert-to-plan, issue:from-brainstorm, issue:new
### Testing Commands Distinction

View File

@@ -760,8 +760,8 @@ todos = [
|---------|---------|
| `workflow:unified-execute-with-file` | Universal execution engine - consumes plan output from collaborative-plan, roadmap, brainstorm |
| `workflow:clean` | Intelligent code cleanup - mainline detection, stale artifact removal |
| `workflow:init` | Initialize `.workflow/project-tech.json` with project analysis |
| `workflow:init-guidelines` | Interactive wizard to fill `specs/*.md` |
| `workflow:spec:setup` | Initialize `.workflow/project-tech.json` with project analysis and specs scaffold |
| `workflow:spec:add` | Interactive wizard to add individual specs with scope selection |
| `workflow:status` | Generate on-demand views for project overview and workflow tasks |
---
@@ -817,7 +817,7 @@ todos = [
# Utility commands (invoked directly, not auto-routed)
# /workflow:unified-execute-with-file # 通用执行引擎(消费 plan 输出)
# /workflow:clean # 智能代码清理
# /workflow:init # 初始化项目状态
# /workflow:init-guidelines # 交互式填充项目规范
# /workflow:spec:setup # 初始化项目状态
# /workflow:spec:add # 交互式填充项目规范
# /workflow:status # 项目概览和工作流状态
```

View File

@@ -93,7 +93,7 @@ async function selectCommandCategory() {
{ label: "Brainstorm", description: "brainstorm-with-file, brainstorm (unified skill)" },
{ label: "Analysis", description: "analyze-with-file" },
{ label: "Issue", description: "discover, plan, queue, execute, from-brainstorm, convert-to-plan" },
{ label: "Utility", description: "clean, init, replan, status" }
{ label: "Utility", description: "clean, spec:setup, spec:add, replan, status" }
],
multiSelect: false
}]
@@ -153,7 +153,7 @@ async function selectCommand(category) {
],
'Utility': [
{ label: "/workflow:clean", description: "Intelligent code cleanup" },
{ label: "/workflow:init", description: "Initialize project-level state" },
{ label: "/workflow:spec:setup", description: "Initialize project-level state" },
{ label: "/workflow:replan", description: "Interactive workflow replanning" },
{ label: "/workflow:status", description: "Generate workflow status views" }
]

View File

@@ -65,6 +65,14 @@ When `--yes` or `-y`: Auto-confirm exploration decisions, use recommended analys
## Implementation
### AskUserQuestion Constraints
All `AskUserQuestion` calls MUST comply:
- **questions**: 1-4 questions per call
- **options**: 2-4 per question (system auto-adds "Other" for free-text input)
- **header**: max 12 characters
- **label**: 1-5 words per option
### Session Initialization
1. Extract topic/question from `$ARGUMENTS`
@@ -79,10 +87,10 @@ When `--yes` or `-y`: Auto-confirm exploration decisions, use recommended analys
### Phase 1: Topic Understanding
1. **Parse Topic & Identify Dimensions** — Match keywords against Analysis Dimensions table
2. **Initial Scoping** (if new session + not auto mode):
- **Focus**: Multi-select from Dimension-Direction Mapping directions
- **Perspectives**: Multi-select up to 4 (see Analysis Perspectives), default: single comprehensive
- **Depth**: Quick Overview (10-15min) / Standard (30-60min) / Deep Dive (1-2hr)
2. **Initial Scoping** (if new session + not auto mode) — use **single AskUserQuestion call with up to 3 questions**:
- Q1 **Focus** (multiSelect: true, header: "分析方向"): Top 3-4 directions from Dimension-Direction Mapping (options max 4)
- Q2 **Perspectives** (multiSelect: true, header: "分析视角"): Up to 4 from Analysis Perspectives table (options max 4), default: single comprehensive
- Q3 **Depth** (multiSelect: false, header: "分析深度"): Quick Overview / Standard / Deep Dive (3 options)
3. **Initialize discussion.md** — Structure includes:
- **Dynamic TOC** (top of file, updated after each round/phase): `## Table of Contents` with links to major sections
- **Current Understanding** (replaceable block, overwritten each round — NOT appended): `## Current Understanding` initialized as "To be populated after exploration"
@@ -223,31 +231,26 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
2. **Present Findings** from explorations.json
3. **Gather Feedback** (AskUserQuestion, single-select):
- **同意,继续深入**: Direction correct, deepen
- **同意,并建议下一步**: Agree with direction, but user has specific next step in mind
- **需要调整方向**: Different focus
3. **Gather Feedback** (AskUserQuestion, single-select, header: "分析反馈"):
- **继续深入**: Direction correct deepen automatically or user specifies direction (combines agree+deepen and agree+suggest)
- **调整方向**: Different focus or specific questions to address
- **补充信息**: User has additional context, constraints, or corrections to provide
- **分析完成**: Sufficient → exit to Phase 4
- **有具体问题**: Specific questions
4. **Process Response** (always record user choice + impact to discussion.md):
**Agree, Deepen** → Dynamically generate deepen directions from current analysis context:
- Extract 2-3 context-driven options from: unresolved questions in explorations.json, low-confidence findings, unexplored dimensions, user-highlighted areas
- Generate 1-2 heuristic options that break current frame: e.g., "compare with best practices in [related domain]", "analyze under extreme load scenarios", "review from security audit perspective", "explore simpler architectural alternatives"
- Each option specifies: label, description, tool (cli-explore-agent for code-level / Gemini CLI for pattern-level), scope
- AskUserQuestion with generated options (single-select)
- Execute selected direction via corresponding tool
- Merge new code_anchors/call_chains into existing results
- Record confirmed assumptions + deepen angle
**继续深入** → Sub-question to choose direction (AskUserQuestion, single-select, header: "深入方向"):
- Dynamically generate **max 3** context-driven options from: unresolved questions, low-confidence findings, unexplored dimensions, user-highlighted areas
- Add **1** heuristic option that breaks current frame (e.g., "compare with best practices", "review from security perspective", "explore simpler alternatives")
- Total: **max 4 options**. Each specifies: label, description, tool (cli-explore-agent for code-level / Gemini CLI for pattern-level), scope
- **"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
**Agree, Suggest Next Step** → AskUserQuestion (free text: "请描述您希望下一步深入的方向") → Execute user's specific direction via cli-explore-agent or CLI → Record user-driven exploration rationale
**调整方向** → AskUserQuestion (header: "新方向", user selects or provides custom via "Other") → new CLI exploration → Record Decision (old vs new direction, reason, impact)
**Adjust Direction** → AskUserQuestion for new focus → new CLI exploration → Record Decision (old vs new direction, reason, impact)
**补充信息** → Capture user input, integrate into context, answer questions via CLI/analysis if needed → Record corrections/additions + updated understanding
**Specific Questions** → Capture, answer via CLI/analysis, document Q&A → Record gaps revealed + new understanding
**Complete** → Exit loop → Record why concluding
**分析完成** → Exit loop → Record why concluding
5. **Update discussion.md**:
- **Append** Round N: user input, direction adjustment, Q&A, corrections, new insights
@@ -319,11 +322,11 @@ CONSTRAINTS: Focus on ${dimensions.join(', ')}
```
For each recommendation (ordered by priority high→medium→low):
1. Present: action, rationale, priority, steps[] (numbered sub-steps)
2. AskUserQuestion (single-select, header: "Rec #N"):
- **确认**: Accept as-is → review_status = "accepted"
- **修改**: User adjusts scope/steps → record modification → review_status = "modified"
- **删除**: Not needed → record reason → review_status = "rejected"
- **跳过逐条审议**: Accept all remaining as-is → break loop
2. AskUserQuestion (single-select, header: "建议#N"):
- **确认** (label: "确认", desc: "Accept as-is") → review_status = "accepted"
- **修改** (label: "修改", desc: "Adjust scope/steps") → record modification → review_status = "modified"
- **删除** (label: "删除", desc: "Not needed") → record reason → review_status = "rejected"
- **跳过审议** (label: "跳过审议", desc: "Accept all remaining") → break loop
3. Record review decision to discussion.md Decision Log
4. Update conclusions.json recommendation.review_status
```

View File

@@ -587,7 +587,11 @@ Schema (tasks): ~/.ccw/workflows/cli-templates/schemas/task-schema.json
- Execution command
- Conflict status
6. **Update Todo**
6. **Sync Session State**
- Execute: `/workflow:session:sync -y "Plan complete: ${subDomains.length} domains, ${allTasks.length} tasks"`
- Updates specs/*.md with planning insights and project-tech.json with planning session entry
7. **Update Todo**
- Set Phase 4 status to `completed`
**plan.md Structure**:

View File

@@ -1,380 +0,0 @@
---
name: init-specs
description: Interactive wizard to create individual specs or personal constraints with scope selection
argument-hint: "[--scope <global|project>] [--dimension <specs|personal>] [--category <general|exploration|planning|execution>]"
examples:
- /workflow:init-specs
- /workflow:init-specs --scope global --dimension personal
- /workflow:init-specs --scope project --dimension specs
---
# Workflow Init Specs Command (/workflow:init-specs)
## Overview
Interactive wizard for creating individual specs or personal constraints with scope selection. This command provides a guided experience for adding new rules to the spec system.
**Key Features**:
- Supports both project specs and personal specs
- Scope selection (global vs project) for personal specs
- Category-based organization for workflow stages
- Interactive mode with smart defaults
## Usage
```bash
/workflow:init-specs # Interactive mode (all prompts)
/workflow:init-specs --scope global # Create global personal spec
/workflow:init-specs --scope project # Create project spec (default)
/workflow:init-specs --dimension specs # Project conventions/constraints
/workflow:init-specs --dimension personal # Personal preferences
/workflow:init-specs --category exploration # Workflow stage category
```
## Parameters
| Parameter | Values | Default | Description |
|-----------|--------|---------|-------------|
| `--scope` | `global`, `project` | `project` | Where to store the spec (only for personal dimension) |
| `--dimension` | `specs`, `personal` | Interactive | Type of spec to create |
| `--category` | `general`, `exploration`, `planning`, `execution` | `general` | Workflow stage category |
## Execution Process
```
Input Parsing:
├─ Parse --scope (global | project)
├─ Parse --dimension (specs | personal)
└─ Parse --category (general | exploration | planning | execution)
Step 1: Gather Requirements (Interactive)
├─ If dimension not specified → Ask dimension
├─ If personal + scope not specified → Ask scope
├─ If category not specified → Ask category
├─ Ask type (convention | constraint | learning)
└─ Ask content (rule text)
Step 2: Determine Target File
├─ specs dimension → .ccw/specs/coding-conventions.md or architecture-constraints.md
└─ personal dimension → ~/.ccw/specs/personal/ or .ccw/specs/personal/
Step 3: Write Spec
├─ Check if file exists, create if needed with proper frontmatter
├─ Append rule to appropriate section
└─ Run ccw spec rebuild
Step 4: Display Confirmation
```
## Implementation
### Step 1: Parse Input and Gather Requirements
```javascript
// Parse arguments
const args = $ARGUMENTS.toLowerCase()
const hasScope = args.includes('--scope')
const hasDimension = args.includes('--dimension')
const hasCategory = args.includes('--category')
// Extract values from arguments
let scope = hasScope ? args.match(/--scope\s+(\w+)/)?.[1] : null
let dimension = hasDimension ? args.match(/--dimension\s+(\w+)/)?.[1] : null
let category = hasCategory ? args.match(/--category\s+(\w+)/)?.[1] : null
// Validate values
if (scope && !['global', 'project'].includes(scope)) {
console.log("Invalid scope. Use 'global' or 'project'.")
return
}
if (dimension && !['specs', 'personal'].includes(dimension)) {
console.log("Invalid dimension. Use 'specs' or 'personal'.")
return
}
if (category && !['general', 'exploration', 'planning', 'execution'].includes(category)) {
console.log("Invalid category. Use 'general', 'exploration', 'planning', or 'execution'.")
return
}
```
### Step 2: Interactive Questions
**If dimension not specified**:
```javascript
if (!dimension) {
const dimensionAnswer = AskUserQuestion({
questions: [{
question: "What type of spec do you want to create?",
header: "Dimension",
multiSelect: false,
options: [
{
label: "Project Spec",
description: "Coding conventions, constraints, quality rules for this project (stored in .ccw/specs/)"
},
{
label: "Personal Spec",
description: "Personal preferences and constraints that follow you across projects (stored in ~/.ccw/specs/personal/ or .ccw/specs/personal/)"
}
]
}]
})
dimension = dimensionAnswer.answers["Dimension"] === "Project Spec" ? "specs" : "personal"
}
```
**If personal dimension and scope not specified**:
```javascript
if (dimension === 'personal' && !scope) {
const scopeAnswer = AskUserQuestion({
questions: [{
question: "Where should this personal spec be stored?",
header: "Scope",
multiSelect: false,
options: [
{
label: "Global (Recommended)",
description: "Apply to ALL projects (~/.ccw/specs/personal/)"
},
{
label: "Project-only",
description: "Apply only to this project (.ccw/specs/personal/)"
}
]
}]
})
scope = scopeAnswer.answers["Scope"].includes("Global") ? "global" : "project"
}
```
**If category not specified**:
```javascript
if (!category) {
const categoryAnswer = AskUserQuestion({
questions: [{
question: "Which workflow stage does this spec apply to?",
header: "Category",
multiSelect: false,
options: [
{
label: "General (Recommended)",
description: "Applies to all stages (default)"
},
{
label: "Exploration",
description: "Code exploration, analysis, debugging"
},
{
label: "Planning",
description: "Task planning, requirements gathering"
},
{
label: "Execution",
description: "Implementation, testing, deployment"
}
]
}]
})
const categoryLabel = categoryAnswer.answers["Category"]
category = categoryLabel.includes("General") ? "general"
: categoryLabel.includes("Exploration") ? "exploration"
: categoryLabel.includes("Planning") ? "planning"
: "execution"
}
```
**Ask type**:
```javascript
const typeAnswer = AskUserQuestion({
questions: [{
question: "What type of rule is this?",
header: "Type",
multiSelect: false,
options: [
{
label: "Convention",
description: "Coding style preference (e.g., use functional components)"
},
{
label: "Constraint",
description: "Hard rule that must not be violated (e.g., no direct DB access)"
},
{
label: "Learning",
description: "Insight or lesson learned (e.g., cache invalidation needs events)"
}
]
}]
})
const type = typeAnswer.answers["Type"]
const isConvention = type.includes("Convention")
const isConstraint = type.includes("Constraint")
const isLearning = type.includes("Learning")
```
**Ask content**:
```javascript
const contentAnswer = AskUserQuestion({
questions: [{
question: "Enter the rule or guideline text:",
header: "Content",
multiSelect: false,
options: []
}]
})
const ruleText = contentAnswer.answers["Content"]
```
### Step 3: Determine Target File
```javascript
const path = require('path')
const os = require('os')
let targetFile: string
let targetDir: string
if (dimension === 'specs') {
// Project specs - use .ccw/specs/ (same as frontend/backend spec-index-builder)
targetDir = '.ccw/specs'
if (isConstraint) {
targetFile = path.join(targetDir, 'architecture-constraints.md')
} else {
targetFile = path.join(targetDir, 'coding-conventions.md')
}
} else {
// Personal specs - use .ccw/personal/ (same as backend spec-index-builder)
if (scope === 'global') {
targetDir = path.join(os.homedir(), '.ccw', 'personal')
} else {
targetDir = path.join('.ccw', 'personal')
}
// Create category-based filename
const typePrefix = isConstraint ? 'constraints' : isLearning ? 'learnings' : 'conventions'
targetFile = path.join(targetDir, `${typePrefix}.md`)
}
```
### Step 4: Write Spec
```javascript
const fs = require('fs')
// Ensure directory exists
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
// Check if file exists
const fileExists = fs.existsSync(targetFile)
if (!fileExists) {
// Create new file with frontmatter
const frontmatter = `---
title: ${dimension === 'specs' ? 'Project' : 'Personal'} ${isConstraint ? 'Constraints' : isLearning ? 'Learnings' : 'Conventions'}
readMode: optional
priority: medium
category: ${category}
scope: ${dimension === 'personal' ? scope : 'project'}
dimension: ${dimension}
keywords: [${category}, ${isConstraint ? 'constraint' : isLearning ? 'learning' : 'convention'}]
---
# ${dimension === 'specs' ? 'Project' : 'Personal'} ${isConstraint ? 'Constraints' : isLearning ? 'Learnings' : 'Conventions'}
`
fs.writeFileSync(targetFile, frontmatter, 'utf8')
}
// Read existing content
let content = fs.readFileSync(targetFile, 'utf8')
// Format the new rule
const timestamp = new Date().toISOString().split('T')[0]
const rulePrefix = isLearning ? `- [learning] ` : `- [${category}] `
const ruleSuffix = isLearning ? ` (${timestamp})` : ''
const newRule = `${rulePrefix}${ruleText}${ruleSuffix}`
// Check for duplicate
if (content.includes(ruleText)) {
console.log(`
Rule already exists in ${targetFile}
Text: "${ruleText}"
`)
return
}
// Append the rule
content = content.trimEnd() + '\n' + newRule + '\n'
fs.writeFileSync(targetFile, content, 'utf8')
// Rebuild spec index
Bash('ccw spec rebuild')
```
### Step 5: Display Confirmation
```
Spec created successfully
Dimension: ${dimension}
Scope: ${dimension === 'personal' ? scope : 'project'}
Category: ${category}
Type: ${type}
Rule: "${ruleText}"
Location: ${targetFile}
Use 'ccw spec list' to view all specs
Use 'ccw spec load --category ${category}' to load specs by category
```
## Target File Resolution
### Project Specs (dimension: specs)
```
.ccw/specs/
├── coding-conventions.md ← conventions, learnings
├── architecture-constraints.md ← constraints
└── quality-rules.md ← quality rules
```
### Personal Specs (dimension: personal)
```
# Global (~/.ccw/personal/)
~/.ccw/personal/
├── conventions.md ← personal conventions (all projects)
├── constraints.md ← personal constraints (all projects)
└── learnings.md ← personal learnings (all projects)
# Project-local (.ccw/personal/)
.ccw/personal/
├── conventions.md ← personal conventions (this project only)
├── constraints.md ← personal constraints (this project only)
└── learnings.md ← personal learnings (this project only)
```
## Category Field Usage
The `category` field in frontmatter enables filtered loading:
| Category | Use Case | Example Rules |
|----------|----------|---------------|
| `general` | Applies to all stages | "Use TypeScript strict mode" |
| `exploration` | Code exploration, debugging | "Always trace the call stack before modifying" |
| `planning` | Task planning, requirements | "Break down tasks into 2-hour chunks" |
| `execution` | Implementation, testing | "Run tests after each file modification" |
## Error Handling
- **File not writable**: Check permissions, suggest manual creation
- **Duplicate rule**: Warn and skip (don't add duplicates)
- **Invalid path**: Exit with error message
## Related Commands
- `/workflow:init` - Initialize project with specs scaffold
- `/workflow:init-guidelines` - Interactive wizard to fill specs
- `/workflow:session:solidify` - Add rules during/after sessions
- `ccw spec list` - View all specs
- `ccw spec load --category <cat>` - Load filtered specs

View File

@@ -1,291 +0,0 @@
---
name: init
description: Initialize project-level state with intelligent project analysis using cli-explore-agent
argument-hint: "[--regenerate] [--skip-specs]"
examples:
- /workflow:init
- /workflow:init --regenerate
- /workflow:init --skip-specs
---
# Workflow Init Command (/workflow:init)
## Overview
Initialize `.workflow/project-tech.json` and `.ccw/specs/*.md` with comprehensive project understanding by delegating analysis to **cli-explore-agent**.
**Dual File System**:
- `project-tech.json`: Auto-generated technical analysis (stack, architecture, components)
- `specs/*.md`: User-maintained rules and constraints (created as scaffold)
**Note**: This command may be called by other workflow commands. Upon completion, return immediately to continue the calling workflow without interrupting the task flow.
## Usage
```bash
/workflow:init # Initialize (skip if exists)
/workflow:init --regenerate # Force regeneration
/workflow:init --skip-specs # Initialize project-tech only, skip spec initialization
```
## Execution Process
```
Input Parsing:
├─ Parse --regenerate flag → regenerate = true | false
└─ Parse --skip-specs flag → skipSpecs = true | false
Decision:
├─ BOTH_EXIST + no --regenerate → Exit: "Already initialized"
├─ EXISTS + --regenerate → Backup existing → Continue analysis
└─ NOT_FOUND → Continue analysis
Analysis Flow:
├─ Get project metadata (name, root)
├─ Invoke cli-explore-agent
│ ├─ Structural scan (get_modules_by_depth.sh, find, wc)
│ ├─ Semantic analysis (Gemini CLI)
│ ├─ Synthesis and merge
│ └─ Write .workflow/project-tech.json
├─ Spec Initialization (if not --skip-specs)
│ ├─ Check if specs/*.md exist
│ ├─ If NOT_FOUND → Run ccw spec init
│ ├─ Run ccw spec rebuild
│ └─ Ask about guidelines configuration
│ ├─ If guidelines empty → Ask user: "Configure now?" or "Skip"
│ │ ├─ Configure now → Skill(skill="workflow:init-guidelines")
│ │ └─ Skip → Show next steps
│ └─ If guidelines populated → Show next steps only
└─ Display summary
Output:
├─ .workflow/project-tech.json (+ .backup if regenerate)
└─ .ccw/specs/*.md (scaffold or configured, unless --skip-specs)
```
## Implementation
### Step 1: Parse Input and Check Existing State
**Parse flags**:
```javascript
const regenerate = $ARGUMENTS.includes('--regenerate')
const skipSpecs = $ARGUMENTS.includes('--skip-specs')
```
**Check existing state**:
```bash
bash(test -f .workflow/project-tech.json && echo "TECH_EXISTS" || echo "TECH_NOT_FOUND")
bash(test -f .ccw/specs/coding-conventions.md && echo "SPECS_EXISTS" || echo "SPECS_NOT_FOUND")
```
**If BOTH_EXIST and no --regenerate**: Exit early
```
Project already initialized:
- Tech analysis: .workflow/project-tech.json
- Guidelines: .ccw/specs/*.md
Use /workflow:init --regenerate to rebuild tech analysis
Use /workflow:session:solidify to add guidelines
Use /workflow:status --project to view state
```
### Step 2: Get Project Metadata
```bash
bash(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
bash(git rev-parse --show-toplevel 2>/dev/null || pwd)
bash(mkdir -p .workflow)
```
### Step 3: Invoke cli-explore-agent
**For --regenerate**: Backup and preserve existing data
```bash
bash(cp .workflow/project-tech.json .workflow/project-tech.json.backup)
```
**Delegate analysis to agent**:
```javascript
Task(
subagent_type="cli-explore-agent",
run_in_background=false,
description="Deep project analysis",
prompt=`
Analyze project for workflow initialization and generate .workflow/project-tech.json.
## MANDATORY FIRST STEPS
1. Execute: cat ~/.ccw/workflows/cli-templates/schemas/project-tech-schema.json (get schema reference)
2. Execute: ccw tool exec get_modules_by_depth '{}' (get project structure)
## Task
Generate complete project-tech.json following the schema structure:
- project_name: "${projectName}"
- initialized_at: ISO 8601 timestamp
- overview: {
description: "Brief project description",
technology_stack: {
languages: [{name, file_count, primary}],
frameworks: ["string"],
build_tools: ["string"],
test_frameworks: ["string"]
},
architecture: {style, layers: [], patterns: []},
key_components: [{name, path, description, importance}]
}
- features: []
- development_index: ${regenerate ? 'preserve from backup' : '{feature: [], enhancement: [], bugfix: [], refactor: [], docs: []}'}
- statistics: ${regenerate ? 'preserve from backup' : '{total_features: 0, total_sessions: 0, last_updated: ISO timestamp}'}
- _metadata: {initialized_by: "cli-explore-agent", analysis_timestamp: ISO timestamp, analysis_mode: "deep-scan"}
## Analysis Requirements
**Technology Stack**:
- Languages: File counts, mark primary
- Frameworks: From package.json, requirements.txt, go.mod, etc.
- Build tools: npm, cargo, maven, webpack, vite
- Test frameworks: jest, pytest, go test, junit
**Architecture**:
- Style: MVC, microservices, layered (from structure & imports)
- Layers: presentation, business-logic, data-access
- Patterns: singleton, factory, repository
- Key components: 5-10 modules {name, path, description, importance}
## Execution
1. Structural scan: get_modules_by_depth.sh, find, wc -l
2. Semantic analysis: Gemini for patterns/architecture
3. Synthesis: Merge findings
4. ${regenerate ? 'Merge with preserved development_index and statistics from .workflow/project-tech.json.backup' : ''}
5. Write JSON: Write('.workflow/project-tech.json', jsonContent)
6. Report: Return brief completion summary
Project root: ${projectRoot}
`
)
```
### Step 3.5: Initialize Spec System (if not --skip-specs)
```javascript
// Skip spec initialization if --skip-specs flag is provided
if (!skipSpecs) {
// Initialize spec system if not already initialized
const specsCheck = Bash('test -f .ccw/specs/coding-conventions.md && echo EXISTS || echo NOT_FOUND')
if (specsCheck.includes('NOT_FOUND')) {
console.log('Initializing spec system...')
Bash('ccw spec init')
Bash('ccw spec rebuild')
}
} else {
console.log('Skipping spec initialization (--skip-specs)')
}
```
### Step 4: Display Summary
```javascript
const projectTech = JSON.parse(Read('.workflow/project-tech.json'));
const specsInitialized = !skipSpecs && file_exists('.ccw/specs/coding-conventions.md');
console.log(`
Project initialized successfully
## Project Overview
Name: ${projectTech.project_name}
Description: ${projectTech.overview.description}
### Technology Stack
Languages: ${projectTech.overview.technology_stack.languages.map(l => l.name).join(', ')}
Frameworks: ${projectTech.overview.technology_stack.frameworks.join(', ')}
### Architecture
Style: ${projectTech.overview.architecture.style}
Components: ${projectTech.overview.key_components.length} core modules
---
Files created:
- Tech analysis: .workflow/project-tech.json
${!skipSpecs ? `- Specs: .ccw/specs/ ${specsInitialized ? '(initialized)' : ''}` : '- Specs: (skipped via --skip-specs)'}
${regenerate ? '- Backup: .workflow/project-tech.json.backup' : ''}
`);
```
### Step 5: Ask About Guidelines Configuration (if not --skip-specs)
After displaying the summary, ask the user if they want to configure project guidelines interactively. Skip this step if `--skip-specs` was provided.
```javascript
// Skip guidelines configuration if --skip-specs was provided
if (skipSpecs) {
console.log(`
Next steps:
- Use /workflow:init-specs to create individual specs
- Use /workflow:init-guidelines to configure specs interactively
- Use /workflow-plan to start planning
`);
return;
}
// Check if specs have user content beyond seed documents
const specsList = Bash('ccw spec list --json');
const specsCount = JSON.parse(specsList).total || 0;
// Only ask if specs are just seeds
if (specsCount <= 5) {
const userChoice = AskUserQuestion({
questions: [{
question: "Would you like to configure project specs now? The wizard will ask targeted questions based on your tech stack.",
header: "Specs",
multiSelect: false,
options: [
{
label: "Configure now (Recommended)",
description: "Interactive wizard to set up coding conventions, constraints, and quality rules"
},
{
label: "Skip for now",
description: "You can run /workflow:init-guidelines later or use ccw spec load to import specs"
}
]
}]
});
if (userChoice.answers["Specs"] === "Configure now (Recommended)") {
console.log("\nStarting specs configuration wizard...\n");
Skill(skill="workflow:init-guidelines");
} else {
console.log(`
Next steps:
- Use /workflow:init-specs to create individual specs
- Use /workflow:init-guidelines to configure specs interactively
- Use ccw spec load to import specs from external sources
- Use /workflow-plan to start planning
`);
}
} else {
console.log(`
Specs already configured (${specsCount} spec files).
Next steps:
- Use /workflow:init-specs to create additional specs
- Use /workflow:init-guidelines --reset to reconfigure
- Use /workflow:session:solidify to add individual rules
- Use /workflow-plan to start planning
`);
}
```
## Error Handling
**Agent Failure**: Fall back to basic initialization with placeholder overview
**Missing Tools**: Agent uses Qwen fallback or bash-only
**Empty Project**: Create minimal JSON with all gaps identified
## Related Commands
- `/workflow:init-specs` - Interactive wizard to create individual specs with scope selection
- `/workflow:init-guidelines` - Interactive wizard to configure project guidelines (called after init)
- `/workflow:session:solidify` - Add individual rules/constraints one at a time
- `workflow-plan` skill - Start planning with initialized project context
- `/workflow:status --project` - View project state and guidelines

View File

@@ -806,6 +806,10 @@ AskUserQuestion({
})
```
4. **Sync Session State** (automatic)
- Execute: `/workflow:session:sync -y "Integration test cycle complete: ${passRate}% pass rate, ${iterations} iterations"`
- Updates specs/*.md with test learnings and project-tech.json with development index entry
---
## Completion Conditions

View File

@@ -1,440 +0,0 @@
---
name: solidify
description: Crystallize session learnings and user-defined constraints into permanent project guidelines, or compress recent memories
argument-hint: "[-y|--yes] [--type <convention|constraint|learning|compress>] [--category <category>] [--limit <N>] \"rule or insight\""
examples:
- /workflow:session:solidify "Use functional components for all React code" --type convention
- /workflow:session:solidify -y "No direct DB access from controllers" --type constraint --category architecture
- /workflow:session:solidify "Cache invalidation requires event sourcing" --type learning --category architecture
- /workflow:session:solidify --interactive
- /workflow:session:solidify --type compress --limit 10
---
## Auto Mode
When `--yes` or `-y`: Auto-categorize and add guideline without confirmation.
# Session Solidify Command (/workflow:session:solidify)
## Overview
Crystallizes ephemeral session context (insights, decisions, constraints) into permanent project guidelines stored in `.ccw/specs/*.md`. This ensures valuable learnings persist across sessions and inform future planning.
## Use Cases
1. **During Session**: Capture important decisions as they're made
2. **After Session**: Reflect on lessons learned before archiving
3. **Proactive**: Add team conventions or architectural rules
## Parameters
| Parameter | Type | Required | Description |
|-----------|------|----------|-------------|
| `rule` | string | Yes (unless --interactive or --type compress) | The rule, convention, or insight to solidify |
| `--type` | enum | No | Type: `convention`, `constraint`, `learning`, `compress` (default: auto-detect) |
| `--category` | string | No | Category for organization (see categories below) |
| `--interactive` | flag | No | Launch guided wizard for adding rules |
| `--limit` | number | No | Number of recent memories to compress (default: 20, only for --type compress) |
### Type Categories
**convention** → Coding style preferences (goes to `conventions` section)
- Subcategories: `coding_style`, `naming_patterns`, `file_structure`, `documentation`
**constraint** → Hard rules that must not be violated (goes to `constraints` section)
- Subcategories: `architecture`, `tech_stack`, `performance`, `security`
**learning** -> Session-specific insights (goes to `learnings` array)
- Subcategories: `architecture`, `performance`, `security`, `testing`, `process`, `other`
**compress** -> Compress/deduplicate recent memories into a single consolidated CMEM
- No subcategories (operates on core memories, not project guidelines)
- Fetches recent non-archived memories, LLM-compresses them, creates a new CMEM
- Source memories are archived after successful compression
## Execution Process
```
Input Parsing:
|- Parse: rule text (required unless --interactive or --type compress)
|- Parse: --type (convention|constraint|learning|compress)
|- Parse: --category (subcategory)
|- Parse: --interactive (flag)
+- Parse: --limit (number, default 20, compress only)
IF --type compress:
Step C1: Fetch Recent Memories
+- Call getRecentMemories(limit, excludeArchived=true)
Step C2: Validate Candidates
+- If fewer than 2 memories found -> abort with message
Step C3: LLM Compress
+- Build compression prompt with all memory contents
+- Send to LLM for consolidation
+- Receive compressed text
Step C4: Merge Tags
+- Collect tags from all source memories
+- Deduplicate into a single merged tag array
Step C5: Create Compressed CMEM
+- Generate new CMEM via upsertMemory with:
- content: compressed text from LLM
- summary: auto-generated
- tags: merged deduplicated tags
- metadata: buildCompressionMetadata(sourceIds, originalSize, compressedSize)
Step C6: Archive Source Memories
+- Call archiveMemories(sourceIds)
Step C7: Display Compression Report
+- Show source count, compression ratio, new CMEM ID
ELSE (convention/constraint/learning):
Step 1: Ensure Guidelines File Exists
+- If not exists -> Create with empty structure
Step 2: Auto-detect Type (if not specified)
+- Analyze rule text for keywords
Step 3: Validate and Format Entry
+- Build entry object based on type
Step 4: Update Guidelines File
+- Add entry to appropriate section
Step 5: Display Confirmation
+- Show what was added and where
```
## Implementation
### Step 1: Ensure Guidelines File Exists
**Uses .ccw/specs/ directory (same as frontend/backend spec-index-builder)**
```bash
bash(test -f .ccw/specs/coding-conventions.md && echo "EXISTS" || echo "NOT_FOUND")
```
**If NOT_FOUND**, initialize spec system:
```bash
Bash('ccw spec init')
Bash('ccw spec rebuild')
```
### Step 2: Auto-detect Type (if not specified)
```javascript
function detectType(ruleText) {
const text = ruleText.toLowerCase();
// Constraint indicators
if (/\b(no|never|must not|forbidden|prohibited|always must)\b/.test(text)) {
return 'constraint';
}
// Learning indicators
if (/\b(learned|discovered|realized|found that|turns out)\b/.test(text)) {
return 'learning';
}
// Default to convention
return 'convention';
}
function detectCategory(ruleText, type) {
const text = ruleText.toLowerCase();
if (type === 'constraint' || type === 'learning') {
if (/\b(architecture|layer|module|dependency|circular)\b/.test(text)) return 'architecture';
if (/\b(security|auth|permission|sanitize|xss|sql)\b/.test(text)) return 'security';
if (/\b(performance|cache|lazy|async|sync|slow)\b/.test(text)) return 'performance';
if (/\b(test|coverage|mock|stub)\b/.test(text)) return 'testing';
}
if (type === 'convention') {
if (/\b(name|naming|prefix|suffix|camel|pascal)\b/.test(text)) return 'naming_patterns';
if (/\b(file|folder|directory|structure|organize)\b/.test(text)) return 'file_structure';
if (/\b(doc|comment|jsdoc|readme)\b/.test(text)) return 'documentation';
return 'coding_style';
}
return type === 'constraint' ? 'tech_stack' : 'other';
}
```
### Step 3: Build Entry
```javascript
function buildEntry(rule, type, category, sessionId) {
if (type === 'learning') {
return {
date: new Date().toISOString().split('T')[0],
session_id: sessionId || null,
insight: rule,
category: category,
context: null
};
}
// For conventions and constraints, just return the rule string
return rule;
}
```
### Step 4: Update Spec Files
```javascript
// Map type+category to target spec file
// Uses .ccw/specs/ directory (same as frontend/backend spec-index-builder)
const specFileMap = {
convention: '.ccw/specs/coding-conventions.md',
constraint: '.ccw/specs/architecture-constraints.md'
}
if (type === 'convention' || type === 'constraint') {
const targetFile = specFileMap[type]
const existing = Read(targetFile)
// Deduplicate: skip if rule text already exists in the file
if (!existing.includes(rule)) {
const ruleText = `- [${category}] ${rule}`
const newContent = existing.trimEnd() + '\n' + ruleText + '\n'
Write(targetFile, newContent)
}
} else if (type === 'learning') {
// Learnings go to coding-conventions.md as a special section
// Uses .ccw/specs/ directory (same as frontend/backend spec-index-builder)
const targetFile = '.ccw/specs/coding-conventions.md'
const existing = Read(targetFile)
const entry = buildEntry(rule, type, category, sessionId)
const learningText = `- [learning/${category}] ${entry.insight} (${entry.date})`
if (!existing.includes(entry.insight)) {
const newContent = existing.trimEnd() + '\n' + learningText + '\n'
Write(targetFile, newContent)
}
}
// Rebuild spec index after modification
Bash('ccw spec rebuild')
```
### Step 5: Display Confirmation
```
Guideline solidified
Type: ${type}
Category: ${category}
Rule: "${rule}"
Location: .ccw/specs/*.md -> ${type}s.${category}
Total ${type}s in ${category}: ${count}
```
## Compress Mode (--type compress)
When `--type compress` is specified, the command operates on core memories instead of project guidelines. It fetches recent memories, sends them to an LLM for consolidation, and creates a new compressed CMEM.
### Step C1: Fetch Recent Memories
```javascript
// Uses CoreMemoryStore.getRecentMemories()
const limit = parsedArgs.limit || 20;
const recentMemories = store.getRecentMemories(limit, /* excludeArchived */ true);
if (recentMemories.length < 2) {
console.log("Not enough non-archived memories to compress (need at least 2).");
return;
}
```
### Step C2: Build Compression Prompt
Concatenate all memory contents and send to LLM with the following prompt:
```
Given these ${N} memories, produce a single consolidated memory that:
1. Preserves all key information and insights
2. Removes redundancy and duplicate concepts
3. Organizes content by theme/topic
4. Maintains specific technical details and decisions
Source memories:
---
[Memory CMEM-XXXXXXXX-XXXXXX]:
${memory.content}
---
[Memory CMEM-XXXXXXXX-XXXXXX]:
${memory.content}
---
...
Output: A single comprehensive memory text.
```
### Step C3: Merge Tags from Source Memories
```javascript
// Collect all tags from source memories and deduplicate
const allTags = new Set();
for (const memory of recentMemories) {
if (memory.tags) {
for (const tag of memory.tags) {
allTags.add(tag);
}
}
}
const mergedTags = Array.from(allTags);
```
### Step C4: Create Compressed CMEM
```javascript
const sourceIds = recentMemories.map(m => m.id);
const originalSize = recentMemories.reduce((sum, m) => sum + m.content.length, 0);
const compressedSize = compressedText.length;
const metadata = store.buildCompressionMetadata(sourceIds, originalSize, compressedSize);
const newMemory = store.upsertMemory({
content: compressedText,
summary: `Compressed from ${sourceIds.length} memories`,
tags: mergedTags,
metadata: metadata
});
```
### Step C5: Archive Source Memories
```javascript
// Archive all source memories after successful compression
store.archiveMemories(sourceIds);
```
### Step C6: Display Compression Report
```
Memory compression complete
New CMEM: ${newMemory.id}
Sources compressed: ${sourceIds.length}
Original size: ${originalSize} chars
Compressed size: ${compressedSize} chars
Compression ratio: ${(compressedSize / originalSize * 100).toFixed(1)}%
Tags merged: ${mergedTags.join(', ') || '(none)'}
Source memories archived: ${sourceIds.join(', ')}
```
### Compressed CMEM Metadata Format
The compressed CMEM's `metadata` field contains a JSON string with:
```json
{
"compressed_from": ["CMEM-20260101-120000", "CMEM-20260102-140000", "..."],
"compression_ratio": 0.45,
"compressed_at": "2026-02-23T10:30:00.000Z"
}
```
- `compressed_from`: Array of source memory IDs that were consolidated
- `compression_ratio`: Ratio of compressed size to original size (lower = more compression)
- `compressed_at`: ISO timestamp of when the compression occurred
## Interactive Mode
When `--interactive` flag is provided:
```javascript
AskUserQuestion({
questions: [
{
question: "What type of guideline are you adding?",
header: "Type",
multiSelect: false,
options: [
{ label: "Convention", description: "Coding style preference (e.g., use functional components)" },
{ label: "Constraint", description: "Hard rule that must not be violated (e.g., no direct DB access)" },
{ label: "Learning", description: "Insight from this session (e.g., cache invalidation needs events)" }
]
}
]
});
// Follow-up based on type selection...
```
## Examples
### Add a Convention
```bash
/workflow:session:solidify "Use async/await instead of callbacks" --type convention --category coding_style
```
Result in `.ccw/specs/coding-conventions.md`:
```markdown
- [coding_style] Use async/await instead of callbacks
```
### Add an Architectural Constraint
```bash
/workflow:session:solidify "No direct DB access from controllers" --type constraint --category architecture
```
Result in `.ccw/specs/architecture-constraints.md`:
```markdown
- [architecture] No direct DB access from controllers
```
### Capture a Session Learning
```bash
/workflow:session:solidify "Cache invalidation requires event sourcing for consistency" --type learning
```
Result in `.ccw/specs/coding-conventions.md`:
```markdown
- [learning/architecture] Cache invalidation requires event sourcing for consistency (2024-12-28)
```
### Compress Recent Memories
```bash
/workflow:session:solidify --type compress --limit 10
```
Result: Creates a new CMEM with consolidated content from the 10 most recent non-archived memories. Source memories are archived. The new CMEM's metadata tracks which memories were compressed:
```json
{
"compressed_from": ["CMEM-20260220-100000", "CMEM-20260221-143000", "..."],
"compression_ratio": 0.42,
"compressed_at": "2026-02-23T10:30:00.000Z"
}
```
## Integration with Planning
The `specs/*.md` is consumed by:
1. **`workflow-plan` skill (context-gather phase)**: Loads guidelines into context-package.json
2. **`workflow-plan` skill**: Passes guidelines to task generation agent
3. **`task-generate-agent`**: Includes guidelines as "CRITICAL CONSTRAINTS" in system prompt
This ensures all future planning respects solidified rules without users needing to re-state them.
## Error Handling
- **Duplicate Rule**: Warn and skip if exact rule already exists
- **Invalid Category**: Suggest valid categories for the type
- **File Corruption**: Backup existing file before modification
## Related Commands
- `/workflow:session:start` - Start a session (may prompt for solidify at end)
- `/workflow:session:complete` - Complete session (prompts for learnings to solidify)
- `/workflow:init` - Creates specs/*.md scaffold if missing
- `/workflow:init-specs` - Interactive wizard to create individual specs with scope selection

View File

@@ -38,7 +38,7 @@ ERROR: Invalid session type. Valid types: workflow, review, tdd, test, docs
## Step 0: Initialize Project State (First-time Only)
**Executed before all modes** - Ensures project-level state files exist by calling `/workflow:init`.
**Executed before all modes** - Ensures project-level state files exist by calling `/workflow:spec:setup`.
### Check and Initialize
```bash
@@ -47,10 +47,10 @@ bash(test -f .workflow/project-tech.json && echo "TECH_EXISTS" || echo "TECH_NOT
bash(test -f .ccw/specs/*.md && echo "GUIDELINES_EXISTS" || echo "GUIDELINES_NOT_FOUND")
```
**If either NOT_FOUND**, delegate to `/workflow:init`:
**If either NOT_FOUND**, delegate to `/workflow:spec:setup`:
```javascript
// Call workflow:init for intelligent project analysis
Skill(skill="workflow:init");
// Call workflow:spec:setup for intelligent project analysis
Skill(skill="workflow:spec:setup");
// Wait for init completion
// project-tech.json and specs/*.md will be created
@@ -58,11 +58,11 @@ Skill(skill="workflow:init");
**Output**:
- If BOTH_EXIST: `PROJECT_STATE: initialized`
- If NOT_FOUND: Calls `/workflow:init` → creates:
- If NOT_FOUND: Calls `/workflow:spec:setup` → creates:
- `.workflow/project-tech.json` with full technical analysis
- `.ccw/specs/*.md` with empty scaffold
**Note**: `/workflow:init` uses cli-explore-agent to build comprehensive project understanding (technology stack, architecture, key components). This step runs once per project. Subsequent executions skip initialization.
**Note**: `/workflow:spec:setup` uses cli-explore-agent to build comprehensive project understanding (technology stack, architecture, key components). This step runs once per project. Subsequent executions skip initialization.
## Mode 1: Discovery Mode (Default)

View File

@@ -190,13 +190,12 @@ Write(techPath, JSON.stringify(tech, null, 2))
| Error | Resolution |
|-------|------------|
| File missing | Create scaffold (same as solidify Step 1) |
| File missing | Create scaffold (same as spec:setup Step 4) |
| No git history | Use user summary or session context only |
| No meaningful updates | Skip guidelines, still add tech entry |
| Duplicate entry | Skip silently (dedup check in Step 4) |
## Related Commands
- `/workflow:init` - Initialize project with specs scaffold
- `/workflow:init-specs` - Interactive wizard to create individual specs with scope selection
- `/workflow:session:solidify` - Add individual rules one at a time
- `/workflow:spec:setup` - Initialize project with specs scaffold
- `/workflow:spec:add` - Interactive wizard to create individual specs with scope selection

View File

@@ -0,0 +1,644 @@
---
name: add
description: Add specs, conventions, constraints, or learnings to project guidelines interactively or automatically
argument-hint: "[-y|--yes] [--type <convention|constraint|learning>] [--category <category>] [--dimension <specs|personal>] [--scope <global|project>] [--interactive] \"rule text\""
examples:
- /workflow:spec:add "Use functional components for all React code"
- /workflow:spec:add -y "No direct DB access from controllers" --type constraint
- /workflow:spec:add --scope global --dimension personal
- /workflow:spec:add --interactive
- /workflow:spec:add "Cache invalidation requires event sourcing" --type learning --category architecture
---
## Auto Mode
When `--yes` or `-y`: Auto-categorize and add guideline without confirmation.
# Spec Add Command (/workflow:spec:add)
## Overview
Unified command for adding specs one at a time. Supports both interactive wizard mode and direct CLI mode.
**Key Features**:
- Supports both project specs and personal specs
- Scope selection (global vs project) for personal specs
- Category-based organization for workflow stages
- Interactive wizard mode with smart defaults
- Direct CLI mode with auto-detection of type and category
- Auto-confirm mode (`-y`/`--yes`) for scripted usage
## Use Cases
1. **During Session**: Capture important decisions as they're made
2. **After Session**: Reflect on lessons learned before archiving
3. **Proactive**: Add team conventions or architectural rules
4. **Interactive**: Guided wizard for adding rules with full control over dimension, scope, and category
## Usage
```bash
/workflow:spec:add # Interactive wizard (all prompts)
/workflow:spec:add --interactive # Explicit interactive wizard
/workflow:spec:add "Use async/await instead of callbacks" # Direct mode (auto-detect type)
/workflow:spec:add -y "No direct DB access" --type constraint # Auto-confirm, skip confirmation
/workflow:spec:add --scope global --dimension personal # Create global personal spec (interactive)
/workflow:spec:add --dimension specs --category exploration # Project spec in exploration category (interactive)
```
## Parameters
| Parameter | Type | Required | Default | Description |
|-----------|------|----------|---------|-------------|
| `rule` | string | Yes (unless `--interactive`) | - | The rule, convention, or insight to add |
| `--type` | enum | No | auto-detect | Type: `convention`, `constraint`, `learning` |
| `--category` | string | No | auto-detect / `general` | Category for organization (see categories below) |
| `--dimension` | enum | No | Interactive | `specs` (project) or `personal` |
| `--scope` | enum | No | `project` | `global` or `project` (only for personal dimension) |
| `--interactive` | flag | No | - | Launch full guided wizard for adding rules |
| `-y` / `--yes` | flag | No | - | Auto-categorize and add without confirmation |
### Type Categories
**convention** - Coding style preferences (goes to `conventions` section)
- Subcategories: `coding_style`, `naming_patterns`, `file_structure`, `documentation`
**constraint** - Hard rules that must not be violated (goes to `constraints` section)
- Subcategories: `architecture`, `tech_stack`, `performance`, `security`
**learning** - Session-specific insights (goes to `learnings` array)
- Subcategories: `architecture`, `performance`, `security`, `testing`, `process`, `other`
### Workflow Stage Categories (for `--category`)
| Category | Use Case | Example Rules |
|----------|----------|---------------|
| `general` | Applies to all stages | "Use TypeScript strict mode" |
| `exploration` | Code exploration, debugging | "Always trace the call stack before modifying" |
| `planning` | Task planning, requirements | "Break down tasks into 2-hour chunks" |
| `execution` | Implementation, testing | "Run tests after each file modification" |
## Execution Process
```
Input Parsing:
|- Parse: rule text (positional argument, optional if --interactive)
|- Parse: --type (convention|constraint|learning)
|- Parse: --category (subcategory)
|- Parse: --dimension (specs|personal)
|- Parse: --scope (global|project)
|- Parse: --interactive (flag)
+- Parse: -y / --yes (flag)
Step 1: Parse Input
Step 2: Determine Mode
|- If --interactive OR no rule text → Full Interactive Wizard (Path A)
+- If rule text provided → Direct Mode (Path B)
Path A: Interactive Wizard
|- Step A1: Ask dimension (if not specified)
|- Step A2: Ask scope (if personal + scope not specified)
|- Step A3: Ask category (if not specified)
|- Step A4: Ask type (convention|constraint|learning)
|- Step A5: Ask content (rule text)
+- Continue to Step 3
Path B: Direct Mode
|- Step B1: Auto-detect type (if not specified) using detectType()
|- Step B2: Auto-detect category (if not specified) using detectCategory()
|- Step B3: Default dimension to 'specs' if not specified
+- Continue to Step 3
Step 3: Determine Target File
|- specs dimension → .ccw/specs/coding-conventions.md or architecture-constraints.md
+- personal dimension → ~/.ccw/personal/ or .ccw/personal/
Step 4: Validate and Write Spec
|- Ensure target directory and file exist
|- Check for duplicates
|- Append rule to appropriate section
+- Run ccw spec rebuild
Step 5: Display Confirmation
+- If -y/--yes: Minimal output
+- Otherwise: Full confirmation with location details
```
## Implementation
### Step 1: Parse Input
```javascript
// Parse arguments
const args = $ARGUMENTS
const argsLower = args.toLowerCase()
// Extract flags
const autoConfirm = argsLower.includes('--yes') || argsLower.includes('-y')
const isInteractive = argsLower.includes('--interactive')
// Extract named parameters
const hasType = argsLower.includes('--type')
const hasCategory = argsLower.includes('--category')
const hasDimension = argsLower.includes('--dimension')
const hasScope = argsLower.includes('--scope')
let type = hasType ? args.match(/--type\s+(\w+)/i)?.[1]?.toLowerCase() : null
let category = hasCategory ? args.match(/--category\s+(\w+)/i)?.[1]?.toLowerCase() : null
let dimension = hasDimension ? args.match(/--dimension\s+(\w+)/i)?.[1]?.toLowerCase() : null
let scope = hasScope ? args.match(/--scope\s+(\w+)/i)?.[1]?.toLowerCase() : null
// Extract rule text (everything before flags, or quoted string)
let ruleText = args
.replace(/--type\s+\w+/gi, '')
.replace(/--category\s+\w+/gi, '')
.replace(/--dimension\s+\w+/gi, '')
.replace(/--scope\s+\w+/gi, '')
.replace(/--interactive/gi, '')
.replace(/--yes/gi, '')
.replace(/-y\b/gi, '')
.replace(/^["']|["']$/g, '')
.trim()
// Validate values
if (scope && !['global', 'project'].includes(scope)) {
console.log("Invalid scope. Use 'global' or 'project'.")
return
}
if (dimension && !['specs', 'personal'].includes(dimension)) {
console.log("Invalid dimension. Use 'specs' or 'personal'.")
return
}
if (type && !['convention', 'constraint', 'learning'].includes(type)) {
console.log("Invalid type. Use 'convention', 'constraint', or 'learning'.")
return
}
if (category) {
const validCategories = [
'general', 'exploration', 'planning', 'execution',
'coding_style', 'naming_patterns', 'file_structure', 'documentation',
'architecture', 'tech_stack', 'performance', 'security',
'testing', 'process', 'other'
]
if (!validCategories.includes(category)) {
console.log(`Invalid category. Valid categories: ${validCategories.join(', ')}`)
return
}
}
```
### Step 2: Determine Mode
```javascript
const useInteractiveWizard = isInteractive || !ruleText
```
### Path A: Interactive Wizard
**If dimension not specified**:
```javascript
if (!dimension) {
const dimensionAnswer = AskUserQuestion({
questions: [{
question: "What type of spec do you want to create?",
header: "Dimension",
multiSelect: false,
options: [
{
label: "Project Spec",
description: "Coding conventions, constraints, quality rules for this project (stored in .ccw/specs/)"
},
{
label: "Personal Spec",
description: "Personal preferences and constraints that follow you across projects (stored in ~/.ccw/specs/personal/ or .ccw/specs/personal/)"
}
]
}]
})
dimension = dimensionAnswer.answers["Dimension"] === "Project Spec" ? "specs" : "personal"
}
```
**If personal dimension and scope not specified**:
```javascript
if (dimension === 'personal' && !scope) {
const scopeAnswer = AskUserQuestion({
questions: [{
question: "Where should this personal spec be stored?",
header: "Scope",
multiSelect: false,
options: [
{
label: "Global (Recommended)",
description: "Apply to ALL projects (~/.ccw/specs/personal/)"
},
{
label: "Project-only",
description: "Apply only to this project (.ccw/specs/personal/)"
}
]
}]
})
scope = scopeAnswer.answers["Scope"].includes("Global") ? "global" : "project"
}
```
**If category not specified**:
```javascript
if (!category) {
const categoryAnswer = AskUserQuestion({
questions: [{
question: "Which workflow stage does this spec apply to?",
header: "Category",
multiSelect: false,
options: [
{
label: "General (Recommended)",
description: "Applies to all stages (default)"
},
{
label: "Exploration",
description: "Code exploration, analysis, debugging"
},
{
label: "Planning",
description: "Task planning, requirements gathering"
},
{
label: "Execution",
description: "Implementation, testing, deployment"
}
]
}]
})
const categoryLabel = categoryAnswer.answers["Category"]
category = categoryLabel.includes("General") ? "general"
: categoryLabel.includes("Exploration") ? "exploration"
: categoryLabel.includes("Planning") ? "planning"
: "execution"
}
```
**Ask type (if not specified)**:
```javascript
if (!type) {
const typeAnswer = AskUserQuestion({
questions: [{
question: "What type of rule is this?",
header: "Type",
multiSelect: false,
options: [
{
label: "Convention",
description: "Coding style preference (e.g., use functional components)"
},
{
label: "Constraint",
description: "Hard rule that must not be violated (e.g., no direct DB access)"
},
{
label: "Learning",
description: "Insight or lesson learned (e.g., cache invalidation needs events)"
}
]
}]
})
const typeLabel = typeAnswer.answers["Type"]
type = typeLabel.includes("Convention") ? "convention"
: typeLabel.includes("Constraint") ? "constraint"
: "learning"
}
```
**Ask content (rule text)**:
```javascript
if (!ruleText) {
const contentAnswer = AskUserQuestion({
questions: [{
question: "Enter the rule or guideline text:",
header: "Content",
multiSelect: false,
options: [
{ label: "Custom rule", description: "Type your own rule using the 'Other' option below" },
{ label: "Skip", description: "Cancel adding a spec" }
]
}]
})
if (contentAnswer.answers["Content"] === "Skip") return
ruleText = contentAnswer.answers["Content"]
}
```
### Path B: Direct Mode
**Auto-detect type if not specified**:
```javascript
function detectType(ruleText) {
const text = ruleText.toLowerCase();
// Constraint indicators
if (/\b(no|never|must not|forbidden|prohibited|always must)\b/.test(text)) {
return 'constraint';
}
// Learning indicators
if (/\b(learned|discovered|realized|found that|turns out)\b/.test(text)) {
return 'learning';
}
// Default to convention
return 'convention';
}
function detectCategory(ruleText, type) {
const text = ruleText.toLowerCase();
if (type === 'constraint' || type === 'learning') {
if (/\b(architecture|layer|module|dependency|circular)\b/.test(text)) return 'architecture';
if (/\b(security|auth|permission|sanitize|xss|sql)\b/.test(text)) return 'security';
if (/\b(performance|cache|lazy|async|sync|slow)\b/.test(text)) return 'performance';
if (/\b(test|coverage|mock|stub)\b/.test(text)) return 'testing';
}
if (type === 'convention') {
if (/\b(name|naming|prefix|suffix|camel|pascal)\b/.test(text)) return 'naming_patterns';
if (/\b(file|folder|directory|structure|organize)\b/.test(text)) return 'file_structure';
if (/\b(doc|comment|jsdoc|readme)\b/.test(text)) return 'documentation';
return 'coding_style';
}
return type === 'constraint' ? 'tech_stack' : 'other';
}
if (!type) {
type = detectType(ruleText)
}
if (!category) {
category = detectCategory(ruleText, type)
}
if (!dimension) {
dimension = 'specs' // Default to project specs in direct mode
}
```
### Step 3: Ensure Guidelines File Exists
**Uses .ccw/specs/ directory (same as frontend/backend spec-index-builder)**
```bash
bash(test -f .ccw/specs/coding-conventions.md && echo "EXISTS" || echo "NOT_FOUND")
```
**If NOT_FOUND**, initialize spec system:
```bash
Bash('ccw spec init')
Bash('ccw spec rebuild')
```
### Step 4: Determine Target File
```javascript
const path = require('path')
const os = require('os')
const isConvention = type === 'convention'
const isConstraint = type === 'constraint'
const isLearning = type === 'learning'
let targetFile
let targetDir
if (dimension === 'specs') {
// Project specs - use .ccw/specs/ (same as frontend/backend spec-index-builder)
targetDir = '.ccw/specs'
if (isConstraint) {
targetFile = path.join(targetDir, 'architecture-constraints.md')
} else {
targetFile = path.join(targetDir, 'coding-conventions.md')
}
} else {
// Personal specs - use .ccw/personal/ (same as backend spec-index-builder)
if (scope === 'global') {
targetDir = path.join(os.homedir(), '.ccw', 'personal')
} else {
targetDir = path.join('.ccw', 'personal')
}
// Create type-based filename
const typePrefix = isConstraint ? 'constraints' : isLearning ? 'learnings' : 'conventions'
targetFile = path.join(targetDir, `${typePrefix}.md`)
}
```
### Step 5: Build Entry
```javascript
function buildEntry(rule, type, category, sessionId) {
if (type === 'learning') {
return {
date: new Date().toISOString().split('T')[0],
session_id: sessionId || null,
insight: rule,
category: category,
context: null
};
}
// For conventions and constraints, just return the rule string
return rule;
}
```
### Step 6: Write Spec
```javascript
const fs = require('fs')
// Ensure directory exists
if (!fs.existsSync(targetDir)) {
fs.mkdirSync(targetDir, { recursive: true })
}
// Check if file exists
const fileExists = fs.existsSync(targetFile)
if (!fileExists) {
// Create new file with frontmatter
const frontmatter = `---
title: ${dimension === 'specs' ? 'Project' : 'Personal'} ${isConstraint ? 'Constraints' : isLearning ? 'Learnings' : 'Conventions'}
readMode: optional
priority: medium
category: ${category}
scope: ${dimension === 'personal' ? scope : 'project'}
dimension: ${dimension}
keywords: [${category}, ${isConstraint ? 'constraint' : isLearning ? 'learning' : 'convention'}]
---
# ${dimension === 'specs' ? 'Project' : 'Personal'} ${isConstraint ? 'Constraints' : isLearning ? 'Learnings' : 'Conventions'}
`
fs.writeFileSync(targetFile, frontmatter, 'utf8')
}
// Read existing content
let content = fs.readFileSync(targetFile, 'utf8')
// Deduplicate: skip if rule text already exists in the file
if (content.includes(ruleText)) {
console.log(`
Rule already exists in ${targetFile}
Text: "${ruleText}"
`)
return
}
// Format the new rule based on type
let newRule
if (isLearning) {
const entry = buildEntry(ruleText, type, category)
newRule = `- [learning/${category}] ${entry.insight} (${entry.date})`
} else {
newRule = `- [${category}] ${ruleText}`
}
// Append the rule
content = content.trimEnd() + '\n' + newRule + '\n'
fs.writeFileSync(targetFile, content, 'utf8')
// Rebuild spec index
Bash('ccw spec rebuild')
```
### Step 7: Display Confirmation
**If `-y`/`--yes` (auto mode)**:
```
Spec added: [${type}/${category}] "${ruleText}" -> ${targetFile}
```
**Otherwise (full confirmation)**:
```
Spec created successfully
Dimension: ${dimension}
Scope: ${dimension === 'personal' ? scope : 'project'}
Category: ${category}
Type: ${type}
Rule: "${ruleText}"
Location: ${targetFile}
Use 'ccw spec list' to view all specs
Use 'ccw spec load --category ${category}' to load specs by category
```
## Target File Resolution
### Project Specs (dimension: specs)
```
.ccw/specs/
|- coding-conventions.md <- conventions, learnings
|- architecture-constraints.md <- constraints
+- quality-rules.md <- quality rules
```
### Personal Specs (dimension: personal)
```
# Global (~/.ccw/personal/)
~/.ccw/personal/
|- conventions.md <- personal conventions (all projects)
|- constraints.md <- personal constraints (all projects)
+- learnings.md <- personal learnings (all projects)
# Project-local (.ccw/personal/)
.ccw/personal/
|- conventions.md <- personal conventions (this project only)
|- constraints.md <- personal constraints (this project only)
+- learnings.md <- personal learnings (this project only)
```
## Examples
### Interactive Wizard
```bash
/workflow:spec:add --interactive
# Prompts for: dimension -> scope (if personal) -> category -> type -> content
```
### Add a Convention (Direct)
```bash
/workflow:spec:add "Use async/await instead of callbacks" --type convention --category coding_style
```
Result in `.ccw/specs/coding-conventions.md`:
```markdown
- [coding_style] Use async/await instead of callbacks
```
### Add an Architectural Constraint (Direct)
```bash
/workflow:spec:add "No direct DB access from controllers" --type constraint --category architecture
```
Result in `.ccw/specs/architecture-constraints.md`:
```markdown
- [architecture] No direct DB access from controllers
```
### Capture a Learning (Direct, Auto-detect)
```bash
/workflow:spec:add "Cache invalidation requires event sourcing for consistency" --type learning
```
Result in `.ccw/specs/coding-conventions.md`:
```markdown
- [learning/architecture] Cache invalidation requires event sourcing for consistency (2026-03-06)
```
### Auto-confirm Mode
```bash
/workflow:spec:add -y "No direct DB access from controllers" --type constraint
# Auto-detects category as 'architecture', writes without confirmation prompt
```
### Personal Spec (Global)
```bash
/workflow:spec:add --scope global --dimension personal --type convention "Prefer descriptive variable names"
```
Result in `~/.ccw/personal/conventions.md`:
```markdown
- [general] Prefer descriptive variable names
```
### Personal Spec (Project)
```bash
/workflow:spec:add --scope project --dimension personal --type constraint "No ORM in this project"
```
Result in `.ccw/personal/constraints.md`:
```markdown
- [general] No ORM in this project
```
## Error Handling
- **Duplicate Rule**: Warn and skip if exact rule text already exists in target file
- **Invalid Category**: Suggest valid categories for the type
- **Invalid Scope**: Exit with error - must be 'global' or 'project'
- **Invalid Dimension**: Exit with error - must be 'specs' or 'personal'
- **Invalid Type**: Exit with error - must be 'convention', 'constraint', or 'learning'
- **File not writable**: Check permissions, suggest manual creation
- **Invalid path**: Exit with error message
- **File Corruption**: Backup existing file before modification
## Related Commands
- `/workflow:spec:setup` - Initialize project with specs scaffold
- `/workflow:session:sync` - Quick-sync session work to specs and project-tech
- `/workflow:session:start` - Start a session
- `/workflow:session:complete` - Complete session (prompts for learnings)
- `ccw spec list` - View all specs
- `ccw spec load --category <cat>` - Load filtered specs
- `ccw spec rebuild` - Rebuild spec index

View File

@@ -1,74 +1,208 @@
---
name: init-guidelines
description: Interactive wizard to fill specs/*.md based on project analysis
argument-hint: "[--reset]"
name: setup
description: Initialize project-level state and configure specs via interactive questionnaire using cli-explore-agent
argument-hint: "[--regenerate] [--skip-specs] [--reset]"
examples:
- /workflow:init-guidelines
- /workflow:init-guidelines --reset
- /workflow:spec:setup
- /workflow:spec:setup --regenerate
- /workflow:spec:setup --skip-specs
- /workflow:spec:setup --reset
---
# Workflow Init Guidelines Command (/workflow:init-guidelines)
# Workflow Spec Setup Command (/workflow:spec:setup)
## Overview
Interactive multi-round wizard that analyzes the current project (via `project-tech.json`) and asks targeted questions to populate `.ccw/specs/*.md` with coding conventions, constraints, and quality rules.
Initialize `.workflow/project-tech.json` and `.ccw/specs/*.md` with comprehensive project understanding by delegating analysis to **cli-explore-agent**, then interactively configure project guidelines through a multi-round questionnaire.
**Dual File System**:
- `project-tech.json`: Auto-generated technical analysis (stack, architecture, components)
- `specs/*.md`: User-maintained rules and constraints (created and populated interactively)
**Design Principle**: Questions are dynamically generated based on the project's tech stack, architecture, and patterns — not generic boilerplate.
**Note**: This command may be called by `/workflow:init` after initialization. Upon completion, return to the calling workflow if applicable.
**Note**: This command may be called by other workflow commands. Upon completion, return immediately to continue the calling workflow without interrupting the task flow.
## Usage
```bash
/workflow:init-guidelines # Fill guidelines interactively (skip if already populated)
/workflow:init-guidelines --reset # Reset and re-fill guidelines from scratch
/workflow:spec:setup # Initialize (skip if exists)
/workflow:spec:setup --regenerate # Force regeneration of project-tech.json
/workflow:spec:setup --skip-specs # Initialize project-tech only, skip spec initialization and questionnaire
/workflow:spec:setup --reset # Reset specs content before questionnaire
```
## Execution Process
```
Input Parsing:
├─ Parse --regenerate flag → regenerate = true | false
├─ Parse --skip-specs flag → skipSpecs = true | false
└─ Parse --reset flag → reset = true | false
Step 1: Check Prerequisites
├─ project-tech.json must exist (run /workflow:init first)
├─ specs/*.md: check if populated or scaffold-only
If populated + no --reset → Ask: "Guidelines already exist. Overwrite or append?"
Decision:
├─ BOTH_EXIST + no --regenerate + no --reset → Exit: "Already initialized"
├─ EXISTS + --regenerate → Backup existing → Continue analysis
EXISTS + --reset → Reset specs, keep project-tech → Skip to questionnaire
└─ NOT_FOUND → Continue full flow
Step 2: Load Project Context
Read project-tech.json → extract tech stack, architecture, patterns
Full Flow:
Step 1: Parse input and check existing state
├─ Step 2: Get project metadata (name, root)
├─ Step 3: Invoke cli-explore-agent
│ ├─ Structural scan (get_modules_by_depth.sh, find, wc)
│ ├─ Semantic analysis (Gemini CLI)
│ ├─ Synthesis and merge
│ └─ Write .workflow/project-tech.json
├─ Step 4: Initialize Spec System (if not --skip-specs)
│ ├─ Check if specs/*.md exist
│ ├─ If NOT_FOUND → Run ccw spec init
│ └─ Run ccw spec rebuild
├─ Step 5: Multi-Round Interactive Questionnaire (if not --skip-specs)
│ ├─ Check if guidelines already populated → Ask: "Append / Reset / Cancel"
│ ├─ Load project context from project-tech.json
│ ├─ Round 1: Coding Conventions (coding_style, naming_patterns)
│ ├─ Round 2: File & Documentation Conventions (file_structure, documentation)
│ ├─ Round 3: Architecture & Tech Constraints (architecture, tech_stack)
│ ├─ Round 4: Performance & Security Constraints (performance, security)
│ └─ Round 5: Quality Rules (quality_rules)
├─ Step 6: Write specs/*.md (if not --skip-specs)
└─ Step 7: Display Summary
Step 3: Multi-Round Interactive Questionnaire
├─ Round 1: Coding Conventions (coding_style, naming_patterns)
Round 2: File & Documentation Conventions (file_structure, documentation)
├─ Round 3: Architecture & Tech Constraints (architecture, tech_stack)
├─ Round 4: Performance & Security Constraints (performance, security)
└─ Round 5: Quality Rules (quality_rules)
Step 4: Write specs/*.md
Step 5: Display Summary
Output:
├─ .workflow/project-tech.json (+ .backup if regenerate)
.ccw/specs/*.md (scaffold or configured, unless --skip-specs)
```
## Implementation
### Step 1: Check Prerequisites
### Step 1: Parse Input and Check Existing State
**Parse flags**:
```javascript
const regenerate = $ARGUMENTS.includes('--regenerate')
const skipSpecs = $ARGUMENTS.includes('--skip-specs')
const reset = $ARGUMENTS.includes('--reset')
```
**Check existing state**:
```bash
bash(test -f .workflow/project-tech.json && echo "TECH_EXISTS" || echo "TECH_NOT_FOUND")
bash(test -f .ccw/specs/coding-conventions.md && echo "SPECS_EXISTS" || echo "SPECS_NOT_FOUND")
```
**If TECH_NOT_FOUND**: Exit with message
**If BOTH_EXIST and no --regenerate and no --reset**: Exit early
```
Project tech analysis not found. Run /workflow:init first.
Project already initialized:
- Tech analysis: .workflow/project-tech.json
- Guidelines: .ccw/specs/*.md
Use /workflow:spec:setup --regenerate to rebuild tech analysis
Use /workflow:spec:setup --reset to reconfigure guidelines
Use /workflow:spec:add to add individual rules
Use /workflow:status --project to view state
```
**Parse --reset flag**:
### Step 2: Get Project Metadata
```bash
bash(basename "$(git rev-parse --show-toplevel 2>/dev/null || pwd)")
bash(git rev-parse --show-toplevel 2>/dev/null || pwd)
bash(mkdir -p .workflow)
```
### Step 3: Invoke cli-explore-agent
**For --regenerate**: Backup and preserve existing data
```bash
bash(cp .workflow/project-tech.json .workflow/project-tech.json.backup)
```
**Delegate analysis to agent**:
```javascript
const reset = $ARGUMENTS.includes('--reset')
Task(
subagent_type="cli-explore-agent",
run_in_background=false,
description="Deep project analysis",
prompt=`
Analyze project for workflow initialization and generate .workflow/project-tech.json.
## MANDATORY FIRST STEPS
1. Execute: cat ~/.ccw/workflows/cli-templates/schemas/project-tech-schema.json (get schema reference)
2. Execute: ccw tool exec get_modules_by_depth '{}' (get project structure)
## Task
Generate complete project-tech.json following the schema structure:
- project_name: "${projectName}"
- initialized_at: ISO 8601 timestamp
- overview: {
description: "Brief project description",
technology_stack: {
languages: [{name, file_count, primary}],
frameworks: ["string"],
build_tools: ["string"],
test_frameworks: ["string"]
},
architecture: {style, layers: [], patterns: []},
key_components: [{name, path, description, importance}]
}
- features: []
- development_index: ${regenerate ? 'preserve from backup' : '{feature: [], enhancement: [], bugfix: [], refactor: [], docs: []}'}
- statistics: ${regenerate ? 'preserve from backup' : '{total_features: 0, total_sessions: 0, last_updated: ISO timestamp}'}
- _metadata: {initialized_by: "cli-explore-agent", analysis_timestamp: ISO timestamp, analysis_mode: "deep-scan"}
## Analysis Requirements
**Technology Stack**:
- Languages: File counts, mark primary
- Frameworks: From package.json, requirements.txt, go.mod, etc.
- Build tools: npm, cargo, maven, webpack, vite
- Test frameworks: jest, pytest, go test, junit
**Architecture**:
- Style: MVC, microservices, layered (from structure & imports)
- Layers: presentation, business-logic, data-access
- Patterns: singleton, factory, repository
- Key components: 5-10 modules {name, path, description, importance}
## Execution
1. Structural scan: get_modules_by_depth.sh, find, wc -l
2. Semantic analysis: Gemini for patterns/architecture
3. Synthesis: Merge findings
4. ${regenerate ? 'Merge with preserved development_index and statistics from .workflow/project-tech.json.backup' : ''}
5. Write JSON: Write('.workflow/project-tech.json', jsonContent)
6. Report: Return brief completion summary
Project root: ${projectRoot}
`
)
```
**If GUIDELINES_EXISTS and not --reset**: Check if guidelines are populated (not just scaffold)
### Step 4: Initialize Spec System (if not --skip-specs)
```javascript
// Skip spec initialization if --skip-specs flag is provided
if (!skipSpecs) {
// Initialize spec system if not already initialized
const specsCheck = Bash('test -f .ccw/specs/coding-conventions.md && echo EXISTS || echo NOT_FOUND')
if (specsCheck.includes('NOT_FOUND')) {
console.log('Initializing spec system...')
Bash('ccw spec init')
Bash('ccw spec rebuild')
}
} else {
console.log('Skipping spec initialization and questionnaire (--skip-specs)')
}
```
If `--skip-specs` is provided, skip directly to Step 7 (Display Summary) with limited output.
### Step 5: Multi-Round Interactive Questionnaire (if not --skip-specs)
#### Step 5.0: Check Existing Guidelines
If guidelines already have content, ask the user how to proceed:
```javascript
// Check if specs already have content via ccw spec list
@@ -76,7 +210,7 @@ const specsList = Bash('ccw spec list --json 2>/dev/null || echo "{}"')
const specsData = JSON.parse(specsList)
const isPopulated = (specsData.total || 0) > 5 // More than seed docs
if (isPopulated) {
if (isPopulated && !reset) {
AskUserQuestion({
questions: [{
question: "Project guidelines already contain entries. How would you like to proceed?",
@@ -93,9 +227,15 @@ if (isPopulated) {
// If Reset → clear all arrays before proceeding
// If Append → keep existing, wizard adds to them
}
// If --reset flag was provided, clear existing entries before proceeding
if (reset) {
// Reset specs content
console.log('Resetting existing guidelines...')
}
```
### Step 2: Load Project Context
#### Step 5.1: Load Project Context
```javascript
// Load project context via ccw spec load for planning context
@@ -112,15 +252,15 @@ const archPatterns = specData.overview?.architecture?.patterns || []
const buildTools = specData.overview?.technology_stack?.build_tools || []
```
### Step 3: Multi-Round Interactive Questionnaire
#### Step 5.2: Multi-Round Questionnaire
Each round uses `AskUserQuestion` with project-aware options. The user can always select "Other" to provide custom input.
**⚠️ CRITICAL**: After each round, collect the user's answers and convert them into guideline entries. Do NOT batch all rounds — process each round's answers before proceeding to the next.
**CRITICAL**: After each round, collect the user's answers and convert them into guideline entries. Do NOT batch all rounds — process each round's answers before proceeding to the next.
---
#### Round 1: Coding Conventions
##### Round 1: Coding Conventions
Generate options dynamically based on detected language/framework:
@@ -175,11 +315,11 @@ AskUserQuestion({
})
```
**Process Round 1 answers** add to `conventions.coding_style` and `conventions.naming_patterns` arrays.
**Process Round 1 answers** -> add to `conventions.coding_style` and `conventions.naming_patterns` arrays.
---
#### Round 2: File Structure & Documentation
##### Round 2: File Structure & Documentation
```javascript
AskUserQuestion({
@@ -210,11 +350,11 @@ AskUserQuestion({
})
```
**Process Round 2 answers** add to `conventions.file_structure` and `conventions.documentation`.
**Process Round 2 answers** -> add to `conventions.file_structure` and `conventions.documentation`.
---
#### Round 3: Architecture & Tech Stack Constraints
##### Round 3: Architecture & Tech Stack Constraints
```javascript
// Build architecture-specific options
@@ -259,11 +399,11 @@ AskUserQuestion({
})
```
**Process Round 3 answers** add to `constraints.architecture` and `constraints.tech_stack`.
**Process Round 3 answers** -> add to `constraints.architecture` and `constraints.tech_stack`.
---
#### Round 4: Performance & Security Constraints
##### Round 4: Performance & Security Constraints
```javascript
AskUserQuestion({
@@ -294,11 +434,11 @@ AskUserQuestion({
})
```
**Process Round 4 answers** add to `constraints.performance` and `constraints.security`.
**Process Round 4 answers** -> add to `constraints.performance` and `constraints.security`.
---
#### Round 5: Quality Rules
##### Round 5: Quality Rules
```javascript
AskUserQuestion({
@@ -318,9 +458,9 @@ AskUserQuestion({
})
```
**Process Round 5 answers** add to `quality_rules` array as `{ rule, scope, enforced_by }` objects.
**Process Round 5 answers** -> add to `quality_rules` array as `{ rule, scope, enforced_by }` objects.
### Step 4: Write specs/*.md
### Step 6: Write specs/*.md (if not --skip-specs)
For each category of collected answers, append rules to the corresponding spec MD file. Each spec file uses YAML frontmatter with `readMode`, `priority`, `category`, and `keywords`.
@@ -404,51 +544,107 @@ keywords: [execution, quality, testing, coverage, lint]
Bash('ccw spec rebuild')
```
### Step 5: Display Summary
#### Answer Processing Rules
When converting user selections to guideline entries:
1. **Selected option** -> Use the option's `description` as the guideline string (it's more precise than the label)
2. **"Other" with custom text** -> Use the user's text directly as the guideline string
3. **Deduplication** -> Skip entries that already exist in the guidelines (exact string match)
4. **Quality rules** -> Convert to `{ rule: description, scope: "all", enforced_by: "code-review" }` format
### Step 7: Display Summary
```javascript
const countConventions = newCodingStyle.length + newNamingPatterns.length
+ newFileStructure.length + newDocumentation.length
const countConstraints = newArchitecture.length + newTechStack.length
+ newPerformance.length + newSecurity.length
const countQuality = newQualityRules.length
const projectTech = JSON.parse(Read('.workflow/project-tech.json'));
// Get updated spec list
const specsList = Bash('ccw spec list --json 2>/dev/null || echo "{}"')
if (skipSpecs) {
// Minimal summary for --skip-specs mode
console.log(`
Project initialized successfully (tech analysis only)
console.log(`
✓ Project guidelines configured
## Project Overview
Name: ${projectTech.project_name}
Description: ${projectTech.overview.description}
## Summary
### Technology Stack
Languages: ${projectTech.overview.technology_stack.languages.map(l => l.name).join(', ')}
Frameworks: ${projectTech.overview.technology_stack.frameworks.join(', ')}
### Architecture
Style: ${projectTech.overview.architecture.style}
Components: ${projectTech.overview.key_components.length} core modules
---
Files created:
- Tech analysis: .workflow/project-tech.json
- Specs: (skipped via --skip-specs)
${regenerate ? '- Backup: .workflow/project-tech.json.backup' : ''}
Next steps:
- Use /workflow:spec:setup (without --skip-specs) to configure guidelines
- Use /workflow:spec:add to create individual specs
- Use workflow-plan skill to start planning
`);
} else {
// Full summary with guidelines stats
const countConventions = newCodingStyle.length + newNamingPatterns.length
+ newFileStructure.length + newDocumentation.length
const countConstraints = newArchitecture.length + newTechStack.length
+ newPerformance.length + newSecurity.length
const countQuality = newQualityRules.length
// Get updated spec list
const specsList = Bash('ccw spec list --json 2>/dev/null || echo "{}"')
console.log(`
Project initialized and guidelines configured
## Project Overview
Name: ${projectTech.project_name}
Description: ${projectTech.overview.description}
### Technology Stack
Languages: ${projectTech.overview.technology_stack.languages.map(l => l.name).join(', ')}
Frameworks: ${projectTech.overview.technology_stack.frameworks.join(', ')}
### Architecture
Style: ${projectTech.overview.architecture.style}
Components: ${projectTech.overview.key_components.length} core modules
### Guidelines Summary
- Conventions: ${countConventions} rules added to coding-conventions.md
- Constraints: ${countConstraints} rules added to architecture-constraints.md
- Quality rules: ${countQuality} rules added to quality-rules.md
Spec index rebuilt. Use \`ccw spec list\` to view all specs.
---
Files created:
- Tech analysis: .workflow/project-tech.json
- Specs: .ccw/specs/ (configured)
${regenerate ? '- Backup: .workflow/project-tech.json.backup' : ''}
Next steps:
- Use /workflow:session:solidify to add individual rules later
- Use /workflow:spec:add to add individual rules later
- Specs are auto-loaded via hook on each prompt
`)
- Use workflow-plan skill to start planning
`);
}
```
## Answer Processing Rules
When converting user selections to guideline entries:
1. **Selected option** → Use the option's `description` as the guideline string (it's more precise than the label)
2. **"Other" with custom text** → Use the user's text directly as the guideline string
3. **Deduplication** → Skip entries that already exist in the guidelines (exact string match)
4. **Quality rules** → Convert to `{ rule: description, scope: "all", enforced_by: "code-review" }` format
## Error Handling
- **No project-tech.json**: Exit with instruction to run `/workflow:init` first
- **User cancels mid-wizard**: Save whatever was collected so far (partial is better than nothing)
- **File write failure**: Report error, suggest manual edit
**Agent Failure**: Fall back to basic initialization with placeholder overview
**Missing Tools**: Agent uses Qwen fallback or bash-only
**Empty Project**: Create minimal JSON with all gaps identified
**No project-tech.json** (when --reset without prior init): Run full flow from Step 2
**User cancels mid-wizard**: Save whatever was collected so far (partial is better than nothing)
**File write failure**: Report error, suggest manual edit
## Related Commands
- `/workflow:init` - Creates scaffold; optionally calls this command
- `/workflow:init-specs` - Interactive wizard to create individual specs with scope selection
- `/workflow:session:solidify` - Add individual rules one at a time
- `/workflow:spec:add` - Interactive wizard to create individual specs with scope selection
- `/workflow:session:sync` - Quick-sync session work to specs and project-tech
- `workflow-plan` skill - Start planning with initialized project context
- `/workflow:status --project` - View project state and guidelines

View File

@@ -658,9 +658,15 @@ ${recommendations.map(r => \`- ${r}\`).join('\\n')}
- "优化执行" → Analyze execution improvements
- "完成" → No further action
5. **Sync Session State** (automatic, unless `--dry-run`)
- Execute: `/workflow:session:sync -y "Execution complete: ${completedCount}/${totalCount} tasks succeeded"`
- Updates specs/*.md with any learnings from execution
- Updates project-tech.json with development index entry
**Success Criteria**:
- [ ] Statistics collected and displayed
- [ ] execution.md updated with final status
- [ ] Session state synced via /workflow:session:sync
- [ ] User informed of completion
---

View File

@@ -1,584 +0,0 @@
# Mermaid Utilities Library
Shared utilities for generating and validating Mermaid diagrams across all analysis skills.
## Sanitization Functions
### sanitizeId
Convert any text to a valid Mermaid node ID.
```javascript
/**
* Sanitize text to valid Mermaid node ID
* - Only alphanumeric and underscore allowed
* - Cannot start with number
* - Truncates to 50 chars max
*
* @param {string} text - Input text
* @returns {string} - Valid Mermaid ID
*/
function sanitizeId(text) {
if (!text) return '_empty';
return text
.replace(/[^a-zA-Z0-9_\u4e00-\u9fa5]/g, '_') // Allow Chinese chars
.replace(/^[0-9]/, '_$&') // Prefix number with _
.replace(/_+/g, '_') // Collapse multiple _
.substring(0, 50); // Limit length
}
// Examples:
// sanitizeId("User-Service") → "User_Service"
// sanitizeId("3rdParty") → "_3rdParty"
// sanitizeId("用户服务") → "用户服务"
```
### escapeLabel
Escape special characters for Mermaid labels.
```javascript
/**
* Escape special characters in Mermaid labels
* Uses HTML entity encoding for problematic chars
*
* @param {string} text - Label text
* @returns {string} - Escaped label
*/
function escapeLabel(text) {
if (!text) return '';
return text
.replace(/"/g, "'") // Avoid quote issues
.replace(/\(/g, '#40;') // (
.replace(/\)/g, '#41;') // )
.replace(/\{/g, '#123;') // {
.replace(/\}/g, '#125;') // }
.replace(/\[/g, '#91;') // [
.replace(/\]/g, '#93;') // ]
.replace(/</g, '#60;') // <
.replace(/>/g, '#62;') // >
.replace(/\|/g, '#124;') // |
.substring(0, 80); // Limit length
}
// Examples:
// escapeLabel("Process(data)") → "Process#40;data#41;"
// escapeLabel("Check {valid?}") → "Check #123;valid?#125;"
```
### sanitizeType
Sanitize type names for class diagrams.
```javascript
/**
* Sanitize type names for Mermaid classDiagram
* Removes generics syntax that causes issues
*
* @param {string} type - Type name
* @returns {string} - Sanitized type
*/
function sanitizeType(type) {
if (!type) return 'any';
return type
.replace(/<[^>]*>/g, '') // Remove generics <T>
.replace(/\|/g, ' or ') // Union types
.replace(/&/g, ' and ') // Intersection types
.replace(/\[\]/g, 'Array') // Array notation
.substring(0, 30);
}
// Examples:
// sanitizeType("Array<string>") → "Array"
// sanitizeType("string | number") → "string or number"
```
## Diagram Generation Functions
### generateFlowchartNode
Generate a flowchart node with proper shape.
```javascript
/**
* Generate flowchart node with shape
*
* @param {string} id - Node ID
* @param {string} label - Display label
* @param {string} type - Node type: start|end|process|decision|io|subroutine
* @returns {string} - Mermaid node definition
*/
function generateFlowchartNode(id, label, type = 'process') {
const safeId = sanitizeId(id);
const safeLabel = escapeLabel(label);
const shapes = {
start: `${safeId}(["${safeLabel}"])`, // Stadium shape
end: `${safeId}(["${safeLabel}"])`, // Stadium shape
process: `${safeId}["${safeLabel}"]`, // Rectangle
decision: `${safeId}{"${safeLabel}"}`, // Diamond
io: `${safeId}[/"${safeLabel}"/]`, // Parallelogram
subroutine: `${safeId}[["${safeLabel}"]]`, // Subroutine
database: `${safeId}[("${safeLabel}")]`, // Cylinder
manual: `${safeId}[/"${safeLabel}"\\]` // Trapezoid
};
return shapes[type] || shapes.process;
}
```
### generateFlowchartEdge
Generate a flowchart edge with optional label.
```javascript
/**
* Generate flowchart edge
*
* @param {string} from - Source node ID
* @param {string} to - Target node ID
* @param {string} label - Edge label (optional)
* @param {string} style - Edge style: solid|dashed|thick
* @returns {string} - Mermaid edge definition
*/
function generateFlowchartEdge(from, to, label = '', style = 'solid') {
const safeFrom = sanitizeId(from);
const safeTo = sanitizeId(to);
const safeLabel = label ? `|"${escapeLabel(label)}"|` : '';
const arrows = {
solid: '-->',
dashed: '-.->',
thick: '==>'
};
const arrow = arrows[style] || arrows.solid;
return ` ${safeFrom} ${arrow}${safeLabel} ${safeTo}`;
}
```
### generateAlgorithmFlowchart (Enhanced)
Generate algorithm flowchart with branch/loop support.
```javascript
/**
* Generate algorithm flowchart with decision support
*
* @param {Object} algorithm - Algorithm definition
* - name: Algorithm name
* - inputs: [{name, type}]
* - outputs: [{name, type}]
* - steps: [{id, description, type, next: [id], conditions: [text]}]
* @returns {string} - Complete Mermaid flowchart
*/
function generateAlgorithmFlowchart(algorithm) {
let mermaid = 'flowchart TD\n';
// Start node
mermaid += ` START(["开始: ${escapeLabel(algorithm.name)}"])\n`;
// Input node (if has inputs)
if (algorithm.inputs?.length > 0) {
const inputList = algorithm.inputs.map(i => `${i.name}: ${i.type}`).join(', ');
mermaid += ` INPUT[/"输入: ${escapeLabel(inputList)}"/]\n`;
mermaid += ` START --> INPUT\n`;
}
// Process nodes
const steps = algorithm.steps || [];
for (const step of steps) {
const nodeId = sanitizeId(step.id || `STEP_${step.step_num}`);
if (step.type === 'decision') {
mermaid += ` ${nodeId}{"${escapeLabel(step.description)}"}\n`;
} else if (step.type === 'io') {
mermaid += ` ${nodeId}[/"${escapeLabel(step.description)}"/]\n`;
} else if (step.type === 'loop_start') {
mermaid += ` ${nodeId}[["循环: ${escapeLabel(step.description)}"]]\n`;
} else {
mermaid += ` ${nodeId}["${escapeLabel(step.description)}"]\n`;
}
}
// Output node
const outputDesc = algorithm.outputs?.map(o => o.name).join(', ') || '结果';
mermaid += ` OUTPUT[/"输出: ${escapeLabel(outputDesc)}"/]\n`;
mermaid += ` END_(["结束"])\n`;
// Connect first step to input/start
if (steps.length > 0) {
const firstStep = sanitizeId(steps[0].id || 'STEP_1');
if (algorithm.inputs?.length > 0) {
mermaid += ` INPUT --> ${firstStep}\n`;
} else {
mermaid += ` START --> ${firstStep}\n`;
}
}
// Connect steps based on next array
for (const step of steps) {
const nodeId = sanitizeId(step.id || `STEP_${step.step_num}`);
if (step.next && step.next.length > 0) {
step.next.forEach((nextId, index) => {
const safeNextId = sanitizeId(nextId);
const condition = step.conditions?.[index];
if (condition) {
mermaid += ` ${nodeId} -->|"${escapeLabel(condition)}"| ${safeNextId}\n`;
} else {
mermaid += ` ${nodeId} --> ${safeNextId}\n`;
}
});
} else if (!step.type?.includes('end')) {
// Default: connect to next step or output
const stepIndex = steps.indexOf(step);
if (stepIndex < steps.length - 1) {
const nextStep = sanitizeId(steps[stepIndex + 1].id || `STEP_${stepIndex + 2}`);
mermaid += ` ${nodeId} --> ${nextStep}\n`;
} else {
mermaid += ` ${nodeId} --> OUTPUT\n`;
}
}
}
// Connect output to end
mermaid += ` OUTPUT --> END_\n`;
return mermaid;
}
```
## Diagram Validation
### validateMermaidSyntax
Comprehensive Mermaid syntax validation.
```javascript
/**
* Validate Mermaid diagram syntax
*
* @param {string} content - Mermaid diagram content
* @returns {Object} - {valid: boolean, issues: string[]}
*/
function validateMermaidSyntax(content) {
const issues = [];
// Check 1: Diagram type declaration
if (!content.match(/^(graph|flowchart|classDiagram|sequenceDiagram|stateDiagram|erDiagram|gantt|pie|mindmap)/m)) {
issues.push('Missing diagram type declaration');
}
// Check 2: Undefined values
if (content.includes('undefined') || content.includes('null')) {
issues.push('Contains undefined/null values');
}
// Check 3: Invalid arrow syntax
if (content.match(/-->\s*-->/)) {
issues.push('Double arrow syntax error');
}
// Check 4: Unescaped special characters in labels
const labelMatches = content.match(/\["[^"]*[(){}[\]<>][^"]*"\]/g);
if (labelMatches?.some(m => !m.includes('#'))) {
issues.push('Unescaped special characters in labels');
}
// Check 5: Node ID starts with number
if (content.match(/\n\s*[0-9][a-zA-Z0-9_]*[\[\({]/)) {
issues.push('Node ID cannot start with number');
}
// Check 6: Nested subgraph syntax error
if (content.match(/subgraph\s+\S+\s*\n[^e]*subgraph/)) {
// This is actually valid, only flag if brackets don't match
const subgraphCount = (content.match(/subgraph/g) || []).length;
const endCount = (content.match(/\bend\b/g) || []).length;
if (subgraphCount > endCount) {
issues.push('Unbalanced subgraph/end blocks');
}
}
// Check 7: Invalid arrow type for diagram type
const diagramType = content.match(/^(graph|flowchart|classDiagram|sequenceDiagram)/m)?.[1];
if (diagramType === 'classDiagram' && content.includes('-->|')) {
issues.push('Invalid edge label syntax for classDiagram');
}
// Check 8: Empty node labels
if (content.match(/\[""\]|\{\}|\(\)/)) {
issues.push('Empty node labels detected');
}
// Check 9: Reserved keywords as IDs
const reserved = ['end', 'graph', 'subgraph', 'direction', 'class', 'click'];
for (const keyword of reserved) {
const pattern = new RegExp(`\\n\\s*${keyword}\\s*[\\[\\(\\{]`, 'i');
if (content.match(pattern)) {
issues.push(`Reserved keyword "${keyword}" used as node ID`);
}
}
// Check 10: Line length (Mermaid has issues with very long lines)
const lines = content.split('\n');
for (let i = 0; i < lines.length; i++) {
if (lines[i].length > 500) {
issues.push(`Line ${i + 1} exceeds 500 characters`);
}
}
return {
valid: issues.length === 0,
issues
};
}
```
### validateDiagramDirectory
Validate all diagrams in a directory.
```javascript
/**
* Validate all Mermaid diagrams in directory
*
* @param {string} diagramDir - Path to diagrams directory
* @returns {Object[]} - Array of {file, valid, issues}
*/
function validateDiagramDirectory(diagramDir) {
const files = Glob(`${diagramDir}/*.mmd`);
const results = [];
for (const file of files) {
const content = Read(file);
const validation = validateMermaidSyntax(content);
results.push({
file: file.split('/').pop(),
path: file,
valid: validation.valid,
issues: validation.issues,
lines: content.split('\n').length
});
}
return results;
}
```
## Class Diagram Utilities
### generateClassDiagram
Generate class diagram with relationships.
```javascript
/**
* Generate class diagram from analysis data
*
* @param {Object} analysis - Data structure analysis
* - entities: [{name, type, properties, methods}]
* - relationships: [{from, to, type, label}]
* @param {Object} options - Generation options
* - maxClasses: Max classes to include (default: 15)
* - maxProperties: Max properties per class (default: 8)
* - maxMethods: Max methods per class (default: 6)
* @returns {string} - Mermaid classDiagram
*/
function generateClassDiagram(analysis, options = {}) {
const maxClasses = options.maxClasses || 15;
const maxProperties = options.maxProperties || 8;
const maxMethods = options.maxMethods || 6;
let mermaid = 'classDiagram\n';
const entities = (analysis.entities || []).slice(0, maxClasses);
// Generate classes
for (const entity of entities) {
const className = sanitizeId(entity.name);
mermaid += ` class ${className} {\n`;
// Properties
for (const prop of (entity.properties || []).slice(0, maxProperties)) {
const vis = {public: '+', private: '-', protected: '#'}[prop.visibility] || '+';
const type = sanitizeType(prop.type);
mermaid += ` ${vis}${type} ${prop.name}\n`;
}
// Methods
for (const method of (entity.methods || []).slice(0, maxMethods)) {
const vis = {public: '+', private: '-', protected: '#'}[method.visibility] || '+';
const params = (method.params || []).map(p => p.name).join(', ');
const returnType = sanitizeType(method.returnType || 'void');
mermaid += ` ${vis}${method.name}(${params}) ${returnType}\n`;
}
mermaid += ' }\n';
// Add stereotype if applicable
if (entity.type === 'interface') {
mermaid += ` <<interface>> ${className}\n`;
} else if (entity.type === 'abstract') {
mermaid += ` <<abstract>> ${className}\n`;
}
}
// Generate relationships
const arrows = {
inheritance: '--|>',
implementation: '..|>',
composition: '*--',
aggregation: 'o--',
association: '-->',
dependency: '..>'
};
for (const rel of (analysis.relationships || [])) {
const from = sanitizeId(rel.from);
const to = sanitizeId(rel.to);
const arrow = arrows[rel.type] || '-->';
const label = rel.label ? ` : ${escapeLabel(rel.label)}` : '';
// Only include if both entities exist
if (entities.some(e => sanitizeId(e.name) === from) &&
entities.some(e => sanitizeId(e.name) === to)) {
mermaid += ` ${from} ${arrow} ${to}${label}\n`;
}
}
return mermaid;
}
```
## Sequence Diagram Utilities
### generateSequenceDiagram
Generate sequence diagram from scenario.
```javascript
/**
* Generate sequence diagram from scenario
*
* @param {Object} scenario - Sequence scenario
* - name: Scenario name
* - actors: [{id, name, type}]
* - messages: [{from, to, description, type}]
* - blocks: [{type, condition, messages}]
* @returns {string} - Mermaid sequenceDiagram
*/
function generateSequenceDiagram(scenario) {
let mermaid = 'sequenceDiagram\n';
// Title
if (scenario.name) {
mermaid += ` title ${escapeLabel(scenario.name)}\n`;
}
// Participants
for (const actor of scenario.actors || []) {
const actorType = actor.type === 'external' ? 'actor' : 'participant';
mermaid += ` ${actorType} ${sanitizeId(actor.id)} as ${escapeLabel(actor.name)}\n`;
}
mermaid += '\n';
// Messages
for (const msg of scenario.messages || []) {
const from = sanitizeId(msg.from);
const to = sanitizeId(msg.to);
const desc = escapeLabel(msg.description);
let arrow;
switch (msg.type) {
case 'async': arrow = '->>'; break;
case 'response': arrow = '-->>'; break;
case 'create': arrow = '->>+'; break;
case 'destroy': arrow = '->>-'; break;
case 'self': arrow = '->>'; break;
default: arrow = '->>';
}
mermaid += ` ${from}${arrow}${to}: ${desc}\n`;
// Activation
if (msg.activate) {
mermaid += ` activate ${to}\n`;
}
if (msg.deactivate) {
mermaid += ` deactivate ${from}\n`;
}
// Notes
if (msg.note) {
mermaid += ` Note over ${to}: ${escapeLabel(msg.note)}\n`;
}
}
// Blocks (loops, alt, opt)
for (const block of scenario.blocks || []) {
switch (block.type) {
case 'loop':
mermaid += ` loop ${escapeLabel(block.condition)}\n`;
break;
case 'alt':
mermaid += ` alt ${escapeLabel(block.condition)}\n`;
break;
case 'opt':
mermaid += ` opt ${escapeLabel(block.condition)}\n`;
break;
}
for (const m of block.messages || []) {
mermaid += ` ${sanitizeId(m.from)}->>${sanitizeId(m.to)}: ${escapeLabel(m.description)}\n`;
}
mermaid += ' end\n';
}
return mermaid;
}
```
## Usage Examples
### Example 1: Algorithm with Branches
```javascript
const algorithm = {
name: "用户认证流程",
inputs: [{name: "credentials", type: "Object"}],
outputs: [{name: "token", type: "JWT"}],
steps: [
{id: "validate", description: "验证输入格式", type: "process"},
{id: "check_user", description: "用户是否存在?", type: "decision",
next: ["verify_pwd", "error_user"], conditions: ["是", "否"]},
{id: "verify_pwd", description: "验证密码", type: "process"},
{id: "pwd_ok", description: "密码正确?", type: "decision",
next: ["gen_token", "error_pwd"], conditions: ["是", "否"]},
{id: "gen_token", description: "生成 JWT Token", type: "process"},
{id: "error_user", description: "返回用户不存在", type: "io"},
{id: "error_pwd", description: "返回密码错误", type: "io"}
]
};
const flowchart = generateAlgorithmFlowchart(algorithm);
```
### Example 2: Validate Before Output
```javascript
const diagram = generateClassDiagram(analysis);
const validation = validateMermaidSyntax(diagram);
if (!validation.valid) {
console.log("Diagram has issues:", validation.issues);
// Fix issues or regenerate
} else {
Write(`${outputDir}/class-diagram.mmd`, diagram);
}
```