mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-03-01 15:03:57 +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).
|
||||
|
||||
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`
|
||||
- Usage: Align analysis scope and patterns with actual project technology choices
|
||||
- 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
|
||||
|
||||
**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
|
||||
{
|
||||
|
||||
@@ -79,7 +79,7 @@ if (file_exists(contextPackagePath)) {
|
||||
```javascript
|
||||
// Load project-level context (from spec system)
|
||||
// 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 projectGuidelines = projectSpecs?.coding_conventions ? projectSpecs : null;
|
||||
|
||||
|
||||
@@ -36,7 +36,7 @@ Phase 5: Fix & Verification
|
||||
## Phase 1: Bug Analysis
|
||||
|
||||
**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**:
|
||||
```javascript
|
||||
|
||||
@@ -99,7 +99,7 @@ if (isPopulated) {
|
||||
|
||||
```javascript
|
||||
// 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)
|
||||
|
||||
// Extract key info from loaded specs for generating smart questions
|
||||
|
||||
@@ -148,7 +148,7 @@ Priority: <issue.priority>
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||
2. Execute ACE searches based on issue keywords
|
||||
3. Run: ccw spec load --keywords exploration
|
||||
3. Run: ccw spec load --category exploration
|
||||
|
||||
## Exploration Focus
|
||||
- Identify files directly related to this issue
|
||||
|
||||
@@ -81,7 +81,7 @@ Session: ${sessionFolder}
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||
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)
|
||||
- **Depth**: ${strategy.depth}
|
||||
|
||||
@@ -138,7 +138,7 @@ Session: <session-folder>
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}'
|
||||
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)
|
||||
<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}"
|
||||
|
||||
# Gemini security analysis
|
||||
ccw spec load --keywords execution
|
||||
ccw spec load --category execution
|
||||
ccw cli -p "
|
||||
PURPOSE: Security audit of completed implementation
|
||||
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`):
|
||||
```bash
|
||||
ccw spec load --keywords execution
|
||||
ccw spec load --category execution
|
||||
ccw cli -p "
|
||||
PURPOSE: Architecture compliance review
|
||||
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`):
|
||||
```bash
|
||||
ccw spec load --keywords execution
|
||||
ccw spec load --category execution
|
||||
ccw cli -p "
|
||||
PURPOSE: Code quality and best practices review
|
||||
TASK: Assess code readability, maintainability, adherence to best practices
|
||||
@@ -139,7 +139,7 @@ for task_file in ${sessionPath}/.task/*.json; do
|
||||
done
|
||||
|
||||
# Cross-check implementation against requirements
|
||||
ccw spec load --keywords execution
|
||||
ccw spec load --category execution
|
||||
ccw cli -p "
|
||||
PURPOSE: Verify all requirements and acceptance criteria are met
|
||||
TASK: Cross-check implementation summaries against original requirements
|
||||
|
||||
@@ -127,10 +127,10 @@ After collecting preferences, enhance context and dispatch:
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
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)
|
||||
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)
|
||||
|
||||
## 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.
|
||||
|
||||
**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}`)
|
||||
}
|
||||
// Project guidelines (user-defined constraints from /workflow:session:solidify)
|
||||
// Loaded via: ccw spec load --keywords planning
|
||||
context.push(`### Project Guidelines\n(Loaded via ccw spec load --keywords planning)`)
|
||||
// Loaded via: ccw spec load --category 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')}`)
|
||||
|
||||
sections.push(`Complete each task according to its "Done when" checklist.`)
|
||||
|
||||
@@ -101,10 +101,10 @@ After collecting preferences, enhance context and dispatch:
|
||||
|
||||
```javascript
|
||||
// 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
|
||||
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)
|
||||
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.
|
||||
|
||||
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
|
||||
- Generate prioritized_context with user_intent alignment
|
||||
- Apply specs/*.md constraints during conflict detection
|
||||
|
||||
@@ -171,7 +171,7 @@ Session ID: ${sessionId}
|
||||
MCP Capabilities: {exa_code, exa_web, code_index}
|
||||
|
||||
## 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, build system, test framework
|
||||
|
||||
@@ -221,7 +221,7 @@ Execute complete context-search-agent workflow for TDD implementation planning:
|
||||
|
||||
### Phase 1: Initialization & Pre-Analysis
|
||||
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.
|
||||
2. **Detection**: Check for existing context-package (early exit if valid)
|
||||
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)
|
||||
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
|
||||
- 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,
|
||||
acceptance criteria, and convergence.verification MUST respect these guidelines
|
||||
- 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)
|
||||
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
|
||||
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.
|
||||
2. **Detection**: Check for existing context-package (early exit if valid)
|
||||
3. **Foundation**: Initialize CodexLens, get project structure, load docs
|
||||
|
||||
@@ -244,7 +244,7 @@ Task(
|
||||
${selectedStrategy} - ${strategyDescription}
|
||||
|
||||
## 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
|
||||
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)
|
||||
|
||||
- **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], 是否正确?需要补充什么?"
|
||||
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
||||
- If missing: Ask user: "未检测到测试框架配置,请指定测试命令(如 `npm test`, `pytest`),或输入 'skip' 跳过测试验证"
|
||||
@@ -36,7 +36,7 @@ Print formatted checklist:
|
||||
✓ 项目根目录: D:\myproject
|
||||
✓ 工作空间: .workflow/.cycle/ 就绪
|
||||
⚠ Git: 3 个未提交变更
|
||||
✓ Project specs: 已加载 (ccw spec load --keywords execution)
|
||||
✓ Project specs: 已加载 (ccw spec load --category execution)
|
||||
⚠ specs: 未找到 (已跳过)
|
||||
✓ 测试框架: 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:
|
||||
- **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
|
||||
|
||||
### 2.5 Assemble Refined Task
|
||||
|
||||
@@ -21,7 +21,7 @@ Check these items. Report results as a checklist.
|
||||
|
||||
### 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], 是否正确?需要补充什么?"
|
||||
- **Test framework**: Detect from config files (jest.config, vitest.config, pytest.ini, etc.)
|
||||
- If missing: Ask: "未检测到测试框架,请指定测试命令(如 `npm test`),或输入 'skip' 跳过"
|
||||
@@ -36,7 +36,7 @@ Print formatted checklist:
|
||||
✓ 项目根目录: D:\myproject
|
||||
✓ .workflow/ 目录就绪
|
||||
⚠ Git: 3 个未提交变更
|
||||
✓ Project specs: 已加载 (ccw spec load --keywords planning)
|
||||
✓ Project specs: 已加载 (ccw spec load --category planning)
|
||||
⚠ specs: 未找到 (Phase 1 将初始化)
|
||||
✓ 测试框架: 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:
|
||||
- **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`
|
||||
|
||||
### 2.5 Assemble Structured Description
|
||||
|
||||
@@ -85,7 +85,7 @@ Step 1: Topic Understanding
|
||||
|
||||
Step 2: Exploration (Inline, No Agents)
|
||||
├─ 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
|
||||
├─ Multi-perspective analysis (if selected, serial)
|
||||
│ ├─ Single: Comprehensive analysis
|
||||
@@ -297,7 +297,7 @@ const hasCodebase = Bash(`
|
||||
|
||||
if (hasCodebase !== 'none') {
|
||||
// 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)
|
||||
|
||||
// 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**:
|
||||
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
|
||||
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
|
||||
|
||||
**exploration-codebase.json Structure**:
|
||||
@@ -358,7 +358,7 @@ const agentIds = perspectives.map(perspective => {
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
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
|
||||
|
||||
---
|
||||
@@ -566,7 +566,7 @@ const deepDiveAgent = spawn_agent({
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
|
||||
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**:
|
||||
1. **Search for references** — Find related documentation, README files, and architecture guides
|
||||
- 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
|
||||
3. **Identify ambiguities** — List any unclear points or multiple possible interpretations
|
||||
4. **Clarify with user** — If ambiguities found, use AskUserQuestion for clarification
|
||||
|
||||
@@ -231,7 +231,7 @@ const agentId = spawn_agent({
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
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
|
||||
${taskContext}
|
||||
|
||||
@@ -268,7 +268,7 @@ const hasCodebase = bash(`
|
||||
// 2. Codebase Exploration (only when hasCodebase !== 'none')
|
||||
if (hasCodebase !== 'none') {
|
||||
// Read project metadata (if exists)
|
||||
// Run `ccw spec load --keywords planning`
|
||||
// Run `ccw spec load --category planning`
|
||||
|
||||
// Search codebase for requirement-relevant context
|
||||
// Use: mcp__ace-tool__search_context, Grep, Glob, Read
|
||||
|
||||
@@ -303,7 +303,7 @@ const agentId = spawn_agent({
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
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
|
||||
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)
|
||||
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)
|
||||
5. Read review state: ${reviewStateJsonPath}
|
||||
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")
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
### Step 2: Load Solution
|
||||
|
||||
@@ -48,7 +48,7 @@ Outputs `ISSUE_READY:{issueId}` after each solution and waits for orchestrator t
|
||||
### Step 1: Load 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
|
||||
|
||||
### Step 2: Parse Input
|
||||
@@ -81,7 +81,7 @@ spawn_agent({
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
${taskContext}
|
||||
|
||||
@@ -75,7 +75,7 @@ const contextAgentId = spawn_agent({
|
||||
|
||||
### MANDATORY FIRST STEPS (Agent Execute)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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 { useIntl } from 'react-intl';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { toast } from 'sonner';
|
||||
import { cn } from '@/lib/utils';
|
||||
import {
|
||||
@@ -28,7 +29,7 @@ import {
|
||||
Plug,
|
||||
Download,
|
||||
CheckCircle2,
|
||||
ExternalLink,
|
||||
Settings,
|
||||
} from 'lucide-react';
|
||||
import { useInstallRecommendedHooks } from '@/hooks/useSystemSettings';
|
||||
|
||||
@@ -325,32 +326,34 @@ export function InjectionControlTab({ className }: InjectionControlTabProps) {
|
||||
</CardDescription>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-4">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={handleInstallAllHooks}
|
||||
disabled={allHooksInstalled || installingHookIds.length > 0}
|
||||
>
|
||||
{allHooksInstalled ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.allHooksInstalled', defaultMessage: 'All Hooks Installed' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.installAllHooks', defaultMessage: 'Install All Hooks' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
||||
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-4">
|
||||
<Button
|
||||
onClick={handleInstallAllHooks}
|
||||
disabled={allHooksInstalled || installingHookIds.length > 0}
|
||||
>
|
||||
{allHooksInstalled ? (
|
||||
<>
|
||||
<CheckCircle2 className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.allHooksInstalled', defaultMessage: 'All Hooks Installed' })}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
<Download className="h-4 w-4 mr-2" />
|
||||
{formatMessage({ id: 'specs.installAllHooks', defaultMessage: 'Install All Hooks' })}
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
<div className="text-sm text-muted-foreground">
|
||||
{installedCount} / {RECOMMENDED_HOOKS.length}{' '}
|
||||
{formatMessage({ id: 'specs.hooksInstalled', defaultMessage: 'installed' })}
|
||||
</div>
|
||||
</div>
|
||||
<Button variant="ghost" size="sm" asChild>
|
||||
<a href="/hooks" target="_blank" rel="noopener noreferrer">
|
||||
<ExternalLink className="h-4 w-4 mr-1" />
|
||||
<Link to="/hooks">
|
||||
<Settings className="h-4 w-4 mr-1" />
|
||||
{formatMessage({ id: 'specs.manageHooks', defaultMessage: 'Manage Hooks' })}
|
||||
</a>
|
||||
</Link>
|
||||
</Button>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -23,6 +23,10 @@ import {
|
||||
Trash2,
|
||||
FileText,
|
||||
Tag,
|
||||
Eye,
|
||||
Globe,
|
||||
Folder,
|
||||
Layers,
|
||||
} from 'lucide-react';
|
||||
|
||||
// ========== Types ==========
|
||||
@@ -32,6 +36,11 @@ import {
|
||||
*/
|
||||
export type SpecDimension = 'specs' | 'personal';
|
||||
|
||||
/**
|
||||
* Spec scope type
|
||||
*/
|
||||
export type SpecScope = 'global' | 'project';
|
||||
|
||||
/**
|
||||
* Spec read mode type
|
||||
*/
|
||||
@@ -42,6 +51,11 @@ export type SpecReadMode = 'required' | 'optional';
|
||||
*/
|
||||
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
|
||||
*/
|
||||
@@ -54,6 +68,10 @@ export interface Spec {
|
||||
file: string;
|
||||
/** Spec dimension/category */
|
||||
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) */
|
||||
readMode: SpecReadMode;
|
||||
/** Priority level */
|
||||
@@ -72,6 +90,8 @@ export interface Spec {
|
||||
export interface SpecCardProps {
|
||||
/** Spec data */
|
||||
spec: Spec;
|
||||
/** Called when view content action is triggered */
|
||||
onView?: (spec: Spec) => void;
|
||||
/** Called when edit action is triggered */
|
||||
onEdit?: (spec: Spec) => void;
|
||||
/** Called when delete action is triggered */
|
||||
@@ -108,6 +128,17 @@ const priorityConfig: Record<
|
||||
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 ==========
|
||||
|
||||
/**
|
||||
@@ -115,6 +146,7 @@ const priorityConfig: Record<
|
||||
*/
|
||||
export function SpecCard({
|
||||
spec,
|
||||
onView,
|
||||
onEdit,
|
||||
onDelete,
|
||||
onToggle,
|
||||
@@ -181,6 +213,10 @@ export function SpecCard({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<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')}>
|
||||
<Edit className="mr-2 h-4 w-4" />
|
||||
{formatMessage({ id: 'specs.actions.edit' })}
|
||||
@@ -201,6 +237,29 @@ export function SpecCard({
|
||||
|
||||
{/* Badges */}
|
||||
<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">
|
||||
{formatMessage({ id: readMode.labelKey })}
|
||||
</Badge>
|
||||
|
||||
@@ -11,8 +11,10 @@ export {
|
||||
export type {
|
||||
Spec,
|
||||
SpecDimension,
|
||||
SpecScope,
|
||||
SpecReadMode,
|
||||
SpecPriority,
|
||||
SpecCategory,
|
||||
SpecCardProps,
|
||||
} from './SpecCard';
|
||||
|
||||
|
||||
@@ -350,6 +350,7 @@ export const specsSettingsKeys = {
|
||||
all: ['specsSettings'] as const,
|
||||
systemSettings: () => [...specsSettingsKeys.all, 'systemSettings'] 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 query = useQuery({
|
||||
queryKey: specsSettingsKeys.specStats(projectPath), // Reuse for specs list
|
||||
queryKey: specsSettingsKeys.specsList(projectPath),
|
||||
queryFn: () => getSpecsList(projectPath),
|
||||
staleTime,
|
||||
enabled,
|
||||
@@ -528,6 +529,7 @@ export function useRebuildSpecIndex(options: UseRebuildSpecIndexOptions = {}) {
|
||||
onSuccess: () => {
|
||||
// Invalidate specs list and stats queries to refresh data
|
||||
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 }) =>
|
||||
updateSpecFrontmatter(file, readMode, projectPath),
|
||||
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.specsList(projectPath) });
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -7268,9 +7268,11 @@ export interface SpecEntry {
|
||||
file: string;
|
||||
title: string;
|
||||
dimension: string;
|
||||
category?: 'general' | 'exploration' | 'planning' | 'execution';
|
||||
readMode: 'required' | 'optional' | 'keywords';
|
||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||
keywords: string[];
|
||||
scope: 'global' | 'project';
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,36 +10,67 @@
|
||||
"rebuildIndex": "Rebuild Index",
|
||||
"loading": "Loading...",
|
||||
"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",
|
||||
"recommendedHooksDesc": "One-click install system-preset spec injection 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",
|
||||
"installedHooksDesc": "Manage your installed hooks configuration",
|
||||
"searchHooks": "Search hooks...",
|
||||
"noHooks": "No hooks installed. Install recommended hooks above.",
|
||||
|
||||
"spec": {
|
||||
"edit": "Edit",
|
||||
"toggle": "Toggle",
|
||||
"delete": "Delete",
|
||||
"required": "Required",
|
||||
"optional": "Optional",
|
||||
"priority": {
|
||||
"critical": "Critical",
|
||||
"high": "High",
|
||||
"medium": "Medium",
|
||||
"low": "Low"
|
||||
}
|
||||
"edit": "Edit Spec",
|
||||
"toggle": "Toggle Status",
|
||||
"delete": "Delete Spec",
|
||||
"deleteConfirm": "Are you sure you want to delete this spec?",
|
||||
"title": "Spec Title",
|
||||
"keywords": "Keywords",
|
||||
"keywordsPlaceholder": "Enter keywords, separated by commas",
|
||||
"readMode": "Read Mode",
|
||||
"priority": "Priority",
|
||||
"file": "File Path"
|
||||
},
|
||||
|
||||
"hook": {
|
||||
"install": "Install",
|
||||
"edit": "Edit",
|
||||
"toggle": "Toggle",
|
||||
"delete": "Delete",
|
||||
"uninstall": "Uninstall",
|
||||
"edit": "Edit Hook",
|
||||
"toggle": "Toggle Status",
|
||||
"delete": "Delete Hook",
|
||||
"enabled": "Enabled",
|
||||
"disabled": "Disabled",
|
||||
"installed": "Installed",
|
||||
"notInstalled": "Not Installed",
|
||||
"scope": {
|
||||
"global": "Global",
|
||||
"project": "Project"
|
||||
@@ -48,21 +79,142 @@
|
||||
"SessionStart": "Session Start",
|
||||
"UserPromptSubmit": "Prompt Submit",
|
||||
"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": {
|
||||
"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",
|
||||
"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",
|
||||
"warnThresholdLabel": "Warning Threshold (characters)",
|
||||
"warnThresholdHelp": "A warning will be displayed when injection length exceeds this value.",
|
||||
"percentage": "Usage",
|
||||
"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",
|
||||
"warning": "Warning",
|
||||
"normal": "Normal"
|
||||
"overLimitDescription": "Current injection content exceeds maximum length of {max} characters. Excess content will be truncated.",
|
||||
"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": {
|
||||
@@ -70,6 +222,7 @@
|
||||
"description": "Configure personal spec defaults and system settings",
|
||||
"personalSpecDefaults": "Personal Spec Defaults",
|
||||
"defaultReadMode": "Default Read Mode",
|
||||
"defaultReadModeHelp": "Default read mode for newly created personal specs",
|
||||
"autoEnable": "Auto Enable",
|
||||
"autoEnableDescription": "Automatically enable newly created personal specs"
|
||||
},
|
||||
@@ -77,17 +230,25 @@
|
||||
"dialog": {
|
||||
"cancel": "Cancel",
|
||||
"save": "Save",
|
||||
"close": "Close",
|
||||
"editSpec": "Edit Spec",
|
||||
"editHook": "Edit Hook",
|
||||
"confirmDelete": "Confirm Delete",
|
||||
"specTitle": "Spec Title",
|
||||
"keywords": "Keywords",
|
||||
"readMode": "Read Mode",
|
||||
"priority": "Priority",
|
||||
"hookName": "Hook Name",
|
||||
"hookEvent": "Event",
|
||||
"hookEvent": "Trigger Event",
|
||||
"hookCommand": "Command",
|
||||
"hookScope": "Scope",
|
||||
"hookTimeout": "Timeout (ms)",
|
||||
"hookFailMode": "Fail Mode"
|
||||
},
|
||||
|
||||
"form": {
|
||||
"readMode": "Read Mode",
|
||||
"priority": "Priority",
|
||||
"keywords": "Keywords"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,16 +10,47 @@
|
||||
"rebuildIndex": "重建索引",
|
||||
"loading": "加载中...",
|
||||
"noSpecs": "未找到规范。请在 .ccw/ 目录中创建规范文件。",
|
||||
"required": "必读",
|
||||
|
||||
"dimension": {
|
||||
"specs": "项目规范",
|
||||
"personal": "个人规范"
|
||||
},
|
||||
|
||||
"scope": {
|
||||
"all": "全部",
|
||||
"global": "全局",
|
||||
"project": "项目"
|
||||
},
|
||||
"filterByScope": "按范围筛选:",
|
||||
|
||||
"category": {
|
||||
"general": "通用",
|
||||
"exploration": "探索",
|
||||
"planning": "规划",
|
||||
"execution": "执行"
|
||||
},
|
||||
|
||||
"recommendedHooks": "推荐钩子",
|
||||
"recommendedHooksDesc": "一键安装系统预设的规范注入钩子",
|
||||
"installAll": "安装所有推荐钩子",
|
||||
"installAllHooks": "安装所有钩子",
|
||||
"allHooksInstalled": "已安装所有钩子",
|
||||
"hooksInstalled": "已安装",
|
||||
"manageHooks": "管理钩子",
|
||||
"hookEvent": "事件",
|
||||
"hookScope": "范围",
|
||||
"install": "安装",
|
||||
"installed": "已安装",
|
||||
"installing": "安装中...",
|
||||
|
||||
"installedHooks": "已安装钩子",
|
||||
"installedHooksDesc": "管理已安装的钩子配置",
|
||||
"searchHooks": "搜索钩子...",
|
||||
"noHooks": "未安装钩子。请安装上方的推荐钩子。",
|
||||
|
||||
"actions": {
|
||||
"view": "查看内容",
|
||||
"edit": "编辑",
|
||||
"delete": "删除",
|
||||
"reset": "重置",
|
||||
@@ -45,6 +76,7 @@
|
||||
},
|
||||
|
||||
"spec": {
|
||||
"view": "查看内容",
|
||||
"edit": "编辑规范",
|
||||
"toggle": "切换状态",
|
||||
"delete": "删除规范",
|
||||
@@ -57,6 +89,23 @@
|
||||
"file": "文件路径"
|
||||
},
|
||||
|
||||
"content": {
|
||||
"edit": "编辑",
|
||||
"view": "查看",
|
||||
"metadata": "元数据",
|
||||
"markdownContent": "Markdown 内容",
|
||||
"noContent": "无内容",
|
||||
"editHint": "编辑完整的 Markdown 内容(包括 frontmatter)。frontmatter 的更改将反映到规范元数据中。",
|
||||
"placeholder": "# 规范标题\n\n内容..."
|
||||
},
|
||||
|
||||
"common": {
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"saving": "保存中...",
|
||||
"close": "关闭"
|
||||
},
|
||||
|
||||
"hook": {
|
||||
"install": "安装",
|
||||
"uninstall": "卸载",
|
||||
@@ -88,6 +137,9 @@
|
||||
},
|
||||
|
||||
"hooks": {
|
||||
"installSuccess": "钩子安装成功",
|
||||
"installError": "钩子安装失败",
|
||||
"installAllSuccess": "所有钩子安装成功",
|
||||
"dialog": {
|
||||
"createTitle": "创建钩子",
|
||||
"editTitle": "编辑钩子",
|
||||
@@ -122,6 +174,12 @@
|
||||
"continue": "继续执行",
|
||||
"warn": "显示警告",
|
||||
"block": "阻止操作"
|
||||
},
|
||||
"validation": {
|
||||
"nameRequired": "名称为必填项",
|
||||
"commandRequired": "命令为必填项",
|
||||
"timeoutMin": "最小超时时间为 1000ms",
|
||||
"timeoutMax": "最大超时时间为 300000ms"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -133,18 +191,8 @@
|
||||
"hookFailMode": "命令执行失败时的处理方式"
|
||||
},
|
||||
|
||||
"common": {
|
||||
"cancel": "取消",
|
||||
"save": "保存",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"reset": "重置",
|
||||
"confirm": "确认"
|
||||
},
|
||||
|
||||
"injection": {
|
||||
"title": "注入控制",
|
||||
"description": "监控和管理规范注入长度",
|
||||
"statusTitle": "当前注入状态",
|
||||
"settingsTitle": "注入控制设置",
|
||||
"settingsDescription": "配置如何将规范内容注入到 AI 上下文中。",
|
||||
@@ -198,5 +246,11 @@
|
||||
"hookScope": "作用域",
|
||||
"hookTimeout": "超时时间(ms)",
|
||||
"hookFailMode": "失败模式"
|
||||
},
|
||||
|
||||
"form": {
|
||||
"readMode": "读取模式",
|
||||
"priority": "优先级",
|
||||
"keywords": "关键词"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
*
|
||||
* Main page for managing spec settings, injection control, and global 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 { 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 { Button } from '@/components/ui/Button';
|
||||
import { Input } from '@/components/ui/Input';
|
||||
import { ScrollText, User, Gauge, Settings, RefreshCw, Search } from 'lucide-react';
|
||||
import { SpecCard, SpecDialog, SpecContentDialog, type Spec, type SpecFormData } from '@/components/specs';
|
||||
import { ScrollText, User, Gauge, Settings, RefreshCw, Search, Globe, Folder, Filter, Layers } from 'lucide-react';
|
||||
import { SpecCard, SpecDialog, SpecContentDialog, type Spec, type SpecFormData, type SpecCategory } from '@/components/specs';
|
||||
import { InjectionControlTab } from '@/components/specs/InjectionControlTab';
|
||||
import { GlobalSettingsTab } from '@/components/specs/GlobalSettingsTab';
|
||||
import { useSpecStats, useSpecsList, useRebuildSpecIndex } from '@/hooks/useSystemSettings';
|
||||
@@ -19,6 +20,11 @@ import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
|
||||
import type { SpecEntry } from '@/lib/api';
|
||||
|
||||
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
|
||||
function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
|
||||
@@ -26,6 +32,8 @@ function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
|
||||
id: entry.file,
|
||||
title: entry.title,
|
||||
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,
|
||||
readMode: entry.readMode as Spec['readMode'],
|
||||
priority: entry.priority as Spec['priority'],
|
||||
@@ -39,8 +47,12 @@ export function SpecsSettingsPage() {
|
||||
const projectPath = useWorkflowStore(selectProjectPath);
|
||||
const [activeTab, setActiveTab] = useState<SettingsTab>('project-specs');
|
||||
const [searchQuery, setSearchQuery] = useState('');
|
||||
const [personalScopeFilter, setPersonalScopeFilter] = useState<PersonalScopeFilter>('all');
|
||||
const [categoryFilter, setCategoryFilter] = useState<CategoryFilter>('all');
|
||||
const [editDialogOpen, setEditDialogOpen] = useState(false);
|
||||
const [contentDialogOpen, setContentDialogOpen] = useState(false);
|
||||
const [editingSpec, setEditingSpec] = useState<Spec | null>(null);
|
||||
const [viewingSpec, setViewingSpec] = useState<Spec | null>(null);
|
||||
|
||||
// Fetch real data
|
||||
const { data: specsListData, isLoading: specsLoading, refetch: refetchSpecs } = useSpecsList({ projectPath });
|
||||
@@ -48,26 +60,50 @@ export function SpecsSettingsPage() {
|
||||
const rebuildMutation = useRebuildSpecIndex();
|
||||
|
||||
// Convert specs data to display format
|
||||
const { projectSpecs, personalSpecs } = useMemo(() => {
|
||||
const { projectSpecs, personalSpecs, globalPersonalSpecs, projectPersonalSpecs, categoryCounts } = useMemo(() => {
|
||||
if (!specsListData?.specs) {
|
||||
return { projectSpecs: [], personalSpecs: [] };
|
||||
return {
|
||||
projectSpecs: [],
|
||||
personalSpecs: [],
|
||||
globalPersonalSpecs: [],
|
||||
projectPersonalSpecs: [],
|
||||
categoryCounts: { general: 0, exploration: 0, planning: 0, execution: 0 }
|
||||
};
|
||||
}
|
||||
|
||||
const specs: 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 entry of entries) {
|
||||
const spec = specEntryToSpec(entry, dimension);
|
||||
// Count by category
|
||||
if (spec.category) {
|
||||
counts[spec.category]++;
|
||||
}
|
||||
if (dimension === 'personal') {
|
||||
personal.push(spec);
|
||||
if (spec.scope === 'global') {
|
||||
globalPersonal.push(spec);
|
||||
} else {
|
||||
projectPersonal.push(spec);
|
||||
}
|
||||
} else {
|
||||
specs.push(spec);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return { projectSpecs: specs, personalSpecs: personal };
|
||||
return {
|
||||
projectSpecs: specs,
|
||||
personalSpecs: personal,
|
||||
globalPersonalSpecs: globalPersonal,
|
||||
projectPersonalSpecs: projectPersonal,
|
||||
categoryCounts: counts
|
||||
};
|
||||
}, [specsListData]);
|
||||
|
||||
const isLoading = specsLoading;
|
||||
@@ -113,16 +149,34 @@ export function SpecsSettingsPage() {
|
||||
};
|
||||
|
||||
const filterSpecs = (specs: Spec[]) => {
|
||||
if (!searchQuery.trim()) return specs;
|
||||
const query = searchQuery.toLowerCase();
|
||||
return specs.filter(spec =>
|
||||
spec.title.toLowerCase().includes(query) ||
|
||||
spec.keywords.some(k => k.toLowerCase().includes(query))
|
||||
);
|
||||
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();
|
||||
result = result.filter(spec =>
|
||||
spec.title.toLowerCase().includes(query) ||
|
||||
spec.keywords.some(k => k.toLowerCase().includes(query))
|
||||
);
|
||||
}
|
||||
return result;
|
||||
};
|
||||
|
||||
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);
|
||||
|
||||
return (
|
||||
@@ -144,13 +198,77 @@ export function SpecsSettingsPage() {
|
||||
</Button>
|
||||
</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 */}
|
||||
{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]) => (
|
||||
<Card key={dim}>
|
||||
<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-xs text-muted-foreground">
|
||||
{(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">
|
||||
{isLoading
|
||||
? 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>
|
||||
</Card>
|
||||
|
||||
@@ -376,13 +376,13 @@ ${chalk.bold('EXAMPLES')}
|
||||
ccw spec init
|
||||
|
||||
${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:')}
|
||||
ccw spec load --keywords "planning auth"
|
||||
ccw spec load --category "planning auth"
|
||||
|
||||
${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):')}
|
||||
ccw spec load --dimension specs --keywords "auth jwt security"
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
* ---
|
||||
* title: "Document Title"
|
||||
* dimension: "specs"
|
||||
* category: "general" # general | exploration | planning | execution
|
||||
* keywords: ["auth", "security"]
|
||||
* readMode: "required" # required | optional
|
||||
* priority: "high" # critical | high | medium | low
|
||||
@@ -19,21 +20,25 @@
|
||||
import matter from 'gray-matter';
|
||||
import { readFileSync, writeFileSync, existsSync, mkdirSync, readdirSync } from 'fs';
|
||||
import { join, basename, extname, relative } from 'path';
|
||||
import { homedir } from 'os';
|
||||
|
||||
// ============================================================================
|
||||
// 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
|
||||
* - planning: Task planning, roadmap, requirements context
|
||||
* - execution: Implementation, testing, deployment context
|
||||
*
|
||||
* Usage: Add these as keywords in spec frontmatter, e.g.:
|
||||
* keywords: [exploration, auth, security]
|
||||
* Usage: Set category field in spec frontmatter:
|
||||
* 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];
|
||||
|
||||
@@ -43,6 +48,7 @@ export type SpecCategory = typeof SPEC_CATEGORIES[number];
|
||||
export interface SpecFrontmatter {
|
||||
title: string;
|
||||
dimension: string;
|
||||
category?: SpecCategory;
|
||||
keywords: string[];
|
||||
readMode: 'required' | 'optional';
|
||||
priority: 'critical' | 'high' | 'medium' | 'low';
|
||||
@@ -58,12 +64,16 @@ export interface SpecIndexEntry {
|
||||
file: string;
|
||||
/** Dimension this spec belongs to */
|
||||
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[];
|
||||
/** Whether this spec is required or optional */
|
||||
readMode: 'required' | 'optional';
|
||||
/** Priority level for ordering */
|
||||
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;
|
||||
|
||||
/**
|
||||
* Valid category values.
|
||||
*/
|
||||
const VALID_CATEGORIES = SPEC_CATEGORIES;
|
||||
|
||||
/**
|
||||
* Directory name for spec index cache files (inside .ccw/).
|
||||
*/
|
||||
@@ -149,45 +164,42 @@ export async function buildDimensionIndex(
|
||||
projectPath: string,
|
||||
dimension: string
|
||||
): Promise<DimensionIndex> {
|
||||
const dimensionDir = getDimensionDir(projectPath, dimension);
|
||||
const entries: SpecIndexEntry[] = [];
|
||||
|
||||
// If directory doesn't exist, return empty index
|
||||
if (!existsSync(dimensionDir)) {
|
||||
return {
|
||||
dimension,
|
||||
entries: [],
|
||||
built_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
// Helper function to scan a directory and add entries
|
||||
const scanDirectory = (dir: string, scope: 'global' | 'project') => {
|
||||
if (!existsSync(dir)) return;
|
||||
|
||||
// Scan for .md files
|
||||
let files: string[];
|
||||
try {
|
||||
files = readdirSync(dimensionDir).filter(
|
||||
f => extname(f).toLowerCase() === '.md'
|
||||
);
|
||||
} catch {
|
||||
// Directory read error - return empty index
|
||||
return {
|
||||
dimension,
|
||||
entries: [],
|
||||
built_at: new Date().toISOString(),
|
||||
};
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(dimensionDir, file);
|
||||
const entry = parseSpecFile(filePath, dimension, projectPath);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`[spec-index-builder] Skipping malformed spec file: ${file}\n`
|
||||
);
|
||||
let files: string[];
|
||||
try {
|
||||
files = readdirSync(dir).filter(f => extname(f).toLowerCase() === '.md');
|
||||
} catch {
|
||||
return;
|
||||
}
|
||||
|
||||
for (const file of files) {
|
||||
const filePath = join(dir, file);
|
||||
const entry = parseSpecFile(filePath, dimension, projectPath, scope);
|
||||
if (entry) {
|
||||
entries.push(entry);
|
||||
} else {
|
||||
process.stderr.write(
|
||||
`[spec-index-builder] Skipping malformed spec file: ${file}\n`
|
||||
);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// 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 {
|
||||
dimension,
|
||||
entries,
|
||||
@@ -315,7 +327,8 @@ export async function getDimensionIndex(
|
||||
function parseSpecFile(
|
||||
filePath: string,
|
||||
dimension: string,
|
||||
projectPath: string
|
||||
projectPath: string,
|
||||
scope: 'global' | 'project' = 'project'
|
||||
): SpecIndexEntry | null {
|
||||
let content: string;
|
||||
try {
|
||||
@@ -340,10 +353,10 @@ function parseSpecFile(
|
||||
if (!title) {
|
||||
// Title is required - use filename as fallback
|
||||
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,
|
||||
dimension: string,
|
||||
projectPath: string,
|
||||
data: Record<string, unknown>
|
||||
data: Record<string, unknown>,
|
||||
scope: 'global' | 'project' = 'project'
|
||||
): SpecIndexEntry {
|
||||
// Compute relative file path from project root using path.relative
|
||||
// Normalize to forward slashes for cross-platform consistency
|
||||
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
|
||||
const keywords = extractStringArray(data, 'keywords');
|
||||
|
||||
@@ -375,9 +393,11 @@ function buildEntry(
|
||||
title,
|
||||
file: relativePath,
|
||||
dimension,
|
||||
category,
|
||||
keywords,
|
||||
readMode,
|
||||
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' {
|
||||
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 {
|
||||
title: string;
|
||||
dimension: string;
|
||||
category?: 'general' | 'exploration' | 'planning' | 'execution';
|
||||
keywords: string[];
|
||||
readMode: 'required' | 'optional';
|
||||
priority: 'high' | 'medium' | 'low';
|
||||
@@ -55,7 +56,8 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
||||
frontmatter: {
|
||||
title: 'Coding Conventions',
|
||||
dimension: 'specs',
|
||||
keywords: ['typescript', 'naming', 'style', 'convention', 'exploration', 'planning', 'execution'],
|
||||
category: 'general',
|
||||
keywords: ['typescript', 'naming', 'style', 'convention'],
|
||||
readMode: 'required',
|
||||
priority: 'high',
|
||||
},
|
||||
@@ -91,7 +93,8 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
||||
frontmatter: {
|
||||
title: 'Architecture Constraints',
|
||||
dimension: 'specs',
|
||||
keywords: ['architecture', 'module', 'layer', 'pattern', 'exploration', 'planning'],
|
||||
category: 'planning',
|
||||
keywords: ['architecture', 'module', 'layer', 'pattern'],
|
||||
readMode: 'required',
|
||||
priority: 'high',
|
||||
},
|
||||
@@ -126,6 +129,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
||||
frontmatter: {
|
||||
title: 'Personal Coding Style',
|
||||
dimension: 'personal',
|
||||
category: 'general',
|
||||
keywords: ['style', 'preference'],
|
||||
readMode: 'optional',
|
||||
priority: 'medium',
|
||||
@@ -153,6 +157,7 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
||||
frontmatter: {
|
||||
title: 'Tool Preferences',
|
||||
dimension: 'personal',
|
||||
category: 'general',
|
||||
keywords: ['tool', 'cli', 'editor'],
|
||||
readMode: 'optional',
|
||||
priority: 'low',
|
||||
@@ -186,16 +191,22 @@ export const SEED_DOCS: Map<string, SeedDoc[]> = new Map([
|
||||
*/
|
||||
export function formatFrontmatter(fm: SpecFrontmatter): string {
|
||||
const keywordsYaml = fm.keywords.map((k) => ` - ${k}`).join('\n');
|
||||
return [
|
||||
const lines = [
|
||||
'---',
|
||||
`title: "${fm.title}"`,
|
||||
`dimension: ${fm.dimension}`,
|
||||
];
|
||||
if (fm.category) {
|
||||
lines.push(`category: ${fm.category}`);
|
||||
}
|
||||
lines.push(
|
||||
`keywords:`,
|
||||
keywordsYaml,
|
||||
`readMode: ${fm.readMode}`,
|
||||
`priority: ${fm.priority}`,
|
||||
'---',
|
||||
].join('\n');
|
||||
'---'
|
||||
);
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
Reference in New Issue
Block a user