feat: Enhance spec management with new hooks and settings features

- Updated test cycle execution steps to streamline agent execution.
- Improved HookDialog component with enhanced validation messages and localization.
- Introduced SpecDialog component for better spec management.
- Added new hooks for fetching and updating specs list and frontmatter.
- Implemented API functions for specs list retrieval and index rebuilding.
- Added localization support for new specs settings and hooks.
- Enhanced SpecsSettingsPage to manage project and personal specs effectively.
- Updated CLI commands to support keyword-based spec loading.
- Improved spec index builder to categorize specs by workflow stages.
This commit is contained in:
catlog22
2026-02-26 22:52:33 +08:00
parent 6155fcc7b8
commit 151b81ee4a
51 changed files with 731 additions and 690 deletions

View File

@@ -54,14 +54,11 @@ Phase 4: Output Generation
- Other schemas as specified in prompt
Read and memorize schema requirements BEFORE any analysis begins (feeds Phase 3 validation).
3. **Project Context Loading** (from init.md products):
- Read `.workflow/project-tech.json` (if exists):
3. **Project Context Loading** (from spec system):
- Load exploration specs using: `ccw spec load --keywords exploration`
- Extract: `tech_stack`, `architecture`, `key_components`, `overview`
- Usage: Align analysis scope and patterns with actual project technology choices
- Read `.workflow/specs/*.md` (if exists):
- Extract: `conventions`, `constraints`, `quality_rules`, `learnings`
- Usage: Apply as constraints during pattern analysis, integration point evaluation, and recommendations
- If either file does not exist, proceed with fresh analysis (no error).
- If no specs are returned, proceed with fresh analysis (no error).
4. **Task Keyword Search** (initial file discovery):
```bash

View File

@@ -54,9 +54,8 @@ When invoked with `process_docs: true` in input context:
## Input Context
**Project Context** (read from init.md products at startup):
- `.workflow/project-tech.json` → tech_stack, architecture, key_components
- `.workflow/specs/*.md` → conventions, constraints, quality_rules
**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
```javascript
{

View File

@@ -77,14 +77,11 @@ if (file_exists(contextPackagePath)) {
**1.1b Project Context Loading** (MANDATORY):
```javascript
// Load project-level context (from workflow:init products)
// Load project-level context (from spec system)
// These provide foundational constraints for ALL context gathering
const projectTech = file_exists('.workflow/project-tech.json')
? JSON.parse(Read('.workflow/project-tech.json')) // tech_stack, architecture_type, key_components, build_system, test_framework
: null;
const projectGuidelines = file_exists('.workflow/specs/*.md')
? JSON.parse(Read('.workflow/specs/*.md')) // coding_conventions, naming_rules, forbidden_patterns, quality_gates
: null;
const projectSpecs = Bash('ccw spec load --keywords "exploration architecture" --stdin');
const projectTech = projectSpecs?.tech_stack ? projectSpecs : null;
const projectGuidelines = projectSpecs?.coding_conventions ? projectSpecs : null;
// Usage:
// - projectTech → Populate project_context fields (tech_stack, architecture_patterns)

View File

@@ -35,9 +35,8 @@ Phase 5: Fix & Verification
## Phase 1: Bug Analysis
**Load Project Context** (from init.md products):
- Read `.workflow/project-tech.json` (if exists) for tech stack context
- Read `.workflow/specs/*.md` (if exists) for coding constraints
**Load Project Context** (from spec system):
- Load exploration specs using: `ccw spec load --keywords exploration` for tech stack context and coding constraints
**Session Setup**:
```javascript

View File

@@ -98,22 +98,18 @@ if (isPopulated) {
### Step 2: Load Project Context
```javascript
const projectTech = JSON.parse(Read('.workflow/project-tech.json'))
// Load project context via ccw spec load for planning context
const projectContext = Bash('ccw spec load --keywords planning 2>/dev/null || echo "{}"')
const specData = JSON.parse(projectContext)
// Extract key info for generating smart questions
const languages = projectTech.technology_analysis?.technology_stack?.languages
|| projectTech.overview?.technology_stack?.languages || []
// Extract key info from loaded specs for generating smart questions
const languages = specData.overview?.technology_stack?.languages || []
const primaryLang = languages.find(l => l.primary)?.name || languages[0]?.name || 'Unknown'
const frameworks = projectTech.technology_analysis?.technology_stack?.frameworks
|| projectTech.overview?.technology_stack?.frameworks || []
const testFrameworks = projectTech.technology_analysis?.technology_stack?.test_frameworks
|| projectTech.overview?.technology_stack?.test_frameworks || []
const archStyle = projectTech.technology_analysis?.architecture?.style
|| projectTech.overview?.architecture?.style || 'Unknown'
const archPatterns = projectTech.technology_analysis?.architecture?.patterns
|| projectTech.overview?.architecture?.patterns || []
const buildTools = projectTech.technology_analysis?.technology_stack?.build_tools
|| projectTech.overview?.technology_stack?.build_tools || []
const frameworks = specData.overview?.technology_stack?.frameworks || []
const testFrameworks = specData.overview?.technology_stack?.test_frameworks || []
const archStyle = specData.overview?.architecture?.style || 'Unknown'
const archPatterns = specData.overview?.architecture?.patterns || []
const buildTools = specData.overview?.technology_stack?.build_tools || []
```
### Step 3: Multi-Round Interactive Questionnaire

View File

@@ -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. Read: .workflow/project-tech.json (if exists)
3. Run: ccw spec load --keywords exploration
## Exploration Focus
- Identify files directly related to this issue

View File

@@ -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. Read: .workflow/project-tech.json (if exists)
3. Run: ccw spec load --keywords exploration
## Exploration Focus (${perspective} angle)
- **Depth**: ${strategy.depth}

View File

@@ -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. Read: .workflow/project-tech.json (if exists)
3. Run: ccw spec load --keywords exploration
## Exploration Focus (<perspective> angle)
<dimensions map to exploration focus areas>

View File

@@ -93,10 +93,11 @@ 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 cli -p "
PURPOSE: Security audit of completed implementation
TASK: Review code for security vulnerabilities, insecure patterns, auth/authz issues
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../specs/*.md
CONTEXT: @.summaries/IMPL-*.md,../..
EXPECTED: Security findings report with severity levels
RULES: Focus on OWASP Top 10, authentication, authorization, data validation, injection risks
" --tool gemini --mode write --cd ${sessionPath}
@@ -104,10 +105,11 @@ RULES: Focus on OWASP Top 10, authentication, authorization, data validation, in
**Architecture Review** (`architecture`):
```bash
ccw spec load --keywords execution
ccw cli -p "
PURPOSE: Architecture compliance review
TASK: Evaluate adherence to architectural patterns, identify technical debt, review design decisions
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../specs/*.md
CONTEXT: @.summaries/IMPL-*.md,../..
EXPECTED: Architecture assessment with recommendations
RULES: Check for patterns, separation of concerns, modularity, scalability
" --tool qwen --mode write --cd ${sessionPath}
@@ -115,10 +117,11 @@ RULES: Check for patterns, separation of concerns, modularity, scalability
**Quality Review** (`quality`):
```bash
ccw spec load --keywords execution
ccw cli -p "
PURPOSE: Code quality and best practices review
TASK: Assess code readability, maintainability, adherence to best practices
CONTEXT: @.summaries/IMPL-*.md,../.. @../../project-tech.json @../../specs/*.md
CONTEXT: @.summaries/IMPL-*.md,../..
EXPECTED: Quality assessment with improvement suggestions
RULES: Check for code smells, duplication, complexity, naming conventions
" --tool gemini --mode write --cd ${sessionPath}
@@ -136,10 +139,11 @@ for task_file in ${sessionPath}/.task/*.json; do
done
# Cross-check implementation against requirements
ccw spec load --keywords execution
ccw cli -p "
PURPOSE: Verify all requirements and acceptance criteria are met
TASK: Cross-check implementation summaries against original requirements
CONTEXT: @.task/IMPL-*.json,.summaries/IMPL-*.md,../.. @../../project-tech.json @../../specs/*.md
CONTEXT: @.task/IMPL-*.json,.summaries/IMPL-*.md,../..
EXPECTED:
- Requirements coverage matrix
- Acceptance criteria verification

View File

@@ -126,17 +126,11 @@ if (autoYes) {
After collecting preferences, enhance context and dispatch:
```javascript
// Step 1: Check for project context files
const hasProjectTech = fileExists('.workflow/project-tech.json')
const hasProjectGuidelines = fileExists('.workflow/specs/*.md')
// Step 1: Load project context via ccw spec
Bash('ccw spec load --keywords planning')
// Step 2: Log available context
if (hasProjectTech) {
console.log('Project tech context available: .workflow/project-tech.json')
}
if (hasProjectGuidelines) {
console.log('Project guidelines available: .workflow/specs/*.md')
}
console.log('Project context loaded via: ccw spec load --keywords planning')
// Step 3: Dispatch to phase (workflowPreferences available as context)
if (mode === 'plan') {

View File

@@ -494,9 +494,9 @@ Generate implementation plan and write plan.json.
## Output Schema Reference
Execute: cat ~/.ccw/workflows/cli-templates/schemas/plan-overview-base-schema.json (get schema reference before generating plan)
## Project Context (MANDATORY - Read Both Files)
1. Read: .workflow/project-tech.json (technology stack, architecture, key components)
2. Read: .workflow/specs/*.md (user-defined constraints and conventions)
## Project Context (MANDATORY - Load via ccw spec)
Execute: ccw spec load --keywords planning
This loads technology stack, architecture, key components, and user-defined constraints/conventions.
**CRITICAL**: All generated tasks MUST comply with constraints in specs/*.md

View File

@@ -485,7 +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)
context.push(`### Project Guidelines\n@.workflow/specs/*.md`)
// Loaded via: ccw spec load --keywords planning
context.push(`### Project Guidelines\n(Loaded via ccw spec load --keywords planning)`)
if (context.length > 0) sections.push(`## Context\n${context.join('\n\n')}`)
sections.push(`Complete each task according to its "Done when" checklist.`)

View File

@@ -100,17 +100,11 @@ if (autoYes) {
After collecting preferences, enhance context and dispatch:
```javascript
// Step 1: Check for project context files
const hasProjectTech = fileExists('.workflow/project-tech.json')
const hasProjectGuidelines = fileExists('.workflow/specs/*.md')
// Step 1: Load project context via ccw spec
Bash('ccw spec load --keywords planning')
// Step 2: Log available context
if (hasProjectTech) {
console.log('Project tech context available: .workflow/project-tech.json')
}
if (hasProjectGuidelines) {
console.log('Project guidelines available: .workflow/specs/*.md')
}
console.log('Project context loaded via: ccw spec load --keywords planning')
// Step 3: Dispatch to phase (workflowPreferences available as context)
if (mode === 'plan') {

View File

@@ -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:
- Load project-tech.json and specs/*.md FIRST (per your spec Phase 1.1b)
- Run: ccw spec load --keywords 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

View File

@@ -170,21 +170,21 @@ Output:
Session ID: ${sessionId}
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:
## PROJECT CONTEXT (MANDATORY - load via ccw spec)
Execute: ccw spec load --keywords planning
1. **.workflow/project-tech.json** (auto-generated tech analysis)
- Contains: tech_stack, architecture_type, key_components, build_system, test_framework
- Usage: Populate plan.json shared_context, align task tech choices, set correct test commands
- If missing: Fall back to context-package.project_context
This loads:
- Technology stack, architecture, key components, build system, test framework
- User-maintained rules and constraints (coding_conventions, naming_rules, forbidden_patterns, quality_gates)
2. **.workflow/specs/*.md** (user-maintained rules and constraints)
- Contains: coding_conventions, naming_rules, forbidden_patterns, quality_gates, custom_constraints
- Usage: 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)
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
Loading order: project-tech.json → specs/*.md → planning-notes.md → context-package.json
If spec load returns empty: Proceed normally with context-package.project_context
Loading order: ccw spec load → planning-notes.md → context-package.json
## USER CONFIGURATION (from Step 4.0)
Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli

View File

@@ -221,8 +221,7 @@ Execute complete context-search-agent workflow for TDD implementation planning:
### Phase 1: Initialization & Pre-Analysis
1. **Project State Loading**:
- Read and parse .workflow/project-tech.json. Use its overview section as the foundational project_context.
- Read and parse .workflow/specs/*.md. Load conventions, constraints, and learnings into a project_guidelines section.
- Run: \`ccw spec load --keywords 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

View File

@@ -231,18 +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. **.workflow/project-tech.json** (auto-generated tech analysis)
- Contains: tech_stack, architecture_type, key_components, build_system, test_framework
1. **ccw spec load --keywords 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
- If missing: Fall back to context-package.project_context
2. **.workflow/specs/*.md** (user-maintained rules and constraints)
- Contains: coding_conventions, naming_rules, forbidden_patterns, quality_gates, custom_constraints
- Usage: 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
- If empty/missing: No additional constraints (proceed normally)
Loading order: project-tech.json → specs/*.md → planning-notes.md → context-package.json
Loading order: \`ccw spec load --keywords execution\` → planning-notes.md → context-package.json
## USER CONFIGURATION (from Phase 0)
Execution Method: ${userConfig.executionMethod} // agent|hybrid|cli

View File

@@ -345,8 +345,7 @@ Execute complete context-search-agent workflow for implementation planning:
### Phase 1: Initialization & Pre-Analysis
1. **Project State Loading**:
- Read and parse .workflow/project-tech.json. Use its overview section as the foundational project_context.
- Read and parse .workflow/specs/*.md. Load conventions, constraints, and learnings into a project_guidelines section.
- Run: \`ccw spec load --keywords 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

View File

@@ -244,8 +244,7 @@ Task(
${selectedStrategy} - ${strategyDescription}
## PROJECT CONTEXT (MANDATORY)
1. Read: .workflow/project-tech.json (tech stack, test framework, build system)
2. Read: .workflow/specs/*.md (constraints — apply as HARD CONSTRAINTS on fixes)
1. Run: \`ccw spec load --keywords execution\` (tech stack, test framework, build system, constraints)
## MANDATORY FIRST STEPS
1. Read test results: ${session.test_results_path}

View File

@@ -21,10 +21,8 @@ Check these items. Report results as a checklist.
### 1.2 Strongly Recommended (warn if missing)
- **project-tech.json**: Check `{projectRoot}/.workflow/project-tech.json`
- If missing: Read `package.json` / `tsconfig.json` / `pyproject.toml` and generate a minimal version. Ask user: "检测到项目使用 [tech stack], 是否正确?需要补充什么?"
- **specs/*.md**: Check `{projectRoot}/.workflow/specs/*.md`
- If missing: Scan for `.eslintrc`, `.prettierrc`, `ruff.toml` etc. Ask user: "未找到 specs/*.md, 是否有特定的编码规范需要遵循?"
- **Project specs**: Run `ccw spec load --keywords 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' 跳过测试验证"
@@ -38,8 +36,8 @@ Print formatted checklist:
✓ 项目根目录: D:\myproject
✓ 工作空间: .workflow/.cycle/ 就绪
⚠ Git: 3 个未提交变更
project-tech.json: 已检测 (Express + TypeORM + PostgreSQL)
⚠ specs/*.md: 未找到 (已跳过)
Project specs: 已加载 (ccw spec load --keywords execution)
⚠ specs: 未找到 (已跳过)
✓ 测试框架: jest (npm test)
```
@@ -166,13 +164,13 @@ Read the user's `$TASK` and score each dimension:
> 请选择适用的或添加自定义约束"
**上下文不足 (score 0-1)**:
> "我从项目中检测到: [tech stack from project-tech.json]。还有其他需要知道的技术细节吗?例如现有的认证机制、相关的工具库、数据模型等"
> "我从项目中检测到: [tech stack from loaded specs]。还有其他需要知道的技术细节吗?例如现有的认证机制、相关的工具库、数据模型等"
### 2.4 Auto-Enhancement
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**: Read `project-tech.json` and key config files
- **Context**: Run `ccw spec load --keywords execution` to load project context
- **Constraints**: Infer from `specs/*.md` and existing patterns
### 2.5 Assemble Refined Task

View File

@@ -1,464 +0,0 @@
---
description: "Interactive pre-flight checklist for ccw-loop. Discovers .task/*.json from collaborative-plan-with-file, analyze-with-file, brainstorm-to-cycle sessions; validates, transforms to ccw-loop task format, writes prep-package.json + .task/*.json, then launches the loop."
argument-hint: '[SOURCE="<path-to-.task/-dir-or-session-folder>"] [MAX_ITER=10]'
---
# Pre-Flight Checklist for CCW Loop
You are an interactive preparation assistant. Your job is to discover and consume task artifacts from upstream planning/analysis/brainstorm skills, validate them, transform into ccw-loop's task format, and launch an **unattended** development loop. Follow each step sequentially. **Ask the user questions when information is missing.**
---
## Step 1: Source Discovery
### 1.1 Auto-Detect Available Sessions
Scan for upstream artifacts from the three supported source skills:
```javascript
const projectRoot = Bash('git rev-parse --show-toplevel 2>/dev/null || pwd').trim()
// Source 1: collaborative-plan-with-file
const cplanSessions = Glob(`${projectRoot}/.workflow/.planning/CPLAN-*/.task/*.json`)
.map(p => ({
path: p.replace(/\/\.task\/[^/]+$/, '/.task'),
source: 'collaborative-plan-with-file',
type: 'task-dir',
session: p.match(/CPLAN-[^/]+/)?.[0],
mtime: fs.statSync(p).mtime
}))
// Deduplicate by session
.filter((v, i, a) => a.findIndex(x => x.session === v.session) === i)
// Source 2: analyze-with-file
const anlSessions = Glob(`${projectRoot}/.workflow/.analysis/ANL-*/.task/*.json`)
.map(p => ({
path: p.replace(/\/\.task\/[^/]+$/, '/.task'),
source: 'analyze-with-file',
type: 'task-dir',
session: p.match(/ANL-[^/]+/)?.[0],
mtime: fs.statSync(p).mtime
}))
.filter((v, i, a) => a.findIndex(x => x.session === v.session) === i)
// Source 3: brainstorm-to-cycle
const bsSessions = Glob(`${projectRoot}/.workflow/.brainstorm/*/cycle-task.md`)
.map(p => ({
path: p,
source: 'brainstorm-to-cycle',
type: 'markdown',
session: p.match(/\.brainstorm\/([^/]+)/)?.[1],
mtime: fs.statSync(p).mtime
}))
const allSources = [...cplanSessions, ...anlSessions, ...bsSessions]
.sort((a, b) => b.mtime - a.mtime) // Most recent first
```
### 1.2 Display Discovered Sources
```
可用的上游任务源
════════════════
collaborative-plan-with-file:
1. CPLAN-auth-redesign-20260208 .task/ (5 tasks, 2h ago)
2. CPLAN-api-cleanup-20260205 .task/ (3 days ago)
analyze-with-file:
3. ANL-perf-audit-20260207 .task/ (8 tasks, 1d ago)
brainstorm-to-cycle:
4. BS-notification-system cycle-task.md (1d ago)
手动输入:
5. 自定义路径 (输入 .task/ 目录路径或任务描述)
```
### 1.3 User Selection
Ask the user to select a source:
> "请选择任务来源(输入编号),或输入 .task/ 目录的完整路径:
> 也可以输入 'manual' 手动输入任务描述(不使用上游任务文件)"
**If `$SOURCE` argument provided**, skip discovery and use directly:
```javascript
if (options.SOURCE) {
// Validate path exists
if (!fs.existsSync(options.SOURCE)) {
console.error(`Path not found: ${options.SOURCE}`)
return
}
selectedSource = {
path: options.SOURCE,
source: inferSource(options.SOURCE),
type: fs.statSync(options.SOURCE).isDirectory() ? 'task-dir' : 'markdown'
}
}
```
---
## Step 2: Source Validation & Task Loading
### 2.1 For .task/ Sources (collaborative-plan / analyze-with-file)
```javascript
function validateAndLoadTaskDir(taskDirPath) {
const taskFiles = Glob(`${taskDirPath}/*.json`).sort()
const tasks = []
const errors = []
for (let i = 0; i < taskFiles.length; i++) {
try {
const content = Read(taskFiles[i])
const task = JSON.parse(content)
// Required fields check (task-schema.json: id, title, description, depends_on, convergence)
const requiredFields = ['id', 'title', 'description']
const missing = requiredFields.filter(f => !task[f])
if (missing.length > 0) {
errors.push(`${taskFiles[i]}: missing fields: ${missing.join(', ')}`)
continue
}
// Validate task structure
if (task.id && task.title && task.description) {
tasks.push(task)
}
} catch (e) {
errors.push(`${taskFiles[i]}: invalid JSON: ${e.message}`)
}
}
return { tasks, errors, total_files: taskFiles.length }
}
```
Display validation results:
```
JSONL 验证
══════════
目录: .workflow/.planning/CPLAN-auth-redesign-20260208/.task/
来源: collaborative-plan-with-file
✓ 5/5 任务文件解析成功
✓ 必需字段完整 (id, title, description)
✓ 3 个任务含收敛标准 (convergence)
⚠ 2 个任务缺少收敛标准 (将使用默认)
任务列表:
TASK-001 [high] Implement JWT token service (feature, 3 files)
TASK-002 [high] Add OAuth2 Google strategy (feature, 2 files)
TASK-003 [medium] Create user session middleware (feature, 4 files)
TASK-004 [low] Add rate limiting to auth endpoints (enhancement, 2 files)
TASK-005 [low] Write integration tests (testing, 5 files)
```
### 2.2 For Markdown Sources (brainstorm-to-cycle)
```javascript
function loadBrainstormTask(mdPath) {
const content = Read(mdPath)
// Extract enriched task description from cycle-task.md
// Format: # Generated Task \n\n **Idea**: ... \n\n --- \n\n {enrichedTask}
const taskMatch = content.match(/---\s*\n([\s\S]+)$/)
const enrichedTask = taskMatch ? taskMatch[1].trim() : content
// Parse into a single composite task
return {
tasks: [{
id: 'TASK-001',
title: extractTitle(content),
description: enrichedTask,
type: 'feature',
priority: 'high',
effort: 'large',
source: { tool: 'brainstorm-to-cycle', path: mdPath }
}],
errors: [],
is_composite: true // Single large task from brainstorm
}
}
```
Display:
```
Brainstorm 任务加载
══════════════════
文件: .workflow/.brainstorm/notification-system/cycle-task.md
来源: brainstorm-to-cycle
脑暴输出为复合任务描述(非结构化 JSONL
标题: Build real-time notification system
类型: feature (composite)
是否需要将其拆分为多个子任务?(Y/n)
```
If user selects **Y** (split), analyze the task description and generate sub-tasks:
```javascript
// Analyze and decompose the composite task into 3-7 sub-tasks
// Use mcp__ace-tool__search_context to find relevant patterns
// Generate structured tasks with convergence criteria
```
If user selects **n** (keep as single), use as-is.
### 2.3 Validation Gate
If validation has errors:
```
⚠ 验证发现 {N} 个问题:
Line 3: missing fields: description
Line 7: invalid JSON
选项:
1. 跳过有问题的行,继续 ({valid_count} 个有效任务)
2. 取消,手动修复后重试
```
**Block if 0 valid tasks.** Warn and continue if some tasks invalid.
---
## Step 3: Task Transformation
Transform unified task JSON files -> ccw-loop `develop.tasks[]` format.
```javascript
function transformToCcwLoopTasks(sourceTasks) {
const now = getUtc8ISOString()
return sourceTasks.map((task, index) => ({
// Core fields (ccw-loop native)
id: task.id || `task-${String(index + 1).padStart(3, '0')}`,
description: task.title
? `${task.title}: ${task.description}`
: task.description,
tool: inferTool(task), // 'gemini' | 'qwen' | 'codex'
mode: 'write',
status: 'pending',
priority: mapPriority(task.priority), // 1 (high) | 2 (medium) | 3 (low)
files_changed: (task.files || []).map(f => f.path || f),
created_at: now,
completed_at: null,
// Extended fields (preserved from source for agent reference)
_source: task.source || { tool: 'manual' },
_convergence: task.convergence || null,
_type: task.type || 'feature',
_effort: task.effort || 'medium',
_depends_on: task.depends_on || []
}))
}
function inferTool(task) {
// Default to gemini for write tasks
return 'gemini'
}
function mapPriority(priority) {
switch (priority) {
case 'high': case 'critical': return 1
case 'medium': return 2
case 'low': return 3
default: return 2
}
}
```
Display transformed tasks:
```
任务转换
════════
源格式: .task/*.json (collaborative-plan-with-file)
目标格式: ccw-loop develop.tasks
task-001 [P1] Implement JWT token service: Create JWT service... gemini/write pending
task-002 [P1] Add OAuth2 Google strategy: Implement passport... gemini/write pending
task-003 [P2] Create user session middleware: Add Express... gemini/write pending
task-004 [P3] Add rate limiting to auth endpoints: Implement... gemini/write pending
task-005 [P3] Write integration tests: Create test suite... gemini/write pending
共 5 个任务 (2 high, 1 medium, 2 low)
```
### 3.1 Task Reordering (Optional)
Ask: "是否需要调整任务顺序或移除某些任务?(输入编号排列如 '1,3,2,5' 或回车保持当前顺序)"
---
## Step 4: Auto-Loop Configuration
### 4.1 Present Defaults
```
自动循环配置
════════════
模式: 全自动 (develop → debug → validate → complete)
最大迭代: $MAX_ITER (默认 10)
超时: 10 分钟/action
收敛标准 (从源任务汇总):
${tasksWithConvergence} 个任务含收敛标准 → 自动验证
${tasksWithoutConvergence} 个任务无收敛标准 → 使用默认 (测试通过)
需要调整参数吗?(直接回车使用默认值)
```
### 4.2 Customization (if requested)
> "请选择要调整的项目:
> 1. 最大迭代次数 (当前: 10)
> 2. 每个 action 超时 (当前: 10 分钟)
> 3. 全部使用默认值"
---
## Step 5: Final Confirmation
```
══════════════════════════════════════════════
Pre-Flight 检查完成
══════════════════════════════════════════════
来源: collaborative-plan-with-file (CPLAN-auth-redesign-20260208)
任务数: 5 个 (2 high, 1 medium, 2 low)
验证: ✓ 5/5 任务格式正确
收敛: 3/5 任务含收敛标准
自动模式: ON (最多 10 次迭代)
任务摘要:
1. [P1] Implement JWT token service
2. [P1] Add OAuth2 Google strategy
3. [P2] Create user session middleware
4. [P3] Add rate limiting to auth endpoints
5. [P3] Write integration tests
══════════════════════════════════════════════
```
Ask: "确认启动?(Y/n)"
- If **Y** → proceed to Step 6
- If **n** → ask which part to revise
---
## Step 6: Write Artifacts
### 6.1 Write prep-package.json
Write to `{projectRoot}/.workflow/.loop/prep-package.json`:
```json
{
"version": "1.0.0",
"generated_at": "{ISO8601_UTC+8}",
"prep_status": "ready",
"target_skill": "ccw-loop",
"environment": {
"project_root": "{projectRoot}",
"tech_stack": "{detected tech stack}",
"test_framework": "{detected test framework}"
},
"source": {
"tool": "collaborative-plan-with-file",
"session_id": "CPLAN-auth-redesign-20260208",
"task_dir": "{projectRoot}/.workflow/.planning/CPLAN-auth-redesign-20260208/.task",
"task_count": 5,
"tasks_with_convergence": 3
},
"tasks": {
"total": 5,
"by_priority": { "high": 2, "medium": 1, "low": 2 },
"by_type": { "feature": 3, "enhancement": 1, "testing": 1 }
},
"auto_loop": {
"enabled": true,
"no_confirmation": true,
"max_iterations": 10,
"timeout_per_action_ms": 600000
}
}
```
### 6.2 Write .task/*.json
Write transformed tasks to `{projectRoot}/.workflow/.loop/.task/` directory (one file per task, following task-schema.json):
```javascript
const taskDir = `${projectRoot}/.workflow/.loop/.task`
Bash(`mkdir -p ${taskDir}`)
for (const task of transformedTasks) {
const fileName = `TASK-${task.id.replace(/^task-/, '')}.json`
Write(`${taskDir}/${fileName}`, JSON.stringify(task, null, 2))
}
```
Confirm:
```
ok prep-package.json -> .workflow/.loop/prep-package.json
ok .task/ directory -> .workflow/.loop/.task/ (5 task files)
```
---
## Step 7: Launch Loop
Invoke the skill:
```
$ccw-loop --auto TASK="Execute tasks from {source.tool} session {source.session_id}"
```
其中:
- `$ccw-loop` — 展开为 skill 调用
- `--auto` — 启用全自动模式
- Skill 端会检测 `prep-package.json` 并加载 `.task/*.json`
**Skill 端会做以下检查**(见 Phase 1 Step 1.1:
1. 检测 `prep-package.json` 是否存在
2. 验证 `prep_status === "ready"`
3. 验证 `target_skill === "ccw-loop"`
4. 校验 `project_root` 与当前项目一致
5. 校验文件时效24h 内生成)
6. 验证 `.task/` 目录存在且含有效任务文件
7. 全部通过 -> 加载预构建任务列表;任一失败 -> 回退到默认 INIT 行为
Print:
```
启动 ccw-loop (自动模式)...
prep-package.json → Phase 1 自动加载并校验
.task/*.json → 5 个预构建任务加载到 develop.tasks
循环: develop → validate → complete (最多 10 次迭代)
```
---
## Error Handling
| 情况 | 处理 |
|------|------|
| 无可用上游会话 | 提示用户先运行 collaborative-plan / analyze-with-file / brainstorm或选择手动输入 |
| JSONL 格式全部无效 | 报告错误,**不启动 loop** |
| JSONL 部分无效 | 警告无效文件,用有效任务继续 |
| brainstorm cycle-task.md 为空 | 报告错误,建议完成 brainstorm 流程 |
| 用户取消确认 | 保存 prep-package.json (prep_status="cancelled"),提示可修改后重新运行 |
| Skill 端 prep-package 校验失败 | Skill 打印警告,回退到无 prep 的默认 INIT 行为(不阻塞执行) |

View File

@@ -21,10 +21,8 @@ Check these items. Report results as a checklist.
### 1.2 Strongly Recommended (warn if missing)
- **project-tech.json**: Check `{projectRoot}/.workflow/project-tech.json`
- If missing: WARN — Phase 1 will call `workflow:init` to generate it. Ask user: "检测到项目使用 [tech stack from package.json], 是否正确?需要补充什么?"
- **specs/*.md**: Check `{projectRoot}/.workflow/specs/*.md`
- If missing: WARN — will be generated as empty scaffold. Ask: "有特定的编码规范需要遵循吗?"
- **Project specs**: Run `ccw spec load --keywords 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' 跳过"
@@ -38,8 +36,8 @@ Print formatted checklist:
✓ 项目根目录: D:\myproject
✓ .workflow/ 目录就绪
⚠ Git: 3 个未提交变更
project-tech.json: 已检测 (Express + TypeORM + PostgreSQL)
⚠ specs/*.md: 未找到 (Phase 1 将生成空模板)
Project specs: 已加载 (ccw spec load --keywords planning)
⚠ specs: 未找到 (Phase 1 将初始化)
✓ 测试框架: jest (npm test)
```
@@ -156,13 +154,13 @@ Each dimension scores 0-2 (0=missing, 1=vague, 2=clear). **Total minimum: 6/10 t
> "有哪些限制条件?常见约束:不破坏现有 API / 使用现有数据库 / 不引入新依赖 / 保持现有模式。请选择或自定义"
**上下文不足 (score 0-1)**:
> "我从项目中检测到: [tech stack from project-tech.json]。还有需要知道的技术细节吗?"
> "我从项目中检测到: [tech stack from loaded specs]。还有需要知道的技术细节吗?"
### 2.4 Auto-Enhancement
For dimensions still at score 1 after Q&A, auto-enhance from codebase:
- **Scope**: Use `Glob` and `Grep` to find related files
- **Context**: Read `project-tech.json` and key config files
- **Context**: Run `ccw spec load --keywords planning` to load project context
- **Constraints**: Infer from `specs/*.md`
### 2.5 Assemble Structured Description

View File

@@ -85,7 +85,7 @@ Step 1: Topic Understanding
Step 2: Exploration (Inline, No Agents)
├─ Detect codebase → search relevant modules, patterns
│ ├─ Read project-tech.json / specs/*.md (if exists)
│ ├─ Run `ccw spec load --keywords 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)
// - .workflow/project-tech.json (tech stack info)
// - Run `ccw spec load --keywords exploration` (load project specs)
// - .workflow/specs/*.md (project conventions)
// 2. Search codebase for relevant content

View File

@@ -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** - Load `{projectRoot}/.workflow/project-tech.json` if available
3. **Read project tech context** - Run `ccw spec load --keywords "exploration planning"` if spec system available
4. **Analyze patterns** - Identify common code patterns and architecture decisions
**exploration-codebase.json Structure**:
@@ -358,8 +358,8 @@ const agentIds = perspectives.map(perspective => {
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/cli-explore-agent.md (MUST read first)
2. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords "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. Read: ${projectRoot}/.workflow/project-tech.json
3. Run: `ccw spec load --keywords "exploration planning"`
---

View File

@@ -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
- Read: `.workflow/project-tech.json`, `.workflow/specs/*.md` (if exists)
- Run: `ccw spec load --keywords 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

View File

@@ -231,8 +231,7 @@ const agentId = spawn_agent({
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
2. Read: {projectRoot}/.workflow/project-tech.json
3. Read: {projectRoot}/.workflow/specs/*.md
2. Execute: ccw spec load --keywords exploration
## TASK CONTEXT
${taskContext}

View File

@@ -268,7 +268,7 @@ const hasCodebase = bash(`
// 2. Codebase Exploration (only when hasCodebase !== 'none')
if (hasCodebase !== 'none') {
// Read project metadata (if exists)
// .workflow/project-tech.json, .workflow/specs/*.md
// Run `ccw spec load --keywords planning`
// Search codebase for requirement-relevant context
// Use: mcp__ace-tool__search_context, Grep, Glob, Read

View File

@@ -303,8 +303,7 @@ const agentId = spawn_agent({
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
2. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Execute: ccw spec load --keywords "exploration execution"
---

View File

@@ -94,8 +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. Read: ${projectRoot}/.workflow/project-tech.json (technology stack and architecture context)
7. Read: ${projectRoot}/.workflow/specs/*.md (user-defined constraints and conventions to validate against)
6. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints)
---
@@ -217,8 +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. Read: ${projectRoot}/.workflow/project-tech.json (technology stack and architecture context)
8. Read: ${projectRoot}/.workflow/specs/*.md (user-defined constraints and conventions to validate against)
7. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints)
---
@@ -336,8 +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. Read: ${projectRoot}/.workflow/project-tech.json (technology stack and architecture context)
8. Read: ${projectRoot}/.workflow/specs/*.md (user-defined constraints for remediation compliance)
7. Execute: ccw spec load --keywords "exploration execution" (technology stack and constraints for remediation)
---

View File

@@ -105,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Execute: ccw spec load --keywords planning
---

View File

@@ -60,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Execute: ccw spec load --keywords execution
---

View File

@@ -38,8 +38,7 @@ completion report.
### Step 1: Load Context
After reading role definition:
- Read: `.workflow/project-tech.json`
- Read: `.workflow/specs/*.md`
- Run: `ccw spec load --keywords execution`
- Extract issue ID, solution file path, session dir from task message
### Step 2: Load Solution

View File

@@ -48,8 +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:
- Read: `.workflow/project-tech.json`
- Read: `.workflow/specs/*.md`
- Run: `ccw spec load --keywords planning`
- Extract session directory and artifacts directory from task message
### Step 2: Parse Input
@@ -82,7 +81,7 @@ spawn_agent({
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/issue-plan-agent.md (MUST read first)
2. Read: .workflow/project-tech.json
2. Run: `ccw spec load --keywords planning`
---

View File

@@ -85,8 +85,7 @@ const plannerAgent = spawn_agent({
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/planex-planner.md (MUST read first)
2. Read: .workflow/project-tech.json
3. Read: .workflow/specs/*.md
2. Run: `ccw spec load --keywords "planning execution"`
---
@@ -156,8 +155,7 @@ while (true) {
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/planex-executor.md (MUST read first)
2. Read: .workflow/project-tech.json
3. Read: .workflow/specs/*.md
2. Run: `ccw spec load --keywords "planning execution"`
---

View File

@@ -88,8 +88,7 @@ const agentId = spawn_agent({
### MANDATORY FIRST STEPS (Agent Execute)
1. **Read role definition**: ~/.codex/agents/{agent-type}.md (MUST read first)
2. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords "planning execution"`
## TASK CONTEXT
${taskContext}

View File

@@ -75,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords planning`
---
@@ -102,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords planning`
---
@@ -176,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords planning`
---
@@ -245,8 +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. Read: ${projectRoot}/.workflow/project-tech.json
3. Read: ${projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords planning`
---

View File

@@ -90,8 +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. Read: {projectRoot}/.workflow/project-tech.json
3. Read: {projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords planning`
---
@@ -157,8 +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. Read: {projectRoot}/.workflow/project-tech.json
3. Read: {projectRoot}/.workflow/specs/*.md
2. Run: `ccw spec load --keywords execution`
---

View File

@@ -111,19 +111,19 @@ export function HookDialog({
const newErrors: Record<string, string> = {};
if (!formData.name.trim()) {
newErrors.name = formatMessage({ id: 'hooks.validation.nameRequired' });
newErrors.name = formatMessage({ id: 'specs.hooks.validation.nameRequired', defaultMessage: 'Name is required' });
}
if (!formData.command.trim()) {
newErrors.command = formatMessage({ id: 'hooks.validation.commandRequired' });
newErrors.command = formatMessage({ id: 'specs.hooks.validation.commandRequired', defaultMessage: 'Command is required' });
}
if (formData.timeout && formData.timeout < 1000) {
newErrors.timeout = formatMessage({ id: 'hooks.validation.timeoutMin' });
newErrors.timeout = formatMessage({ id: 'specs.hooks.validation.timeoutMin', defaultMessage: 'Minimum timeout is 1000ms' });
}
if (formData.timeout && formData.timeout > 300000) {
newErrors.timeout = formatMessage({ id: 'hooks.validation.timeoutMax' });
newErrors.timeout = formatMessage({ id: 'specs.hooks.validation.timeoutMax', defaultMessage: 'Maximum timeout is 300000ms' });
}
setErrors(newErrors);
@@ -153,11 +153,11 @@ export function HookDialog({
<DialogHeader>
<DialogTitle>
{isEditing
? formatMessage({ id: 'hooks.dialog.editTitle' })
: formatMessage({ id: 'hooks.dialog.createTitle' })}
? formatMessage({ id: 'specs.hooks.dialog.editTitle', defaultMessage: 'Edit Hook' })
: formatMessage({ id: 'specs.hooks.dialog.createTitle', defaultMessage: 'Create Hook' })}
</DialogTitle>
<DialogDescription>
{formatMessage({ id: 'hooks.dialog.description' })}
{formatMessage({ id: 'specs.hooks.dialog.description', defaultMessage: 'Configure the hook trigger event, command, and other settings.' })}
</DialogDescription>
</DialogHeader>
@@ -165,13 +165,13 @@ export function HookDialog({
{/* Name field */}
<div className="space-y-2">
<Label htmlFor="name" className="required">
{formatMessage({ id: 'hooks.fields.name' })}
{formatMessage({ id: 'specs.hooks.fields.name', defaultMessage: 'Hook Name' })}
</Label>
<Input
id="name"
value={formData.name}
onChange={(e) => updateField('name', e.target.value)}
placeholder={formatMessage({ id: 'hooks.placeholders.name' })}
placeholder={formatMessage({ id: 'specs.hooks.placeholders.name', defaultMessage: 'Enter hook name' })}
className={errors.name ? 'border-destructive' : ''}
disabled={isLoading}
/>
@@ -183,7 +183,7 @@ export function HookDialog({
{/* Event type field */}
<div className="space-y-2">
<Label htmlFor="event" className="required">
{formatMessage({ id: 'hooks.fields.event' })}
{formatMessage({ id: 'specs.hooks.fields.event', defaultMessage: 'Trigger Event' })}
</Label>
<Select
value={formData.event}
@@ -191,29 +191,29 @@ export function HookDialog({
disabled={isLoading}
>
<SelectTrigger id="event">
<SelectValue placeholder={formatMessage({ id: 'hooks.placeholders.event' })} />
<SelectValue placeholder={formatMessage({ id: 'specs.hooks.placeholders.event', defaultMessage: 'Select event' })} />
</SelectTrigger>
<SelectContent>
<SelectItem value="SessionStart">
{formatMessage({ id: 'hooks.events.sessionStart' })}
{formatMessage({ id: 'specs.hooks.events.sessionStart', defaultMessage: 'Session Start' })}
</SelectItem>
<SelectItem value="UserPromptSubmit">
{formatMessage({ id: 'hooks.events.userPromptSubmit' })}
{formatMessage({ id: 'specs.hooks.events.userPromptSubmit', defaultMessage: 'User Prompt Submit' })}
</SelectItem>
<SelectItem value="SessionEnd">
{formatMessage({ id: 'hooks.events.sessionEnd' })}
{formatMessage({ id: 'specs.hooks.events.sessionEnd', defaultMessage: 'Session End' })}
</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'hints.hookEvents' })}
{formatMessage({ id: 'specs.hints.hookEvents', defaultMessage: 'Select when this hook should be triggered' })}
</p>
</div>
{/* Scope field */}
<div className="space-y-2">
<Label className="required">
{formatMessage({ id: 'hooks.fields.scope' })}
{formatMessage({ id: 'specs.hooks.fields.scope', defaultMessage: 'Scope' })}
</Label>
<RadioGroup
value={formData.scope}
@@ -225,32 +225,32 @@ export function HookDialog({
<RadioGroupItem value="global" id="scope-global" />
<Label htmlFor="scope-global" className="flex items-center gap-1.5 cursor-pointer">
<Globe className="h-4 w-4" />
{formatMessage({ id: 'hooks.scope.global' })}
{formatMessage({ id: 'specs.hooks.scope.global', defaultMessage: 'Global' })}
</Label>
</div>
<div className="flex items-center space-x-2">
<RadioGroupItem value="project" id="scope-project" />
<Label htmlFor="scope-project" className="flex items-center gap-1.5 cursor-pointer">
<Folder className="h-4 w-4" />
{formatMessage({ id: 'hooks.scope.project' })}
{formatMessage({ id: 'specs.hooks.scope.project', defaultMessage: 'Project' })}
</Label>
</div>
</RadioGroup>
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'hints.hookScope' })}
{formatMessage({ id: 'specs.hints.hookScope', defaultMessage: 'Global hooks apply to all projects, project hooks only to current project' })}
</p>
</div>
{/* Command field */}
<div className="space-y-2">
<Label htmlFor="command" className="required">
{formatMessage({ id: 'hooks.fields.command' })}
{formatMessage({ id: 'specs.hooks.fields.command', defaultMessage: 'Command' })}
</Label>
<Input
id="command"
value={formData.command}
onChange={(e) => updateField('command', e.target.value)}
placeholder={formatMessage({ id: 'hooks.placeholders.command' })}
placeholder={formatMessage({ id: 'specs.hooks.placeholders.command', defaultMessage: 'Enter command to execute' })}
className={cn('font-mono', errors.command ? 'border-destructive' : '')}
disabled={isLoading}
/>
@@ -258,20 +258,20 @@ export function HookDialog({
<p className="text-xs text-destructive">{errors.command}</p>
)}
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'hints.hookCommand' })}
{formatMessage({ id: 'specs.hints.hookCommand', defaultMessage: 'Command to execute, can use environment variables' })}
</p>
</div>
{/* Description field */}
<div className="space-y-2">
<Label htmlFor="description">
{formatMessage({ id: 'hooks.fields.description' })}
{formatMessage({ id: 'specs.hooks.fields.description', defaultMessage: 'Description' })}
</Label>
<Textarea
id="description"
value={formData.description || ''}
onChange={(e) => updateField('description', e.target.value)}
placeholder={formatMessage({ id: 'hooks.placeholders.description' })}
placeholder={formatMessage({ id: 'specs.hooks.placeholders.description', defaultMessage: 'Enter description (optional)' })}
rows={2}
disabled={isLoading}
/>
@@ -280,9 +280,9 @@ export function HookDialog({
{/* Timeout field */}
<div className="space-y-2">
<Label htmlFor="timeout" className="flex items-center gap-1">
{formatMessage({ id: 'hooks.fields.timeout' })}
{formatMessage({ id: 'specs.hooks.fields.timeout', defaultMessage: 'Timeout' })}
<span className="text-xs text-muted-foreground">
({formatMessage({ id: 'hooks.fields.timeoutUnit' })})
({formatMessage({ id: 'specs.hooks.fields.timeoutUnit', defaultMessage: 'ms' })})
</span>
</Label>
<Input
@@ -300,14 +300,14 @@ export function HookDialog({
<p className="text-xs text-destructive">{errors.timeout}</p>
)}
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'hints.hookTimeout' })}
{formatMessage({ id: 'specs.hints.hookTimeout', defaultMessage: 'Timeout for command execution' })}
</p>
</div>
{/* Fail mode field */}
<div className="space-y-2">
<Label htmlFor="failMode" className="flex items-center gap-1">
{formatMessage({ id: 'hooks.fields.failMode' })}
{formatMessage({ id: 'specs.hooks.fields.failMode', defaultMessage: 'Failure Mode' })}
<HelpCircle className="h-3.5 w-3.5 text-muted-foreground" />
</Label>
<Select
@@ -320,30 +320,30 @@ export function HookDialog({
</SelectTrigger>
<SelectContent>
<SelectItem value="continue">
{formatMessage({ id: 'hooks.failModes.continue' })}
{formatMessage({ id: 'specs.hooks.failModes.continue', defaultMessage: 'Continue' })}
</SelectItem>
<SelectItem value="warn">
{formatMessage({ id: 'hooks.failModes.warn' })}
{formatMessage({ id: 'specs.hooks.failModes.warn', defaultMessage: 'Warn' })}
</SelectItem>
<SelectItem value="block">
{formatMessage({ id: 'hooks.failModes.block' })}
{formatMessage({ id: 'specs.hooks.failModes.block', defaultMessage: 'Block' })}
</SelectItem>
</SelectContent>
</Select>
<p className="text-xs text-muted-foreground">
{formatMessage({ id: 'hints.hookFailMode' })}
{formatMessage({ id: 'specs.hints.hookFailMode', defaultMessage: 'How to handle command execution failure' })}
</p>
</div>
</div>
<DialogFooter>
<Button variant="outline" onClick={handleCancel} disabled={isLoading}>
{formatMessage({ id: 'common.cancel' })}
{formatMessage({ id: 'specs.common.cancel', defaultMessage: 'Cancel' })}
</Button>
<Button onClick={handleSave} disabled={isLoading}>
{isLoading
? formatMessage({ id: 'common.saving' })
: formatMessage({ id: 'common.save' })}
? formatMessage({ id: 'specs.common.saving', defaultMessage: 'Saving...' })
: formatMessage({ id: 'specs.common.save', defaultMessage: 'Save' })}
</Button>
</DialogFooter>
</DialogContent>

View File

@@ -5,7 +5,6 @@
import * as React from 'react';
import { useIntl } from 'react-intl';
import { cn } from '@/lib/utils';
import {
Dialog,
DialogContent,

View File

@@ -336,10 +336,13 @@ import {
updateSystemSettings,
installRecommendedHooks,
getSpecStats,
getSpecsList,
rebuildSpecIndex,
updateSpecFrontmatter,
type SystemSettings,
type UpdateSystemSettingsInput,
type InstallRecommendedHooksResponse,
type SpecStats,
type SpecsListResponse,
} from '../lib/api';
// Query keys for specs settings
@@ -463,3 +466,110 @@ export function useSpecStats(options: UseSpecStatsOptions = {}): UseSpecStatsRet
refetch: () => { query.refetch(); },
};
}
// ========================================
// Specs List Hook
// ========================================
export interface UseSpecsListOptions {
projectPath?: string;
enabled?: boolean;
staleTime?: number;
}
export interface UseSpecsListReturn {
data: SpecsListResponse | undefined;
isLoading: boolean;
error: Error | null;
refetch: () => void;
}
/**
* Hook to fetch specs list for all dimensions
* @param options - Options including projectPath for workspace isolation
*/
export function useSpecsList(options: UseSpecsListOptions = {}): UseSpecsListReturn {
const { projectPath, enabled = true, staleTime = STALE_TIME } = options;
const query = useQuery({
queryKey: specsSettingsKeys.specStats(projectPath), // Reuse for specs list
queryFn: () => getSpecsList(projectPath),
staleTime,
enabled,
retry: 1,
});
return {
data: query.data,
isLoading: query.isLoading,
error: query.error,
refetch: () => { query.refetch(); },
};
}
// ========================================
// Rebuild Spec Index Mutation Hook
// ========================================
export interface UseRebuildSpecIndexOptions {
projectPath?: string;
}
/**
* Hook to rebuild spec index
* @param options - Options including projectPath for workspace isolation
*/
export function useRebuildSpecIndex(options: UseRebuildSpecIndexOptions = {}) {
const { projectPath } = options;
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: () => rebuildSpecIndex(projectPath),
onSuccess: () => {
// Invalidate specs list and stats queries to refresh data
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
},
});
return {
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isPending: mutation.isPending,
error: mutation.error,
data: mutation.data,
};
}
// ========================================
// Update Spec Frontmatter Mutation Hook
// ========================================
export interface UseUpdateSpecFrontmatterOptions {
projectPath?: string;
}
/**
* Hook to update spec frontmatter (e.g., toggle readMode)
* @param options - Options including projectPath for workspace isolation
*/
export function useUpdateSpecFrontmatter(options: UseUpdateSpecFrontmatterOptions = {}) {
const { projectPath } = options;
const queryClient = useQueryClient();
const mutation = useMutation({
mutationFn: ({ file, readMode }: { file: string; readMode: string }) =>
updateSpecFrontmatter(file, readMode, projectPath),
onSuccess: () => {
// Invalidate specs list to refresh data
queryClient.invalidateQueries({ queryKey: specsSettingsKeys.specStats(projectPath) });
},
});
return {
mutate: mutation.mutate,
mutateAsync: mutation.mutateAsync,
isPending: mutation.isPending,
error: mutation.error,
data: mutation.data,
};
}

