mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-28 09:23:08 +08:00
feat: add category and scope to specs for enhanced filtering and organization
- Introduced SpecCategory and SpecScope types to categorize specs by workflow stage and scope (global/project). - Updated Spec interface to include category and scope properties. - Enhanced SpecCard component to display category and scope badges. - Implemented category and scope filtering in SpecsSettingsPage. - Updated localization files to support new category and scope labels. - Modified spec loading commands to utilize category instead of keywords. - Adjusted spec index builder to handle category and scope during spec parsing. - Updated seed documents to include category information.
This commit is contained in:
@@ -55,7 +55,7 @@ Phase 4: Output Generation
|
|||||||
Read and memorize schema requirements BEFORE any analysis begins (feeds Phase 3 validation).
|
Read and memorize schema requirements BEFORE any analysis begins (feeds Phase 3 validation).
|
||||||
|
|
||||||
3. **Project Context Loading** (from spec system):
|
3. **Project Context Loading** (from spec system):
|
||||||
- Load exploration specs using: `ccw spec load --keywords exploration`
|
- Load exploration specs using: `ccw spec load --category exploration`
|
||||||
- Extract: `tech_stack`, `architecture`, `key_components`, `overview`
|
- Extract: `tech_stack`, `architecture`, `key_components`, `overview`
|
||||||
- Usage: Align analysis scope and patterns with actual project technology choices
|
- Usage: Align analysis scope and patterns with actual project technology choices
|
||||||
- If no specs are returned, proceed with fresh analysis (no error).
|
- If no specs are returned, proceed with fresh analysis (no error).
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ When invoked with `process_docs: true` in input context:
|
|||||||
## Input Context
|
## Input Context
|
||||||
|
|
||||||
**Project Context** (loaded from spec system at startup):
|
**Project Context** (loaded from spec system at startup):
|
||||||
- Load specs using: `ccw spec load --keywords "exploration architecture"` → tech_stack, architecture, key_components, conventions, constraints, quality_rules
|
- Load specs using: `ccw spec load --category "exploration architecture"` → tech_stack, architecture, key_components, conventions, constraints, quality_rules
|
||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -79,7 +79,7 @@ if (file_exists(contextPackagePath)) {
|
|||||||
```javascript
|
```javascript
|
||||||
// Load project-level context (from spec system)
|
// Load project-level context (from spec system)
|
||||||
// These provide foundational constraints for ALL context gathering
|
// These provide foundational constraints for ALL context gathering
|
||||||
const projectSpecs = Bash('ccw spec load --keywords "exploration architecture" --stdin');
|
const projectSpecs = Bash('ccw spec load --category "exploration architecture" --stdin');
|
||||||
const projectTech = projectSpecs?.tech_stack ? projectSpecs : null;
|
const projectTech = projectSpecs?.tech_stack ? projectSpecs : null;
|
||||||
const projectGuidelines = projectSpecs?.coding_conventions ? projectSpecs : null;
|
const projectGuidelines = projectSpecs?.coding_conventions ? projectSpecs : null;
|
||||||
|
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ Phase 5: Fix & Verification
|
|||||||
## Phase 1: Bug Analysis
|
## Phase 1: Bug Analysis
|
||||||
|
|
||||||
**Load Project Context** (from spec system):
|
**Load Project Context** (from spec system):
|
||||||
- Load exploration specs using: `ccw spec load --keywords exploration` for tech stack context and coding constraints
|
- Load exploration specs using: `ccw spec load --category exploration` for tech stack context and coding constraints
|
||||||
|
|
||||||
**Session Setup**:
|
**Session Setup**:
|
||||||
```javascript
|
```javascript
|
||||||
|
|||||||
@@ -99,7 +99,7 @@ if (isPopulated) {
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Load project context via ccw spec load for planning context
|
// Load project context via ccw spec load for planning context
|
||||||
const projectContext = Bash('ccw spec load --keywords planning 2>/dev/null || echo "{}"')
|
const projectContext = Bash('ccw spec load --category planning 2>/dev/null || echo "{}"')
|
||||||
const specData = JSON.parse(projectContext)
|
const specData = JSON.parse(projectContext)
|
||||||
|
|
||||||
// Extract key info from loaded specs for generating smart questions
|
// Extract key info from loaded specs for generating smart questions
|
||||||
|
|||||||
@@ -148,7 +148,7 @@ Priority: <issue.priority>
|
|||||||
## MANDATORY FIRST STEPS
|
## MANDATORY FIRST STEPS
|
||||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||||
2. Execute ACE searches based on issue keywords
|
2. Execute ACE searches based on issue keywords
|
||||||
3. Run: ccw spec load --keywords exploration
|
3. Run: ccw spec load --category exploration
|
||||||
|
|
||||||
## Exploration Focus
|
## Exploration Focus
|
||||||
- Identify files directly related to this issue
|
- Identify files directly related to this issue
|
||||||
|
|||||||
@@ -81,7 +81,7 @@ Session: ${sessionFolder}
|
|||||||
## MANDATORY FIRST STEPS
|
## MANDATORY FIRST STEPS
|
||||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||||
2. Execute searches: ${strategy.searches.map(s => `"${s}"`).join(', ')}
|
2. Execute searches: ${strategy.searches.map(s => `"${s}"`).join(', ')}
|
||||||
3. Run: ccw spec load --keywords exploration
|
3. Run: ccw spec load --category exploration
|
||||||
|
|
||||||
## Exploration Focus (${perspective} angle)
|
## Exploration Focus (${perspective} angle)
|
||||||
- **Depth**: ${strategy.depth}
|
- **Depth**: ${strategy.depth}
|
||||||
|
|||||||
@@ -138,7 +138,7 @@ Session: <session-folder>
|
|||||||
## MANDATORY FIRST STEPS
|
## MANDATORY FIRST STEPS
|
||||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||||
2. Execute relevant searches based on topic keywords
|
2. Execute relevant searches based on topic keywords
|
||||||
3. Run: ccw spec load --keywords exploration
|
3. Run: ccw spec load --category exploration
|
||||||
|
|
||||||
## Exploration Focus (<perspective> angle)
|
## Exploration Focus (<perspective> angle)
|
||||||
<dimensions map to exploration focus areas>
|
<dimensions map to exploration focus areas>
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ rg "password|token|secret|auth" -g "*.{ts,js,py}"
|
|||||||
rg "eval|exec|innerHTML|dangerouslySetInnerHTML" -g "*.{ts,js,tsx}"
|
rg "eval|exec|innerHTML|dangerouslySetInnerHTML" -g "*.{ts,js,tsx}"
|
||||||
|
|
||||||
# Gemini security analysis
|
# Gemini security analysis
|
||||||
ccw spec load --keywords execution
|
ccw spec load --category execution
|
||||||
ccw cli -p "
|
ccw cli -p "
|
||||||
PURPOSE: Security audit of completed implementation
|
PURPOSE: Security audit of completed implementation
|
||||||
TASK: Review code for security vulnerabilities, insecure patterns, auth/authz issues
|
TASK: Review code for security vulnerabilities, insecure patterns, auth/authz issues
|
||||||
@@ -105,7 +105,7 @@ RULES: Focus on OWASP Top 10, authentication, authorization, data validation, in
|
|||||||
|
|
||||||
**Architecture Review** (`architecture`):
|
**Architecture Review** (`architecture`):
|
||||||
```bash
|
```bash
|
||||||
ccw spec load --keywords execution
|
ccw spec load --category execution
|
||||||
ccw cli -p "
|
ccw cli -p "
|
||||||
PURPOSE: Architecture compliance review
|
PURPOSE: Architecture compliance review
|
||||||
TASK: Evaluate adherence to architectural patterns, identify technical debt, review design decisions
|
TASK: Evaluate adherence to architectural patterns, identify technical debt, review design decisions
|
||||||
@@ -117,7 +117,7 @@ RULES: Check for patterns, separation of concerns, modularity, scalability
|
|||||||
|
|
||||||
**Quality Review** (`quality`):
|
**Quality Review** (`quality`):
|
||||||
```bash
|
```bash
|
||||||
ccw spec load --keywords execution
|
ccw spec load --category execution
|
||||||
ccw cli -p "
|
ccw cli -p "
|
||||||
PURPOSE: Code quality and best practices review
|
PURPOSE: Code quality and best practices review
|
||||||
TASK: Assess code readability, maintainability, adherence to best practices
|
TASK: Assess code readability, maintainability, adherence to best practices
|
||||||
@@ -139,7 +139,7 @@ for task_file in ${sessionPath}/.task/*.json; do
|
|||||||
done
|
done
|
||||||
|
|
||||||
# Cross-check implementation against requirements
|
# Cross-check implementation against requirements
|
||||||
ccw spec load --keywords execution
|
ccw spec load --category execution
|
||||||
ccw cli -p "
|
ccw cli -p "
|
||||||
PURPOSE: Verify all requirements and acceptance criteria are met
|
PURPOSE: Verify all requirements and acceptance criteria are met
|
||||||
TASK: Cross-check implementation summaries against original requirements
|
TASK: Cross-check implementation summaries against original requirements
|
||||||
|
|||||||
@@ -127,10 +127,10 @@ After collecting preferences, enhance context and dispatch:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Step 1: Load project context via ccw spec
|
// Step 1: Load project context via ccw spec
|
||||||
Bash('ccw spec load --keywords planning')
|
Bash('ccw spec load --category planning')
|
||||||
|
|
||||||
// Step 2: Log available context
|
// Step 2: Log available context
|
||||||
console.log('Project context loaded via: ccw spec load --keywords planning')
|
console.log('Project context loaded via: ccw spec load --category planning')
|
||||||
|
|
||||||
// Step 3: Dispatch to phase (workflowPreferences available as context)
|
// Step 3: Dispatch to phase (workflowPreferences available as context)
|
||||||
if (mode === 'plan') {
|
if (mode === 'plan') {
|
||||||
|
|||||||
@@ -495,7 +495,7 @@ Generate implementation plan and write plan.json.
|
|||||||
Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json (get schema reference before generating plan)
|
Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json (get schema reference before generating plan)
|
||||||
|
|
||||||
## Project Context (MANDATORY - Load via ccw spec)
|
## Project Context (MANDATORY - Load via ccw spec)
|
||||||
Execute: ccw spec load --keywords planning
|
Execute: ccw spec load --category planning
|
||||||
This loads technology stack, architecture, key components, and user-defined constraints/conventions.
|
This loads technology stack, architecture, key components, and user-defined constraints/conventions.
|
||||||
|
|
||||||
**CRITICAL**: All generated tasks MUST comply with constraints in specs/*.md
|
**CRITICAL**: All generated tasks MUST comply with constraints in specs/*.md
|
||||||
|
|||||||
@@ -485,8 +485,8 @@ ${(t.test?.success_metrics || []).length > 0 ? `\n**Success metrics**: ${t.test.
|
|||||||
context.push(`### Artifacts\nPlan: ${executionContext.session.artifacts.plan}`)
|
context.push(`### Artifacts\nPlan: ${executionContext.session.artifacts.plan}`)
|
||||||
}
|
}
|
||||||
// Project guidelines (user-defined constraints from /workflow:session:solidify)
|
// Project guidelines (user-defined constraints from /workflow:session:solidify)
|
||||||
// Loaded via: ccw spec load --keywords planning
|
// Loaded via: ccw spec load --category planning
|
||||||
context.push(`### Project Guidelines\n(Loaded via ccw spec load --keywords planning)`)
|
context.push(`### Project Guidelines\n(Loaded via ccw spec load --category planning)`)
|
||||||
if (context.length > 0) sections.push(`## Context\n${context.join('\n\n')}`)
|
if (context.length > 0) sections.push(`## Context\n${context.join('\n\n')}`)
|
||||||
|
|
||||||
sections.push(`Complete each task according to its "Done when" checklist.`)
|
sections.push(`Complete each task according to its "Done when" checklist.`)
|
||||||
|
|||||||
@@ -101,10 +101,10 @@ After collecting preferences, enhance context and dispatch:
|
|||||||
|
|
||||||
```javascript
|
```javascript
|
||||||
// Step 1: Load project context via ccw spec
|
// Step 1: Load project context via ccw spec
|
||||||
Bash('ccw spec load --keywords planning')
|
Bash('ccw spec load --category planning')
|
||||||
|
|
||||||
// Step 2: Log available context
|
// Step 2: Log available context
|
||||||
console.log('Project context loaded via: ccw spec load --keywords planning')
|
console.log('Project context loaded via: ccw spec load --category planning')
|
||||||
|
|
||||||
// Step 3: Dispatch to phase (workflowPreferences available as context)
|
// Step 3: Dispatch to phase (workflowPreferences available as context)
|
||||||
if (mode === 'plan') {
|
if (mode === 'plan') {
|
||||||
|
|||||||
@@ -203,7 +203,7 @@ This is the PRIMARY context source - all subsequent analysis must align with use
|
|||||||
Execute complete context-search-agent workflow (Phase 1-3) for implementation planning.
|
Execute complete context-search-agent workflow (Phase 1-3) for implementation planning.
|
||||||
|
|
||||||
Key emphasis:
|
Key emphasis:
|
||||||
- Run: ccw spec load --keywords exploration FIRST (per your spec Phase 1.1b)
|
- Run: ccw spec load --category exploration FIRST (per your spec Phase 1.1b)
|
||||||
- Synthesize exploration results with project context
|
- Synthesize exploration results with project context
|
||||||
- Generate prioritized_context with user_intent alignment
|
- Generate prioritized_context with user_intent alignment
|
||||||
- Apply specs/*.md constraints during conflict detection
|
- Apply specs/*.md constraints during conflict detection
|
||||||
|
|||||||
@@ -171,7 +171,7 @@ Session ID: ${sessionId}
|
|||||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||||
|
|
||||||
## PROJECT CONTEXT (MANDATORY - load via ccw spec)
|
## PROJECT CONTEXT (MANDATORY - load via ccw spec)
|
||||||
Execute: ccw spec load --keywords planning
|
Execute: ccw spec load --category planning
|
||||||
|
|
||||||
This loads:
|
This loads:
|
||||||
- Technology stack, architecture, key components, build system, test framework
|
- Technology stack, architecture, key components, build system, test framework
|
||||||
|
|||||||
@@ -221,7 +221,7 @@ Execute complete context-search-agent workflow for TDD implementation planning:
|
|||||||
|
|
||||||
### Phase 1: Initialization & Pre-Analysis
|
### Phase 1: Initialization & Pre-Analysis
|
||||||
1. **Project State Loading**:
|
1. **Project State Loading**:
|
||||||
- Run: \`ccw spec load --keywords execution\` to load project context, tech stack, and guidelines.
|
- Run: \`ccw spec load --category execution\` to load project context, tech stack, and guidelines.
|
||||||
- If files don't exist, proceed with fresh analysis.
|
- If files don't exist, proceed with fresh analysis.
|
||||||
2. **Detection**: Check for existing context-package (early exit if valid)
|
2. **Detection**: Check for existing context-package (early exit if valid)
|
||||||
3. **Foundation**: Initialize CodexLens, get project structure, load docs
|
3. **Foundation**: Initialize CodexLens, get project structure, load docs
|
||||||
|
|||||||
@@ -231,14 +231,14 @@ MCP Capabilities: {exa_code, exa_web, code_index}
|
|||||||
## PROJECT CONTEXT (MANDATORY - load before planning-notes)
|
## PROJECT CONTEXT (MANDATORY - load before planning-notes)
|
||||||
These files provide project-level constraints that apply to ALL tasks:
|
These files provide project-level constraints that apply to ALL tasks:
|
||||||
|
|
||||||
1. **ccw spec load --keywords execution** (project specs and tech analysis)
|
1. **ccw spec load --category execution** (project specs and tech analysis)
|
||||||
- Contains: tech_stack, architecture_type, key_components, build_system, test_framework, coding_conventions, naming_rules, forbidden_patterns, quality_gates, custom_constraints
|
- Contains: tech_stack, architecture_type, key_components, build_system, test_framework, coding_conventions, naming_rules, forbidden_patterns, quality_gates, custom_constraints
|
||||||
- Usage: Populate plan.json shared_context, align task tech choices, set correct test commands
|
- Usage: Populate plan.json shared_context, align task tech choices, set correct test commands
|
||||||
- Apply as HARD CONSTRAINTS on all generated tasks — task implementation steps,
|
- Apply as HARD CONSTRAINTS on all generated tasks — task implementation steps,
|
||||||
acceptance criteria, and convergence.verification MUST respect these guidelines
|
acceptance criteria, and convergence.verification MUST respect these guidelines
|
||||||
- If empty/missing: No additional constraints (proceed normally)
|
- If empty/missing: No additional constraints (proceed normally)
|
||||||
|
|
||||||
Loading order: \`ccw spec load --keywords execution\` → planning-notes.md → context-package.json
|
Loading order: \`ccw spec load --category execution\` → planning-notes.md → context-package.json
|
||||||
|
|
||||||
## USER CONFIGURATION (from Phase 0)
|
## USER CONFIGURATION (from Phase 0)
|
||||||
Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli
|
Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli
|
||||||
|
|||||||
@@ -345,7 +345,7 @@ Execute complete context-search-agent workflow for implementation planning:
|
|||||||
|
|
||||||
### Phase 1: Initialization & Pre-Analysis
|
### Phase 1: Initialization & Pre-Analysis
|
||||||
1. **Project State Loading**:
|
1. **Project State Loading**:
|
||||||
- Run: \`ccw spec load --keywords execution\` to load project context, tech stack, and guidelines.
|
- Run: \`ccw spec load --category execution\` to load project context, tech stack, and guidelines.
|
||||||
- If files don't exist, proceed with fresh analysis.
|
- If files don't exist, proceed with fresh analysis.
|
||||||
2. **Detection**: Check for existing context-package (early exit if valid)
|
2. **Detection**: Check for existing context-package (early exit if valid)
|
||||||
3. **Foundation**: Initialize CodexLens, get project structure, load docs
|
3. **Foundation**: Initialize CodexLens, get project structure, load docs
|
||||||
|
|||||||
@@ -244,7 +244,7 @@ Task(
|
|||||||
${selectedStrategy} - ${strategyDescription}
|
${selectedStrategy} - ${strategyDescription}
|
||||||
|
|
||||||
## PROJECT CONTEXT (MANDATORY)
|
## PROJECT CONTEXT (MANDATORY)
|
||||||
1. Run: \`ccw spec load --keywords execution\` (tech stack, test framework, build system, constraints)
|
1. Run: \`ccw spec load --category execution\` (tech stack, test framework, build system, constraints)
|
||||||
|
|
||||||
## MANDATORY FIRST STEPS
|
## MANDATORY FIRST STEPS
|
||||||
1. Read test results: ${session.test_results_path}
|
1. Read test results: ${session.test_results_path}
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Check these items. Report results as a checklist.
|
|||||||
|
|
||||||
### 1.2 Strongly Recommended (warn if missing)
|
### 1.2 Strongly Recommended (warn if missing)
|
||||||
|
|
||||||
- **Project specs**: Run `ccw spec load --keywords execution` to load project context
|
- **Project specs**: Run `ccw spec load --category execution` to load project context
|
||||||
- If spec system unavailable: Read `package.json` / `tsconfig.json` / `pyproject.toml` and generate a minimal version. Ask user: "检测到项目使用 [tech stack], 是否正确?需要补充什么?"
|
- If spec system unavailable: Read `package.json` / `tsconfig.json` / `pyproject.toml` and generate a minimal version. Ask user: "检测到项目使用 [tech stack], 是否正确?需要补充什么?"
|
||||||
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
||||||
- If missing: Ask user: "未检测到测试框架配置,请指定测试命令(如 `npm test`, `pytest`),或输入 'skip' 跳过测试验证"
|
- If missing: Ask user: "未检测到测试框架配置,请指定测试命令(如 `npm test`, `pytest`),或输入 'skip' 跳过测试验证"
|
||||||
@@ -36,7 +36,7 @@ Print formatted checklist:
|
|||||||
✓ 项目根目录: D:\myproject
|
✓ 项目根目录: D:\myproject
|
||||||
✓ 工作空间: .workflow/.cycle/ 就绪
|
✓ 工作空间: .workflow/.cycle/ 就绪
|
||||||
⚠ Git: 3 个未提交变更
|
⚠ Git: 3 个未提交变更
|
||||||
✓ Project specs: 已加载 (ccw spec load --keywords execution)
|
✓ Project specs: 已加载 (ccw spec load --category execution)
|
||||||
⚠ specs: 未找到 (已跳过)
|
⚠ specs: 未找到 (已跳过)
|
||||||
✓ 测试框架: jest (npm test)
|
✓ 测试框架: jest (npm test)
|
||||||
```
|
```
|
||||||
@@ -170,7 +170,7 @@ Read the user's `$TASK` and score each dimension:
|
|||||||
|
|
||||||
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
|
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
|
||||||
- **Scope**: Use `Glob` and `Grep` to find related files, list them
|
- **Scope**: Use `Glob` and `Grep` to find related files, list them
|
||||||
- **Context**: Run `ccw spec load --keywords execution` to load project context
|
- **Context**: Run `ccw spec load --category execution` to load project context
|
||||||
- **Constraints**: Infer from `specs/*.md` and existing patterns
|
- **Constraints**: Infer from `specs/*.md` and existing patterns
|
||||||
|
|
||||||
### 2.5 Assemble Refined Task
|
### 2.5 Assemble Refined Task
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ Check these items. Report results as a checklist.
|
|||||||
|
|
||||||
### 1.2 Strongly Recommended (warn if missing)
|
### 1.2 Strongly Recommended (warn if missing)
|
||||||
|
|
||||||
- **Project specs**: Run `ccw spec load --keywords planning` to load project context
|
- **Project specs**: Run `ccw spec load --category planning` to load project context
|
||||||
- If spec system unavailable: WARN — Phase 1 will call `workflow:init` to initialize. Ask user: "检测到项目使用 [tech stack from package.json], 是否正确?需要补充什么?"
|
- If spec system unavailable: WARN — Phase 1 will call `workflow:init` to initialize. Ask user: "检测到项目使用 [tech stack from package.json], 是否正确?需要补充什么?"
|
||||||
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
||||||
- If missing: Ask: "未检测到测试框架,请指定测试命令(如 `npm test`),或输入 'skip' 跳过"
|
- If missing: Ask: "未检测到测试框架,请指定测试命令(如 `npm test`),或输入 'skip' 跳过"
|
||||||
@@ -36,7 +36,7 @@ Print formatted checklist:
|
|||||||
✓ 项目根目录: D:\myproject
|
✓ 项目根目录: D:\myproject
|
||||||
✓ .workflow/ 目录就绪
|
✓ .workflow/ 目录就绪
|
||||||
⚠ Git: 3 个未提交变更
|
⚠ Git: 3 个未提交变更
|
||||||
✓ Project specs: 已加载 (ccw spec load --keywords planning)
|
✓ Project specs: 已加载 (ccw spec load --category planning)
|
||||||
⚠ specs: 未找到 (Phase 1 将初始化)
|
⚠ specs: 未找到 (Phase 1 将初始化)
|
||||||
✓ 测试框架: jest (npm test)
|
✓ 测试框架: jest (npm test)
|
||||||
```
|
```
|
||||||
@@ -160,7 +160,7 @@ Each dimension scores 0-2 (0=missing, 1=vague, 2=clear). **Total minimum: 6/10 t
|
|||||||
|
|
||||||
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
|
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
|
||||||
- **Scope**: Use `Glob` and `Grep` to find related files
|
- **Scope**: Use `Glob` and `Grep` to find related files
|
||||||
- **Context**: Run `ccw spec load --keywords planning` to load project context
|
- **Context**: Run `ccw spec load --category planning` to load project context
|
||||||
- **Constraints**: Infer from `specs/*.md`
|
- **Constraints**: Infer from `specs/*.md`
|
||||||
|
|
||||||
### 2.5 Assemble Structured Description
|
### 2.5 Assemble Structured Description
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ Step 1: Topic Understanding
|
|||||||
|
|
||||||
Step 2: Exploration (Inline, No Agents)
|
Step 2: Exploration (Inline, No Agents)
|
||||||
├─ Detect codebase → search relevant modules, patterns
|
├─ Detect codebase → search relevant modules, patterns
|
||||||
│ ├─ Run `ccw spec load --keywords exploration` (if spec system available)
|
│ ├─ Run `ccw spec load --category exploration` (if spec system available)
|
||||||
│ └─ Use Grep, Glob, Read, mcp__ace-tool__search_context
|
│ └─ Use Grep, Glob, Read, mcp__ace-tool__search_context
|
||||||
├─ Multi-perspective analysis (if selected, serial)
|
├─ Multi-perspective analysis (if selected, serial)
|
||||||
│ ├─ Single: Comprehensive analysis
|
│ ├─ Single: Comprehensive analysis
|
||||||
@@ -297,7 +297,7 @@ const hasCodebase = Bash(`
|
|||||||
|
|
||||||
if (hasCodebase !== 'none') {
|
if (hasCodebase !== 'none') {
|
||||||
// 1. Read project metadata (if exists)
|
// 1. Read project metadata (if exists)
|
||||||
// - Run `ccw spec load --keywords exploration` (load project specs)
|
// - Run `ccw spec load --category exploration` (load project specs)
|
||||||
// - .workflow/specs/*.md (project conventions)
|
// - .workflow/specs/*.md (project conventions)
|
||||||
|
|
||||||
// 2. Search codebase for relevant content
|
// 2. Search codebase for relevant content
|
||||||
|
|||||||
@@ -282,7 +282,7 @@ Use built-in tools to understand the codebase structure before spawning perspect
|
|||||||
**Context Gathering Activities**:
|
**Context Gathering Activities**:
|
||||||
1. **Get project structure** - Execute `ccw tool exec get_modules_by_depth '{}'`
|
1. **Get project structure** - Execute `ccw tool exec get_modules_by_depth '{}'`
|
||||||
2. **Search for related code** - Use Grep/Glob to find files matching topic keywords
|
2. **Search for related code** - Use Grep/Glob to find files matching topic keywords
|
||||||
3. **Read project tech context** - Run `ccw spec load --keywords "exploration planning"` if spec system available
|
3. **Read project tech context** - Run `ccw spec load --category "exploration planning"` if spec system available
|
||||||
4. **Analyze patterns** - Identify common code patterns and architecture decisions
|
4. **Analyze patterns** - Identify common code patterns and architecture decisions
|
||||||
|
|
||||||
**exploration-codebase.json Structure**:
|
**exploration-codebase.json Structure**:
|
||||||
@@ -358,7 +358,7 @@ const agentIds = perspectives.map(perspective => {
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords "exploration planning"`
|
2. Run: `ccw spec load --category "exploration planning"`
|
||||||
3. Read project tech context from loaded specs
|
3. Read project tech context from loaded specs
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -566,7 +566,7 @@ const deepDiveAgent = spawn_agent({
|
|||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||||
2. Read: ${sessionFolder}/perspectives.json (prior findings)
|
2. Read: ${sessionFolder}/perspectives.json (prior findings)
|
||||||
3. Run: `ccw spec load --keywords "exploration planning"`
|
3. Run: `ccw spec load --category "exploration planning"`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -194,7 +194,7 @@ Use built-in tools directly to understand the task scope and identify sub-domain
|
|||||||
**Analysis Activities**:
|
**Analysis Activities**:
|
||||||
1. **Search for references** — Find related documentation, README files, and architecture guides
|
1. **Search for references** — Find related documentation, README files, and architecture guides
|
||||||
- Use: `mcp__ace-tool__search_context`, Grep, Glob, Read
|
- Use: `mcp__ace-tool__search_context`, Grep, Glob, Read
|
||||||
- Run: `ccw spec load --keywords planning` (if spec system available)
|
- Run: `ccw spec load --category planning` (if spec system available)
|
||||||
2. **Extract task keywords** — Identify key terms and concepts from the task description
|
2. **Extract task keywords** — Identify key terms and concepts from the task description
|
||||||
3. **Identify ambiguities** — List any unclear points or multiple possible interpretations
|
3. **Identify ambiguities** — List any unclear points or multiple possible interpretations
|
||||||
4. **Clarify with user** — If ambiguities found, use AskUserQuestion for clarification
|
4. **Clarify with user** — If ambiguities found, use AskUserQuestion for clarification
|
||||||
|
|||||||
@@ -231,7 +231,7 @@ const agentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
||||||
2. Execute: ccw spec load --keywords exploration
|
2. Execute: ccw spec load --category exploration
|
||||||
|
|
||||||
## TASK CONTEXT
|
## TASK CONTEXT
|
||||||
${taskContext}
|
${taskContext}
|
||||||
|
|||||||
@@ -268,7 +268,7 @@ const hasCodebase = bash(`
|
|||||||
// 2. Codebase Exploration (only when hasCodebase !== 'none')
|
// 2. Codebase Exploration (only when hasCodebase !== 'none')
|
||||||
if (hasCodebase !== 'none') {
|
if (hasCodebase !== 'none') {
|
||||||
// Read project metadata (if exists)
|
// Read project metadata (if exists)
|
||||||
// Run `ccw spec load --keywords planning`
|
// Run `ccw spec load --category planning`
|
||||||
|
|
||||||
// Search codebase for requirement-relevant context
|
// Search codebase for requirement-relevant context
|
||||||
// Use: mcp__ace-tool__search_context, Grep, Glob, Read
|
// Use: mcp__ace-tool__search_context, Grep, Glob, Read
|
||||||
|
|||||||
@@ -303,7 +303,7 @@ const agentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
||||||
2. Execute: ccw spec load --keywords "exploration execution"
|
2. Execute: ccw spec load --category "exploration execution"
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -94,7 +94,7 @@ dimensions.forEach(dimension => {
|
|||||||
3. Get target files: Read resolved_files from review-state.json
|
3. Get target files: Read resolved_files from review-state.json
|
||||||
4. Validate file access: bash(ls -la ${targetFiles.join(' ')})
|
4. Validate file access: bash(ls -la ${targetFiles.join(' ')})
|
||||||
5. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-dimension-results-schema.json (get output schema reference)
|
5. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-dimension-results-schema.json (get output schema reference)
|
||||||
6. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints)
|
6. Execute: ccw spec load --category "exploration execution" (technology stack and constraints)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -216,7 +216,7 @@ dimensions.forEach(dimension => {
|
|||||||
4. Get changed files: bash(cd ${workflowDir} && git log --since="${sessionCreatedAt}" --name-only --pretty=format: | sort -u)
|
4. Get changed files: bash(cd ${workflowDir} && git log --since="${sessionCreatedAt}" --name-only --pretty=format: | sort -u)
|
||||||
5. Read review state: ${reviewStateJsonPath}
|
5. Read review state: ${reviewStateJsonPath}
|
||||||
6. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-dimension-results-schema.json (get output schema reference)
|
6. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-dimension-results-schema.json (get output schema reference)
|
||||||
7. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints)
|
7. Execute: ccw spec load --category "exploration execution" (technology stack and constraints)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -334,7 +334,7 @@ const deepDiveAgentId = spawn_agent({
|
|||||||
4. Identify related code: bash(grep -r "import.*${basename(file)}" ${projectDir}/src --include="*.ts")
|
4. Identify related code: bash(grep -r "import.*${basename(file)}" ${projectDir}/src --include="*.ts")
|
||||||
5. Read test files: bash(find ${projectDir}/tests -name "*${basename(file, '.ts')}*" -type f)
|
5. Read test files: bash(find ${projectDir}/tests -name "*${basename(file, '.ts')}*" -type f)
|
||||||
6. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-deep-dive-results-schema.json (get output schema reference)
|
6. Execute: cat ~/.ccw/workflows/cli-templates/schemas/review-deep-dive-results-schema.json (get output schema reference)
|
||||||
7. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints for remediation)
|
7. Execute: ccw spec load --category "exploration execution" (technology stack and constraints for remediation)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -105,7 +105,7 @@ const agentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-planning-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-planning-agent.md (MUST read first)
|
||||||
2. Execute: ccw spec load --keywords planning
|
2. Execute: ccw spec load --category planning
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ const execAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-execution-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-execution-agent.md (MUST read first)
|
||||||
2. Execute: ccw spec load --keywords execution
|
2. Execute: ccw spec load --category execution
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -38,7 +38,7 @@ completion report.
|
|||||||
### Step 1: Load Context
|
### Step 1: Load Context
|
||||||
|
|
||||||
After reading role definition:
|
After reading role definition:
|
||||||
- Run: `ccw spec load --keywords execution`
|
- Run: `ccw spec load --category execution`
|
||||||
- Extract issue ID, solution file path, session dir from task message
|
- Extract issue ID, solution file path, session dir from task message
|
||||||
|
|
||||||
### Step 2: Load Solution
|
### Step 2: Load Solution
|
||||||
|
|||||||
@@ -48,7 +48,7 @@ Outputs `ISSUE_READY:{issueId}` after each solution and waits for orchestrator t
|
|||||||
### Step 1: Load Context
|
### Step 1: Load Context
|
||||||
|
|
||||||
After reading role definition, load project context:
|
After reading role definition, load project context:
|
||||||
- Run: `ccw spec load --keywords planning`
|
- Run: `ccw spec load --category planning`
|
||||||
- Extract session directory and artifacts directory from task message
|
- Extract session directory and artifacts directory from task message
|
||||||
|
|
||||||
### Step 2: Parse Input
|
### Step 2: Parse Input
|
||||||
@@ -81,7 +81,7 @@ spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ const plannerAgent = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/planex-planner.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/planex-planner.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords "planning execution"`
|
2. Run: `ccw spec load --category "planning execution"`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -155,7 +155,7 @@ while (true) {
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/planex-executor.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/planex-executor.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords "planning execution"`
|
2. Run: `ccw spec load --category "planning execution"`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@ const agentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords "planning execution"`
|
2. Run: `ccw spec load --category "planning execution"`
|
||||||
|
|
||||||
## TASK CONTEXT
|
## TASK CONTEXT
|
||||||
${taskContext}
|
${taskContext}
|
||||||
|
|||||||
@@ -75,7 +75,7 @@ const contextAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/test-context-search-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/test-context-search-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -101,7 +101,7 @@ const contextAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/context-search-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/context-search-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -174,7 +174,7 @@ const analysisAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-execution-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-execution-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -242,7 +242,7 @@ const taskGenAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/action-planning-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/action-planning-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -90,7 +90,7 @@ const analysisAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/cli-planning-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/cli-planning-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords planning`
|
2. Run: `ccw spec load --category planning`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -156,7 +156,7 @@ const fixAgentId = spawn_agent({
|
|||||||
|
|
||||||
### MANDATORY FIRST STEPS (Agent Execute)
|
### MANDATORY FIRST STEPS (Agent Execute)
|
||||||
1. **Read role definition**: ~/.codex/agents/test-fix-agent.md (MUST read first)
|
1. **Read role definition**: ~/.codex/agents/test-fix-agent.md (MUST read first)
|
||||||
2. Run: `ccw spec load --keywords execution`
|
2. Run: `ccw spec load --category execution`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
import { useState, useEffect, useCallback, useMemo } from 'react';
|
import { useState, useEffect, useCallback, useMemo } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
|
import { Link } from 'react-router-dom';
|
||||||
import { toast } from 'sonner';
|
import { toast } from 'sonner';
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
import {
|
import {
|
||||||
@@ -28,7 +29,7 @@ import {
|
|||||||
Plug,
|
Plug,
|
||||||
Download,
|
Download,
|
||||||
CheckCircle2,
|
CheckCircle2,
|
||||||
ExternalLink,
|
Settings,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
import { useInstallRecommendedHooks } from '@/hooks/useSystemSettings';
|
import { useInstallRecommendedHooks } from '@/hooks/useSystemSettings';
|
||||||
|
|
||||||
@@ -325,6 +326,7 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
|||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className="space-y-4">
|
<CardContent className="space-y-4">
|
||||||
|
<div className="flex items-center justify-between">
|
||||||
<div className="flex items-center gap-4">
|
<div className="flex items-center gap-4">
|
||||||
<Button
|
<Button
|
||||||
onClick={handleInstallAllHooks}
|
onClick={handleInstallAllHooks}
|
||||||
@@ -346,11 +348,12 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
|||||||
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
||||||
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
<Button variant="ghost" size="sm" asChild>
|
<Button variant="ghost" size="sm" asChild>
|
||||||
<a href="/hooks" target="_blank" rel="noopener noreferrer">
|
<Link to="/hooks">
|
||||||
<ExternalLink className="h-4 w-4 mr-1" />
|
<Settings className="h-4 w-4 mr-1" />
|
||||||
{formatMessage({ id: 'specs.manageHooks', defaultMessage: 'Manage Hooks' })}
|
{formatMessage({ id: 'specs.manageHooks', defaultMessage: 'Manage Hooks' })}
|
||||||
</a>
|
</Link>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,10 @@ import {
|
|||||||
Trash2,
|
Trash2,
|
||||||
FileText,
|
FileText,
|
||||||
Tag,
|
Tag,
|
||||||
|
Eye,
|
||||||
|
Globe,
|
||||||
|
Folder,
|
||||||
|
Layers,
|
||||||
} from 'lucide-react';
|
} from 'lucide-react';
|
||||||
|
|
||||||
// ========== Types ==========
|
// ========== Types ==========
|
||||||
@@ -32,6 +36,11 @@ import {
|
|||||||
*/
|
*/
|
||||||
export type SpecDimension = 'specs' | 'personal';
|
export type SpecDimension = 'specs' | 'personal';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec scope type
|
||||||
|
*/
|
||||||
|
export type SpecScope = 'global' | 'project';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spec read mode type
|
* Spec read mode type
|
||||||
*/
|
*/
|
||||||
@@ -42,6 +51,11 @@ export type SpecReadMode = 'required' | 'optional';
|
|||||||
*/
|
*/
|
||||||
export type SpecPriority = 'critical' | 'high' | 'medium' | 'low';
|
export type SpecPriority = 'critical' | 'high' | 'medium' | 'low';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spec category type for workflow stage-based loading
|
||||||
|
*/
|
||||||
|
export type SpecCategory = 'general' | 'exploration' | 'planning' | 'execution';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spec data structure
|
* Spec data structure
|
||||||
*/
|
*/
|
||||||
@@ -54,6 +68,10 @@ export interface Spec {
|
|||||||
file: string;
|
file: string;
|
||||||
/** Spec dimension/category */
|
/** Spec dimension/category */
|
||||||
dimension: SpecDimension;
|
dimension: SpecDimension;
|
||||||
|
/** Scope: global (from ~/.ccw/) or project (from .ccw/) */
|
||||||
|
scope: SpecScope;
|
||||||
|
/** Workflow stage category for system-level loading */
|
||||||
|
category?: SpecCategory;
|
||||||
/** Read mode: required (always inject) or optional (keyword match) */
|
/** Read mode: required (always inject) or optional (keyword match) */
|
||||||
readMode: SpecReadMode;
|
readMode: SpecReadMode;
|
||||||
/** Priority level */
|
/** Priority level */
|
||||||
@@ -72,6 +90,8 @@ export interface Spec {
|
|||||||
export interface SpecCardProps {
|
export interface SpecCardProps {
|
||||||
/** Spec data */
|
/** Spec data */
|
||||||
spec: Spec;
|
spec: Spec;
|
||||||
|
/** Called when view content action is triggered */
|
||||||
|
onView?: (spec: Spec) => void;
|
||||||
/** Called when edit action is triggered */
|
/** Called when edit action is triggered */
|
||||||
onEdit?: (spec: Spec) => void;
|
onEdit?: (spec: Spec) => void;
|
||||||
/** Called when delete action is triggered */
|
/** Called when delete action is triggered */
|
||||||
@@ -108,6 +128,17 @@ const priorityConfig: Record<
|
|||||||
low: { variant: 'secondary', labelKey: 'specs.priority.low' },
|
low: { variant: 'secondary', labelKey: 'specs.priority.low' },
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Category badge configuration for workflow stage
|
||||||
|
const categoryConfig: Record<
|
||||||
|
SpecCategory,
|
||||||
|
{ variant: 'default' | 'secondary' | 'outline'; labelKey: string }
|
||||||
|
> = {
|
||||||
|
general: { variant: 'secondary', labelKey: 'specs.category.general' },
|
||||||
|
exploration: { variant: 'outline', labelKey: 'specs.category.exploration' },
|
||||||
|
planning: { variant: 'outline', labelKey: 'specs.category.planning' },
|
||||||
|
execution: { variant: 'outline', labelKey: 'specs.category.execution' },
|
||||||
|
};
|
||||||
|
|
||||||
// ========== Component ==========
|
// ========== Component ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -115,6 +146,7 @@ const priorityConfig: Record<
|
|||||||
*/
|
*/
|
||||||
export function SpecCard({
|
export function SpecCard({
|
||||||
spec,
|
spec,
|
||||||
|
onView,
|
||||||
onEdit,
|
onEdit,
|
||||||
onDelete,
|
onDelete,
|
||||||
onToggle,
|
onToggle,
|
||||||
@@ -181,6 +213,10 @@ export function SpecCard({
|
|||||||
</Button>
|
</Button>
|
||||||
</DropdownMenuTrigger>
|
</DropdownMenuTrigger>
|
||||||
<DropdownMenuContent align="end">
|
<DropdownMenuContent align="end">
|
||||||
|
<DropdownMenuItem onClick={(e) => { e.stopPropagation(); onView?.(spec); }}>
|
||||||
|
<Eye className="mr-2 h-4 w-4" />
|
||||||
|
{formatMessage({ id: 'specs.actions.view', defaultMessage: 'View Content' })}
|
||||||
|
</DropdownMenuItem>
|
||||||
<DropdownMenuItem onClick={(e) => handleAction(e, 'edit')}>
|
<DropdownMenuItem onClick={(e) => handleAction(e, 'edit')}>
|
||||||
<Edit className="mr-2 h-4 w-4" />
|
<Edit className="mr-2 h-4 w-4" />
|
||||||
{formatMessage({ id: 'specs.actions.edit' })}
|
{formatMessage({ id: 'specs.actions.edit' })}
|
||||||
@@ -201,6 +237,29 @@ export function SpecCard({
|
|||||||
|
|
||||||
{/* Badges */}
|
{/* Badges */}
|
||||||
<div className="mt-3 flex flex-wrap items-center gap-2">
|
<div className="mt-3 flex flex-wrap items-center gap-2">
|
||||||
|
{/* Category badge - workflow stage */}
|
||||||
|
{spec.category && (
|
||||||
|
<Badge variant={categoryConfig[spec.category].variant} className="text-xs gap-1">
|
||||||
|
<Layers className="h-3 w-3" />
|
||||||
|
{formatMessage({ id: categoryConfig[spec.category].labelKey, defaultMessage: spec.category })}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
|
{/* Scope badge - only show for personal specs */}
|
||||||
|
{spec.dimension === 'personal' && (
|
||||||
|
<Badge variant="outline" className="text-xs gap-1">
|
||||||
|
{spec.scope === 'global' ? (
|
||||||
|
<>
|
||||||
|
<Globe className="h-3 w-3" />
|
||||||
|
{formatMessage({ id: 'specs.scope.global', defaultMessage: 'Global' })}
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Folder className="h-3 w-3" />
|
||||||
|
{formatMessage({ id: 'specs.scope.project', defaultMessage: 'Project' })}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Badge>
|
||||||
|
)}
|
||||||
<Badge variant={readMode.variant} className="text-xs">
|
<Badge variant={readMode.variant} className="text-xs">
|
||||||
{formatMessage({ id: readMode.labelKey })}
|
{formatMessage({ id: readMode.labelKey })}
|
||||||
</Badge>
|
</Badge>
|
||||||
|
|||||||
@@ -11,8 +11,10 @@ export {
|
|||||||
export type {
|
export type {
|
||||||
Spec,
|
Spec,
|
||||||
SpecDimension,
|
SpecDimension,
|
||||||
|
SpecScope,
|
||||||
SpecReadMode,
|
SpecReadMode,
|
||||||
SpecPriority,
|
SpecPriority,
|
||||||
|
SpecCategory,
|
||||||
SpecCardProps,
|
SpecCardProps,
|
||||||
} from './SpecCard';
|
} from './SpecCard';
|
||||||
|
|
||||||
|
|||||||
@@ -350,6 +350,7 @@ export const specsSettingsKeys = {
|
|||||||
all: ['specsSettings'] as const,
|
all: ['specsSettings'] as const,
|
||||||
systemSettings: () => [...specsSettingsKeys.all, 'systemSettings'] as const,
|
systemSettings: () => [...specsSettingsKeys.all, 'systemSettings'] as const,
|
||||||
specStats: (projectPath?: string) => [...specsSettingsKeys.all, 'specStats', projectPath] as const,
|
specStats: (projectPath?: string) => [...specsSettingsKeys.all, 'specStats', projectPath] as const,
|
||||||
|
specsList: (projectPath?: string) => [...specsSettingsKeys.all, 'specsList', projectPath] as const,
|
||||||
};
|
};
|
||||||
|
|
||||||
// ========================================
|
// ========================================
|
||||||
@@ -492,7 +493,7 @@ export function useSpecsList(options: UseSpecsListOptions = {}): UseSpecsListRet
|
|||||||
const { projectPath, enabled = true, staleTime = STALE_TIME } = options;
|
const { projectPath, enabled = true, staleTime = STALE_TIME } = options;
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: specsSettingsKeys.specStats(projectPath), // Reuse for specs list
|
queryKey: specsSettingsKeys.specsList(projectPath),
|
||||||
queryFn: () => getSpecsList(projectPath),
|
queryFn: () => getSpecsList(projectPath),
|
||||||
staleTime,
|
staleTime,
|
||||||
enabled,
|
enabled,
|
||||||
@@ -528,6 +529,7 @@ export function useRebuildSpecIndex(options: UseRebuildSpecIndexOptions = {}) {
|
|||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate specs list and stats queries to refresh data
|
// Invalidate specs list and stats queries to refresh data
|
||||||
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
|
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
|
||||||
|
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specsList(projectPath) });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -560,8 +562,9 @@ export function useUpdateSpecFrontmatter(options: UseUpdateSpecFrontmatterOption
|
|||||||
mutationFn: ({ file, readMode }: { file: string; readMode: string }) =>
|
mutationFn: ({ file, readMode }: { file: string; readMode: string }) =>
|
||||||
updateSpecFrontmatter(file, readMode, projectPath),
|
updateSpecFrontmatter(file, readMode, projectPath),
|
||||||
onSuccess: () => {
|
onSuccess: () => {
|
||||||
// Invalidate specs list to refresh data
|
// Invalidate specs list and stats to refresh data
|
||||||
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
|
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
|
||||||
|
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specsList(projectPath) });
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -7268,9 +7268,11 @@ export interface SpecEntry {
|
|||||||
file: string;
|
file: string;
|
||||||
title: string;
|
title: string;
|
||||||
dimension: string;
|
dimension: string;
|
||||||
|
category?: 'general' | 'exploration' | 'planning' | 'execution';
|
||||||
readMode: 'required' | 'optional' | 'keywords';
|
readMode: 'required' | 'optional' | 'keywords';
|
||||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
|
scope: 'global' | 'project';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -10,36 +10,67 @@
|
|||||||
"rebuildIndex": "Rebuild Index",
|
"rebuildIndex": "Rebuild Index",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"noSpecs": "No specs found. Create specs in .ccw/ directory.",
|
"noSpecs": "No specs found. Create specs in .ccw/ directory.",
|
||||||
|
"required": "required",
|
||||||
|
|
||||||
|
"dimension": {
|
||||||
|
"specs": "Project Specs",
|
||||||
|
"personal": "Personal"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scope": {
|
||||||
|
"all": "All",
|
||||||
|
"global": "Global",
|
||||||
|
"project": "Project"
|
||||||
|
},
|
||||||
|
"filterByScope": "Filter by scope:",
|
||||||
|
|
||||||
|
"category": {
|
||||||
|
"general": "General",
|
||||||
|
"exploration": "Exploration",
|
||||||
|
"planning": "Planning",
|
||||||
|
"execution": "Execution"
|
||||||
|
},
|
||||||
|
|
||||||
"recommendedHooks": "Recommended Hooks",
|
"recommendedHooks": "Recommended Hooks",
|
||||||
"recommendedHooksDesc": "One-click install system-preset spec injection hooks",
|
"recommendedHooksDesc": "One-click install system-preset spec injection hooks",
|
||||||
"installAll": "Install All Recommended Hooks",
|
"installAll": "Install All Recommended Hooks",
|
||||||
|
"installAllHooks": "Install All Hooks",
|
||||||
|
"allHooksInstalled": "All Hooks Installed",
|
||||||
|
"hooksInstalled": "installed",
|
||||||
|
"manageHooks": "Manage Hooks",
|
||||||
|
"hookEvent": "Event",
|
||||||
|
"hookScope": "Scope",
|
||||||
|
"install": "Install",
|
||||||
|
"installed": "Installed",
|
||||||
|
"installing": "Installing...",
|
||||||
"installedHooks": "Installed Hooks",
|
"installedHooks": "Installed Hooks",
|
||||||
"installedHooksDesc": "Manage your installed hooks configuration",
|
"installedHooksDesc": "Manage your installed hooks configuration",
|
||||||
"searchHooks": "Search hooks...",
|
"searchHooks": "Search hooks...",
|
||||||
"noHooks": "No hooks installed. Install recommended hooks above.",
|
"noHooks": "No hooks installed. Install recommended hooks above.",
|
||||||
|
|
||||||
"spec": {
|
"spec": {
|
||||||
"edit": "Edit",
|
"edit": "Edit Spec",
|
||||||
"toggle": "Toggle",
|
"toggle": "Toggle Status",
|
||||||
"delete": "Delete",
|
"delete": "Delete Spec",
|
||||||
"required": "Required",
|
"deleteConfirm": "Are you sure you want to delete this spec?",
|
||||||
"optional": "Optional",
|
"title": "Spec Title",
|
||||||
"priority": {
|
"keywords": "Keywords",
|
||||||
"critical": "Critical",
|
"keywordsPlaceholder": "Enter keywords, separated by commas",
|
||||||
"high": "High",
|
"readMode": "Read Mode",
|
||||||
"medium": "Medium",
|
"priority": "Priority",
|
||||||
"low": "Low"
|
"file": "File Path"
|
||||||
}
|
|
||||||
},
|
},
|
||||||
|
|
||||||
"hook": {
|
"hook": {
|
||||||
"install": "Install",
|
"install": "Install",
|
||||||
"edit": "Edit",
|
"uninstall": "Uninstall",
|
||||||
"toggle": "Toggle",
|
"edit": "Edit Hook",
|
||||||
"delete": "Delete",
|
"toggle": "Toggle Status",
|
||||||
|
"delete": "Delete Hook",
|
||||||
"enabled": "Enabled",
|
"enabled": "Enabled",
|
||||||
"disabled": "Disabled",
|
"disabled": "Disabled",
|
||||||
|
"installed": "Installed",
|
||||||
|
"notInstalled": "Not Installed",
|
||||||
"scope": {
|
"scope": {
|
||||||
"global": "Global",
|
"global": "Global",
|
||||||
"project": "Project"
|
"project": "Project"
|
||||||
@@ -48,21 +79,142 @@
|
|||||||
"SessionStart": "Session Start",
|
"SessionStart": "Session Start",
|
||||||
"UserPromptSubmit": "Prompt Submit",
|
"UserPromptSubmit": "Prompt Submit",
|
||||||
"SessionEnd": "Session End"
|
"SessionEnd": "Session End"
|
||||||
|
},
|
||||||
|
"name": "Hook Name",
|
||||||
|
"eventLabel": "Trigger Event",
|
||||||
|
"command": "Command",
|
||||||
|
"scopeLabel": "Scope",
|
||||||
|
"timeout": "Timeout (ms)",
|
||||||
|
"failMode": "Fail Mode",
|
||||||
|
"failModeContinue": "Continue",
|
||||||
|
"failModeBlock": "Block",
|
||||||
|
"failModeWarn": "Warn"
|
||||||
|
},
|
||||||
|
|
||||||
|
"actions": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"delete": "Delete",
|
||||||
|
"reset": "Reset",
|
||||||
|
"save": "Save",
|
||||||
|
"saving": "Saving...",
|
||||||
|
"view": "View Content"
|
||||||
|
},
|
||||||
|
|
||||||
|
"status": {
|
||||||
|
"enabled": "Enabled",
|
||||||
|
"disabled": "Disabled"
|
||||||
|
},
|
||||||
|
|
||||||
|
"readMode": {
|
||||||
|
"required": "Required",
|
||||||
|
"optional": "Optional"
|
||||||
|
},
|
||||||
|
|
||||||
|
"priority": {
|
||||||
|
"critical": "Critical",
|
||||||
|
"high": "High",
|
||||||
|
"medium": "Medium",
|
||||||
|
"low": "Low"
|
||||||
|
},
|
||||||
|
|
||||||
|
"hooks": {
|
||||||
|
"dialog": {
|
||||||
|
"createTitle": "Create Hook",
|
||||||
|
"editTitle": "Edit Hook",
|
||||||
|
"description": "Configure the hook trigger event, command, and other settings."
|
||||||
|
},
|
||||||
|
"fields": {
|
||||||
|
"name": "Hook Name",
|
||||||
|
"event": "Trigger Event",
|
||||||
|
"scope": "Scope",
|
||||||
|
"command": "Command",
|
||||||
|
"description": "Description",
|
||||||
|
"timeout": "Timeout",
|
||||||
|
"timeoutUnit": "ms",
|
||||||
|
"failMode": "Failure Mode"
|
||||||
|
},
|
||||||
|
"placeholders": {
|
||||||
|
"name": "Enter hook name",
|
||||||
|
"event": "Select event",
|
||||||
|
"command": "Enter command to execute",
|
||||||
|
"description": "Enter description (optional)"
|
||||||
|
},
|
||||||
|
"events": {
|
||||||
|
"sessionStart": "Session Start",
|
||||||
|
"userPromptSubmit": "Prompt Submit",
|
||||||
|
"sessionEnd": "Session End"
|
||||||
|
},
|
||||||
|
"scope": {
|
||||||
|
"global": "Global",
|
||||||
|
"project": "Project"
|
||||||
|
},
|
||||||
|
"failModes": {
|
||||||
|
"continue": "Continue",
|
||||||
|
"warn": "Show Warning",
|
||||||
|
"block": "Block Operation"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"nameRequired": "Name is required",
|
||||||
|
"commandRequired": "Command is required",
|
||||||
|
"timeoutMin": "Minimum timeout is 1000ms",
|
||||||
|
"timeoutMax": "Maximum timeout is 300000ms"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"hints": {
|
||||||
|
"hookEvents": "Select when this hook should be triggered",
|
||||||
|
"hookScope": "Global hooks apply to all projects, project hooks only to current project",
|
||||||
|
"hookCommand": "Command to execute, can use environment variables",
|
||||||
|
"hookTimeout": "Timeout for command execution",
|
||||||
|
"hookFailMode": "How to handle command execution failure"
|
||||||
|
},
|
||||||
|
|
||||||
|
"common": {
|
||||||
|
"cancel": "Cancel",
|
||||||
|
"save": "Save",
|
||||||
|
"delete": "Delete",
|
||||||
|
"edit": "Edit",
|
||||||
|
"reset": "Reset",
|
||||||
|
"confirm": "Confirm",
|
||||||
|
"close": "Close"
|
||||||
|
},
|
||||||
|
|
||||||
|
"content": {
|
||||||
|
"edit": "Edit",
|
||||||
|
"view": "View",
|
||||||
|
"metadata": "Metadata",
|
||||||
|
"markdownContent": "Markdown Content",
|
||||||
|
"noContent": "No content available",
|
||||||
|
"editHint": "Edit the full markdown content including frontmatter. Changes to frontmatter will be reflected in the spec metadata.",
|
||||||
|
"placeholder": "# Spec Title\n\nContent here..."
|
||||||
|
},
|
||||||
|
|
||||||
"injection": {
|
"injection": {
|
||||||
"title": "Injection Control",
|
"title": "Injection Control",
|
||||||
"description": "Monitor and manage spec injection length",
|
"statusTitle": "Current Injection Status",
|
||||||
|
"settingsTitle": "Injection Control Settings",
|
||||||
|
"settingsDescription": "Configure how spec content is injected into AI context.",
|
||||||
"currentLength": "Current Length",
|
"currentLength": "Current Length",
|
||||||
"maxLength": "Max Length",
|
"maxLength": "Max Injection Length (characters)",
|
||||||
|
"maxLengthHelp": "Recommended: 4000-10000. Too large may consume too much context; too small may truncate important specs.",
|
||||||
"warnThreshold": "Warn Threshold",
|
"warnThreshold": "Warn Threshold",
|
||||||
|
"warnThresholdLabel": "Warning Threshold (characters)",
|
||||||
|
"warnThresholdHelp": "A warning will be displayed when injection length exceeds this value.",
|
||||||
"percentage": "Usage",
|
"percentage": "Usage",
|
||||||
"truncateOnExceed": "Truncate on Exceed",
|
"truncateOnExceed": "Truncate on Exceed",
|
||||||
"truncateDescription": "Automatically truncate when injection exceeds max length",
|
"truncateHelp": "Automatically truncate content when it exceeds the maximum length.",
|
||||||
"overLimit": "Over Limit",
|
"overLimit": "Over Limit",
|
||||||
"warning": "Warning",
|
"overLimitDescription": "Current injection content exceeds maximum length of {max} characters. Excess content will be truncated.",
|
||||||
"normal": "Normal"
|
"warning": "Approaching Limit",
|
||||||
|
"normal": "Normal",
|
||||||
|
"characters": "characters",
|
||||||
|
"statsInfo": "Statistics",
|
||||||
|
"requiredLength": "Required specs length:",
|
||||||
|
"matchedLength": "Keyword-matched length:",
|
||||||
|
"remaining": "Remaining space:",
|
||||||
|
"loadError": "Failed to load stats",
|
||||||
|
"saveSuccess": "Settings saved successfully",
|
||||||
|
"saveError": "Failed to save settings"
|
||||||
},
|
},
|
||||||
|
|
||||||
"settings": {
|
"settings": {
|
||||||
@@ -70,6 +222,7 @@
|
|||||||
"description": "Configure personal spec defaults and system settings",
|
"description": "Configure personal spec defaults and system settings",
|
||||||
"personalSpecDefaults": "Personal Spec Defaults",
|
"personalSpecDefaults": "Personal Spec Defaults",
|
||||||
"defaultReadMode": "Default Read Mode",
|
"defaultReadMode": "Default Read Mode",
|
||||||
|
"defaultReadModeHelp": "Default read mode for newly created personal specs",
|
||||||
"autoEnable": "Auto Enable",
|
"autoEnable": "Auto Enable",
|
||||||
"autoEnableDescription": "Automatically enable newly created personal specs"
|
"autoEnableDescription": "Automatically enable newly created personal specs"
|
||||||
},
|
},
|
||||||
@@ -77,17 +230,25 @@
|
|||||||
"dialog": {
|
"dialog": {
|
||||||
"cancel": "Cancel",
|
"cancel": "Cancel",
|
||||||
"save": "Save",
|
"save": "Save",
|
||||||
|
"close": "Close",
|
||||||
"editSpec": "Edit Spec",
|
"editSpec": "Edit Spec",
|
||||||
"editHook": "Edit Hook",
|
"editHook": "Edit Hook",
|
||||||
|
"confirmDelete": "Confirm Delete",
|
||||||
"specTitle": "Spec Title",
|
"specTitle": "Spec Title",
|
||||||
"keywords": "Keywords",
|
"keywords": "Keywords",
|
||||||
"readMode": "Read Mode",
|
"readMode": "Read Mode",
|
||||||
"priority": "Priority",
|
"priority": "Priority",
|
||||||
"hookName": "Hook Name",
|
"hookName": "Hook Name",
|
||||||
"hookEvent": "Event",
|
"hookEvent": "Trigger Event",
|
||||||
"hookCommand": "Command",
|
"hookCommand": "Command",
|
||||||
"hookScope": "Scope",
|
"hookScope": "Scope",
|
||||||
"hookTimeout": "Timeout (ms)",
|
"hookTimeout": "Timeout (ms)",
|
||||||
"hookFailMode": "Fail Mode"
|
"hookFailMode": "Fail Mode"
|
||||||
|
},
|
||||||
|
|
||||||
|
"form": {
|
||||||
|
"readMode": "Read Mode",
|
||||||
|
"priority": "Priority",
|
||||||
|
"keywords": "Keywords"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,16 +10,47 @@
|
|||||||
"rebuildIndex": "重建索引",
|
"rebuildIndex": "重建索引",
|
||||||
"loading": "加载中...",
|
"loading": "加载中...",
|
||||||
"noSpecs": "未找到规范。请在 .ccw/ 目录中创建规范文件。",
|
"noSpecs": "未找到规范。请在 .ccw/ 目录中创建规范文件。",
|
||||||
|
"required": "必读",
|
||||||
|
|
||||||
|
"dimension": {
|
||||||
|
"specs": "项目规范",
|
||||||
|
"personal": "个人规范"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scope": {
|
||||||
|
"all": "全部",
|
||||||
|
"global": "全局",
|
||||||
|
"project": "项目"
|
||||||
|
},
|
||||||
|
"filterByScope": "按范围筛选:",
|
||||||
|
|
||||||
|
"category": {
|
||||||
|
"general": "通用",
|
||||||
|
"exploration": "探索",
|
||||||
|
"planning": "规划",
|
||||||
|
"execution": "执行"
|
||||||
|
},
|
||||||
|
|
||||||
"recommendedHooks": "推荐钩子",
|
"recommendedHooks": "推荐钩子",
|
||||||
"recommendedHooksDesc": "一键安装系统预设的规范注入钩子",
|
"recommendedHooksDesc": "一键安装系统预设的规范注入钩子",
|
||||||
"installAll": "安装所有推荐钩子",
|
"installAll": "安装所有推荐钩子",
|
||||||
|
"installAllHooks": "安装所有钩子",
|
||||||
|
"allHooksInstalled": "已安装所有钩子",
|
||||||
|
"hooksInstalled": "已安装",
|
||||||
|
"manageHooks": "管理钩子",
|
||||||
|
"hookEvent": "事件",
|
||||||
|
"hookScope": "范围",
|
||||||
|
"install": "安装",
|
||||||
|
"installed": "已安装",
|
||||||
|
"installing": "安装中...",
|
||||||
|
|
||||||
"installedHooks": "已安装钩子",
|
"installedHooks": "已安装钩子",
|
||||||
"installedHooksDesc": "管理已安装的钩子配置",
|
"installedHooksDesc": "管理已安装的钩子配置",
|
||||||
"searchHooks": "搜索钩子...",
|
"searchHooks": "搜索钩子...",
|
||||||
"noHooks": "未安装钩子。请安装上方的推荐钩子。",
|
"noHooks": "未安装钩子。请安装上方的推荐钩子。",
|
||||||
|
|
||||||
"actions": {
|
"actions": {
|
||||||
|
"view": "查看内容",
|
||||||
"edit": "编辑",
|
"edit": "编辑",
|
||||||
"delete": "删除",
|
"delete": "删除",
|
||||||
"reset": "重置",
|
"reset": "重置",
|
||||||
@@ -45,6 +76,7 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"spec": {
|
"spec": {
|
||||||
|
"view": "查看内容",
|
||||||
"edit": "编辑规范",
|
"edit": "编辑规范",
|
||||||
"toggle": "切换状态",
|
"toggle": "切换状态",
|
||||||
"delete": "删除规范",
|
"delete": "删除规范",
|
||||||
@@ -57,6 +89,23 @@
|
|||||||
"file": "文件路径"
|
"file": "文件路径"
|
||||||
},
|
},
|
||||||
|
|
||||||
|
"content": {
|
||||||
|
"edit": "编辑",
|
||||||
|
"view": "查看",
|
||||||
|
"metadata": "元数据",
|
||||||
|
"markdownContent": "Markdown 内容",
|
||||||
|
"noContent": "无内容",
|
||||||
|
"editHint": "编辑完整的 Markdown 内容(包括 frontmatter)。frontmatter 的更改将反映到规范元数据中。",
|
||||||
|
"placeholder": "# 规范标题\n\n内容..."
|
||||||
|
},
|
||||||
|
|
||||||
|
"common": {
|
||||||
|
"cancel": "取消",
|
||||||
|
"save": "保存",
|
||||||
|
"saving": "保存中...",
|
||||||
|
"close": "关闭"
|
||||||
|
},
|
||||||
|
|
||||||
"hook": {
|
"hook": {
|
||||||
"install": "安装",
|
"install": "安装",
|
||||||
"uninstall": "卸载",
|
"uninstall": "卸载",
|
||||||
@@ -88,6 +137,9 @@
|
|||||||
},
|
},
|
||||||
|
|
||||||
"hooks": {
|
"hooks": {
|
||||||
|
"installSuccess": "钩子安装成功",
|
||||||
|
"installError": "钩子安装失败",
|
||||||
|
"installAllSuccess": "所有钩子安装成功",
|
||||||
"dialog": {
|
"dialog": {
|
||||||
"createTitle": "创建钩子",
|
"createTitle": "创建钩子",
|
||||||
"editTitle": "编辑钩子",
|
"editTitle": "编辑钩子",
|
||||||
@@ -122,6 +174,12 @@
|
|||||||
"continue": "继续执行",
|
"continue": "继续执行",
|
||||||
"warn": "显示警告",
|
"warn": "显示警告",
|
||||||
"block": "阻止操作"
|
"block": "阻止操作"
|
||||||
|
},
|
||||||
|
"validation": {
|
||||||
|
"nameRequired": "名称为必填项",
|
||||||
|
"commandRequired": "命令为必填项",
|
||||||
|
"timeoutMin": "最小超时时间为 1000ms",
|
||||||
|
"timeoutMax": "最大超时时间为 300000ms"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -133,18 +191,8 @@
|
|||||||
"hookFailMode": "命令执行失败时的处理方式"
|
"hookFailMode": "命令执行失败时的处理方式"
|
||||||
},
|
},
|
||||||
|
|
||||||
"common": {
|
|
||||||
"cancel": "取消",
|
|
||||||
"save": "保存",
|
|
||||||
"delete": "删除",
|
|
||||||
"edit": "编辑",
|
|
||||||
"reset": "重置",
|
|
||||||
"confirm": "确认"
|
|
||||||
},
|
|
||||||
|
|
||||||
"injection": {
|
"injection": {
|
||||||
"title": "注入控制",
|
"title": "注入控制",
|
||||||
"description": "监控和管理规范注入长度",
|
|
||||||
"statusTitle": "当前注入状态",
|
"statusTitle": "当前注入状态",
|
||||||
"settingsTitle": "注入控制设置",
|
"settingsTitle": "注入控制设置",
|
||||||
"settingsDescription": "配置如何将规范内容注入到 AI 上下文中。",
|
"settingsDescription": "配置如何将规范内容注入到 AI 上下文中。",
|
||||||
@@ -198,5 +246,11 @@
|
|||||||
"hookScope": "作用域",
|
"hookScope": "作用域",
|
||||||
"hookTimeout": "超时时间(ms)",
|
"hookTimeout": "超时时间(ms)",
|
||||||
"hookFailMode": "失败模式"
|
"hookFailMode": "失败模式"
|
||||||
|
},
|
||||||
|
|
||||||
|
"form": {
|
||||||
|
"readMode": "读取模式",
|
||||||
|
"priority": "优先级",
|
||||||
|
"keywords": "关键词"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@
|
|||||||
*
|
*
|
||||||
* Main page for managing spec settings, injection control, and global settings.
|
* Main page for managing spec settings, injection control, and global settings.
|
||||||
* Uses 4 tabs: Project Specs | Personal Specs | Injection | Settings
|
* Uses 4 tabs: Project Specs | Personal Specs | Injection | Settings
|
||||||
|
* Supports category filtering (workflow stage) and scope filtering (personal only)
|
||||||
*/
|
*/
|
||||||
import { useState, useMemo } from 'react';
|
import { useState, useMemo } from 'react';
|
||||||
import { useIntl } from 'react-intl';
|
import { useIntl } from 'react-intl';
|
||||||
@@ -10,8 +11,8 @@ import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs';
|
|||||||
import { Card, CardContent } from '@/components/ui/Card';
|
import { Card, CardContent } from '@/components/ui/Card';
|
||||||
import { Button } from '@/components/ui/Button';
|
import { Button } from '@/components/ui/Button';
|
||||||
import { Input } from '@/components/ui/Input';
|
import { Input } from '@/components/ui/Input';
|
||||||
import { ScrollText, User, Gauge, Settings, RefreshCw, Search } from 'lucide-react';
|
import { ScrollText, User, Gauge, Settings, RefreshCw, Search, Globe, Folder, Filter, Layers } from 'lucide-react';
|
||||||
import { SpecCard, SpecDialog, SpecContentDialog, type Spec, type SpecFormData } from '@/components/specs';
|
import { SpecCard, SpecDialog, SpecContentDialog, type Spec, type SpecFormData, type SpecCategory } from '@/components/specs';
|
||||||
import { InjectionControlTab } from '@/components/specs/InjectionControlTab';
|
import { InjectionControlTab } from '@/components/specs/InjectionControlTab';
|
||||||
import { GlobalSettingsTab } from '@/components/specs/GlobalSettingsTab';
|
import { GlobalSettingsTab } from '@/components/specs/GlobalSettingsTab';
|
||||||
import { useSpecStats, useSpecsList, useRebuildSpecIndex } from '@/hooks/useSystemSettings';
|
import { useSpecStats, useSpecsList, useRebuildSpecIndex } from '@/hooks/useSystemSettings';
|
||||||
@@ -19,6 +20,11 @@ import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
|||||||
import type { SpecEntry } from '@/lib/api';
|
import type { SpecEntry } from '@/lib/api';
|
||||||
|
|
||||||
type SettingsTab = 'project-specs' | 'personal-specs' | 'injection' | 'settings';
|
type SettingsTab = 'project-specs' | 'personal-specs' | 'injection' | 'settings';
|
||||||
|
type PersonalScopeFilter = 'all' | 'global' | 'project';
|
||||||
|
type CategoryFilter = 'all' | SpecCategory;
|
||||||
|
|
||||||
|
// All available categories
|
||||||
|
const SPEC_CATEGORIES: SpecCategory[] = ['general', 'exploration', 'planning', 'execution'];
|
||||||
|
|
||||||
// Convert SpecEntry to Spec for display
|
// Convert SpecEntry to Spec for display
|
||||||
function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
|
function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
|
||||||
@@ -26,6 +32,8 @@ function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
|
|||||||
id: entry.file,
|
id: entry.file,
|
||||||
title: entry.title,
|
title: entry.title,
|
||||||
dimension: dimension as Spec['dimension'],
|
dimension: dimension as Spec['dimension'],
|
||||||
|
scope: entry.scope || 'project', // Default to project if not specified
|
||||||
|
category: entry.category || 'general', // Default to general if not specified
|
||||||
keywords: entry.keywords,
|
keywords: entry.keywords,
|
||||||
readMode: entry.readMode as Spec['readMode'],
|
readMode: entry.readMode as Spec['readMode'],
|
||||||
priority: entry.priority as Spec['priority'],
|
priority: entry.priority as Spec['priority'],
|
||||||
@@ -39,8 +47,12 @@ export function SpecsSettingsPage() {
|
|||||||
const projectPath = useWorkflowStore(selectProjectPath);
|
const projectPath = useWorkflowStore(selectProjectPath);
|
||||||
const [activeTab, setActiveTab] = useState<SettingsTab>('project-specs');
|
const [activeTab, setActiveTab] = useState<SettingsTab>('project-specs');
|
||||||
const [searchQuery, setSearchQuery] = useState('');
|
const [searchQuery, setSearchQuery] = useState('');
|
||||||
|
const [personalScopeFilter, setPersonalScopeFilter] = useState<PersonalScopeFilter>('all');
|
||||||
|
const [categoryFilter, setCategoryFilter] = useState<CategoryFilter>('all');
|
||||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||||
|
const [contentDialogOpen, setContentDialogOpen] = useState(false);
|
||||||
const [editingSpec, setEditingSpec] = useState<Spec | null>(null);
|
const [editingSpec, setEditingSpec] = useState<Spec | null>(null);
|
||||||
|
const [viewingSpec, setViewingSpec] = useState<Spec | null>(null);
|
||||||
|
|
||||||
// Fetch real data
|
// Fetch real data
|
||||||
const { data: specsListData, isLoading: specsLoading, refetch: refetchSpecs } = useSpecsList({ projectPath });
|
const { data: specsListData, isLoading: specsLoading, refetch: refetchSpecs } = useSpecsList({ projectPath });
|
||||||
@@ -48,26 +60,50 @@ export function SpecsSettingsPage() {
|
|||||||
const rebuildMutation = useRebuildSpecIndex();
|
const rebuildMutation = useRebuildSpecIndex();
|
||||||
|
|
||||||
// Convert specs data to display format
|
// Convert specs data to display format
|
||||||
const { projectSpecs, personalSpecs } = useMemo(() => {
|
const { projectSpecs, personalSpecs, globalPersonalSpecs, projectPersonalSpecs, categoryCounts } = useMemo(() => {
|
||||||
if (!specsListData?.specs) {
|
if (!specsListData?.specs) {
|
||||||
return { projectSpecs: [], personalSpecs: [] };
|
return {
|
||||||
|
projectSpecs: [],
|
||||||
|
personalSpecs: [],
|
||||||
|
globalPersonalSpecs: [],
|
||||||
|
projectPersonalSpecs: [],
|
||||||
|
categoryCounts: { general: 0, exploration: 0, planning: 0, execution: 0 }
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const specs: Spec[] = [];
|
const specs: Spec[] = [];
|
||||||
const personal: Spec[] = [];
|
const personal: Spec[] = [];
|
||||||
|
const globalPersonal: Spec[] = [];
|
||||||
|
const projectPersonal: Spec[] = [];
|
||||||
|
const counts: Record<SpecCategory, number> = { general: 0, exploration: 0, planning: 0, execution: 0 };
|
||||||
|
|
||||||
for (const [dimension, entries] of Object.entries(specsListData.specs)) {
|
for (const [dimension, entries] of Object.entries(specsListData.specs)) {
|
||||||
for (const entry of entries) {
|
for (const entry of entries) {
|
||||||
const spec = specEntryToSpec(entry, dimension);
|
const spec = specEntryToSpec(entry, dimension);
|
||||||
|
// Count by category
|
||||||
|
if (spec.category) {
|
||||||
|
counts[spec.category]++;
|
||||||
|
}
|
||||||
if (dimension === 'personal') {
|
if (dimension === 'personal') {
|
||||||
personal.push(spec);
|
personal.push(spec);
|
||||||
|
if (spec.scope === 'global') {
|
||||||
|
globalPersonal.push(spec);
|
||||||
|
} else {
|
||||||
|
projectPersonal.push(spec);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
specs.push(spec);
|
specs.push(spec);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return { projectSpecs: specs, personalSpecs: personal };
|
return {
|
||||||
|
projectSpecs: specs,
|
||||||
|
personalSpecs: personal,
|
||||||
|
globalPersonalSpecs: globalPersonal,
|
||||||
|
projectPersonalSpecs: projectPersonal,
|
||||||
|
categoryCounts: counts
|
||||||
|
};
|
||||||
}, [specsListData]);
|
}, [specsListData]);
|
||||||
|
|
||||||
const isLoading = specsLoading;
|
const isLoading = specsLoading;
|
||||||
@@ -113,16 +149,34 @@ export function SpecsSettingsPage() {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const filterSpecs = (specs: Spec[]) => {
|
const filterSpecs = (specs: Spec[]) => {
|
||||||
if (!searchQuery.trim()) return specs;
|
let result = specs;
|
||||||
|
// Filter by category
|
||||||
|
if (categoryFilter !== 'all') {
|
||||||
|
result = result.filter(spec => spec.category === categoryFilter);
|
||||||
|
}
|
||||||
|
// Filter by search query
|
||||||
|
if (searchQuery.trim()) {
|
||||||
const query = searchQuery.toLowerCase();
|
const query = searchQuery.toLowerCase();
|
||||||
return specs.filter(spec =>
|
result = result.filter(spec =>
|
||||||
spec.title.toLowerCase().includes(query) ||
|
spec.title.toLowerCase().includes(query) ||
|
||||||
spec.keywords.some(k => k.toLowerCase().includes(query))
|
spec.keywords.some(k => k.toLowerCase().includes(query))
|
||||||
);
|
);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
const renderSpecsTab = (dimension: 'project' | 'personal') => {
|
const renderSpecsTab = (dimension: 'project' | 'personal') => {
|
||||||
const specs = dimension === 'project' ? projectSpecs : personalSpecs;
|
let specs = dimension === 'project' ? projectSpecs : personalSpecs;
|
||||||
|
|
||||||
|
// Apply scope filter for personal specs
|
||||||
|
if (dimension === 'personal') {
|
||||||
|
if (personalScopeFilter === 'global') {
|
||||||
|
specs = globalPersonalSpecs;
|
||||||
|
} else if (personalScopeFilter === 'project') {
|
||||||
|
specs = projectPersonalSpecs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const filteredSpecs = filterSpecs(specs);
|
const filteredSpecs = filterSpecs(specs);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -144,13 +198,77 @@ export function SpecsSettingsPage() {
|
|||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* Scope filter for personal specs */}
|
||||||
|
{dimension === 'personal' && (
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Filter className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{formatMessage({ id: 'specs.filterByScope', defaultMessage: 'Filter by scope:' })}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<Button
|
||||||
|
variant={personalScopeFilter === 'all' ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPersonalScopeFilter('all')}
|
||||||
|
>
|
||||||
|
{formatMessage({ id: 'specs.scope.all', defaultMessage: 'All' })} ({personalSpecs.length})
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={personalScopeFilter === 'global' ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPersonalScopeFilter('global')}
|
||||||
|
>
|
||||||
|
<Globe className="h-3 w-3 mr-1" />
|
||||||
|
{formatMessage({ id: 'specs.scope.global', defaultMessage: 'Global' })} ({globalPersonalSpecs.length})
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant={personalScopeFilter === 'project' ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setPersonalScopeFilter('project')}
|
||||||
|
>
|
||||||
|
<Folder className="h-3 w-3 mr-1" />
|
||||||
|
{formatMessage({ id: 'specs.scope.project', defaultMessage: 'Project' })} ({projectPersonalSpecs.length})
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{/* Category filter for workflow stage */}
|
||||||
|
<div className="flex items-center gap-2">
|
||||||
|
<Layers className="h-4 w-4 text-muted-foreground" />
|
||||||
|
<span className="text-sm text-muted-foreground">
|
||||||
|
{formatMessage({ id: 'specs.filterByCategory', defaultMessage: 'Workflow stage:' })}
|
||||||
|
</span>
|
||||||
|
<div className="flex gap-2 flex-wrap">
|
||||||
|
<Button
|
||||||
|
variant={categoryFilter === 'all' ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCategoryFilter('all')}
|
||||||
|
>
|
||||||
|
{formatMessage({ id: 'specs.category.all', defaultMessage: 'All' })}
|
||||||
|
</Button>
|
||||||
|
{SPEC_CATEGORIES.map(cat => (
|
||||||
|
<Button
|
||||||
|
key={cat}
|
||||||
|
variant={categoryFilter === cat ? 'default' : 'outline'}
|
||||||
|
size="sm"
|
||||||
|
onClick={() => setCategoryFilter(cat)}
|
||||||
|
>
|
||||||
|
{formatMessage({ id: `specs.category.${cat}`, defaultMessage: cat })} ({categoryCounts[cat]})
|
||||||
|
</Button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
{/* Stats Summary */}
|
{/* Stats Summary */}
|
||||||
{statsData?.dimensions && (
|
{statsData?.dimensions && (
|
||||||
<div className="grid grid-cols-4 gap-4">
|
<div className="grid grid-cols-2 md:grid-cols-4 gap-4">
|
||||||
{Object.entries(statsData.dimensions).map(([dim, data]) => (
|
{Object.entries(statsData.dimensions).map(([dim, data]) => (
|
||||||
<Card key={dim}>
|
<Card key={dim}>
|
||||||
<CardContent className="pt-4">
|
<CardContent className="pt-4">
|
||||||
<div className="text-sm text-muted-foreground capitalize">{dim}</div>
|
<div className="text-sm text-muted-foreground">
|
||||||
|
{formatMessage({ id: `specs.dimension.${dim}`, defaultMessage: dim })}
|
||||||
|
</div>
|
||||||
<div className="text-2xl font-bold">{(data as { count: number }).count}</div>
|
<div className="text-2xl font-bold">{(data as { count: number }).count}</div>
|
||||||
<div className="text-xs text-muted-foreground">
|
<div className="text-xs text-muted-foreground">
|
||||||
{(data as { requiredCount: number }).requiredCount} {formatMessage({ id: 'specs.required', defaultMessage: 'required' })}
|
{(data as { requiredCount: number }).requiredCount} {formatMessage({ id: 'specs.required', defaultMessage: 'required' })}
|
||||||
@@ -167,7 +285,7 @@ export function SpecsSettingsPage() {
|
|||||||
<CardContent className="py-8 text-center text-muted-foreground">
|
<CardContent className="py-8 text-center text-muted-foreground">
|
||||||
{isLoading
|
{isLoading
|
||||||
? formatMessage({ id: 'specs.loading', defaultMessage: 'Loading specs...' })
|
? formatMessage({ id: 'specs.loading', defaultMessage: 'Loading specs...' })
|
||||||
: formatMessage({ id: 'specs.noSpecs', defaultMessage: 'No specs found. Create specs in .workflow/ directory.' })
|
: formatMessage({ id: 'specs.noSpecs', defaultMessage: 'No specs found. Create specs in .ccw/ directory.' })
|
||||||
}
|
}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|||||||
@@ -376,13 +376,13 @@ ${chalk.bold('EXAMPLES')}
|
|||||||
ccw spec init
|
ccw spec init
|
||||||
|
|
||||||
${chalk.gray('# Load exploration-phase specs:')}
|
${chalk.gray('# Load exploration-phase specs:')}
|
||||||
ccw spec load --keywords exploration
|
ccw spec load --category exploration
|
||||||
|
|
||||||
${chalk.gray('# Load planning-phase specs with auth topic:')}
|
${chalk.gray('# Load planning-phase specs with auth topic:')}
|
||||||
ccw spec load --keywords "planning auth"
|
ccw spec load --category "planning auth"
|
||||||
|
|
||||||
${chalk.gray('# Load execution-phase specs:')}
|
${chalk.gray('# Load execution-phase specs:')}
|
||||||
ccw spec load --keywords execution
|
ccw spec load --category execution
|
||||||
|
|
||||||
${chalk.gray('# Load specs for a topic (CLI mode):')}
|
${chalk.gray('# Load specs for a topic (CLI mode):')}
|
||||||
ccw spec load --dimension specs --keywords "auth jwt security"
|
ccw spec load --dimension specs --keywords "auth jwt security"
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
* ---
|
* ---
|
||||||
* title: "Document Title"
|
* title: "Document Title"
|
||||||
* dimension: "specs"
|
* dimension: "specs"
|
||||||
|
* category: "general" # general | exploration | planning | execution
|
||||||
* keywords: ["auth", "security"]
|
* keywords: ["auth", "security"]
|
||||||
* readMode: "required" # required | optional
|
* readMode: "required" # required | optional
|
||||||
* priority: "high" # critical | high | medium | low
|
* priority: "high" # critical | high | medium | low
|
||||||
@@ -19,21 +20,25 @@
|
|||||||
import matter from 'gray-matter';
|
import matter from 'gray-matter';
|
||||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
||||||
import { join, basename, extname, relative } from 'path';
|
import { join, basename, extname, relative } from 'path';
|
||||||
|
import { homedir } from 'os';
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Types
|
// Types
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Spec categories for workflow stage-based loading (used as keywords).
|
* Spec categories for workflow stage-based loading.
|
||||||
|
* - general: Applies to all stages (e.g. coding conventions)
|
||||||
* - exploration: Code exploration, analysis, debugging context
|
* - exploration: Code exploration, analysis, debugging context
|
||||||
* - planning: Task planning, roadmap, requirements context
|
* - planning: Task planning, roadmap, requirements context
|
||||||
* - execution: Implementation, testing, deployment context
|
* - execution: Implementation, testing, deployment context
|
||||||
*
|
*
|
||||||
* Usage: Add these as keywords in spec frontmatter, e.g.:
|
* Usage: Set category field in spec frontmatter:
|
||||||
* keywords: [exploration, auth, security]
|
* category: exploration
|
||||||
|
*
|
||||||
|
* System-level loading by stage: ccw spec load --category exploration
|
||||||
*/
|
*/
|
||||||
export const SPEC_CATEGORIES = ['exploration', 'planning', 'execution'] as const;
|
export const SPEC_CATEGORIES = ['general', 'exploration', 'planning', 'execution'] as const;
|
||||||
|
|
||||||
export type SpecCategory = typeof SPEC_CATEGORIES[number];
|
export type SpecCategory = typeof SPEC_CATEGORIES[number];
|
||||||
|
|
||||||
@@ -43,6 +48,7 @@ export type SpecCategory = typeof SPEC_CATEGORIES[number];
|
|||||||
export interface SpecFrontmatter {
|
export interface SpecFrontmatter {
|
||||||
title: string;
|
title: string;
|
||||||
dimension: string;
|
dimension: string;
|
||||||
|
category?: SpecCategory;
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
readMode: 'required' | 'optional';
|
readMode: 'required' | 'optional';
|
||||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||||
@@ -58,12 +64,16 @@ export interface SpecIndexEntry {
|
|||||||
file: string;
|
file: string;
|
||||||
/** Dimension this spec belongs to */
|
/** Dimension this spec belongs to */
|
||||||
dimension: string;
|
dimension: string;
|
||||||
/** Keywords for matching against user prompts (may include category markers) */
|
/** Workflow stage category for system-level loading */
|
||||||
|
category: SpecCategory;
|
||||||
|
/** Keywords for matching against user prompts */
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
/** Whether this spec is required or optional */
|
/** Whether this spec is required or optional */
|
||||||
readMode: 'required' | 'optional';
|
readMode: 'required' | 'optional';
|
||||||
/** Priority level for ordering */
|
/** Priority level for ordering */
|
||||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||||
|
/** Scope: global (from ~/.ccw/) or project (from .ccw/) */
|
||||||
|
scope: 'global' | 'project';
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -101,6 +111,11 @@ const VALID_READ_MODES = ['required', 'optional'] as const;
|
|||||||
*/
|
*/
|
||||||
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'] as const;
|
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'] as const;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Valid category values.
|
||||||
|
*/
|
||||||
|
const VALID_CATEGORIES = SPEC_CATEGORIES;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Directory name for spec index cache files (inside .ccw/).
|
* Directory name for spec index cache files (inside .ccw/).
|
||||||
*/
|
*/
|
||||||
@@ -149,36 +164,22 @@ export async function buildDimensionIndex(
|
|||||||
projectPath: string,
|
projectPath: string,
|
||||||
dimension: string
|
dimension: string
|
||||||
): Promise<DimensionIndex> {
|
): Promise<DimensionIndex> {
|
||||||
const dimensionDir = getDimensionDir(projectPath, dimension);
|
|
||||||
const entries: SpecIndexEntry[] = [];
|
const entries: SpecIndexEntry[] = [];
|
||||||
|
|
||||||
// If directory doesn't exist, return empty index
|
// Helper function to scan a directory and add entries
|
||||||
if (!existsSync(dimensionDir)) {
|
const scanDirectory = (dir: string, scope: 'global' | 'project') => {
|
||||||
return {
|
if (!existsSync(dir)) return;
|
||||||
dimension,
|
|
||||||
entries: [],
|
|
||||||
built_at: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Scan for .md files
|
|
||||||
let files: string[];
|
let files: string[];
|
||||||
try {
|
try {
|
||||||
files = readdirSync(dimensionDir).filter(
|
files = readdirSync(dir).filter(f => extname(f).toLowerCase() === '.md');
|
||||||
f => extname(f).toLowerCase() === '.md'
|
|
||||||
);
|
|
||||||
} catch {
|
} catch {
|
||||||
// Directory read error - return empty index
|
return;
|
||||||
return {
|
|
||||||
dimension,
|
|
||||||
entries: [],
|
|
||||||
built_at: new Date().toISOString(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const file of files) {
|
for (const file of files) {
|
||||||
const filePath = join(dimensionDir, file);
|
const filePath = join(dir, file);
|
||||||
const entry = parseSpecFile(filePath, dimension, projectPath);
|
const entry = parseSpecFile(filePath, dimension, projectPath, scope);
|
||||||
if (entry) {
|
if (entry) {
|
||||||
entries.push(entry);
|
entries.push(entry);
|
||||||
} else {
|
} else {
|
||||||
@@ -187,6 +188,17 @@ export async function buildDimensionIndex(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// For personal dimension, also scan global ~/.ccw/personal/
|
||||||
|
if (dimension === 'personal') {
|
||||||
|
const globalPersonalDir = join(homedir(), '.ccw', 'personal');
|
||||||
|
scanDirectory(globalPersonalDir, 'global');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Scan project dimension directory
|
||||||
|
const dimensionDir = getDimensionDir(projectPath, dimension);
|
||||||
|
scanDirectory(dimensionDir, 'project');
|
||||||
|
|
||||||
return {
|
return {
|
||||||
dimension,
|
dimension,
|
||||||
@@ -315,7 +327,8 @@ export async function getDimensionIndex(
|
|||||||
function parseSpecFile(
|
function parseSpecFile(
|
||||||
filePath: string,
|
filePath: string,
|
||||||
dimension: string,
|
dimension: string,
|
||||||
projectPath: string
|
projectPath: string,
|
||||||
|
scope: 'global' | 'project' = 'project'
|
||||||
): SpecIndexEntry | null {
|
): SpecIndexEntry | null {
|
||||||
let content: string;
|
let content: string;
|
||||||
try {
|
try {
|
||||||
@@ -340,10 +353,10 @@ function parseSpecFile(
|
|||||||
if (!title) {
|
if (!title) {
|
||||||
// Title is required - use filename as fallback
|
// Title is required - use filename as fallback
|
||||||
const fallbackTitle = basename(filePath, extname(filePath));
|
const fallbackTitle = basename(filePath, extname(filePath));
|
||||||
return buildEntry(fallbackTitle, filePath, dimension, projectPath, data);
|
return buildEntry(fallbackTitle, filePath, dimension, projectPath, data, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
return buildEntry(title, filePath, dimension, projectPath, data);
|
return buildEntry(title, filePath, dimension, projectPath, data, scope);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -354,12 +367,17 @@ function buildEntry(
|
|||||||
filePath: string,
|
filePath: string,
|
||||||
dimension: string,
|
dimension: string,
|
||||||
projectPath: string,
|
projectPath: string,
|
||||||
data: Record<string, unknown>
|
data: Record<string, unknown>,
|
||||||
|
scope: 'global' | 'project' = 'project'
|
||||||
): SpecIndexEntry {
|
): SpecIndexEntry {
|
||||||
// Compute relative file path from project root using path.relative
|
// Compute relative file path from project root using path.relative
|
||||||
// Normalize to forward slashes for cross-platform consistency
|
// Normalize to forward slashes for cross-platform consistency
|
||||||
const relativePath = relative(projectPath, filePath).replace(/\\/g, '/');
|
const relativePath = relative(projectPath, filePath).replace(/\\/g, '/');
|
||||||
|
|
||||||
|
// Extract category with validation (defaults to 'general')
|
||||||
|
const rawCategory = extractString(data, 'category');
|
||||||
|
const category = isValidCategory(rawCategory) ? rawCategory : 'general';
|
||||||
|
|
||||||
// Extract keywords - accept string[] or single string
|
// Extract keywords - accept string[] or single string
|
||||||
const keywords = extractStringArray(data, 'keywords');
|
const keywords = extractStringArray(data, 'keywords');
|
||||||
|
|
||||||
@@ -375,9 +393,11 @@ function buildEntry(
|
|||||||
title,
|
title,
|
||||||
file: relativePath,
|
file: relativePath,
|
||||||
dimension,
|
dimension,
|
||||||
|
category,
|
||||||
keywords,
|
keywords,
|
||||||
readMode,
|
readMode,
|
||||||
priority,
|
priority,
|
||||||
|
scope,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -435,3 +455,10 @@ function isValidReadMode(value: string | null): value is 'required' | 'optional'
|
|||||||
function isValidPriority(value: string | null): value is 'critical' | 'high' | 'medium' | 'low' {
|
function isValidPriority(value: string | null): value is 'critical' | 'high' | 'medium' | 'low' {
|
||||||
return value !== null && (VALID_PRIORITIES as readonly string[]).includes(value);
|
return value !== null && (VALID_PRIORITIES as readonly string[]).includes(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Type guard for valid category values.
|
||||||
|
*/
|
||||||
|
function isValidCategory(value: string | null): value is SpecCategory {
|
||||||
|
return value !== null && (VALID_CATEGORIES as readonly string[]).includes(value);
|
||||||
|
}
|
||||||
|
|||||||
@@ -18,6 +18,7 @@ import { join } from 'path';
|
|||||||
export interface SpecFrontmatter {
|
export interface SpecFrontmatter {
|
||||||
title: string;
|
title: string;
|
||||||
dimension: string;
|
dimension: string;
|
||||||
|
category?: 'general' | 'exploration' | 'planning' | 'execution';
|
||||||
keywords: string[];
|
keywords: string[];
|
||||||
readMode: 'required' | 'optional';
|
readMode: 'required' | 'optional';
|
||||||
priority: 'high' | 'medium' | 'low';
|
priority: 'high' | 'medium' | 'low';
|
||||||
@@ -55,7 +56,8 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: 'Coding Conventions',
|
title: 'Coding Conventions',
|
||||||
dimension: 'specs',
|
dimension: 'specs',
|
||||||
keywords: ['typescript', 'naming', 'style', 'convention', 'exploration', 'planning', 'execution'],
|
category: 'general',
|
||||||
|
keywords: ['typescript', 'naming', 'style', 'convention'],
|
||||||
readMode: 'required',
|
readMode: 'required',
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
},
|
},
|
||||||
@@ -91,7 +93,8 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: 'Architecture Constraints',
|
title: 'Architecture Constraints',
|
||||||
dimension: 'specs',
|
dimension: 'specs',
|
||||||
keywords: ['architecture', 'module', 'layer', 'pattern', 'exploration', 'planning'],
|
category: 'planning',
|
||||||
|
keywords: ['architecture', 'module', 'layer', 'pattern'],
|
||||||
readMode: 'required',
|
readMode: 'required',
|
||||||
priority: 'high',
|
priority: 'high',
|
||||||
},
|
},
|
||||||
@@ -126,6 +129,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: 'Personal Coding Style',
|
title: 'Personal Coding Style',
|
||||||
dimension: 'personal',
|
dimension: 'personal',
|
||||||
|
category: 'general',
|
||||||
keywords: ['style', 'preference'],
|
keywords: ['style', 'preference'],
|
||||||
readMode: 'optional',
|
readMode: 'optional',
|
||||||
priority: 'medium',
|
priority: 'medium',
|
||||||
@@ -153,6 +157,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
|||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: 'Tool Preferences',
|
title: 'Tool Preferences',
|
||||||
dimension: 'personal',
|
dimension: 'personal',
|
||||||
|
category: 'general',
|
||||||
keywords: ['tool', 'cli', 'editor'],
|
keywords: ['tool', 'cli', 'editor'],
|
||||||
readMode: 'optional',
|
readMode: 'optional',
|
||||||
priority: 'low',
|
priority: 'low',
|
||||||
@@ -186,16 +191,22 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
|||||||
*/
|
*/
|
||||||
export function formatFrontmatter(fm: SpecFrontmatter): string {
|
export function formatFrontmatter(fm: SpecFrontmatter): string {
|
||||||
const keywordsYaml = fm.keywords.map((k) => ` - ${k}`).join('\n');
|
const keywordsYaml = fm.keywords.map((k) => ` - ${k}`).join('\n');
|
||||||
return [
|
const lines = [
|
||||||
'---',
|
'---',
|
||||||
`title: "${fm.title}"`,
|
`title: "${fm.title}"`,
|
||||||
`dimension: ${fm.dimension}`,
|
`dimension: ${fm.dimension}`,
|
||||||
|
];
|
||||||
|
if (fm.category) {
|
||||||
|
lines.push(`category: ${fm.category}`);
|
||||||
|
}
|
||||||
|
lines.push(
|
||||||
`keywords:`,
|
`keywords:`,
|
||||||
keywordsYaml,
|
keywordsYaml,
|
||||||
`readMode: ${fm.readMode}`,
|
`readMode: ${fm.readMode}`,
|
||||||
`priority: ${fm.priority}`,
|
`priority: ${fm.priority}`,
|
||||||
'---',
|
'---'
|
||||||
].join('\n');
|
);
|
||||||
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
Reference in New Issue
Block a user