feat(workflow): add unified workflow spec command system

- Add /workflow:init-specs command for interactive spec creation with scope selection (global/project)
- Update /workflow:init to chain solidify and add --skip-specs flag
- Add category field support to generated specs frontmatter
- Add GET /api/project-tech/stats endpoint for development progress stats
- Add devProgressInjection settings to system configuration
- Add development progress injection control card to GlobalSettingsTab
- Add i18n keys for new settings in en/zh locales
This commit is contained in:
catlog22
2026-02-27 12:25:26 +08:00
parent 4d755ff9b4
commit 99a3561f71
10 changed files with 877 additions and 33 deletions

View File

@@ -322,27 +322,55 @@ AskUserQuestion({
### Step 4: Write specs/*.md
For each category of collected answers, append rules to the corresponding spec MD file. Each spec file uses YAML frontmatter with `readMode`, `priority`, and `keywords`.
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`.
**Category Assignment**: Based on the round and question type:
- Round 1-2 (conventions): `category: general` (applies to all stages)
- Round 3 (architecture/tech): `category: planning` (planning phase)
- Round 4 (performance/security): `category: execution` (implementation phase)
- Round 5 (quality): `category: execution` (testing phase)
```javascript
// Helper: append rules to a spec MD file
function appendRulesToSpecFile(filePath, rules) {
// Helper: append rules to a spec MD file with category support
function appendRulesToSpecFile(filePath, rules, defaultCategory = 'general') {
if (rules.length === 0) return
// Check if file exists
if (!file_exists(filePath)) {
// Create file with frontmatter including category
const frontmatter = `---
title: ${filePath.includes('conventions') ? 'Coding Conventions' : filePath.includes('constraints') ? 'Architecture Constraints' : 'Quality Rules'}
readMode: optional
priority: medium
category: ${defaultCategory}
scope: project
dimension: specs
keywords: [${defaultCategory}, ${filePath.includes('conventions') ? 'convention' : filePath.includes('constraints') ? 'constraint' : 'quality'}]
---
# ${filePath.includes('conventions') ? 'Coding Conventions' : filePath.includes('constraints') ? 'Architecture Constraints' : 'Quality Rules'}
`
Write(filePath, frontmatter)
}
const existing = Read(filePath)
// Append new rules as markdown list items after existing content
const newContent = existing.trimEnd() + '\n' + rules.map(r => `- ${r}`).join('\n') + '\n'
Write(filePath, newContent)
}
// Write conventions
// Write conventions (general category)
appendRulesToSpecFile('.workflow/specs/coding-conventions.md',
[...newCodingStyle, ...newNamingPatterns, ...newFileStructure, ...newDocumentation])
[...newCodingStyle, ...newNamingPatterns, ...newFileStructure, ...newDocumentation],
'general')
// Write constraints
// Write constraints (planning category)
appendRulesToSpecFile('.workflow/specs/architecture-constraints.md',
[...newArchitecture, ...newTechStack, ...newPerformance, ...newSecurity])
[...newArchitecture, ...newTechStack, ...newPerformance, ...newSecurity],
'planning')
// Write quality rules (create file if needed)
// Write quality rules (execution category)
if (newQualityRules.length > 0) {
const qualityPath = '.workflow/specs/quality-rules.md'
if (!file_exists(qualityPath)) {
@@ -350,7 +378,10 @@ if (newQualityRules.length > 0) {
title: Quality Rules
readMode: required
priority: high
keywords: [quality, testing, coverage, lint]
category: execution
scope: project
dimension: specs
keywords: [execution, quality, testing, coverage, lint]
---
# Quality Rules
@@ -358,7 +389,8 @@ keywords: [quality, testing, coverage, lint]
`)
}
appendRulesToSpecFile(qualityPath,
newQualityRules.map(q => `${q.rule} (scope: ${q.scope}, enforced by: ${q.enforced_by})`))
newQualityRules.map(q => `${q.rule} (scope: ${q.scope}, enforced by: ${q.enforced_by})`),
'execution')
}
// Rebuild spec index after writing
@@ -411,4 +443,5 @@ When converting user selections to guideline entries:
## 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

View File

@@ -0,0 +1,380 @@
---
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 → .workflow/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 .workflow/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
targetDir = '.workflow/specs'
if (isConstraint) {
targetFile = path.join(targetDir, 'architecture-constraints.md')
} else {
targetFile = path.join(targetDir, 'coding-conventions.md')
}
} else {
// Personal specs
if (scope === 'global') {
targetDir = path.join(os.homedir(), '.ccw', 'specs', 'personal')
} else {
targetDir = path.join('.ccw', 'specs', '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)
```
.workflow/specs/
├── coding-conventions.md ← conventions, learnings
├── architecture-constraints.md ← constraints
└── quality-rules.md ← quality rules
```
### Personal Specs (dimension: personal)
```
# Global (~/.ccw/specs/personal/)
~/.ccw/specs/personal/
├── conventions.md ← personal conventions (all projects)
├── constraints.md ← personal constraints (all projects)
└── learnings.md ← personal learnings (all projects)
# Project-local (.ccw/specs/personal/)
.ccw/specs/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,10 +1,11 @@
---
name: init
description: Initialize project-level state with intelligent project analysis using cli-explore-agent
argument-hint: "[--regenerate]"
argument-hint: "[--regenerate] [--skip-specs]"
examples:
- /workflow:init
- /workflow:init --regenerate
- /workflow:init --skip-specs
---
# Workflow Init Command (/workflow:init)
@@ -22,13 +23,15 @@ Initialize `.workflow/project-tech.json` and `.workflow/specs/*.md` with compreh
```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 --regenerate flag → regenerate = true | false
└─ Parse --skip-specs flag → skipSpecs = true | false
Decision:
├─ BOTH_EXIST + no --regenerate → Exit: "Already initialized"
@@ -42,27 +45,30 @@ Analysis Flow:
│ ├─ Semantic analysis (Gemini CLI)
│ ├─ Synthesis and merge
│ └─ Write .workflow/project-tech.json
├─ Create guidelines scaffold (if not exists)
Write .workflow/specs/*.md (empty structure)
├─ Display summary
└─ 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
├─ 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)
└─ .workflow/specs/*.md (scaffold or configured)
└─ .workflow/specs/*.md (scaffold or configured, unless --skip-specs)
```
## Implementation
### Step 1: Parse Input and Check Existing State
**Parse --regenerate flag**:
**Parse flags**:
```javascript
const regenerate = $ARGUMENTS.includes('--regenerate')
const skipSpecs = $ARGUMENTS.includes('--skip-specs')
```
**Check existing state**:
@@ -159,13 +165,20 @@ Project root: ${projectRoot}
)
```
### Step 3.5: Initialize Spec System (if not exists)
### Step 3.5: Initialize Spec System (if not --skip-specs)
```javascript
// Initialize spec system if not already initialized
if (!file_exists('.workflow/specs/coding-conventions.md')) {
Bash('ccw spec init');
Bash('ccw spec rebuild');
// Skip spec initialization if --skip-specs flag is provided
if (!skipSpecs) {
// Initialize spec system if not already initialized
const specsCheck = Bash('test -f .workflow/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)')
}
```
@@ -173,7 +186,7 @@ if (!file_exists('.workflow/specs/coding-conventions.md')) {
```javascript
const projectTech = JSON.parse(Read('.workflow/project-tech.json'));
const specsInitialized = file_exists('.workflow/specs/coding-conventions.md');
const specsInitialized = !skipSpecs && file_exists('.workflow/specs/coding-conventions.md');
console.log(`
Project initialized successfully
@@ -193,16 +206,27 @@ Components: ${projectTech.overview.key_components.length} core modules
---
Files created:
- Tech analysis: .workflow/project-tech.json
- Specs: .workflow/specs/ ${specsInitialized ? '(initialized)' : ''}
${!skipSpecs ? `- Specs: .workflow/specs/ ${specsInitialized ? '(initialized)' : ''}` : '- Specs: (skipped via --skip-specs)'}
${regenerate ? '- Backup: .workflow/project-tech.json.backup' : ''}
`);
```
### Step 5: Ask About Guidelines Configuration
### 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.
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;
@@ -233,6 +257,7 @@ if (specsCount <= 5) {
} 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
@@ -243,6 +268,7 @@ Next steps:
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
@@ -258,6 +284,7 @@ Next steps:
## 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

View File

@@ -450,3 +450,4 @@ This ensures all future planning respects solidified rules without users needing
- `/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

@@ -193,3 +193,9 @@ Write(techPath, JSON.stringify(tech, null, 2))
| 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