View File

@@ -7261,6 +7261,65 @@ export async function getSpecStats(projectPath?: string): Promise<SpecStats> {
return fetchApi<SpecStats>(url);
}
/**
* Spec entry from index
*/
export interface SpecEntry {
file: string;
title: string;
dimension: string;
readMode: 'required' | 'optional' | 'keywords';
priority: 'critical' | 'high' | 'medium' | 'low';
keywords: string[];
}
/**
* Specs list response from /api/specs/list
*/
export interface SpecsListResponse {
specs: Record<string, SpecEntry[]>;
}
/**
* Fetch specs list for all dimensions
* @param projectPath - Optional project path
*/
export async function getSpecsList(projectPath?: string): Promise<SpecsListResponse> {
const url = projectPath
? `/api/specs/list?path=${encodeURIComponent(projectPath)}`
: '/api/specs/list';
return fetchApi<SpecsListResponse>(url);
}
/**
* Rebuild spec index
*/
export async function rebuildSpecIndex(projectPath?: string): Promise<{ success: boolean; stats?: Record<string, number> }> {
const url = projectPath
? `/api/specs/rebuild?path=${encodeURIComponent(projectPath)}`
: '/api/specs/rebuild';
return fetchApi<{ success: boolean; stats?: Record<string, number> }>(url, {
method: 'POST',
});
}
/**
* Update spec frontmatter (toggle readMode)
*/
export async function updateSpecFrontmatter(
file: string,
readMode: string,
projectPath?: string
): Promise<{ success: boolean; readMode?: string }> {
const url = projectPath
? `/api/specs/update-frontmatter?path=${encodeURIComponent(projectPath)}`
: '/api/specs/update-frontmatter';
return fetchApi<{ success: boolean; readMode?: string }>(url, {
method: 'PUT',
body: JSON.stringify({ file, readMode }),
});
}
// ========== Analysis API ==========
import type { AnalysisSessionSummary, AnalysisSessionDetail } from '../types/analysis';

View File

@@ -42,6 +42,7 @@ import team from './team.json';
import terminalDashboard from './terminal-dashboard.json';
import skillHub from './skill-hub.json';
import nativeSession from './native-session.json';
import specs from './specs.json';
/**
* Flattens nested JSON object to dot-separated keys
@@ -107,4 +108,5 @@ export default {
...flattenMessages(terminalDashboard, 'terminalDashboard'),
...flattenMessages(skillHub, 'skillHub'),
...flattenMessages(nativeSession, 'nativeSession'),
...flattenMessages(specs, 'specs'),
} as Record<string, string>;

View File

@@ -0,0 +1,93 @@
{
"pageTitle": "Spec Settings",
"pageDescription": "Manage specification injection, hooks, and system settings",
"tabProjectSpecs": "Project Specs",
"tabPersonalSpecs": "Personal",
"tabHooks": "Hooks",
"tabInjection": "Injection",
"tabSettings": "Settings",
"searchPlaceholder": "Search specs...",
"rebuildIndex": "Rebuild Index",
"loading": "Loading...",
"noSpecs": "No specs found. Create specs in .workflow/ directory.",
"recommendedHooks": "Recommended Hooks",
"recommendedHooksDesc": "One-click install system-preset spec injection hooks",
"installAll": "Install All Recommended Hooks",
"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"
}
},
"hook": {
"install": "Install",
"edit": "Edit",
"toggle": "Toggle",
"delete": "Delete",
"enabled": "Enabled",
"disabled": "Disabled",
"scope": {
"global": "Global",
"project": "Project"
},
"event": {
"SessionStart": "Session Start",
"UserPromptSubmit": "Prompt Submit",
"SessionEnd": "Session End"
}
},
"injection": {
"title": "Injection Control",
"description": "Monitor and manage spec injection length",
"currentLength": "Current Length",
"maxLength": "Max Length",
"warnThreshold": "Warn Threshold",
"percentage": "Usage",
"truncateOnExceed": "Truncate on Exceed",
"truncateDescription": "Automatically truncate when injection exceeds max length",
"overLimit": "Over Limit",
"warning": "Warning",
"normal": "Normal"
},
"settings": {
"title": "Global Settings",
"description": "Configure personal spec defaults and system settings",
"personalSpecDefaults": "Personal Spec Defaults",
"defaultReadMode": "Default Read Mode",
"autoEnable": "Auto Enable",
"autoEnableDescription": "Automatically enable newly created personal specs"
},
"dialog": {
"cancel": "Cancel",
"save": "Save",
"editSpec": "Edit Spec",
"editHook": "Edit Hook",
"specTitle": "Spec Title",
"keywords": "Keywords",
"readMode": "Read Mode",
"priority": "Priority",
"hookName": "Hook Name",
"hookEvent": "Event",
"hookCommand": "Command",
"hookScope": "Scope",
"hookTimeout": "Timeout (ms)",
"hookFailMode": "Fail Mode"
}
}

View File

@@ -42,6 +42,7 @@ import team from './team.json';
import terminalDashboard from './terminal-dashboard.json';
import skillHub from './skill-hub.json';
import nativeSession from './native-session.json';
import specs from './specs.json';
/**
* Flattens nested JSON object to dot-separated keys
@@ -107,4 +108,5 @@ export default {
...flattenMessages(terminalDashboard, 'terminalDashboard'),
...flattenMessages(skillHub, 'skillHub'),
...flattenMessages(nativeSession, 'nativeSession'),
...flattenMessages(specs, 'specs'),
} as Record<string, string>;

View File

@@ -0,0 +1,202 @@
{
"pageTitle": "规范设置",
"pageDescription": "管理规范注入、钩子和系统设置",
"tabProjectSpecs": "项目规范",
"tabPersonalSpecs": "个人",
"tabHooks": "钩子",
"tabInjection": "注入控制",
"tabSettings": "设置",
"searchPlaceholder": "搜索规范...",
"rebuildIndex": "重建索引",
"loading": "加载中...",
"noSpecs": "未找到规范。请在 .workflow/ 目录中创建规范文件。",
"recommendedHooks": "推荐钩子",
"recommendedHooksDesc": "一键安装系统预设的规范注入钩子",
"installAll": "安装所有推荐钩子",
"installedHooks": "已安装钩子",
"installedHooksDesc": "管理已安装的钩子配置",
"searchHooks": "搜索钩子...",
"noHooks": "未安装钩子。请安装上方的推荐钩子。",
"actions": {
"edit": "编辑",
"delete": "删除",
"reset": "重置",
"save": "保存",
"saving": "保存中..."
},
"status": {
"enabled": "已启用",
"disabled": "已禁用"
},
"readMode": {
"required": "必读",
"optional": "选读"
},
"priority": {
"critical": "关键",
"high": "高",
"medium": "中",
"low": "低"
},
"spec": {
"edit": "编辑规范",
"toggle": "切换状态",
"delete": "删除规范",
"deleteConfirm": "确定要删除此规范吗?",
"title": "规范标题",
"keywords": "关键词",
"keywordsPlaceholder": "输入关键词,用逗号分隔",
"readMode": "读取模式",
"priority": "优先级",
"file": "文件路径"
},
"hook": {
"install": "安装",
"uninstall": "卸载",
"edit": "编辑钩子",
"toggle": "切换状态",
"delete": "删除钩子",
"enabled": "已启用",
"disabled": "已禁用",
"installed": "已安装",
"notInstalled": "未安装",
"scope": {
"global": "全局",
"project": "项目"
},
"event": {
"SessionStart": "会话开始",
"UserPromptSubmit": "提示词提交",
"SessionEnd": "会话结束"
},
"name": "钩子名称",
"eventLabel": "触发事件",
"command": "执行命令",
"scopeLabel": "作用域",
"timeout": "超时时间(ms)",
"failMode": "失败模式",
"failModeContinue": "继续",
"failModeBlock": "阻止",
"failModeWarn": "警告"
},
"hooks": {
"dialog": {
"createTitle": "创建钩子",
"editTitle": "编辑钩子",
"description": "配置钩子的触发事件、执行命令和其他参数。"
},
"fields": {
"name": "钩子名称",
"event": "触发事件",
"scope": "作用域",
"command": "执行命令",
"description": "描述",
"timeout": "超时时间",
"timeoutUnit": "毫秒",
"failMode": "失败处理模式"
},
"placeholders": {
"name": "输入钩子名称",
"event": "选择触发事件",
"command": "输入要执行的命令",
"description": "输入钩子描述(可选)"
},
"events": {
"sessionStart": "会话开始",
"userPromptSubmit": "提示词提交",
"sessionEnd": "会话结束"
},
"scope": {
"global": "全局",
"project": "项目"
},
"failModes": {
"continue": "继续执行",
"warn": "显示警告",
"block": "阻止操作"
}
},
"hints": {
"hookEvents": "选择钩子触发的事件类型",
"hookScope": "全局钩子应用于所有项目,项目钩子仅当前项目",
"hookCommand": "执行的命令,可使用环境变量",
"hookTimeout": "命令执行的超时时间",
"hookFailMode": "命令执行失败时的处理方式"
},
"common": {
"cancel": "取消",
"save": "保存",
"delete": "删除",
"edit": "编辑",
"reset": "重置",
"confirm": "确认"
},
"injection": {
"title": "注入控制",
"description": "监控和管理规范注入长度",
"statusTitle": "当前注入状态",
"settingsTitle": "注入控制设置",
"settingsDescription": "配置如何将规范内容注入到 AI 上下文中。",
"currentLength": "当前长度",
"maxLength": "最大注入长度(字符)",
"maxLengthHelp": "推荐值4000-10000。过大会消耗过多上下文过小可能截断重要规范。",
"warnThreshold": "警告阈值",
"warnThresholdLabel": "警告阈值(字符)",
"warnThresholdHelp": "当注入长度超过此值时显示警告。",
"percentage": "使用率",
"truncateOnExceed": "超出时截断",
"truncateHelp": "当内容超出最大长度时自动截断。",
"overLimit": "已超出限制",
"overLimitDescription": "当前注入内容已超出最大长度限制 {max} 字符,超出部分将被截断。",
"warning": "接近限制",
"normal": "正常",
"characters": "字符",
"statsInfo": "统计信息",
"requiredLength": "必读规范长度:",
"matchedLength": "关键词匹配长度:",
"remaining": "剩余空间:",
"loadError": "加载统计数据失败",
"saveSuccess": "设置已保存",
"saveError": "保存设置失败"
},
"settings": {
"title": "全局设置",
"description": "配置个人规范默认值和系统设置",
"personalSpecDefaults": "个人规范默认值",
"defaultReadMode": "默认读取模式",
"defaultReadModeHelp": "新创建的个人规范的默认读取模式",
"autoEnable": "自动启用",
"autoEnableDescription": "新创建的个人规范自动启用"
},
"dialog": {
"cancel": "取消",
"save": "保存",
"close": "关闭",
"editSpec": "编辑规范",
"editHook": "编辑钩子",
"confirmDelete": "确认删除",
"specTitle": "规范标题",
"keywords": "关键词",
"readMode": "读取模式",
"priority": "优先级",
"hookName": "钩子名称",
"hookEvent": "触发事件",
"hookCommand": "执行命令",
"hookScope": "作用域",
"hookTimeout": "超时时间(ms)",
"hookFailMode": "失败模式"
}
}

View File

@@ -4,24 +4,40 @@
* Main page for managing spec settings, hooks, injection control, and global settings.
* Uses 5 tabs: Project Specs | Personal Specs | Hooks | Injection | Settings
*/
import { useState } from 'react';
import { useState, useMemo } from 'react';
import { useIntl } from 'react-intl';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
import { Badge } from '@/components/ui/badge';
import { Button } from '@/components/ui/button';
import { Input } from '@/components/ui/input';
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/Tabs';
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/Card';
import { Button } from '@/components/ui/Button';
import { Input } from '@/components/ui/Input';
import { ScrollText, User, Plug, Gauge, Settings, RefreshCw, Search } from 'lucide-react';
import { SpecCard, SpecDialog, type Spec, type SpecFormData } from '@/components/specs';
import { HookCard, HookDialog, type HookConfig } from '@/components/specs';
import { InjectionControlTab } from '@/components/specs/InjectionControlTab';
import { GlobalSettingsTab } from '@/components/specs/GlobalSettingsTab';
import { useSpecStats } from '@/hooks/useSystemSettings';
import { useSpecStats, useSpecsList, useSystemSettings, useRebuildSpecIndex } from '@/hooks/useSystemSettings';
import { useWorkflowStore, selectProjectPath } from '@/stores/workflowStore';
import type { SpecEntry } from '@/lib/api';
type SettingsTab = 'project-specs' | 'personal-specs' | 'hooks' | 'injection' | 'settings';
// Convert SpecEntry to Spec for display
function specEntryToSpec(entry: SpecEntry, dimension: string): Spec {
return {
id: entry.file,
title: entry.title,
dimension: dimension as Spec['dimension'],
keywords: entry.keywords,
readMode: entry.readMode as Spec['readMode'],
priority: entry.priority as Spec['priority'],
file: entry.file,
enabled: true, // Default to enabled
};
}
export function SpecsSettingsPage() {
const { formatMessage } = useIntl();
const projectPath = useWorkflowStore(selectProjectPath);
const [activeTab, setActiveTab] = useState<SettingsTab>('project-specs');
const [searchQuery, setSearchQuery] = useState('');
const [editDialogOpen, setEditDialogOpen] = useState(false);
@@ -29,13 +45,50 @@ export function SpecsSettingsPage() {
const [editingSpec, setEditingSpec] = useState<Spec | null>(null);
const [editingHook, setEditingHook] = useState<HookConfig | null>(null);
// Mock data for demonstration - will be replaced with real API calls
const [projectSpecs] = useState<Spec[]>([]);
const [personalSpecs] = useState<Spec[]>([]);
const [hooks] = useState<HookConfig[]>([]);
const [isLoading] = useState(false);
// Fetch real data
const { data: specsListData, isLoading: specsLoading, refetch: refetchSpecs } = useSpecsList({ projectPath });
const { data: statsData } = useSpecStats({ projectPath });
const { data: systemSettings } = useSystemSettings();
const rebuildMutation = useRebuildSpecIndex();
const { data: statsData, refetch: refetchStats } = useSpecStats();
// Convert specs data to display format
const { projectSpecs, personalSpecs } = useMemo(() => {
if (!specsListData?.specs) {
return { projectSpecs: [], personalSpecs: [] };
}
const specs: Spec[] = [];
const personal: Spec[] = [];
for (const [dimension, entries] of Object.entries(specsListData.specs)) {
for (const entry of entries) {
const spec = specEntryToSpec(entry, dimension);
if (dimension === 'personal') {
personal.push(spec);
} else {
specs.push(spec);
}
}
}
return { projectSpecs: specs, personalSpecs: personal };
}, [specsListData]);
// Get hooks from system settings
const hooks: HookConfig[] = useMemo(() => {
return systemSettings?.recommendedHooks?.map(h => ({
id: h.id,
name: h.name,
event: h.event as HookConfig['event'],
command: h.command,
description: h.description,
scope: h.scope as HookConfig['scope'],
enabled: h.autoInstall ?? false,
installed: h.autoInstall ?? false,
})) ?? [];
}, [systemSettings]);
const isLoading = specsLoading;
const handleSpecEdit = (spec: Spec) => {
setEditingSpec(spec);
@@ -81,7 +134,11 @@ export function SpecsSettingsPage() {
const handleRebuildIndex = async () => {
console.log('Rebuilding index...');
// TODO: Implement rebuild logic
rebuildMutation.mutate(undefined, {
onSuccess: () => {
refetchSpecs();
}
});
};
const filterSpecs = (specs: Spec[]) => {
@@ -117,7 +174,7 @@ export function SpecsSettingsPage() {
</div>
{/* Stats Summary */}
{statsData && (
{statsData?.dimensions && (
<div className="grid grid-cols-4 gap-4">
{Object.entries(statsData.dimensions).map(([dim, data]) => (
<Card key={dim}>
@@ -178,7 +235,7 @@ export function SpecsSettingsPage() {
scope: 'global',
enabled: true,
timeout: 5000,
failMode: 'silent'
failMode: 'continue'
},
{
id: 'spec-injection-prompt',
@@ -188,7 +245,7 @@ export function SpecsSettingsPage() {
scope: 'project',
enabled: true,
timeout: 5000,
failMode: 'silent'
failMode: 'continue'
}
];
@@ -219,11 +276,11 @@ export function SpecsSettingsPage() {
<HookCard
key={hook.id}
hook={hook}
isRecommended={true}
isRecommendedCard={true}
onInstall={() => console.log('Install:', hook.id)}
onEdit={handleHookEdit}
onToggle={handleHookToggle}
onDelete={handleHookDelete}
onUninstall={handleHookDelete}
/>
))}
</div>
@@ -261,7 +318,7 @@ export function SpecsSettingsPage() {
hook={hook}
onEdit={handleHookEdit}
onToggle={handleHookToggle}
onDelete={handleHookDelete}
onUninstall={handleHookDelete}
/>
))}
</div>
@@ -273,7 +330,7 @@ export function SpecsSettingsPage() {
};
return (
<div className="container py-6 max-w-6xl">
<div className="max-w-6xl mx-auto">
{/* Page Header */}
<div className="mb-6">
<h1 className="text-2xl font-bold flex items-center gap-2">
@@ -343,8 +400,10 @@ export function SpecsSettingsPage() {
<HookDialog
open={hookDialogOpen}
onOpenChange={setHookDialogOpen}
hook={editingHook}
onSave={handleHookSave}
hook={editingHook ?? undefined}
onSave={(hookData) => {
handleHookSave(editingHook?.id ?? null, hookData);
}}
/>
</div>
);

View File

@@ -11,7 +11,7 @@ import chalk from 'chalk';
interface SpecOptions {
dimension?: string;
context?: string;
keywords?: string;
stdin?: boolean;
json?: boolean;
}
@@ -60,11 +60,11 @@ function getProjectPath(hookCwd?: string): string {
/**
* Load action - load specs matching dimension/keywords.
*
* CLI mode: --dimension and --context options, outputs formatted markdown.
* CLI mode: --dimension and --keywords options, outputs formatted markdown.
* Hook mode: --stdin reads JSON {session_id, cwd, user_prompt}, outputs JSON {continue, systemMessage}.
*/
async function loadAction(options: SpecOptions): Promise<void> {
const { stdin, dimension, context } = options;
const { stdin, dimension, keywords: keywordsInput } = options;
let projectPath: string;
let stdinData: StdinData | undefined;
@@ -89,8 +89,8 @@ async function loadAction(options: SpecOptions): Promise<void> {
try {
const { loadSpecs } = await import('../tools/spec-loader.js');
const keywords = context
? context.split(/[\s,]+/).filter(Boolean)
const keywords = keywordsInput
? keywordsInput.split(/[\s,]+/).filter(Boolean)
: undefined;
const result = await loadSpecs({
@@ -361,19 +361,31 @@ ${chalk.bold('SUBCOMMANDS')}
${chalk.bold('OPTIONS')}
--dimension <dim> Target dimension: specs, roadmap, changelog, personal
--context <text> Context text for keyword extraction (CLI mode)
--keywords <text> Keywords for spec matching (space or comma separated)
--stdin Read input from stdin (Hook mode)
--json Output as JSON
${chalk.bold('KEYWORD CATEGORIES')}
Use these predefined keywords to load specs for specific workflow stages:
${chalk.cyan('exploration')} - Code exploration, analysis, debugging context
${chalk.cyan('planning')} - Task planning, roadmap, requirements context
${chalk.cyan('execution')} - Implementation, testing, deployment context
${chalk.bold('EXAMPLES')}
${chalk.gray('# Initialize spec system:')}
ccw spec init
${chalk.gray('# Load specs for a topic (CLI mode):')}
ccw spec load --dimension specs --context "auth jwt security"
${chalk.gray('# Load exploration-phase specs:')}
ccw spec load --keywords exploration
${chalk.gray('# Load all matching specs:')}
ccw spec load --context "implement user authentication"
${chalk.gray('# Load planning-phase specs with auth topic:')}
ccw spec load --keywords "planning auth"
${chalk.gray('# Load execution-phase specs:')}
ccw spec load --keywords execution
${chalk.gray('# Load specs for a topic (CLI mode):')}
ccw spec load --dimension specs --keywords "auth jwt security"
${chalk.gray('# Use as Claude Code hook (settings.json):')}
ccw spec load --stdin

View File

@@ -24,6 +24,19 @@ import { join, basename, extname, relative } from 'path';
// Types
// ============================================================================
/**
* Spec categories for workflow stage-based loading (used as keywords).
* - 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]
*/
export const SPEC_CATEGORIES = ['exploration', 'planning', 'execution'] as const;
export type SpecCategory = typeof SPEC_CATEGORIES[number];
/**
* YAML frontmatter schema for spec MD files.
*/
@@ -45,7 +58,7 @@ export interface SpecIndexEntry {
file: string;
/** Dimension this spec belongs to */
dimension: string;
/** Keywords for matching against user prompts */
/** Keywords for matching against user prompts (may include category markers) */
keywords: string[];
/** Whether this spec is required or optional */
readMode: 'required' | 'optional';
@@ -87,8 +100,9 @@ const VALID_READ_MODES = ['required', 'optional'] as const;
const VALID_PRIORITIES = ['critical', 'high', 'medium', 'low'] as const;
/**
* Directory name for spec index cache files.
* Directory name for spec index cache files (inside .workflow/).
*/
const WORKFLOW_DIR = '.workflow';
const SPEC_INDEX_DIR = '.spec-index';
// ============================================================================
@@ -100,10 +114,10 @@ const SPEC_INDEX_DIR = '.spec-index';
*
* @param projectPath - Project root directory
* @param dimension - The dimension name
* @returns Absolute path to .spec-index/{dimension}.index.json
* @returns Absolute path to .workflow/.spec-index/{dimension}.index.json
*/
export function getIndexPath(projectPath: string, dimension: string): string {
return join(projectPath, SPEC_INDEX_DIR, `${dimension}.index.json`);
return join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR, `${dimension}.index.json`);
}
/**
@@ -188,7 +202,7 @@ export async function buildDimensionIndex(
* @param projectPath - Project root directory
*/
export async function buildAllIndices(projectPath: string): Promise<void> {
const indexDir = join(projectPath, SPEC_INDEX_DIR);
const indexDir = join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR);
// Ensure .spec-index directory exists
if (!existsSync(indexDir)) {
@@ -269,7 +283,7 @@ export async function getDimensionIndex(
// Build fresh and cache
const index = await buildDimensionIndex(projectPath, dimension);
const indexDir = join(projectPath, SPEC_INDEX_DIR);
const indexDir = join(projectPath, WORKFLOW_DIR, SPEC_INDEX_DIR);
if (!existsSync(indexDir)) {
mkdirSync(indexDir, { recursive: true });
}

View File

@@ -22,6 +22,7 @@ import {
SpecIndexEntry,
DimensionIndex,
SPEC_DIMENSIONS,
SPEC_CATEGORIES,
type SpecDimension,
} from './spec-index-builder.js';