mirror of
https://github.com/catlog22/Claude-Code-Workflow.git
synced 2026-02-06 01:54:11 +08:00
Compare commits
21 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8dee45c0a3 | ||
|
|
99ead8b165 | ||
|
|
0c7f13d9a4 | ||
|
|
ed32b95de1 | ||
|
|
beacc2e26b | ||
|
|
389621c954 | ||
|
|
2ba7756d13 | ||
|
|
02f77c0a51 | ||
|
|
5aa8d37cd0 | ||
|
|
a7b8ffc716 | ||
|
|
b0bc53646e | ||
|
|
5f31c9ad7e | ||
|
|
818d9f3f5d | ||
|
|
1c3c070db4 | ||
|
|
91e4792aa9 | ||
|
|
813bfa8f97 | ||
|
|
8b29f6bb7c | ||
|
|
27273405f7 | ||
|
|
f4299457fb | ||
|
|
06983a35ad | ||
|
|
a80953527b |
@@ -392,7 +392,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
// Pattern: Project structure analysis
|
||||
{
|
||||
"step": "analyze_project_architecture",
|
||||
"commands": ["bash(~/.claude/scripts/get_modules_by_depth.sh)"],
|
||||
"commands": ["bash(ccw tool exec get_modules_by_depth '{}')"],
|
||||
"output_to": "project_architecture"
|
||||
},
|
||||
|
||||
|
||||
@@ -67,7 +67,7 @@ Score = 0
|
||||
|
||||
**1. Project Structure**:
|
||||
```bash
|
||||
~/.claude/scripts/get_modules_by_depth.sh
|
||||
ccw tool exec get_modules_by_depth '{}'
|
||||
```
|
||||
|
||||
**2. Content Search**:
|
||||
|
||||
@@ -67,7 +67,7 @@ Phase 4: Output Generation
|
||||
|
||||
```bash
|
||||
# Project structure
|
||||
~/.claude/scripts/get_modules_by_depth.sh
|
||||
ccw tool exec get_modules_by_depth '{}'
|
||||
|
||||
# Pattern discovery (adapt based on language)
|
||||
rg "^export (class|interface|function) " --type ts -n
|
||||
|
||||
@@ -31,7 +31,7 @@ You are a context discovery specialist focused on gathering relevant project inf
|
||||
### 1. Reference Documentation (Project Standards)
|
||||
**Tools**:
|
||||
- `Read()` - Load CLAUDE.md, README.md, architecture docs
|
||||
- `Bash(~/.claude/scripts/get_modules_by_depth.sh)` - Project structure
|
||||
- `Bash(ccw tool exec get_modules_by_depth '{}')` - Project structure
|
||||
- `Glob()` - Find documentation files
|
||||
|
||||
**Use**: Phase 0 foundation setup
|
||||
@@ -82,7 +82,7 @@ mcp__code-index__set_project_path(process.cwd())
|
||||
mcp__code-index__refresh_index()
|
||||
|
||||
// 2. Project Structure
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}')
|
||||
|
||||
// 3. Load Documentation (if not in memory)
|
||||
if (!memory.has("CLAUDE.md")) Read(CLAUDE.md)
|
||||
|
||||
@@ -8,7 +8,7 @@ You are a documentation update coordinator for complex projects. Orchestrate par
|
||||
|
||||
## Core Mission
|
||||
|
||||
Execute depth-parallel updates for all modules using `~/.claude/scripts/update_module_claude.sh`. **Every module path must be processed**.
|
||||
Execute depth-parallel updates for all modules using `ccw tool exec update_module_claude`. **Every module path must be processed**.
|
||||
|
||||
## Input Context
|
||||
|
||||
@@ -42,12 +42,12 @@ TodoWrite([
|
||||
# 3. Launch parallel jobs (max 4)
|
||||
|
||||
# Depth 5 example (Layer 3 - use multi-layer):
|
||||
~/.claude/scripts/update_module_claude.sh "multi-layer" "./.claude/workflows/cli-templates/prompts/analysis" "gemini" &
|
||||
~/.claude/scripts/update_module_claude.sh "multi-layer" "./.claude/workflows/cli-templates/prompts/development" "gemini" &
|
||||
ccw tool exec update_module_claude '{"strategy":"multi-layer","path":"./.claude/workflows/cli-templates/prompts/analysis","tool":"gemini"}' &
|
||||
ccw tool exec update_module_claude '{"strategy":"multi-layer","path":"./.claude/workflows/cli-templates/prompts/development","tool":"gemini"}' &
|
||||
|
||||
# Depth 1 example (Layer 2 - use single-layer):
|
||||
~/.claude/scripts/update_module_claude.sh "single-layer" "./src/auth" "gemini" &
|
||||
~/.claude/scripts/update_module_claude.sh "single-layer" "./src/api" "gemini" &
|
||||
ccw tool exec update_module_claude '{"strategy":"single-layer","path":"./src/auth","tool":"gemini"}' &
|
||||
ccw tool exec update_module_claude '{"strategy":"single-layer","path":"./src/api","tool":"gemini"}' &
|
||||
# ... up to 4 concurrent jobs
|
||||
|
||||
# 4. Wait for all depth jobs to complete
|
||||
|
||||
@@ -191,7 +191,7 @@ target/
|
||||
### Step 2: Workspace Analysis (MANDATORY FIRST)
|
||||
```bash
|
||||
# Analyze workspace structure
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh json)
|
||||
bash(ccw tool exec get_modules_by_depth '{"format":"json"}')
|
||||
```
|
||||
|
||||
### Step 3: Technology Detection
|
||||
|
||||
@@ -101,10 +101,10 @@ src/ (depth 1) → SINGLE STRATEGY
|
||||
Bash({command: "pwd && basename \"$(pwd)\" && git rev-parse --show-toplevel 2>/dev/null || pwd", run_in_background: false});
|
||||
|
||||
// Get module structure with classification
|
||||
Bash({command: "~/.claude/scripts/get_modules_by_depth.sh list | ~/.claude/scripts/classify-folders.sh", run_in_background: false});
|
||||
Bash({command: "ccw tool exec get_modules_by_depth '{\"format\":\"list\"}' | ccw tool exec classify_folders '{}'", run_in_background: false});
|
||||
|
||||
// OR with path parameter
|
||||
Bash({command: "cd <target-path> && ~/.claude/scripts/get_modules_by_depth.sh list | ~/.claude/scripts/classify-folders.sh", run_in_background: false});
|
||||
Bash({command: "cd <target-path> && ccw tool exec get_modules_by_depth '{\"format\":\"list\"}' | ccw tool exec classify_folders '{}'", run_in_background: false});
|
||||
```
|
||||
|
||||
**Parse output** `depth:N|path:<PATH>|type:<code|navigation>|...` to extract module paths, types, and count.
|
||||
@@ -200,7 +200,7 @@ for (let layer of [3, 2, 1]) {
|
||||
let strategy = module.depth >= 3 ? "full" : "single";
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/generate_module_docs.sh "${strategy}" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec generate_module_docs '{"strategy":"${strategy}","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -263,7 +263,7 @@ MODULES:
|
||||
|
||||
TOOLS (try in order): {{tool_1}}, {{tool_2}}, {{tool_3}}
|
||||
|
||||
EXECUTION SCRIPT: ~/.claude/scripts/generate_module_docs.sh
|
||||
EXECUTION SCRIPT: ccw tool exec generate_module_docs
|
||||
- Accepts strategy parameter: full | single
|
||||
- Accepts folder type detection: code | navigation
|
||||
- Tool execution via direct CLI commands (gemini/qwen/codex)
|
||||
@@ -273,7 +273,7 @@ EXECUTION FLOW (for each module):
|
||||
1. Tool fallback loop (exit on first success):
|
||||
for tool in {{tool_1}} {{tool_2}} {{tool_3}}; do
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "{{strategy}}" "." "{{project_name}}" "${tool}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"{{strategy}}","sourcePath":".","projectName":"{{project_name}}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
exit_code=$?
|
||||
@@ -322,7 +322,7 @@ let project_root = get_project_root();
|
||||
report("Generating project README.md...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "project-readme" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"project-readme","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -335,7 +335,7 @@ for (let tool of tool_order) {
|
||||
report("Generating ARCHITECTURE.md and EXAMPLES.md...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "project-architecture" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"project-architecture","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -350,7 +350,7 @@ if (bash_result.stdout.includes("API_FOUND")) {
|
||||
report("Generating HTTP API documentation...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "http-api" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"http-api","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
|
||||
@@ -51,7 +51,7 @@ Orchestrates context-aware documentation generation/update for changed modules u
|
||||
Bash({command: "pwd && basename \"$(pwd)\" && git rev-parse --show-toplevel 2>/dev/null || pwd", run_in_background: false});
|
||||
|
||||
// Detect changed modules
|
||||
Bash({command: "~/.claude/scripts/detect_changed_modules.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec detect_changed_modules '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// Cache git changes
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
@@ -123,7 +123,7 @@ for (let depth of sorted_depths.reverse()) { // N → 0
|
||||
return async () => {
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/generate_module_docs.sh "single" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -207,21 +207,21 @@ EXECUTION:
|
||||
For each module above:
|
||||
1. Try tool 1:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_1}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_1}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_1}}", proceed to next module
|
||||
→ Failure: Try tool 2
|
||||
2. Try tool 2:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_2}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_2}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_2}}", proceed to next module
|
||||
→ Failure: Try tool 3
|
||||
3. Try tool 3:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_3}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_3}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_3}}", proceed to next module
|
||||
|
||||
@@ -85,10 +85,10 @@ bash(jq '. + {"target_path":"{target_path}","project_root":"{project_root}","pro
|
||||
|
||||
```bash
|
||||
# 1. Run folder analysis
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh | ~/.claude/scripts/classify-folders.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}' | ccw tool exec classify_folders '{}')
|
||||
|
||||
# 2. Get top-level directories (first 2 path levels)
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh | ~/.claude/scripts/classify-folders.sh | awk -F'|' '{print $1}' | sed 's|^\./||' | awk -F'/' '{if(NF>=2) print $1"/"$2; else if(NF==1) print $1}' | sort -u)
|
||||
bash(ccw tool exec get_modules_by_depth '{}' | ccw tool exec classify_folders '{}' | awk -F'|' '{print $1}' | sed 's|^\./||' | awk -F'/' '{if(NF>=2) print $1"/"$2; else if(NF==1) print $1}' | sort -u)
|
||||
|
||||
# 3. Find existing docs (if directory exists)
|
||||
bash(if [ -d .workflow/docs/\${project_name} ]; then find .workflow/docs/\${project_name} -type f -name "*.md" ! -path "*/README.md" ! -path "*/ARCHITECTURE.md" ! -path "*/EXAMPLES.md" ! -path "*/api/*" 2>/dev/null; fi)
|
||||
|
||||
@@ -109,7 +109,7 @@ Task(
|
||||
|
||||
1. **Project Structure**
|
||||
\`\`\`bash
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}')
|
||||
\`\`\`
|
||||
|
||||
2. **Core Documentation**
|
||||
|
||||
@@ -99,10 +99,10 @@ src/ (depth 1) → SINGLE-LAYER STRATEGY
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
|
||||
// Get module structure
|
||||
Bash({command: "~/.claude/scripts/get_modules_by_depth.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec get_modules_by_depth '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// OR with --path
|
||||
Bash({command: "cd <target-path> && ~/.claude/scripts/get_modules_by_depth.sh list", run_in_background: false});
|
||||
Bash({command: "cd <target-path> && ccw tool exec get_modules_by_depth '{\"format\":\"list\"}'", run_in_background: false});
|
||||
```
|
||||
|
||||
**Parse output** `depth:N|path:<PATH>|...` to extract module paths and count.
|
||||
@@ -185,7 +185,7 @@ for (let layer of [3, 2, 1]) {
|
||||
let strategy = module.depth >= 3 ? "multi-layer" : "single-layer";
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/update_module_claude.sh "${strategy}" "." "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec update_module_claude '{"strategy":"${strategy}","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -244,7 +244,7 @@ MODULES:
|
||||
|
||||
TOOLS (try in order): {{tool_1}}, {{tool_2}}, {{tool_3}}
|
||||
|
||||
EXECUTION SCRIPT: ~/.claude/scripts/update_module_claude.sh
|
||||
EXECUTION SCRIPT: ccw tool exec update_module_claude
|
||||
- Accepts strategy parameter: multi-layer | single-layer
|
||||
- Tool execution via direct CLI commands (gemini/qwen/codex)
|
||||
|
||||
@@ -252,7 +252,7 @@ EXECUTION FLOW (for each module):
|
||||
1. Tool fallback loop (exit on first success):
|
||||
for tool in {{tool_1}} {{tool_2}} {{tool_3}}; do
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "{{strategy}}" "." "${tool}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"{{strategy}}","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
exit_code=$?
|
||||
|
||||
@@ -41,7 +41,7 @@ Orchestrates context-aware CLAUDE.md updates for changed modules using batched a
|
||||
|
||||
```javascript
|
||||
// Detect changed modules
|
||||
Bash({command: "~/.claude/scripts/detect_changed_modules.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec detect_changed_modules '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// Cache git changes
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
@@ -102,7 +102,7 @@ for (let depth of sorted_depths.reverse()) { // N → 0
|
||||
return async () => {
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -184,21 +184,21 @@ EXECUTION:
|
||||
For each module above:
|
||||
1. Try tool 1:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_1}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_1}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_1}}", proceed to next module
|
||||
→ Failure: Try tool 2
|
||||
2. Try tool 2:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_2}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_2}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_2}}", proceed to next module
|
||||
→ Failure: Try tool 3
|
||||
3. Try tool 3:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_3}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_3}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_3}}", proceed to next module
|
||||
|
||||
@@ -92,7 +92,7 @@ Analyze project for workflow initialization and generate .workflow/project.json.
|
||||
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Execute: cat ~/.claude/workflows/cli-templates/schemas/project-json-schema.json (get schema reference)
|
||||
2. Execute: ~/.claude/scripts/get_modules_by_depth.sh (get project structure)
|
||||
2. Execute: ccw tool exec get_modules_by_depth '{}' (get project structure)
|
||||
|
||||
## Task
|
||||
Generate complete project.json with:
|
||||
|
||||
@@ -177,7 +177,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{error_keyword_from_bug}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json (get output schema reference)
|
||||
|
||||
|
||||
@@ -170,7 +170,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference)
|
||||
|
||||
|
||||
@@ -135,7 +135,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference)
|
||||
|
||||
|
||||
@@ -320,7 +320,7 @@ Read({base_path}/prototypes/{target}-style-{style_id}-layout-{layout_id}.html)
|
||||
|
||||
### Step 1: Run Preview Generation Script
|
||||
```bash
|
||||
bash(~/.claude/scripts/ui-generate-preview.sh "{base_path}/prototypes")
|
||||
bash(ccw tool exec ui_generate_preview '{"prototypesDir":"{base_path}/prototypes"}')
|
||||
```
|
||||
|
||||
**Script generates**:
|
||||
@@ -432,7 +432,7 @@ bash(test -f {base_path}/prototypes/compare.html && echo "exists")
|
||||
bash(mkdir -p {base_path}/prototypes)
|
||||
|
||||
# Run preview script
|
||||
bash(~/.claude/scripts/ui-generate-preview.sh "{base_path}/prototypes")
|
||||
bash(ccw tool exec ui_generate_preview '{"prototypesDir":"{base_path}/prototypes"}')
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
@@ -467,7 +467,7 @@ ERROR: Agent assembly failed
|
||||
→ Check inputs exist, validate JSON structure
|
||||
|
||||
ERROR: Script permission denied
|
||||
→ chmod +x ~/.claude/scripts/ui-generate-preview.sh
|
||||
→ Verify ccw tool is available: ccw tool list
|
||||
```
|
||||
|
||||
### Recovery Strategies
|
||||
|
||||
@@ -106,7 +106,7 @@ echo " Output: $base_path"
|
||||
|
||||
# 3. Discover files using script
|
||||
discovery_file="${intermediates_dir}/discovered-files.json"
|
||||
~/.claude/scripts/discover-design-files.sh "$source" "$discovery_file"
|
||||
ccw tool exec discover_design_files '{"sourceDir":"'"$source"'","outputPath":"'"$discovery_file"'"}'
|
||||
|
||||
echo " Output: $discovery_file"
|
||||
```
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec classify_folders '{"path":".","outputFormat":"json"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Classify folders by type for documentation generation
|
||||
# Usage: get_modules_by_depth.sh | classify-folders.sh
|
||||
# Output: folder_path|folder_type|code:N|dirs:N
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec convert_tokens_to_css '{"inputPath":"design-tokens.json","outputPath":"tokens.css"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Convert design-tokens.json to tokens.css with Google Fonts import and global font rules
|
||||
# Usage: cat design-tokens.json | ./convert_tokens_to_css.sh > tokens.css
|
||||
# Or: ./convert_tokens_to_css.sh < design-tokens.json > tokens.css
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec detect_changed_modules '{"baseBranch":"main","format":"list"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Detect modules affected by git changes or recent modifications
|
||||
# Usage: detect_changed_modules.sh [format]
|
||||
# format: list|grouped|paths (default: paths)
|
||||
@@ -154,4 +158,4 @@ detect_changed_modules() {
|
||||
# Execute function if script is run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
detect_changed_modules "$@"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/usr/bin/env bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec discover_design_files '{"sourceDir":".","outputPath":"output.json"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# discover-design-files.sh - Discover design-related files and output JSON
|
||||
# Usage: discover-design-files.sh <source_dir> <output_json>
|
||||
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec generate_module_docs '{"path":".","strategy":"single-layer","tool":"gemini"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Generate documentation for modules and projects with multiple strategies
|
||||
# Usage: generate_module_docs.sh <strategy> <source_path> <project_name> [tool] [model]
|
||||
# strategy: full|single|project-readme|project-architecture|http-api
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec get_modules_by_depth '{"format":"list","path":"."}' OR ccw tool exec get_modules_by_depth '{}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Get modules organized by directory depth (deepest first)
|
||||
# Usage: get_modules_by_depth.sh [format]
|
||||
# format: list|grouped|json (default: list)
|
||||
@@ -163,4 +167,4 @@ get_modules_by_depth() {
|
||||
# Execute function if script is run directly
|
||||
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
|
||||
get_modules_by_depth "$@"
|
||||
fi
|
||||
fi
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec ui_generate_preview '{"designPath":"design-run-1","outputDir":"preview"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
#
|
||||
# UI Generate Preview v2.0 - Template-Based Preview Generation
|
||||
# Purpose: Generate compare.html and index.html using template substitution
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec ui_instantiate_prototypes '{"designPath":"design-run-1","outputDir":"output"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
|
||||
# UI Prototype Instantiation Script with Preview Generation (v3.0 - Auto-detect)
|
||||
# Purpose: Generate S × L × P final prototypes from templates + interactive preview files
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
#!/bin/bash
|
||||
# ⚠️ DEPRECATED: This script is deprecated.
|
||||
# Please use: ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"gemini"}'
|
||||
# This file will be removed in a future version.
|
||||
|
||||
# Update CLAUDE.md for modules with two strategies
|
||||
# Usage: update_module_claude.sh <strategy> <module_path> [tool] [model]
|
||||
# strategy: single-layer|multi-layer
|
||||
|
||||
@@ -509,24 +509,13 @@
|
||||
"name": "start",
|
||||
"command": "/workflow:session:start",
|
||||
"description": "Discover existing sessions or start new workflow session with intelligent session management and conflict detection",
|
||||
"arguments": "[--auto|--new] [optional: task description for new session]",
|
||||
"arguments": "[--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]",
|
||||
"category": "workflow",
|
||||
"subcategory": "session",
|
||||
"usage_scenario": "general",
|
||||
"difficulty": "Intermediate",
|
||||
"file_path": "workflow/session/start.md"
|
||||
},
|
||||
{
|
||||
"name": "workflow:status",
|
||||
"command": "/workflow:status",
|
||||
"description": "Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view",
|
||||
"arguments": "[optional: --project|task-id|--validate|--dashboard]",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "session-management",
|
||||
"difficulty": "Beginner",
|
||||
"file_path": "workflow/status.md"
|
||||
},
|
||||
{
|
||||
"name": "tdd-plan",
|
||||
"command": "/workflow:tdd-plan",
|
||||
|
||||
@@ -358,17 +358,6 @@
|
||||
"difficulty": "Intermediate",
|
||||
"file_path": "workflow/review.md"
|
||||
},
|
||||
{
|
||||
"name": "workflow:status",
|
||||
"command": "/workflow:status",
|
||||
"description": "Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view",
|
||||
"arguments": "[optional: --project|task-id|--validate|--dashboard]",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "session-management",
|
||||
"difficulty": "Beginner",
|
||||
"file_path": "workflow/status.md"
|
||||
},
|
||||
{
|
||||
"name": "tdd-plan",
|
||||
"command": "/workflow:tdd-plan",
|
||||
@@ -597,7 +586,7 @@
|
||||
"name": "start",
|
||||
"command": "/workflow:session:start",
|
||||
"description": "Discover existing sessions or start new workflow session with intelligent session management and conflict detection",
|
||||
"arguments": "[--auto|--new] [optional: task description for new session]",
|
||||
"arguments": "[--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]",
|
||||
"category": "workflow",
|
||||
"subcategory": "session",
|
||||
"usage_scenario": "general",
|
||||
|
||||
@@ -224,7 +224,7 @@
|
||||
"name": "start",
|
||||
"command": "/workflow:session:start",
|
||||
"description": "Discover existing sessions or start new workflow session with intelligent session management and conflict detection",
|
||||
"arguments": "[--auto|--new] [optional: task description for new session]",
|
||||
"arguments": "[--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]",
|
||||
"category": "workflow",
|
||||
"subcategory": "session",
|
||||
"usage_scenario": "general",
|
||||
@@ -713,17 +713,6 @@
|
||||
"usage_scenario": "session-management",
|
||||
"difficulty": "Intermediate",
|
||||
"file_path": "workflow/session/resume.md"
|
||||
},
|
||||
{
|
||||
"name": "workflow:status",
|
||||
"command": "/workflow:status",
|
||||
"description": "Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view",
|
||||
"arguments": "[optional: --project|task-id|--validate|--dashboard]",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "session-management",
|
||||
"difficulty": "Beginner",
|
||||
"file_path": "workflow/status.md"
|
||||
}
|
||||
],
|
||||
"testing": [
|
||||
|
||||
@@ -43,22 +43,11 @@
|
||||
"difficulty": "Intermediate",
|
||||
"file_path": "workflow/execute.md"
|
||||
},
|
||||
{
|
||||
"name": "workflow:status",
|
||||
"command": "/workflow:status",
|
||||
"description": "Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view",
|
||||
"arguments": "[optional: --project|task-id|--validate|--dashboard]",
|
||||
"category": "workflow",
|
||||
"subcategory": null,
|
||||
"usage_scenario": "session-management",
|
||||
"difficulty": "Beginner",
|
||||
"file_path": "workflow/status.md"
|
||||
},
|
||||
{
|
||||
"name": "start",
|
||||
"command": "/workflow:session:start",
|
||||
"description": "Discover existing sessions or start new workflow session with intelligent session management and conflict detection",
|
||||
"arguments": "[--auto|--new] [optional: task description for new session]",
|
||||
"arguments": "[--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]",
|
||||
"category": "workflow",
|
||||
"subcategory": "session",
|
||||
"usage_scenario": "general",
|
||||
|
||||
@@ -16,11 +16,9 @@ description: |
|
||||
color: yellow
|
||||
---
|
||||
|
||||
You are a pure execution agent specialized in creating actionable implementation plans. You receive requirements and control flags from the command layer and execute planning tasks without complex decision-making logic.
|
||||
|
||||
## Overview
|
||||
|
||||
**Agent Role**: Transform user requirements and brainstorming artifacts into structured, executable implementation plans with quantified deliverables and measurable acceptance criteria.
|
||||
**Agent Role**: Pure execution agent that transforms user requirements and brainstorming artifacts into structured, executable implementation plans with quantified deliverables and measurable acceptance criteria. Receives requirements and control flags from the command layer and executes planning tasks without complex decision-making logic.
|
||||
|
||||
**Core Capabilities**:
|
||||
- Load and synthesize context from multiple sources (session metadata, context packages, brainstorming artifacts)
|
||||
@@ -33,7 +31,7 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
|
||||
---
|
||||
|
||||
## 1. Execution Process
|
||||
## 1. Input & Execution
|
||||
|
||||
### 1.1 Input Processing
|
||||
|
||||
@@ -50,7 +48,7 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
- **Control flags**: DEEP_ANALYSIS_REQUIRED, etc.
|
||||
- **Task requirements**: Direct task description
|
||||
|
||||
### 1.2 Two-Phase Execution Flow
|
||||
### 1.2 Execution Flow
|
||||
|
||||
#### Phase 1: Context Loading & Assembly
|
||||
|
||||
@@ -88,6 +86,27 @@ You are a pure execution agent specialized in creating actionable implementation
|
||||
6. Assess task complexity (simple/medium/complex)
|
||||
```
|
||||
|
||||
**MCP Integration** (when `mcp_capabilities` available):
|
||||
|
||||
```javascript
|
||||
// Exa Code Context (mcp_capabilities.exa_code = true)
|
||||
mcp__exa__get_code_context_exa(
|
||||
query="TypeScript OAuth2 JWT authentication patterns",
|
||||
tokensNum="dynamic"
|
||||
)
|
||||
|
||||
// Integration in flow_control.pre_analysis
|
||||
{
|
||||
"step": "local_codebase_exploration",
|
||||
"action": "Explore codebase structure",
|
||||
"commands": [
|
||||
"bash(rg '^(function|class|interface).*[task_keyword]' --type ts -n --max-count 15)",
|
||||
"bash(find . -name '*[task_keyword]*' -type f | grep -v node_modules | head -10)"
|
||||
],
|
||||
"output_to": "codebase_structure"
|
||||
}
|
||||
```
|
||||
|
||||
**Context Package Structure** (fields defined by context-search-agent):
|
||||
|
||||
**Always Present**:
|
||||
@@ -169,30 +188,6 @@ if (contextPackage.brainstorm_artifacts?.role_analyses?.length > 0) {
|
||||
5. Update session state for execution readiness
|
||||
```
|
||||
|
||||
### 1.3 MCP Integration Guidelines
|
||||
|
||||
**Exa Code Context** (`mcp_capabilities.exa_code = true`):
|
||||
```javascript
|
||||
// Get best practices and examples
|
||||
mcp__exa__get_code_context_exa(
|
||||
query="TypeScript OAuth2 JWT authentication patterns",
|
||||
tokensNum="dynamic"
|
||||
)
|
||||
```
|
||||
|
||||
**Integration in flow_control.pre_analysis**:
|
||||
```json
|
||||
{
|
||||
"step": "local_codebase_exploration",
|
||||
"action": "Explore codebase structure",
|
||||
"commands": [
|
||||
"bash(rg '^(function|class|interface).*[task_keyword]' --type ts -n --max-count 15)",
|
||||
"bash(find . -name '*[task_keyword]*' -type f | grep -v node_modules | head -10)"
|
||||
],
|
||||
"output_to": "codebase_structure"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. Output Specifications
|
||||
@@ -213,7 +208,11 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
```
|
||||
|
||||
**Field Descriptions**:
|
||||
- `id`: Task identifier (format: `IMPL-N`)
|
||||
- `id`: Task identifier
|
||||
- Single module format: `IMPL-N` (e.g., IMPL-001, IMPL-002)
|
||||
- Multi-module format: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1, IMPL-C1)
|
||||
- Prefix: A, B, C... (assigned by module detection order)
|
||||
- Sequence: 1, 2, 3... (per-module increment)
|
||||
- `title`: Descriptive task name summarizing the work
|
||||
- `status`: Task state - `pending` (not started), `active` (in progress), `completed` (done), `blocked` (waiting on dependencies)
|
||||
- `context_package_path`: Path to smart context package containing project structure, dependencies, and brainstorming artifacts catalog
|
||||
@@ -225,7 +224,8 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
"meta": {
|
||||
"type": "feature|bugfix|refactor|test-gen|test-fix|docs",
|
||||
"agent": "@code-developer|@action-planning-agent|@test-fix-agent|@universal-executor",
|
||||
"execution_group": "parallel-abc123|null"
|
||||
"execution_group": "parallel-abc123|null",
|
||||
"module": "frontend|backend|shared|null"
|
||||
}
|
||||
}
|
||||
```
|
||||
@@ -234,6 +234,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
- `type`: Task category - `feature` (new functionality), `bugfix` (fix defects), `refactor` (restructure code), `test-gen` (generate tests), `test-fix` (fix failing tests), `docs` (documentation)
|
||||
- `agent`: Assigned agent for execution
|
||||
- `execution_group`: Parallelization group ID (tasks with same ID can run concurrently) or `null` for sequential tasks
|
||||
- `module`: Module identifier for multi-module projects (e.g., `frontend`, `backend`, `shared`) or `null` for single-module
|
||||
|
||||
**Test Task Extensions** (for type="test-gen" or type="test-fix"):
|
||||
|
||||
@@ -391,7 +392,7 @@ Generate individual `.task/IMPL-*.json` files with the following structure:
|
||||
// Pattern: Project structure analysis
|
||||
{
|
||||
"step": "analyze_project_architecture",
|
||||
"commands": ["bash(~/.claude/scripts/get_modules_by_depth.sh)"],
|
||||
"commands": ["bash(ccw tool exec get_modules_by_depth '{}')"],
|
||||
"output_to": "project_architecture"
|
||||
},
|
||||
|
||||
@@ -604,10 +605,42 @@ Agent determines CLI tool usage per-step based on user semantics and task nature
|
||||
- Analysis results (technical approach, architecture decisions)
|
||||
- Brainstorming artifacts (role analyses, guidance specifications)
|
||||
|
||||
**Multi-Module Format** (when modules detected):
|
||||
|
||||
When multiple modules are detected (frontend/backend, etc.), organize IMPL_PLAN.md by module:
|
||||
|
||||
```markdown
|
||||
# Implementation Plan
|
||||
|
||||
## Module A: Frontend (N tasks)
|
||||
### IMPL-A1: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
### IMPL-A2: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
## Module B: Backend (N tasks)
|
||||
### IMPL-B1: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
### IMPL-B2: [Task Title]
|
||||
[Task details...]
|
||||
|
||||
## Cross-Module Dependencies
|
||||
- IMPL-A1 → IMPL-B1 (Frontend depends on Backend API)
|
||||
- IMPL-A2 → IMPL-B2 (UI state depends on Backend service)
|
||||
```
|
||||
|
||||
**Cross-Module Dependency Notation**:
|
||||
- During parallel planning, use `CROSS::{module}::{pattern}` format
|
||||
- Example: `depends_on: ["CROSS::B::api-endpoint"]`
|
||||
- Integration phase resolves to actual task IDs: `CROSS::B::api → IMPL-B1`
|
||||
|
||||
### 2.3 TODO_LIST.md Structure
|
||||
|
||||
Generate at `.workflow/active/{session_id}/TODO_LIST.md`:
|
||||
|
||||
**Single Module Format**:
|
||||
```markdown
|
||||
# Tasks: {Session Topic}
|
||||
|
||||
@@ -621,30 +654,54 @@ Generate at `.workflow/active/{session_id}/TODO_LIST.md`:
|
||||
- `- [x]` = Completed task
|
||||
```
|
||||
|
||||
**Multi-Module Format** (hierarchical by module):
|
||||
```markdown
|
||||
# Tasks: {Session Topic}
|
||||
|
||||
## Module A (Frontend)
|
||||
- [ ] **IMPL-A1**: [Task Title] → [📋](./.task/IMPL-A1.json)
|
||||
- [ ] **IMPL-A2**: [Task Title] → [📋](./.task/IMPL-A2.json)
|
||||
|
||||
## Module B (Backend)
|
||||
- [ ] **IMPL-B1**: [Task Title] → [📋](./.task/IMPL-B1.json)
|
||||
- [ ] **IMPL-B2**: [Task Title] → [📋](./.task/IMPL-B2.json)
|
||||
|
||||
## Cross-Module Dependencies
|
||||
- IMPL-A1 → IMPL-B1 (Frontend depends on Backend API)
|
||||
|
||||
## Status Legend
|
||||
- `- [ ]` = Pending task
|
||||
- `- [x]` = Completed task
|
||||
```
|
||||
|
||||
**Linking Rules**:
|
||||
- Todo items → task JSON: `[📋](./.task/IMPL-XXX.json)`
|
||||
- Completed tasks → summaries: `[✅](./.summaries/IMPL-XXX-summary.md)`
|
||||
- Consistent ID schemes: IMPL-XXX
|
||||
- Consistent ID schemes: `IMPL-N` (single) or `IMPL-{prefix}{seq}` (multi-module)
|
||||
|
||||
### 2.4 Complexity-Based Structure Selection
|
||||
### 2.4 Complexity & Structure Selection
|
||||
|
||||
Use `analysis_results.complexity` or task count to determine structure:
|
||||
|
||||
**Simple Tasks** (≤5 tasks):
|
||||
- Flat structure: IMPL_PLAN.md + TODO_LIST.md + task JSONs
|
||||
- All tasks at same level
|
||||
**Single Module Mode**:
|
||||
- **Simple Tasks** (≤5 tasks): Flat structure
|
||||
- **Medium Tasks** (6-12 tasks): Flat structure
|
||||
- **Complex Tasks** (>12 tasks): Re-scope required (maximum 12 tasks hard limit)
|
||||
|
||||
**Medium Tasks** (6-12 tasks):
|
||||
- Flat structure: IMPL_PLAN.md + TODO_LIST.md + task JSONs
|
||||
- All tasks at same level
|
||||
**Multi-Module Mode** (N+1 parallel planning):
|
||||
- **Per-module limit**: ≤9 tasks per module
|
||||
- **Total limit**: Sum of all module tasks ≤27 (3 modules × 9 tasks)
|
||||
- **Task ID format**: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1)
|
||||
- **Structure**: Hierarchical by module in IMPL_PLAN.md and TODO_LIST.md
|
||||
|
||||
**Complex Tasks** (>12 tasks):
|
||||
- **Re-scope required**: Maximum 12 tasks hard limit
|
||||
- If analysis_results contains >12 tasks, consolidate or request re-scoping
|
||||
**Multi-Module Detection Triggers**:
|
||||
- Explicit frontend/backend separation (`src/frontend`, `src/backend`)
|
||||
- Monorepo structure (`packages/*`, `apps/*`)
|
||||
- Context-package dependency clustering (2+ distinct module groups)
|
||||
|
||||
---
|
||||
|
||||
## 3. Quality & Standards
|
||||
## 3. Quality Standards
|
||||
|
||||
### 3.1 Quantification Requirements (MANDATORY)
|
||||
|
||||
@@ -670,47 +727,46 @@ Use `analysis_results.complexity` or task count to determine structure:
|
||||
- [ ] Each implementation step has its own acceptance criteria
|
||||
|
||||
**Examples**:
|
||||
- ✅ GOOD: `"Implement 5 commands: [cmd1, cmd2, cmd3, cmd4, cmd5]"`
|
||||
- ❌ BAD: `"Implement new commands"`
|
||||
- ✅ GOOD: `"5 files created: verify by ls .claude/commands/*.md | wc -l = 5"`
|
||||
- ❌ BAD: `"All commands implemented successfully"`
|
||||
- GOOD: `"Implement 5 commands: [cmd1, cmd2, cmd3, cmd4, cmd5]"`
|
||||
- BAD: `"Implement new commands"`
|
||||
- GOOD: `"5 files created: verify by ls .claude/commands/*.md | wc -l = 5"`
|
||||
- BAD: `"All commands implemented successfully"`
|
||||
|
||||
### 3.2 Planning Principles
|
||||
### 3.2 Planning & Organization Standards
|
||||
|
||||
**Planning Principles**:
|
||||
- Each stage produces working, testable code
|
||||
- Clear success criteria for each deliverable
|
||||
- Dependencies clearly identified between stages
|
||||
- Incremental progress over big bangs
|
||||
|
||||
### 3.3 File Organization
|
||||
|
||||
**File Organization**:
|
||||
- Session naming: `WFS-[topic-slug]`
|
||||
- Task IDs: IMPL-XXX (flat structure only)
|
||||
- Directory structure: flat task organization
|
||||
|
||||
### 3.4 Document Standards
|
||||
- Task IDs:
|
||||
- Single module: `IMPL-N` (e.g., IMPL-001, IMPL-002)
|
||||
- Multi-module: `IMPL-{prefix}{seq}` (e.g., IMPL-A1, IMPL-B1)
|
||||
- Directory structure: flat task organization (all tasks in `.task/`)
|
||||
|
||||
**Document Standards**:
|
||||
- Proper linking between documents
|
||||
- Consistent navigation and references
|
||||
|
||||
---
|
||||
|
||||
## 4. Key Reminders
|
||||
### 3.3 Guidelines Checklist
|
||||
|
||||
**ALWAYS:**
|
||||
- **Apply Quantification Requirements**: All requirements, acceptance criteria, and modification points MUST include explicit counts and enumerations
|
||||
- **Load IMPL_PLAN template**: Read(~/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt) before generating IMPL_PLAN.md
|
||||
- **Use provided context package**: Extract all information from structured context
|
||||
- **Respect memory-first rule**: Use provided content (already loaded from memory/file)
|
||||
- **Follow 6-field schema**: All task JSONs must have id, title, status, context_package_path, meta, context, flow_control
|
||||
- **Map artifacts**: Use artifacts_inventory to populate task.context.artifacts array
|
||||
- **Add MCP integration**: Include MCP tool steps in flow_control.pre_analysis when capabilities available
|
||||
- **Validate task count**: Maximum 12 tasks hard limit, request re-scope if exceeded
|
||||
- **Use session paths**: Construct all paths using provided session_id
|
||||
- **Link documents properly**: Use correct linking format (📋 for JSON, ✅ for summaries)
|
||||
- **Run validation checklist**: Verify all quantification requirements before finalizing task JSONs
|
||||
- **Apply 举一反三 principle**: Adapt pre-analysis patterns to task-specific needs dynamically
|
||||
- **Follow template validation**: Complete IMPL_PLAN.md template validation checklist before finalization
|
||||
- Apply Quantification Requirements to all requirements, acceptance criteria, and modification points
|
||||
- Load IMPL_PLAN template: `Read(~/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt)` before generating IMPL_PLAN.md
|
||||
- Use provided context package: Extract all information from structured context
|
||||
- Respect memory-first rule: Use provided content (already loaded from memory/file)
|
||||
- Follow 6-field schema: All task JSONs must have id, title, status, context_package_path, meta, context, flow_control
|
||||
- Map artifacts: Use artifacts_inventory to populate task.context.artifacts array
|
||||
- Add MCP integration: Include MCP tool steps in flow_control.pre_analysis when capabilities available
|
||||
- Validate task count: Maximum 12 tasks hard limit, request re-scope if exceeded
|
||||
- Use session paths: Construct all paths using provided session_id
|
||||
- Link documents properly: Use correct linking format (📋 for JSON, ✅ for summaries)
|
||||
- Run validation checklist: Verify all quantification requirements before finalizing task JSONs
|
||||
- Apply 举一反三 principle: Adapt pre-analysis patterns to task-specific needs dynamically
|
||||
- Follow template validation: Complete IMPL_PLAN.md template validation checklist before finalization
|
||||
|
||||
**NEVER:**
|
||||
- Load files directly (use provided context package instead)
|
||||
|
||||
@@ -67,7 +67,7 @@ Score = 0
|
||||
|
||||
**1. Project Structure**:
|
||||
```bash
|
||||
~/.claude/scripts/get_modules_by_depth.sh
|
||||
ccw tool exec get_modules_by_depth '{}'
|
||||
```
|
||||
|
||||
**2. Content Search**:
|
||||
|
||||
@@ -67,7 +67,7 @@ Phase 4: Output Generation
|
||||
|
||||
```bash
|
||||
# Project structure
|
||||
~/.claude/scripts/get_modules_by_depth.sh
|
||||
ccw tool exec get_modules_by_depth '{}'
|
||||
|
||||
# Pattern discovery (adapt based on language)
|
||||
rg "^export (class|interface|function) " --type ts -n
|
||||
|
||||
@@ -119,17 +119,6 @@ This agent processes **simplified inline [FLOW_CONTROL]** format from brainstorm
|
||||
- No dependency management
|
||||
- Used for temporary context preparation
|
||||
|
||||
### NOT Handled by This Agent
|
||||
|
||||
**JSON format** (used by code-developer, test-fix-agent):
|
||||
```json
|
||||
"flow_control": {
|
||||
"pre_analysis": [...],
|
||||
"implementation_approach": [...]
|
||||
}
|
||||
```
|
||||
|
||||
This complete JSON format is stored in `.task/IMPL-*.json` files and handled by implementation agents, not conceptual-planning-agent.
|
||||
|
||||
### Role-Specific Analysis Dimensions
|
||||
|
||||
@@ -146,14 +135,14 @@ This complete JSON format is stored in `.task/IMPL-*.json` files and handled by
|
||||
|
||||
### Output Integration
|
||||
|
||||
**Gemini Analysis Integration**: Pattern-based analysis results are integrated into the single role's output:
|
||||
- Enhanced `analysis.md` with codebase insights and architectural patterns
|
||||
**Gemini Analysis Integration**: Pattern-based analysis results are integrated into role output documents:
|
||||
- Enhanced analysis documents with codebase insights and architectural patterns
|
||||
- Role-specific technical recommendations based on existing conventions
|
||||
- Pattern-based best practices from actual code examination
|
||||
- Realistic feasibility assessments based on current implementation
|
||||
|
||||
**Codex Analysis Integration**: Autonomous analysis results provide comprehensive insights:
|
||||
- Enhanced `analysis.md` with autonomous development recommendations
|
||||
- Enhanced analysis documents with autonomous development recommendations
|
||||
- Role-specific strategy based on intelligent system understanding
|
||||
- Autonomous development approaches and implementation guidance
|
||||
- Self-guided optimization and integration recommendations
|
||||
@@ -229,26 +218,23 @@ Generate documents according to loaded role template specifications:
|
||||
|
||||
**Output Location**: `.workflow/WFS-[session]/.brainstorming/[assigned-role]/`
|
||||
|
||||
**Required Files**:
|
||||
- **analysis.md**: Main role perspective analysis incorporating user context and role template
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
**Output Files**:
|
||||
- **analysis.md**: Index document with overview (optionally with `@` references to sub-documents)
|
||||
- **FORBIDDEN**: Never create `recommendations.md` or any file not starting with `analysis` prefix
|
||||
- **Auto-split if large**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files: analysis.md, analysis-1.md, analysis-2.md)
|
||||
- **Content**: Includes both analysis AND recommendations sections within analysis files
|
||||
- **[role-deliverables]/**: Directory for specialized role outputs as defined in planning role template (optional)
|
||||
- **analysis-{slug}.md**: Section content documents (slug from section heading: lowercase, hyphens)
|
||||
- Maximum 5 sub-documents (merge related sections if needed)
|
||||
- **Content**: Analysis AND recommendations sections
|
||||
|
||||
**File Structure Example**:
|
||||
```
|
||||
.workflow/WFS-[session]/.brainstorming/system-architect/
|
||||
├── analysis.md # Main system architecture analysis with recommendations
|
||||
├── analysis-1.md # (Optional) Continuation if content >800 lines
|
||||
└── deliverables/ # (Optional) Additional role-specific outputs
|
||||
├── technical-architecture.md # System design specifications
|
||||
├── technology-stack.md # Technology selection rationale
|
||||
└── scalability-plan.md # Scaling strategy
|
||||
├── analysis.md # Index with overview + @references
|
||||
├── analysis-architecture-assessment.md # Section content
|
||||
├── analysis-technology-evaluation.md # Section content
|
||||
├── analysis-integration-strategy.md # Section content
|
||||
└── analysis-recommendations.md # Section content (max 5 sub-docs total)
|
||||
|
||||
NOTE: ALL brainstorming output files MUST start with 'analysis' prefix
|
||||
FORBIDDEN: recommendations.md, recommendations-*.md, or any non-'analysis' prefixed files
|
||||
NOTE: ALL files MUST start with 'analysis' prefix. Max 5 sub-documents.
|
||||
```
|
||||
|
||||
## Role-Specific Planning Process
|
||||
@@ -268,14 +254,10 @@ FORBIDDEN: recommendations.md, recommendations-*.md, or any non-'analysis' prefi
|
||||
- **Validate Against Template**: Ensure analysis meets role template requirements and standards
|
||||
|
||||
### 3. Brainstorming Documentation Phase
|
||||
- **Create analysis.md**: Generate comprehensive role perspective analysis in designated output directory
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
- **FORBIDDEN**: Never create `recommendations.md` or any file not starting with `analysis` prefix
|
||||
- **Content**: Include both analysis AND recommendations sections within analysis files
|
||||
- **Auto-split**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files total)
|
||||
- **Generate Role Deliverables**: Create specialized outputs as defined in planning role template (optional)
|
||||
- **Create analysis.md**: Main document with overview (optionally with `@` references)
|
||||
- **Create sub-documents**: `analysis-{slug}.md` for major sections (max 5)
|
||||
- **Validate Output Structure**: Ensure all files saved to correct `.brainstorming/[role]/` directory
|
||||
- **Naming Validation**: Verify NO files with `recommendations` prefix exist
|
||||
- **Naming Validation**: Verify ALL files start with `analysis` prefix
|
||||
- **Quality Review**: Ensure outputs meet role template standards and user requirements
|
||||
|
||||
## Role-Specific Analysis Framework
|
||||
@@ -324,5 +306,3 @@ When analysis is complete, ensure:
|
||||
- **Relevance**: Directly addresses user's specified requirements
|
||||
- **Actionability**: Provides concrete next steps and recommendations
|
||||
|
||||
### Windows Path Format Guidelines
|
||||
- **Quick Ref**: `C:\Users` → MCP: `C:\\Users` | Bash: `/c/Users` or `C:/Users`
|
||||
|
||||
@@ -31,7 +31,7 @@ You are a context discovery specialist focused on gathering relevant project inf
|
||||
### 1. Reference Documentation (Project Standards)
|
||||
**Tools**:
|
||||
- `Read()` - Load CLAUDE.md, README.md, architecture docs
|
||||
- `Bash(~/.claude/scripts/get_modules_by_depth.sh)` - Project structure
|
||||
- `Bash(ccw tool exec get_modules_by_depth '{}')` - Project structure
|
||||
- `Glob()` - Find documentation files
|
||||
|
||||
**Use**: Phase 0 foundation setup
|
||||
@@ -82,7 +82,7 @@ mcp__code-index__set_project_path(process.cwd())
|
||||
mcp__code-index__refresh_index()
|
||||
|
||||
// 2. Project Structure
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}')
|
||||
|
||||
// 3. Load Documentation (if not in memory)
|
||||
if (!memory.has("CLAUDE.md")) Read(CLAUDE.md)
|
||||
@@ -449,7 +449,12 @@ Calculate risk level based on:
|
||||
{
|
||||
"path": "system-architect/analysis.md",
|
||||
"type": "primary",
|
||||
"content": "# System Architecture Analysis\n\n## Overview\n..."
|
||||
"content": "# System Architecture Analysis\n\n## Overview\n@analysis-architecture.md\n@analysis-recommendations.md"
|
||||
},
|
||||
{
|
||||
"path": "system-architect/analysis-architecture.md",
|
||||
"type": "supplementary",
|
||||
"content": "# Architecture Assessment\n\n..."
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ You are a documentation update coordinator for complex projects. Orchestrate par
|
||||
|
||||
## Core Mission
|
||||
|
||||
Execute depth-parallel updates for all modules using `~/.claude/scripts/update_module_claude.sh`. **Every module path must be processed**.
|
||||
Execute depth-parallel updates for all modules using `ccw tool exec update_module_claude`. **Every module path must be processed**.
|
||||
|
||||
## Input Context
|
||||
|
||||
@@ -42,12 +42,12 @@ TodoWrite([
|
||||
# 3. Launch parallel jobs (max 4)
|
||||
|
||||
# Depth 5 example (Layer 3 - use multi-layer):
|
||||
~/.claude/scripts/update_module_claude.sh "multi-layer" "./.claude/workflows/cli-templates/prompts/analysis" "gemini" &
|
||||
~/.claude/scripts/update_module_claude.sh "multi-layer" "./.claude/workflows/cli-templates/prompts/development" "gemini" &
|
||||
ccw tool exec update_module_claude '{"strategy":"multi-layer","path":"./.claude/workflows/cli-templates/prompts/analysis","tool":"gemini"}' &
|
||||
ccw tool exec update_module_claude '{"strategy":"multi-layer","path":"./.claude/workflows/cli-templates/prompts/development","tool":"gemini"}' &
|
||||
|
||||
# Depth 1 example (Layer 2 - use single-layer):
|
||||
~/.claude/scripts/update_module_claude.sh "single-layer" "./src/auth" "gemini" &
|
||||
~/.claude/scripts/update_module_claude.sh "single-layer" "./src/api" "gemini" &
|
||||
ccw tool exec update_module_claude '{"strategy":"single-layer","path":"./src/auth","tool":"gemini"}' &
|
||||
ccw tool exec update_module_claude '{"strategy":"single-layer","path":"./src/api","tool":"gemini"}' &
|
||||
# ... up to 4 concurrent jobs
|
||||
|
||||
# 4. Wait for all depth jobs to complete
|
||||
|
||||
@@ -191,7 +191,7 @@ target/
|
||||
### Step 2: Workspace Analysis (MANDATORY FIRST)
|
||||
```bash
|
||||
# Analyze workspace structure
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh json)
|
||||
bash(ccw tool exec get_modules_by_depth '{"format":"json"}')
|
||||
```
|
||||
|
||||
### Step 3: Technology Detection
|
||||
|
||||
@@ -101,10 +101,10 @@ src/ (depth 1) → SINGLE STRATEGY
|
||||
Bash({command: "pwd && basename \"$(pwd)\" && git rev-parse --show-toplevel 2>/dev/null || pwd", run_in_background: false});
|
||||
|
||||
// Get module structure with classification
|
||||
Bash({command: "~/.claude/scripts/get_modules_by_depth.sh list | ~/.claude/scripts/classify-folders.sh", run_in_background: false});
|
||||
Bash({command: "ccw tool exec get_modules_by_depth '{\"format\":\"list\"}' | ccw tool exec classify_folders '{}'", run_in_background: false});
|
||||
|
||||
// OR with path parameter
|
||||
Bash({command: "cd <target-path> && ~/.claude/scripts/get_modules_by_depth.sh list | ~/.claude/scripts/classify-folders.sh", run_in_background: false});
|
||||
Bash({command: "cd <target-path> && ccw tool exec get_modules_by_depth '{\"format\":\"list\"}' | ccw tool exec classify_folders '{}'", run_in_background: false});
|
||||
```
|
||||
|
||||
**Parse output** `depth:N|path:<PATH>|type:<code|navigation>|...` to extract module paths, types, and count.
|
||||
@@ -200,7 +200,7 @@ for (let layer of [3, 2, 1]) {
|
||||
let strategy = module.depth >= 3 ? "full" : "single";
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/generate_module_docs.sh "${strategy}" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec generate_module_docs '{"strategy":"${strategy}","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -263,7 +263,7 @@ MODULES:
|
||||
|
||||
TOOLS (try in order): {{tool_1}}, {{tool_2}}, {{tool_3}}
|
||||
|
||||
EXECUTION SCRIPT: ~/.claude/scripts/generate_module_docs.sh
|
||||
EXECUTION SCRIPT: ccw tool exec generate_module_docs
|
||||
- Accepts strategy parameter: full | single
|
||||
- Accepts folder type detection: code | navigation
|
||||
- Tool execution via direct CLI commands (gemini/qwen/codex)
|
||||
@@ -273,7 +273,7 @@ EXECUTION FLOW (for each module):
|
||||
1. Tool fallback loop (exit on first success):
|
||||
for tool in {{tool_1}} {{tool_2}} {{tool_3}}; do
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "{{strategy}}" "." "{{project_name}}" "${tool}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"{{strategy}}","sourcePath":".","projectName":"{{project_name}}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
exit_code=$?
|
||||
@@ -322,7 +322,7 @@ let project_root = get_project_root();
|
||||
report("Generating project README.md...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "project-readme" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"project-readme","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -335,7 +335,7 @@ for (let tool of tool_order) {
|
||||
report("Generating ARCHITECTURE.md and EXAMPLES.md...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "project-architecture" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"project-architecture","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -350,7 +350,7 @@ if (bash_result.stdout.includes("API_FOUND")) {
|
||||
report("Generating HTTP API documentation...");
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${project_root} && ~/.claude/scripts/generate_module_docs.sh "http-api" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${project_root} && ccw tool exec generate_module_docs '{"strategy":"http-api","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
|
||||
@@ -51,7 +51,7 @@ Orchestrates context-aware documentation generation/update for changed modules u
|
||||
Bash({command: "pwd && basename \"$(pwd)\" && git rev-parse --show-toplevel 2>/dev/null || pwd", run_in_background: false});
|
||||
|
||||
// Detect changed modules
|
||||
Bash({command: "~/.claude/scripts/detect_changed_modules.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec detect_changed_modules '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// Cache git changes
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
@@ -123,7 +123,7 @@ for (let depth of sorted_depths.reverse()) { // N → 0
|
||||
return async () => {
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/generate_module_docs.sh "single" "." "${project_name}" "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"${project_name}","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -207,21 +207,21 @@ EXECUTION:
|
||||
For each module above:
|
||||
1. Try tool 1:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_1}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_1}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_1}}", proceed to next module
|
||||
→ Failure: Try tool 2
|
||||
2. Try tool 2:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_2}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_2}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_2}}", proceed to next module
|
||||
→ Failure: Try tool 3
|
||||
3. Try tool 3:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/generate_module_docs.sh "single" "." "{{project_name}}" "{{tool_3}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec generate_module_docs '{"strategy":"single","sourcePath":".","projectName":"{{project_name}}","tool":"{{tool_3}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} docs generated with {{tool_3}}", proceed to next module
|
||||
|
||||
@@ -64,12 +64,17 @@ Lightweight planner that analyzes project structure, decomposes documentation wo
|
||||
```bash
|
||||
# Get target path, project name, and root
|
||||
bash(pwd && basename "$(pwd)" && git rev-parse --show-toplevel 2>/dev/null || pwd && date +%Y%m%d-%H%M%S)
|
||||
```
|
||||
|
||||
# Create session directories (replace timestamp)
|
||||
bash(mkdir -p .workflow/active/WFS-docs-{timestamp}/.{task,process,summaries})
|
||||
```javascript
|
||||
// Create docs session (type: docs)
|
||||
SlashCommand(command="/workflow:session:start --type docs --new \"{project_name}-docs-{timestamp}\"")
|
||||
// Parse output to get sessionId
|
||||
```
|
||||
|
||||
# Create workflow-session.json (replace values)
|
||||
bash(echo '{"session_id":"WFS-docs-{timestamp}","project":"{project} documentation","status":"planning","timestamp":"2024-01-20T14:30:22+08:00","path":".","target_path":"{target_path}","project_root":"{project_root}","project_name":"{project_name}","mode":"full","tool":"gemini","cli_execute":false}' | jq '.' > .workflow/active/WFS-docs-{timestamp}/workflow-session.json)
|
||||
```bash
|
||||
# Update workflow-session.json with docs-specific fields
|
||||
bash(jq '. + {"target_path":"{target_path}","project_root":"{project_root}","project_name":"{project_name}","mode":"full","tool":"gemini","cli_execute":false}' .workflow/active/{sessionId}/workflow-session.json > tmp.json && mv tmp.json .workflow/active/{sessionId}/workflow-session.json)
|
||||
```
|
||||
|
||||
### Phase 2: Analyze Structure
|
||||
@@ -80,10 +85,10 @@ bash(echo '{"session_id":"WFS-docs-{timestamp}","project":"{project} documentati
|
||||
|
||||
```bash
|
||||
# 1. Run folder analysis
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh | ~/.claude/scripts/classify-folders.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}' | ccw tool exec classify_folders '{}')
|
||||
|
||||
# 2. Get top-level directories (first 2 path levels)
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh | ~/.claude/scripts/classify-folders.sh | awk -F'|' '{print $1}' | sed 's|^\./||' | awk -F'/' '{if(NF>=2) print $1"/"$2; else if(NF==1) print $1}' | sort -u)
|
||||
bash(ccw tool exec get_modules_by_depth '{}' | ccw tool exec classify_folders '{}' | awk -F'|' '{print $1}' | sed 's|^\./||' | awk -F'/' '{if(NF>=2) print $1"/"$2; else if(NF==1) print $1}' | sort -u)
|
||||
|
||||
# 3. Find existing docs (if directory exists)
|
||||
bash(if [ -d .workflow/docs/\${project_name} ]; then find .workflow/docs/\${project_name} -type f -name "*.md" ! -path "*/README.md" ! -path "*/ARCHITECTURE.md" ! -path "*/EXAMPLES.md" ! -path "*/api/*" 2>/dev/null; fi)
|
||||
|
||||
@@ -109,7 +109,7 @@ Task(
|
||||
|
||||
1. **Project Structure**
|
||||
\`\`\`bash
|
||||
bash(~/.claude/scripts/get_modules_by_depth.sh)
|
||||
bash(ccw tool exec get_modules_by_depth '{}')
|
||||
\`\`\`
|
||||
|
||||
2. **Core Documentation**
|
||||
|
||||
@@ -99,10 +99,10 @@ src/ (depth 1) → SINGLE-LAYER STRATEGY
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
|
||||
// Get module structure
|
||||
Bash({command: "~/.claude/scripts/get_modules_by_depth.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec get_modules_by_depth '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// OR with --path
|
||||
Bash({command: "cd <target-path> && ~/.claude/scripts/get_modules_by_depth.sh list", run_in_background: false});
|
||||
Bash({command: "cd <target-path> && ccw tool exec get_modules_by_depth '{\"format\":\"list\"}'", run_in_background: false});
|
||||
```
|
||||
|
||||
**Parse output** `depth:N|path:<PATH>|...` to extract module paths and count.
|
||||
@@ -185,7 +185,7 @@ for (let layer of [3, 2, 1]) {
|
||||
let strategy = module.depth >= 3 ? "multi-layer" : "single-layer";
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/update_module_claude.sh "${strategy}" "." "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec update_module_claude '{"strategy":"${strategy}","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -244,7 +244,7 @@ MODULES:
|
||||
|
||||
TOOLS (try in order): {{tool_1}}, {{tool_2}}, {{tool_3}}
|
||||
|
||||
EXECUTION SCRIPT: ~/.claude/scripts/update_module_claude.sh
|
||||
EXECUTION SCRIPT: ccw tool exec update_module_claude
|
||||
- Accepts strategy parameter: multi-layer | single-layer
|
||||
- Tool execution via direct CLI commands (gemini/qwen/codex)
|
||||
|
||||
@@ -252,7 +252,7 @@ EXECUTION FLOW (for each module):
|
||||
1. Tool fallback loop (exit on first success):
|
||||
for tool in {{tool_1}} {{tool_2}} {{tool_3}}; do
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "{{strategy}}" "." "${tool}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"{{strategy}}","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
exit_code=$?
|
||||
|
||||
@@ -41,7 +41,7 @@ Orchestrates context-aware CLAUDE.md updates for changed modules using batched a
|
||||
|
||||
```javascript
|
||||
// Detect changed modules
|
||||
Bash({command: "~/.claude/scripts/detect_changed_modules.sh list", run_in_background: false});
|
||||
Bash({command: "ccw tool exec detect_changed_modules '{\"format\":\"list\"}'", run_in_background: false});
|
||||
|
||||
// Cache git changes
|
||||
Bash({command: "git add -A 2>/dev/null || true", run_in_background: false});
|
||||
@@ -102,7 +102,7 @@ for (let depth of sorted_depths.reverse()) { // N → 0
|
||||
return async () => {
|
||||
for (let tool of tool_order) {
|
||||
Bash({
|
||||
command: `cd ${module.path} && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "${tool}"`,
|
||||
command: `cd ${module.path} && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"${tool}"}'`,
|
||||
run_in_background: false
|
||||
});
|
||||
if (bash_result.exit_code === 0) {
|
||||
@@ -184,21 +184,21 @@ EXECUTION:
|
||||
For each module above:
|
||||
1. Try tool 1:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_1}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_1}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_1}}", proceed to next module
|
||||
→ Failure: Try tool 2
|
||||
2. Try tool 2:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_2}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_2}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_2}}", proceed to next module
|
||||
→ Failure: Try tool 3
|
||||
3. Try tool 3:
|
||||
Bash({
|
||||
command: `cd "{{module_path}}" && ~/.claude/scripts/update_module_claude.sh "single-layer" "." "{{tool_3}}"`,
|
||||
command: `cd "{{module_path}}" && ccw tool exec update_module_claude '{"strategy":"single-layer","path":".","tool":"{{tool_3}}"}'`,
|
||||
run_in_background: false
|
||||
})
|
||||
→ Success: Report "✅ {{module_path}} updated with {{tool_3}}", proceed to next module
|
||||
|
||||
@@ -2,452 +2,359 @@
|
||||
name: artifacts
|
||||
description: Interactive clarification generating confirmed guidance specification through role-based analysis and synthesis
|
||||
argument-hint: "topic or challenge description [--count N]"
|
||||
allowed-tools: TodoWrite(*), Read(*), Write(*), Glob(*)
|
||||
allowed-tools: TodoWrite(*), Read(*), Write(*), Glob(*), AskUserQuestion(*)
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Six-phase workflow: **Automatic project context collection** → Extract topic challenges → Select roles → Generate task-specific questions → Detect conflicts → Generate confirmed guidance (declarative statements only).
|
||||
Seven-phase workflow: **Context collection** → **Topic analysis** → **Role selection** → **Role questions** → **Conflict resolution** → **Final check** → **Generate specification**
|
||||
|
||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
||||
|
||||
**Input**: `"GOAL: [objective] SCOPE: [boundaries] CONTEXT: [background]" [--count N]`
|
||||
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md` (CONFIRMED/SELECTED format)
|
||||
**Core Principle**: Questions dynamically generated from project context + topic keywords/challenges, NOT from generic templates
|
||||
**Output**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
||||
**Core Principle**: Questions dynamically generated from project context + topic keywords, NOT generic templates
|
||||
|
||||
**Parameters**:
|
||||
- `topic` (required): Topic or challenge description (structured format recommended)
|
||||
- `--count N` (optional): Number of roles user WANTS to select (system will recommend N+2 options for user to choose from, default: 3)
|
||||
- `--count N` (optional): Number of roles to select (system recommends N+2 options, default: 3)
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Phase Summary
|
||||
|
||||
| Phase | Goal | AskUserQuestion | Storage |
|
||||
|-------|------|-----------------|---------|
|
||||
| 0 | Context collection | - | context-package.json |
|
||||
| 1 | Topic analysis | 2-4 questions | intent_context |
|
||||
| 2 | Role selection | 1 multi-select | selected_roles |
|
||||
| 3 | Role questions | 3-4 per role | role_decisions[role] |
|
||||
| 4 | Conflict resolution | max 4 per round | cross_role_decisions |
|
||||
| 4.5 | Final check | progressive rounds | additional_decisions |
|
||||
| 5 | Generate spec | - | guidance-specification.md |
|
||||
|
||||
### AskUserQuestion Pattern
|
||||
|
||||
```javascript
|
||||
// Single-select (Phase 1, 3, 4)
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "{问题文本}",
|
||||
header: "{短标签}", // max 12 chars
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "{选项}", description: "{说明和影响}" },
|
||||
{ label: "{选项}", description: "{说明和影响}" },
|
||||
{ label: "{选项}", description: "{说明和影响}" }
|
||||
]
|
||||
}
|
||||
// ... max 4 questions per call
|
||||
]
|
||||
})
|
||||
|
||||
// Multi-select (Phase 2)
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择 {count} 个角色",
|
||||
header: "角色选择",
|
||||
multiSelect: true,
|
||||
options: [/* max 4 options per call */]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
### Multi-Round Execution
|
||||
|
||||
```javascript
|
||||
const BATCH_SIZE = 4;
|
||||
for (let i = 0; i < allQuestions.length; i += BATCH_SIZE) {
|
||||
const batch = allQuestions.slice(i, i + BATCH_SIZE);
|
||||
AskUserQuestion({ questions: batch });
|
||||
// Store responses before next round
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task Tracking
|
||||
|
||||
**⚠️ TodoWrite Rule**: EXTEND auto-parallel's task list (NOT replace/overwrite)
|
||||
**TodoWrite Rule**: EXTEND auto-parallel's task list (NOT replace/overwrite)
|
||||
|
||||
**When called from auto-parallel**:
|
||||
- Find the artifacts parent task: "Execute artifacts command for interactive framework generation"
|
||||
- Mark parent task as "in_progress"
|
||||
- APPEND artifacts sub-tasks AFTER the parent task (Phase 0-5)
|
||||
- Mark each sub-task as it completes
|
||||
- When Phase 5 completes, mark parent task as "completed"
|
||||
- **PRESERVE all other auto-parallel tasks** (role agents, synthesis)
|
||||
- Find artifacts parent task → Mark "in_progress"
|
||||
- APPEND sub-tasks (Phase 0-5) → Mark each as completes
|
||||
- When Phase 5 completes → Mark parent "completed"
|
||||
- PRESERVE all other auto-parallel tasks
|
||||
|
||||
**Standalone Mode**:
|
||||
```json
|
||||
[
|
||||
{"content": "Initialize session (.workflow/active/ session check, parse --count parameter)", "status": "pending", "activeForm": "Initializing"},
|
||||
{"content": "Phase 0: Automatic project context collection (call context-gather)", "status": "pending", "activeForm": "Phase 0 context collection"},
|
||||
{"content": "Phase 1: Extract challenges, output 2-4 task-specific questions, wait for user input", "status": "pending", "activeForm": "Phase 1 topic analysis"},
|
||||
{"content": "Phase 2: Recommend count+2 roles, output role selection, wait for user input", "status": "pending", "activeForm": "Phase 2 role selection"},
|
||||
{"content": "Phase 3: Generate 3-4 questions per role, output and wait for answers (max 10 per round)", "status": "pending", "activeForm": "Phase 3 role questions"},
|
||||
{"content": "Phase 4: Detect conflicts, output clarifications, wait for answers (max 10 per round)", "status": "pending", "activeForm": "Phase 4 conflict resolution"},
|
||||
{"content": "Phase 5: Transform Q&A to declarative statements, write guidance-specification.md", "status": "pending", "activeForm": "Phase 5 document generation"}
|
||||
{"content": "Initialize session", "status": "pending", "activeForm": "Initializing"},
|
||||
{"content": "Phase 0: Context collection", "status": "pending", "activeForm": "Phase 0"},
|
||||
{"content": "Phase 1: Topic analysis (2-4 questions)", "status": "pending", "activeForm": "Phase 1"},
|
||||
{"content": "Phase 2: Role selection", "status": "pending", "activeForm": "Phase 2"},
|
||||
{"content": "Phase 3: Role questions (per role)", "status": "pending", "activeForm": "Phase 3"},
|
||||
{"content": "Phase 4: Conflict resolution", "status": "pending", "activeForm": "Phase 4"},
|
||||
{"content": "Phase 4.5: Final clarification", "status": "pending", "activeForm": "Phase 4.5"},
|
||||
{"content": "Phase 5: Generate specification", "status": "pending", "activeForm": "Phase 5"}
|
||||
]
|
||||
```
|
||||
|
||||
## User Interaction Protocol
|
||||
|
||||
### Question Output Format
|
||||
|
||||
All questions output as structured text (detailed format with descriptions):
|
||||
|
||||
```markdown
|
||||
【问题{N} - {短标签}】{问题文本}
|
||||
a) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
b) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
c) {选项标签}
|
||||
说明:{选项说明和影响}
|
||||
|
||||
请回答:{N}a 或 {N}b 或 {N}c
|
||||
```
|
||||
|
||||
**Multi-select format** (Phase 2 role selection):
|
||||
```markdown
|
||||
【角色选择】请选择 {count} 个角色参与头脑风暴分析
|
||||
|
||||
a) {role-name} ({中文名})
|
||||
推荐理由:{基于topic的相关性说明}
|
||||
b) {role-name} ({中文名})
|
||||
推荐理由:{基于topic的相关性说明}
|
||||
...
|
||||
|
||||
支持格式:
|
||||
- 分别选择:2a 2c 2d (选择第2题的a、c、d选项)
|
||||
- 合并语法:2acd (选择a、c、d)
|
||||
- 逗号分隔:2a,c,d
|
||||
|
||||
请输入选择:
|
||||
```
|
||||
|
||||
### Input Parsing Rules
|
||||
|
||||
**Supported formats** (intelligent parsing):
|
||||
|
||||
1. **Space-separated**: `1a 2b 3c` → Q1:a, Q2:b, Q3:c
|
||||
2. **Comma-separated**: `1a,2b,3c` → Q1:a, Q2:b, Q3:c
|
||||
3. **Multi-select combined**: `2abc` → Q2: options a,b,c
|
||||
4. **Multi-select spaces**: `2 a b c` → Q2: options a,b,c
|
||||
5. **Multi-select comma**: `2a,b,c` → Q2: options a,b,c
|
||||
6. **Natural language**: `问题1选a` → 1a (fallback parsing)
|
||||
|
||||
**Parsing algorithm**:
|
||||
- Extract question numbers and option letters
|
||||
- Validate question numbers match output
|
||||
- Validate option letters exist for each question
|
||||
- If ambiguous/invalid, output example format and request re-input
|
||||
|
||||
**Error handling** (lenient):
|
||||
- Recognize common variations automatically
|
||||
- If parsing fails, show example and wait for clarification
|
||||
- Support re-input without penalty
|
||||
|
||||
### Batching Strategy
|
||||
|
||||
**Batch limits**:
|
||||
- **Default**: Maximum 10 questions per round
|
||||
- **Phase 2 (role selection)**: Display all recommended roles at once (count+2 roles)
|
||||
- **Auto-split**: If questions > 10, split into multiple rounds with clear round indicators
|
||||
|
||||
**Round indicators**:
|
||||
```markdown
|
||||
===== 第 1 轮问题 (共2轮) =====
|
||||
【问题1 - ...】...
|
||||
【问题2 - ...】...
|
||||
...
|
||||
【问题10 - ...】...
|
||||
|
||||
请回答 (格式: 1a 2b ... 10c):
|
||||
```
|
||||
|
||||
### Interaction Flow
|
||||
|
||||
**Standard flow**:
|
||||
1. Output questions in formatted text
|
||||
2. Output expected input format example
|
||||
3. Wait for user input
|
||||
4. Parse input with intelligent matching
|
||||
5. If parsing succeeds → Store answers and continue
|
||||
6. If parsing fails → Show error, example, and wait for re-input
|
||||
|
||||
**No question/option limits**: Text-based interaction removes previous 4-question and 4-option restrictions
|
||||
---
|
||||
|
||||
## Execution Phases
|
||||
|
||||
### Session Management
|
||||
|
||||
- Check `.workflow/active/` for existing sessions
|
||||
- Multiple sessions → Prompt selection | Single → Use it | None → Create `WFS-[topic-slug]`
|
||||
- Parse `--count N` parameter from user input (default: 3 if not specified)
|
||||
- Store decisions in `workflow-session.json` including count parameter
|
||||
- Multiple → Prompt selection | Single → Use it | None → Create `WFS-[topic-slug]`
|
||||
- Parse `--count N` parameter (default: 3)
|
||||
- Store decisions in `workflow-session.json`
|
||||
|
||||
### Phase 0: Automatic Project Context Collection
|
||||
### Phase 0: Context Collection
|
||||
|
||||
**Goal**: Gather project architecture, documentation, and relevant code context BEFORE user interaction
|
||||
**Goal**: Gather project context BEFORE user interaction
|
||||
|
||||
**Detection Mechanism** (execute first):
|
||||
```javascript
|
||||
// Check if context-package already exists
|
||||
const contextPackagePath = `.workflow/active/WFS-{session-id}/.process/context-package.json`;
|
||||
**Steps**:
|
||||
1. Check if `context-package.json` exists → Skip if valid
|
||||
2. Invoke `context-search-agent` (BRAINSTORM MODE - lightweight)
|
||||
3. Output: `.workflow/active/WFS-{session-id}/.process/context-package.json`
|
||||
|
||||
if (file_exists(contextPackagePath)) {
|
||||
// Validate package
|
||||
const package = Read(contextPackagePath);
|
||||
if (package.metadata.session_id === session_id) {
|
||||
console.log("✅ Valid context-package found, skipping Phase 0");
|
||||
return; // Skip to Phase 1
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Implementation**: Invoke `context-search-agent` only if package doesn't exist
|
||||
**Graceful Degradation**: If agent fails, continue to Phase 1 without context
|
||||
|
||||
```javascript
|
||||
Task(
|
||||
subagent_type="context-search-agent",
|
||||
description="Gather project context for brainstorm",
|
||||
prompt=`
|
||||
You are executing as context-search-agent (.claude/agents/context-search-agent.md).
|
||||
Execute context-search-agent in BRAINSTORM MODE (Phase 1-2 only).
|
||||
|
||||
## Execution Mode
|
||||
**BRAINSTORM MODE** (Lightweight) - Phase 1-2 only (skip deep analysis)
|
||||
Session: ${session_id}
|
||||
Task: ${task_description}
|
||||
Output: .workflow/${session_id}/.process/context-package.json
|
||||
|
||||
## Session Information
|
||||
- **Session ID**: ${session_id}
|
||||
- **Task Description**: ${task_description}
|
||||
- **Output Path**: .workflow/${session_id}/.process/context-package.json
|
||||
|
||||
## Mission
|
||||
Execute complete context-search-agent workflow for implementation planning:
|
||||
|
||||
### Phase 1: Initialization & Pre-Analysis
|
||||
1. **Detection**: Check for existing context-package (early exit if valid)
|
||||
2. **Foundation**: Initialize code-index, get project structure, load docs
|
||||
3. **Analysis**: Extract keywords, determine scope, classify complexity
|
||||
|
||||
### Phase 2: Multi-Source Context Discovery
|
||||
Execute all 3 discovery tracks:
|
||||
- **Track 1**: Reference documentation (CLAUDE.md, architecture docs)
|
||||
- **Track 2**: Web examples (use Exa MCP for unfamiliar tech/APIs)
|
||||
- **Track 3**: Codebase analysis (5-layer discovery: files, content, patterns, deps, config/tests)
|
||||
|
||||
### Phase 3: Synthesis, Assessment & Packaging
|
||||
1. Apply relevance scoring and build dependency graph
|
||||
2. Synthesize 3-source data (docs > code > web)
|
||||
3. Integrate brainstorm artifacts (if .brainstorming/ exists, read content)
|
||||
4. Perform conflict detection with risk assessment
|
||||
5. Generate and validate context-package.json
|
||||
|
||||
## Output Requirements
|
||||
Complete context-package.json with:
|
||||
- **metadata**: task_description, keywords, complexity, tech_stack, session_id
|
||||
- **project_context**: architecture_patterns, coding_conventions, tech_stack
|
||||
- **assets**: {documentation[], source_code[], config[], tests[]} with relevance scores
|
||||
- **dependencies**: {internal[], external[]} with dependency graph
|
||||
- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content
|
||||
- **conflict_detection**: {risk_level, risk_factors, affected_modules[], mitigation_strategy}
|
||||
|
||||
## Quality Validation
|
||||
Before completion verify:
|
||||
- [ ] Valid JSON format with all required fields
|
||||
- [ ] File relevance accuracy >80%
|
||||
- [ ] Dependency graph complete (max 2 transitive levels)
|
||||
- [ ] Conflict risk level calculated correctly
|
||||
- [ ] No sensitive data exposed
|
||||
- [ ] Total files ≤50 (prioritize high-relevance)
|
||||
|
||||
Execute autonomously following agent documentation.
|
||||
Report completion with statistics.
|
||||
Required fields: metadata, project_context, assets, dependencies, conflict_detection
|
||||
`
|
||||
)
|
||||
```
|
||||
|
||||
**Graceful Degradation**:
|
||||
- If agent fails: Log warning, continue to Phase 1 without project context
|
||||
- If package invalid: Re-run context-search-agent
|
||||
### Phase 1: Topic Analysis
|
||||
|
||||
### Phase 1: Topic Analysis & Intent Classification
|
||||
|
||||
**Goal**: Extract keywords/challenges to drive all subsequent question generation, **enriched by Phase 0 project context**
|
||||
**Goal**: Extract keywords/challenges enriched by Phase 0 context
|
||||
|
||||
**Steps**:
|
||||
1. **Load Phase 0 context** (if available):
|
||||
- Read `.workflow/active/WFS-{session-id}/.process/context-package.json`
|
||||
- Extract: tech_stack, existing modules, conflict_risk, relevant files
|
||||
1. Load Phase 0 context (tech_stack, modules, conflict_risk)
|
||||
2. Deep topic analysis (entities, challenges, constraints, metrics)
|
||||
3. Generate 2-4 context-aware probing questions
|
||||
4. AskUserQuestion → Store to `session.intent_context`
|
||||
|
||||
2. **Deep topic analysis** (context-aware):
|
||||
- Extract technical entities from topic + existing codebase
|
||||
- Identify core challenges considering existing architecture
|
||||
- Consider constraints (timeline/budget/compliance)
|
||||
- Define success metrics based on current project state
|
||||
|
||||
3. **Generate 2-4 context-aware probing questions**:
|
||||
- Reference existing tech stack in questions
|
||||
- Consider integration with existing modules
|
||||
- Address identified conflict risks from Phase 0
|
||||
- Target root challenges and trade-off priorities
|
||||
|
||||
4. **User interaction**: Output questions using text format (see User Interaction Protocol), wait for user input
|
||||
|
||||
5. **Parse user answers**: Use intelligent parsing to extract answers from user input (support multiple formats)
|
||||
|
||||
6. **Storage**: Store answers to `session.intent_context` with `{extracted_keywords, identified_challenges, user_answers, project_context_used}`
|
||||
|
||||
**Example Output**:
|
||||
```markdown
|
||||
===== Phase 1: 项目意图分析 =====
|
||||
|
||||
【问题1 - 核心挑战】实时协作平台的主要技术挑战?
|
||||
a) 实时数据同步
|
||||
说明:100+用户同时在线,状态同步复杂度高
|
||||
b) 可扩展性架构
|
||||
说明:用户规模增长时的系统扩展能力
|
||||
c) 冲突解决机制
|
||||
说明:多用户同时编辑的冲突处理策略
|
||||
|
||||
【问题2 - 优先级】MVP阶段最关注的指标?
|
||||
a) 功能完整性
|
||||
说明:实现所有核心功能
|
||||
b) 用户体验
|
||||
说明:流畅的交互体验和响应速度
|
||||
c) 系统稳定性
|
||||
说明:高可用性和数据一致性
|
||||
|
||||
请回答 (格式: 1a 2b):
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "实时协作平台的主要技术挑战?",
|
||||
header: "核心挑战",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "实时数据同步", description: "100+用户同时在线,状态同步复杂度高" },
|
||||
{ label: "可扩展性架构", description: "用户规模增长时的系统扩展能力" },
|
||||
{ label: "冲突解决机制", description: "多用户同时编辑的冲突处理策略" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "MVP阶段最关注的指标?",
|
||||
header: "优先级",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "功能完整性", description: "实现所有核心功能" },
|
||||
{ label: "用户体验", description: "流畅的交互体验和响应速度" },
|
||||
{ label: "系统稳定性", description: "高可用性和数据一致性" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**User input examples**:
|
||||
- `1a 2c` → Q1:a, Q2:c
|
||||
- `1a,2c` → Q1:a, Q2:c
|
||||
|
||||
**⚠️ CRITICAL**: Questions MUST reference topic keywords. Generic "Project type?" violates dynamic generation.
|
||||
|
||||
### Phase 2: Role Selection
|
||||
|
||||
**⚠️ CRITICAL**: User MUST interact to select roles. NEVER auto-select without user confirmation.
|
||||
**Goal**: User selects roles from intelligent recommendations
|
||||
|
||||
**Available Roles**:
|
||||
- data-architect (数据架构师)
|
||||
- product-manager (产品经理)
|
||||
- product-owner (产品负责人)
|
||||
- scrum-master (敏捷教练)
|
||||
- subject-matter-expert (领域专家)
|
||||
- system-architect (系统架构师)
|
||||
- test-strategist (测试策略师)
|
||||
- ui-designer (UI 设计师)
|
||||
- ux-expert (UX 专家)
|
||||
**Available Roles**: data-architect, product-manager, product-owner, scrum-master, subject-matter-expert, system-architect, test-strategist, ui-designer, ux-expert
|
||||
|
||||
**Steps**:
|
||||
1. **Intelligent role recommendation** (AI analysis):
|
||||
- Analyze Phase 1 extracted keywords and challenges
|
||||
- Use AI reasoning to determine most relevant roles for the specific topic
|
||||
- Recommend count+2 roles (e.g., if user wants 3 roles, recommend 5 options)
|
||||
- Provide clear rationale for each recommended role based on topic context
|
||||
1. Analyze Phase 1 keywords → Recommend count+2 roles with rationale
|
||||
2. AskUserQuestion (multiSelect=true) → Store to `session.selected_roles`
|
||||
3. If count+2 > 4, split into multiple rounds
|
||||
|
||||
2. **User selection** (text interaction):
|
||||
- Output all recommended roles at once (no batching needed for count+2 roles)
|
||||
- Display roles with labels and relevance rationale
|
||||
- Wait for user input in multi-select format
|
||||
- Parse user input (support multiple formats)
|
||||
- **Storage**: Store selections to `session.selected_roles`
|
||||
|
||||
**Example Output**:
|
||||
```markdown
|
||||
===== Phase 2: 角色选择 =====
|
||||
|
||||
【角色选择】请选择 3 个角色参与头脑风暴分析
|
||||
|
||||
a) system-architect (系统架构师)
|
||||
推荐理由:实时同步架构设计和技术选型的核心角色
|
||||
b) ui-designer (UI设计师)
|
||||
推荐理由:协作界面用户体验和实时状态展示
|
||||
c) product-manager (产品经理)
|
||||
推荐理由:功能优先级和MVP范围决策
|
||||
d) data-architect (数据架构师)
|
||||
推荐理由:数据同步模型和存储方案设计
|
||||
e) ux-expert (UX专家)
|
||||
推荐理由:多用户协作交互流程优化
|
||||
|
||||
支持格式:
|
||||
- 分别选择:2a 2c 2d (选择a、c、d)
|
||||
- 合并语法:2acd (选择a、c、d)
|
||||
- 逗号分隔:2a,c,d (选择a、c、d)
|
||||
|
||||
请输入选择:
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择 3 个角色参与头脑风暴分析",
|
||||
header: "角色选择",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "system-architect", description: "实时同步架构设计和技术选型" },
|
||||
{ label: "ui-designer", description: "协作界面用户体验和状态展示" },
|
||||
{ label: "product-manager", description: "功能优先级和MVP范围决策" },
|
||||
{ label: "data-architect", description: "数据同步模型和存储方案设计" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
**User input examples**:
|
||||
- `2acd` → Roles: a, c, d (system-architect, product-manager, data-architect)
|
||||
- `2a 2c 2d` → Same result
|
||||
- `2a,c,d` → Same result
|
||||
**⚠️ CRITICAL**: User MUST interact. NEVER auto-select without confirmation.
|
||||
|
||||
**Role Recommendation Rules**:
|
||||
- NO hardcoded keyword-to-role mappings
|
||||
- Use intelligent analysis of topic, challenges, and requirements
|
||||
- Consider role synergies and coverage gaps
|
||||
- Explain WHY each role is relevant to THIS specific topic
|
||||
- Default recommendation: count+2 roles for user to choose from
|
||||
|
||||
### Phase 3: Role-Specific Questions (Dynamic Generation)
|
||||
### Phase 3: Role-Specific Questions
|
||||
|
||||
**Goal**: Generate deep questions mapping role expertise to Phase 1 challenges
|
||||
|
||||
**Algorithm**:
|
||||
```
|
||||
FOR each selected role:
|
||||
1. Map Phase 1 challenges to role domain:
|
||||
- "real-time sync" + system-architect → State management pattern
|
||||
- "100 users" + system-architect → Communication protocol
|
||||
- "low latency" + system-architect → Conflict resolution
|
||||
1. FOR each selected role:
|
||||
- Map Phase 1 challenges to role domain
|
||||
- Generate 3-4 questions (implementation depth, trade-offs, edge cases)
|
||||
- AskUserQuestion per role → Store to `session.role_decisions[role]`
|
||||
2. Process roles sequentially (one at a time for clarity)
|
||||
3. If role needs > 4 questions, split into multiple rounds
|
||||
|
||||
2. Generate 3-4 questions per role probing implementation depth, trade-offs, edge cases:
|
||||
Q: "How handle real-time state sync for 100+ users?" (explores approach)
|
||||
Q: "How resolve conflicts when 2 users edit simultaneously?" (explores edge case)
|
||||
Options: [Event Sourcing/Centralized/CRDT] (concrete, explain trade-offs for THIS use case)
|
||||
|
||||
3. Output questions in text format per role:
|
||||
- Display all questions for current role (3-4 questions, no 10-question limit)
|
||||
- Questions in Chinese (用中文提问)
|
||||
- Wait for user input
|
||||
- Parse answers using intelligent parsing
|
||||
- Store answers to session.role_decisions[role]
|
||||
**Example** (system-architect):
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "100+ 用户实时状态同步方案?",
|
||||
header: "状态同步",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "Event Sourcing", description: "完整事件历史,支持回溯,存储成本高" },
|
||||
{ label: "集中式状态管理", description: "实现简单,单点瓶颈风险" },
|
||||
{ label: "CRDT", description: "去中心化,自动合并,学习曲线陡" }
|
||||
]
|
||||
},
|
||||
{
|
||||
question: "两个用户同时编辑冲突如何解决?",
|
||||
header: "冲突解决",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "自动合并", description: "用户无感知,可能产生意外结果" },
|
||||
{ label: "手动解决", description: "用户控制,增加交互复杂度" },
|
||||
{ label: "版本控制", description: "保留历史,需要分支管理" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
**Batching Strategy**:
|
||||
- Each role outputs all its questions at once (typically 3-4 questions)
|
||||
- No need to split per role (within 10-question batch limit)
|
||||
- Multiple roles processed sequentially (one role at a time for clarity)
|
||||
### Phase 4: Conflict Resolution
|
||||
|
||||
**Output Format**: Follow standard format from "User Interaction Protocol" section (single-choice question format)
|
||||
|
||||
**Example Topic-Specific Questions** (system-architect role for "real-time collaboration platform"):
|
||||
- "100+ 用户实时状态同步方案?" → Options: Event Sourcing / 集中式状态管理 / CRDT
|
||||
- "两个用户同时编辑冲突如何解决?" → Options: 自动合并 / 手动解决 / 版本控制
|
||||
- "低延迟通信协议选择?" → Options: WebSocket / SSE / 轮询
|
||||
- "系统扩展性架构方案?" → Options: 微服务 / 单体+缓存 / Serverless
|
||||
|
||||
**Quality Requirements**: See "Question Generation Guidelines" section for detailed rules
|
||||
|
||||
### Phase 4: Cross-Role Clarification (Conflict Detection)
|
||||
|
||||
**Goal**: Resolve ACTUAL conflicts from Phase 3 answers, not pre-defined relationships
|
||||
**Goal**: Resolve ACTUAL conflicts from Phase 3 answers
|
||||
|
||||
**Algorithm**:
|
||||
```
|
||||
1. Analyze Phase 3 answers for conflicts:
|
||||
- Contradictory choices: product-manager "fast iteration" vs system-architect "complex Event Sourcing"
|
||||
- Missing integration: ui-designer "Optimistic updates" but system-architect didn't address conflict handling
|
||||
- Implicit dependencies: ui-designer "Live cursors" but no auth approach defined
|
||||
|
||||
2. FOR each detected conflict:
|
||||
Generate clarification questions referencing SPECIFIC Phase 3 choices
|
||||
|
||||
3. Output clarification questions in text format:
|
||||
- Batch conflicts into rounds (max 10 questions per round)
|
||||
- Display questions with context from Phase 3 answers
|
||||
- Questions in Chinese (用中文提问)
|
||||
- Wait for user input
|
||||
- Parse answers using intelligent parsing
|
||||
- Store answers to session.cross_role_decisions
|
||||
|
||||
- Contradictory choices (e.g., "fast iteration" vs "complex Event Sourcing")
|
||||
- Missing integration (e.g., "Optimistic updates" but no conflict handling)
|
||||
- Implicit dependencies (e.g., "Live cursors" but no auth defined)
|
||||
2. Generate clarification questions referencing SPECIFIC Phase 3 choices
|
||||
3. AskUserQuestion (max 4 per call, multi-round) → Store to `session.cross_role_decisions`
|
||||
4. If NO conflicts: Skip Phase 4 (inform user: "未检测到跨角色冲突,跳过Phase 4")
|
||||
|
||||
**Example**:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "CRDT 与 UI 回滚期望冲突,如何解决?\n背景:system-architect选择CRDT,ui-designer期望回滚UI",
|
||||
header: "架构冲突",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "采用 CRDT", description: "保持去中心化,调整UI期望" },
|
||||
{ label: "显示合并界面", description: "增加用户交互,展示冲突详情" },
|
||||
{ label: "切换到 OT", description: "支持回滚,增加服务器复杂度" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
|
||||
**Batching Strategy**:
|
||||
- Maximum 10 clarification questions per round
|
||||
- If conflicts > 10, split into multiple rounds
|
||||
- Prioritize most critical conflicts first
|
||||
### Phase 4.5: Final Clarification
|
||||
|
||||
**Output Format**: Follow standard format from "User Interaction Protocol" section (single-choice question format with background context)
|
||||
|
||||
**Example Conflict Detection** (from Phase 3 answers):
|
||||
- **Architecture Conflict**: "CRDT 与 UI 回滚期望冲突,如何解决?"
|
||||
- Background: system-architect chose CRDT, ui-designer expects rollback UI
|
||||
- Options: 采用 CRDT / 显示合并界面 / 切换到 OT
|
||||
- **Integration Gap**: "实时光标功能缺少身份认证方案"
|
||||
- Background: ui-designer chose live cursors, no auth defined
|
||||
- Options: OAuth 2.0 / JWT Token / Session-based
|
||||
|
||||
**Quality Requirements**: See "Question Generation Guidelines" section for conflict-specific rules
|
||||
|
||||
### Phase 5: Generate Guidance Specification
|
||||
**Purpose**: Ensure no important points missed before generating specification
|
||||
|
||||
**Steps**:
|
||||
1. Load all decisions: `intent_context` + `selected_roles` + `role_decisions` + `cross_role_decisions`
|
||||
2. Transform Q&A pairs to declarative: Questions → Headers, Answers → CONFIRMED/SELECTED statements
|
||||
3. Generate guidance-specification.md (template below) - **PRIMARY OUTPUT FILE**
|
||||
4. Update workflow-session.json with **METADATA ONLY**:
|
||||
- session_id (e.g., "WFS-topic-slug")
|
||||
- selected_roles[] (array of role names, e.g., ["system-architect", "ui-designer", "product-manager"])
|
||||
- topic (original user input string)
|
||||
- timestamp (ISO-8601 format)
|
||||
- phase_completed: "artifacts"
|
||||
- count_parameter (number from --count flag)
|
||||
5. Validate: No interrogative sentences in .md file, all decisions traceable, no content duplication in .json
|
||||
1. Ask initial check:
|
||||
```javascript
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "在生成最终规范之前,是否有前面未澄清的重点需要补充?",
|
||||
header: "补充确认",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "无需补充", description: "前面的讨论已经足够完整" },
|
||||
{ label: "需要补充", description: "还有重要内容需要澄清" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
```
|
||||
2. If "需要补充":
|
||||
- Analyze user's additional points
|
||||
- Generate progressive questions (not role-bound, interconnected)
|
||||
- AskUserQuestion (max 4 per round) → Store to `session.additional_decisions`
|
||||
- Repeat until user confirms completion
|
||||
3. If "无需补充": Proceed to Phase 5
|
||||
|
||||
**⚠️ CRITICAL OUTPUT SEPARATION**:
|
||||
- **guidance-specification.md**: Full guidance content (decisions, rationale, integration points)
|
||||
- **workflow-session.json**: Session metadata ONLY (no guidance content, no decisions, no Q&A pairs)
|
||||
- **NO content duplication**: Guidance stays in .md, metadata stays in .json
|
||||
**Progressive Pattern**: Questions interconnected, each round informs next, continue until resolved.
|
||||
|
||||
## Output Document Template
|
||||
### Phase 5: Generate Specification
|
||||
|
||||
**Steps**:
|
||||
1. Load all decisions: `intent_context` + `selected_roles` + `role_decisions` + `cross_role_decisions` + `additional_decisions`
|
||||
2. Transform Q&A to declarative: Questions → Headers, Answers → CONFIRMED/SELECTED statements
|
||||
3. Generate `guidance-specification.md`
|
||||
4. Update `workflow-session.json` (metadata only)
|
||||
5. Validate: No interrogative sentences, all decisions traceable
|
||||
|
||||
---
|
||||
|
||||
## Question Guidelines
|
||||
|
||||
### Core Principle
|
||||
|
||||
**Target**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Question Structure**: `[业务场景/需求前提] + [技术关注点]`
|
||||
**Option Structure**: `标签:[技术方案] + 说明:[业务影响] + [技术权衡]`
|
||||
|
||||
### Quality Rules
|
||||
|
||||
**MUST Include**:
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 业务场景作为问题前提
|
||||
- ✅ 技术选项的业务影响说明
|
||||
- ✅ 量化指标和约束条件
|
||||
|
||||
**MUST Avoid**:
|
||||
- ❌ 纯技术选型无业务上下文
|
||||
- ❌ 过度抽象的用户体验问题
|
||||
- ❌ 脱离话题的通用架构问题
|
||||
|
||||
### Phase-Specific Requirements
|
||||
|
||||
| Phase | Focus | Key Requirements |
|
||||
|-------|-------|------------------|
|
||||
| 1 | 意图理解 | Reference topic keywords, 用户场景、业务约束、优先级 |
|
||||
| 2 | 角色推荐 | Intelligent analysis (NOT keyword mapping), explain relevance |
|
||||
| 3 | 角色问题 | Reference Phase 1 keywords, concrete options with trade-offs |
|
||||
| 4 | 冲突解决 | Reference SPECIFIC Phase 3 choices, explain impact on both roles |
|
||||
|
||||
---
|
||||
|
||||
## Output & Governance
|
||||
|
||||
### Output Template
|
||||
|
||||
**File**: `.workflow/active/WFS-{topic}/.brainstorming/guidance-specification.md`
|
||||
|
||||
@@ -478,9 +385,9 @@ FOR each selected role:
|
||||
|
||||
## Next Steps
|
||||
**⚠️ Automatic Continuation** (when called from auto-parallel):
|
||||
- auto-parallel will assign agents to generate role-specific analysis documents
|
||||
- Each selected role gets dedicated conceptual-planning-agent
|
||||
- Agents read this guidance-specification.md for framework context
|
||||
- auto-parallel assigns agents for role-specific analysis
|
||||
- Each selected role gets conceptual-planning-agent
|
||||
- Agents read this guidance-specification.md for context
|
||||
|
||||
## Appendix: Decision Tracking
|
||||
| Decision ID | Category | Question | Selected | Phase | Rationale |
|
||||
@@ -490,95 +397,19 @@ FOR each selected role:
|
||||
| D-003+ | [Role] | [Q] | [A] | 3 | [Why] |
|
||||
```
|
||||
|
||||
## Question Generation Guidelines
|
||||
|
||||
### Core Principle: Developer-Facing Questions with User Context
|
||||
|
||||
**Target Audience**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Generation Philosophy**:
|
||||
1. **Phase 1**: 用户场景、业务约束、优先级(建立上下文)
|
||||
2. **Phase 2**: 基于话题分析的智能角色推荐(非关键词映射)
|
||||
3. **Phase 3**: 业务需求 + 技术选型(需求驱动的技术决策)
|
||||
4. **Phase 4**: 技术冲突的业务权衡(帮助开发者理解影响)
|
||||
|
||||
### Universal Quality Rules
|
||||
|
||||
**Question Structure** (all phases):
|
||||
```
|
||||
[业务场景/需求前提] + [技术关注点]
|
||||
```
|
||||
|
||||
**Option Structure** (all phases):
|
||||
```
|
||||
标签:[技术方案简称] + (业务特征)
|
||||
说明:[业务影响] + [技术权衡]
|
||||
```
|
||||
|
||||
**MUST Include** (all phases):
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 业务场景作为问题前提
|
||||
- ✅ 技术选项的业务影响说明
|
||||
- ✅ 量化指标和约束条件
|
||||
|
||||
**MUST Avoid** (all phases):
|
||||
- ❌ 纯技术选型无业务上下文
|
||||
- ❌ 过度抽象的用户体验问题
|
||||
- ❌ 脱离话题的通用架构问题
|
||||
|
||||
### Phase-Specific Requirements
|
||||
|
||||
**Phase 1 Requirements**:
|
||||
- Questions MUST reference topic keywords (NOT generic "Project type?")
|
||||
- Focus: 用户使用场景(谁用?怎么用?多频繁?)、业务约束(预算、时间、团队、合规)
|
||||
- Success metrics: 性能指标、用户体验目标
|
||||
- Priority ranking: MVP vs 长期规划
|
||||
|
||||
**Phase 3 Requirements**:
|
||||
- Questions MUST reference Phase 1 keywords (e.g., "real-time", "100 users")
|
||||
- Options MUST be concrete approaches with relevance to topic
|
||||
- Each option includes trade-offs specific to this use case
|
||||
- Include 业务需求驱动的技术问题、量化指标(并发数、延迟、可用性)
|
||||
|
||||
**Phase 4 Requirements**:
|
||||
- Questions MUST reference SPECIFIC Phase 3 choices in background context
|
||||
- Options address the detected conflict directly
|
||||
- Each option explains impact on both conflicting roles
|
||||
- NEVER use static "Cross-Role Matrix" - ALWAYS analyze actual Phase 3 answers
|
||||
- Focus: 技术冲突的业务权衡、帮助开发者理解不同选择的影响
|
||||
|
||||
## Validation Checklist
|
||||
|
||||
Generated guidance-specification.md MUST:
|
||||
- ✅ No interrogative sentences (use CONFIRMED/SELECTED)
|
||||
- ✅ Every decision traceable to user answer
|
||||
- ✅ Cross-role conflicts resolved or documented
|
||||
- ✅ Next steps concrete and specific
|
||||
- ✅ All Phase 1-4 decisions in session metadata
|
||||
|
||||
## Update Mechanism
|
||||
### File Structure
|
||||
|
||||
```
|
||||
IF guidance-specification.md EXISTS:
|
||||
Prompt: "Regenerate completely / Update sections / Cancel"
|
||||
ELSE:
|
||||
Run full Phase 1-5 flow
|
||||
.workflow/active/WFS-[topic]/
|
||||
├── workflow-session.json # Metadata ONLY
|
||||
├── .process/
|
||||
│ └── context-package.json # Phase 0 output
|
||||
└── .brainstorming/
|
||||
└── guidance-specification.md # Full guidance content
|
||||
```
|
||||
|
||||
## Governance Rules
|
||||
### Session Metadata
|
||||
|
||||
**Output Requirements**:
|
||||
- All decisions MUST use CONFIRMED/SELECTED (NO "?" in decision sections)
|
||||
- Every decision MUST trace to user answer
|
||||
- Conflicts MUST be resolved (not marked "TBD")
|
||||
- Next steps MUST be actionable
|
||||
- Topic preserved as authoritative reference in session
|
||||
|
||||
**CRITICAL**: Guidance is single source of truth for downstream phases. Ambiguity violates governance.
|
||||
|
||||
## Storage Validation
|
||||
|
||||
**workflow-session.json** (metadata only):
|
||||
```json
|
||||
{
|
||||
"session_id": "WFS-{topic-slug}",
|
||||
@@ -591,14 +422,31 @@ ELSE:
|
||||
}
|
||||
```
|
||||
|
||||
**⚠️ Rule**: Session JSON stores ONLY metadata (session_id, selected_roles[], topic, timestamps). All guidance content goes to guidance-specification.md.
|
||||
**⚠️ Rule**: Session JSON stores ONLY metadata. All guidance content goes to guidance-specification.md.
|
||||
|
||||
## File Structure
|
||||
### Validation Checklist
|
||||
|
||||
- ✅ No interrogative sentences (use CONFIRMED/SELECTED)
|
||||
- ✅ Every decision traceable to user answer
|
||||
- ✅ Cross-role conflicts resolved or documented
|
||||
- ✅ Next steps concrete and specific
|
||||
- ✅ No content duplication between .json and .md
|
||||
|
||||
### Update Mechanism
|
||||
|
||||
```
|
||||
.workflow/active/WFS-[topic]/
|
||||
├── workflow-session.json # Session metadata ONLY
|
||||
└── .brainstorming/
|
||||
└── guidance-specification.md # Full guidance content
|
||||
IF guidance-specification.md EXISTS:
|
||||
Prompt: "Regenerate completely / Update sections / Cancel"
|
||||
ELSE:
|
||||
Run full Phase 0-5 flow
|
||||
```
|
||||
|
||||
### Governance Rules
|
||||
|
||||
- All decisions MUST use CONFIRMED/SELECTED (NO "?" in decision sections)
|
||||
- Every decision MUST trace to user answer
|
||||
- Conflicts MUST be resolved (not marked "TBD")
|
||||
- Next steps MUST be actionable
|
||||
- Topic preserved as authoritative reference
|
||||
|
||||
**CRITICAL**: Guidance is single source of truth for downstream phases. Ambiguity violates governance.
|
||||
|
||||
@@ -141,26 +141,10 @@ OUTPUT_LOCATION: .workflow/active/WFS-{session}/.brainstorming/{role}/
|
||||
TOPIC: {user-provided-topic}
|
||||
|
||||
## Flow Control Steps
|
||||
1. **load_topic_framework**
|
||||
- Action: Load structured topic discussion framework
|
||||
- Command: Read(.workflow/active/WFS-{session}/.brainstorming/guidance-specification.md)
|
||||
- Output: topic_framework_content
|
||||
|
||||
2. **load_role_template**
|
||||
- Action: Load {role-name} planning template
|
||||
- Command: Read(~/.claude/workflows/cli-templates/planning-roles/{role}.md)
|
||||
- Output: role_template_guidelines
|
||||
|
||||
3. **load_session_metadata**
|
||||
- Action: Load session metadata and original user intent
|
||||
- Command: Read(.workflow/active/WFS-{session}/workflow-session.json)
|
||||
- Output: session_context (contains original user prompt as PRIMARY reference)
|
||||
|
||||
4. **load_style_skill** (ONLY for ui-designer role when style_skill_package exists)
|
||||
- Action: Load style SKILL package for design system reference
|
||||
- Command: Read(.claude/skills/style-{style_skill_package}/SKILL.md) AND Read(.workflow/reference_style/{style_skill_package}/design-tokens.json)
|
||||
- Output: style_skill_content, design_tokens
|
||||
- Usage: Apply design tokens in ui-designer analysis and artifacts
|
||||
1. load_topic_framework → .workflow/active/WFS-{session}/.brainstorming/guidance-specification.md
|
||||
2. load_role_template → ~/.claude/workflows/cli-templates/planning-roles/{role}.md
|
||||
3. load_session_metadata → .workflow/active/WFS-{session}/workflow-session.json
|
||||
4. load_style_skill (ui-designer only, if style_skill_package) → .claude/skills/style-{style_skill_package}/
|
||||
|
||||
## Analysis Requirements
|
||||
**Primary Reference**: Original user prompt from workflow-session.json is authoritative
|
||||
@@ -170,13 +154,9 @@ TOPIC: {user-provided-topic}
|
||||
**Template Integration**: Apply role template guidelines within framework structure
|
||||
|
||||
## Expected Deliverables
|
||||
1. **analysis.md**: Comprehensive {role-name} analysis addressing all framework discussion points
|
||||
- **File Naming**: MUST start with `analysis` prefix (e.g., `analysis.md`, `analysis-1.md`, `analysis-2.md`)
|
||||
- **FORBIDDEN**: Never use `recommendations.md` or any filename not starting with `analysis`
|
||||
- **Auto-split if large**: If content >800 lines, split to `analysis-1.md`, `analysis-2.md` (max 3 files: analysis.md, analysis-1.md, analysis-2.md)
|
||||
- **Content**: Includes both analysis AND recommendations sections within analysis files
|
||||
2. **Framework Reference**: Include @../guidance-specification.md reference in analysis
|
||||
3. **User Intent Alignment**: Validate analysis aligns with original user objectives from session_context
|
||||
1. **analysis.md** (optionally with analysis-{slug}.md sub-documents)
|
||||
2. **Framework Reference**: @../guidance-specification.md
|
||||
3. **User Intent Alignment**: Validate against session_context
|
||||
|
||||
## Completion Criteria
|
||||
- Address each discussion point from guidance-specification.md with {role-name} expertise
|
||||
@@ -199,10 +179,10 @@ TOPIC: {user-provided-topic}
|
||||
- guidance-specification.md path
|
||||
|
||||
**Validation**:
|
||||
- Each role creates `.workflow/active/WFS-{topic}/.brainstorming/{role}/analysis.md` (primary file)
|
||||
- If content is large (>800 lines), may split to `analysis-1.md`, `analysis-2.md` (max 3 files total)
|
||||
- **File naming pattern**: ALL files MUST start with `analysis` prefix (use `analysis*.md` for globbing)
|
||||
- **FORBIDDEN naming**: No `recommendations.md`, `recommendations-*.md`, or any non-`analysis` prefixed files
|
||||
- Each role creates `.workflow/active/WFS-{topic}/.brainstorming/{role}/analysis.md`
|
||||
- Optionally with `analysis-{slug}.md` sub-documents (max 5)
|
||||
- **File pattern**: `analysis*.md` for globbing
|
||||
- **FORBIDDEN**: `recommendations.md` or any non-`analysis` prefixed files
|
||||
- All N role analyses completed
|
||||
|
||||
**TodoWrite Update (Phase 2 agents dispatched - tasks attached in parallel)**:
|
||||
@@ -453,12 +433,9 @@ CONTEXT_VARS:
|
||||
├── workflow-session.json # Session metadata ONLY
|
||||
└── .brainstorming/
|
||||
├── guidance-specification.md # Framework (Phase 1)
|
||||
├── {role-1}/
|
||||
│ └── analysis.md # Role analysis (Phase 2)
|
||||
├── {role-2}/
|
||||
│ └── analysis.md
|
||||
├── {role-N}/
|
||||
│ └── analysis.md
|
||||
├── {role}/
|
||||
│ ├── analysis.md # Main document (with optional @references)
|
||||
│ └── analysis-{slug}.md # Section documents (max 5)
|
||||
└── synthesis-specification.md # Integration (Phase 3)
|
||||
```
|
||||
|
||||
|
||||
@@ -2,325 +2,318 @@
|
||||
name: synthesis
|
||||
description: Clarify and refine role analyses through intelligent Q&A and targeted updates with synthesis agent
|
||||
argument-hint: "[optional: --session session-id]"
|
||||
allowed-tools: Task(conceptual-planning-agent), TodoWrite(*), Read(*), Write(*), Edit(*), Glob(*)
|
||||
allowed-tools: Task(conceptual-planning-agent), TodoWrite(*), Read(*), Write(*), Edit(*), Glob(*), AskUserQuestion(*)
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
Three-phase workflow to eliminate ambiguities and enhance conceptual depth in role analyses:
|
||||
Six-phase workflow to eliminate ambiguities and enhance conceptual depth in role analyses:
|
||||
|
||||
**Phase 1-2 (Main Flow)**: Session detection → File discovery → Path preparation
|
||||
**Phase 1-2**: Session detection → File discovery → Path preparation
|
||||
**Phase 3A**: Cross-role analysis agent → Generate recommendations
|
||||
**Phase 4**: User selects enhancements → User answers clarifications (via AskUserQuestion)
|
||||
**Phase 5**: Parallel update agents (one per role)
|
||||
**Phase 6**: Context package update → Metadata update → Completion report
|
||||
|
||||
**Phase 3A (Analysis Agent)**: Cross-role analysis → Generate recommendations
|
||||
|
||||
**Phase 4 (Main Flow)**: User selects enhancements → User answers clarifications → Build update plan
|
||||
|
||||
**Phase 5 (Parallel Update Agents)**: Each agent updates ONE role document → Parallel execution
|
||||
|
||||
**Phase 6 (Main Flow)**: Metadata update → Completion report
|
||||
|
||||
**Key Features**:
|
||||
- Multi-agent architecture (analysis agent + parallel update agents)
|
||||
- Clear separation: Agent analysis vs Main flow interaction
|
||||
- Parallel document updates (one agent per role)
|
||||
- User intent alignment validation
|
||||
All user interactions use AskUserQuestion tool (max 4 questions per call, multi-round).
|
||||
|
||||
**Document Flow**:
|
||||
- Input: `[role]/analysis*.md`, `guidance-specification.md`, session metadata
|
||||
- Output: Updated `[role]/analysis*.md` with Enhancements + Clarifications sections
|
||||
|
||||
---
|
||||
|
||||
## Quick Reference
|
||||
|
||||
### Phase Summary
|
||||
|
||||
| Phase | Goal | Executor | Output |
|
||||
|-------|------|----------|--------|
|
||||
| 1 | Session detection | Main flow | session_id, brainstorm_dir |
|
||||
| 2 | File discovery | Main flow | role_analysis_paths |
|
||||
| 3A | Cross-role analysis | Agent | enhancement_recommendations |
|
||||
| 4 | User interaction | Main flow + AskUserQuestion | update_plan |
|
||||
| 5 | Document updates | Parallel agents | Updated analysis*.md |
|
||||
| 6 | Finalization | Main flow | context-package.json, report |
|
||||
|
||||
### AskUserQuestion Pattern
|
||||
|
||||
```javascript
|
||||
// Enhancement selection (multi-select)
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: "请选择要应用的改进建议",
|
||||
header: "改进选择",
|
||||
multiSelect: true,
|
||||
options: [
|
||||
{ label: "EP-001: API Contract", description: "添加详细的请求/响应 schema 定义" },
|
||||
{ label: "EP-002: User Intent", description: "明确用户需求优先级和验收标准" }
|
||||
]
|
||||
}]
|
||||
})
|
||||
|
||||
// Clarification questions (single-select, multi-round)
|
||||
AskUserQuestion({
|
||||
questions: [
|
||||
{
|
||||
question: "MVP 阶段的核心目标是什么?",
|
||||
header: "用户意图",
|
||||
multiSelect: false,
|
||||
options: [
|
||||
{ label: "快速验证", description: "最小功能集,快速上线获取反馈" },
|
||||
{ label: "技术壁垒", description: "完善架构,为长期发展打基础" },
|
||||
{ label: "功能完整", description: "覆盖所有规划功能,延迟上线" }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Task Tracking
|
||||
|
||||
```json
|
||||
[
|
||||
{"content": "Detect session and validate analyses", "status": "in_progress", "activeForm": "Detecting session"},
|
||||
{"content": "Detect session and validate analyses", "status": "pending", "activeForm": "Detecting session"},
|
||||
{"content": "Discover role analysis file paths", "status": "pending", "activeForm": "Discovering paths"},
|
||||
{"content": "Execute analysis agent (cross-role analysis)", "status": "pending", "activeForm": "Executing analysis agent"},
|
||||
{"content": "Present enhancements for user selection", "status": "pending", "activeForm": "Presenting enhancements"},
|
||||
{"content": "Generate and present clarification questions", "status": "pending", "activeForm": "Clarifying with user"},
|
||||
{"content": "Build update plan from user input", "status": "pending", "activeForm": "Building update plan"},
|
||||
{"content": "Execute parallel update agents (one per role)", "status": "pending", "activeForm": "Updating documents in parallel"},
|
||||
{"content": "Update session metadata and generate report", "status": "pending", "activeForm": "Finalizing session"}
|
||||
{"content": "Execute analysis agent (cross-role analysis)", "status": "pending", "activeForm": "Executing analysis"},
|
||||
{"content": "Present enhancements via AskUserQuestion", "status": "pending", "activeForm": "Selecting enhancements"},
|
||||
{"content": "Clarification questions via AskUserQuestion", "status": "pending", "activeForm": "Clarifying"},
|
||||
{"content": "Execute parallel update agents", "status": "pending", "activeForm": "Updating documents"},
|
||||
{"content": "Update context package and metadata", "status": "pending", "activeForm": "Finalizing"}
|
||||
]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Execution Phases
|
||||
|
||||
### Phase 1: Discovery & Validation
|
||||
|
||||
1. **Detect Session**: Use `--session` parameter or find `.workflow/active/WFS-*` directories
|
||||
1. **Detect Session**: Use `--session` parameter or find `.workflow/active/WFS-*`
|
||||
2. **Validate Files**:
|
||||
- `guidance-specification.md` (optional, warn if missing)
|
||||
- `*/analysis*.md` (required, error if empty)
|
||||
3. **Load User Intent**: Extract from `workflow-session.json` (project/description field)
|
||||
3. **Load User Intent**: Extract from `workflow-session.json`
|
||||
|
||||
### Phase 2: Role Discovery & Path Preparation
|
||||
|
||||
**Main flow prepares file paths for Agent**:
|
||||
|
||||
1. **Discover Analysis Files**:
|
||||
- Glob(.workflow/active/WFS-{session}/.brainstorming/*/analysis*.md)
|
||||
- Supports: analysis.md, analysis-1.md, analysis-2.md, analysis-3.md
|
||||
- Validate: At least one file exists (error if empty)
|
||||
- Glob: `.workflow/active/WFS-{session}/.brainstorming/*/analysis*.md`
|
||||
- Supports: analysis.md + analysis-{slug}.md (max 5)
|
||||
|
||||
2. **Extract Role Information**:
|
||||
- `role_analysis_paths`: Relative paths from brainstorm_dir
|
||||
- `participating_roles`: Role names extracted from directory paths
|
||||
- `role_analysis_paths`: Relative paths
|
||||
- `participating_roles`: Role names from directories
|
||||
|
||||
3. **Pass to Agent** (Phase 3):
|
||||
- `session_id`
|
||||
- `brainstorm_dir`: .workflow/active/WFS-{session}/.brainstorming/
|
||||
- `role_analysis_paths`: ["product-manager/analysis.md", "system-architect/analysis-1.md", ...]
|
||||
- `participating_roles`: ["product-manager", "system-architect", ...]
|
||||
|
||||
**Main Flow Responsibility**: File discovery and path preparation only (NO file content reading)
|
||||
3. **Pass to Agent**: session_id, brainstorm_dir, role_analysis_paths, participating_roles
|
||||
|
||||
### Phase 3A: Analysis & Enhancement Agent
|
||||
|
||||
**First agent call**: Cross-role analysis and generate enhancement recommendations
|
||||
**Agent executes cross-role analysis**:
|
||||
|
||||
```bash
|
||||
Task(conceptual-planning-agent): "
|
||||
```javascript
|
||||
Task(conceptual-planning-agent, `
|
||||
## Agent Mission
|
||||
Analyze role documents, identify conflicts/gaps, and generate enhancement recommendations
|
||||
Analyze role documents, identify conflicts/gaps, generate enhancement recommendations
|
||||
|
||||
## Input from Main Flow
|
||||
- brainstorm_dir: {brainstorm_dir}
|
||||
- role_analysis_paths: {role_analysis_paths}
|
||||
- participating_roles: {participating_roles}
|
||||
## Input
|
||||
- brainstorm_dir: ${brainstorm_dir}
|
||||
- role_analysis_paths: ${role_analysis_paths}
|
||||
- participating_roles: ${participating_roles}
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
## Flow Control Steps
|
||||
1. load_session_metadata → Read workflow-session.json
|
||||
2. load_role_analyses → Read all analysis files
|
||||
3. cross_role_analysis → Identify consensus, conflicts, gaps, ambiguities
|
||||
4. generate_recommendations → Format as EP-001, EP-002, ...
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute these analysis steps sequentially with context accumulation:
|
||||
|
||||
1. **load_session_metadata**
|
||||
- Action: Load original user intent as primary reference
|
||||
- Command: Read({brainstorm_dir}/../workflow-session.json)
|
||||
- Output: original_user_intent (from project/description field)
|
||||
|
||||
2. **load_role_analyses**
|
||||
- Action: Load all role analysis documents
|
||||
- Command: For each path in role_analysis_paths: Read({brainstorm_dir}/{path})
|
||||
- Output: role_analyses_content_map = {role_name: content}
|
||||
|
||||
3. **cross_role_analysis**
|
||||
- Action: Identify consensus themes, conflicts, gaps, underspecified areas
|
||||
- Output: consensus_themes, conflicting_views, gaps_list, ambiguities
|
||||
|
||||
4. **generate_recommendations**
|
||||
- Action: Convert cross-role analysis findings into structured enhancement recommendations
|
||||
- Format: EP-001, EP-002, ... (sequential numbering)
|
||||
- Fields: id, title, affected_roles, category, current_state, enhancement, rationale, priority
|
||||
- Taxonomy: Map to 9 categories (User Intent, Requirements, Architecture, UX, Feasibility, Risk, Process, Decisions, Terminology)
|
||||
- Output: enhancement_recommendations (JSON array)
|
||||
|
||||
### Output to Main Flow
|
||||
Return JSON array:
|
||||
## Output Format
|
||||
[
|
||||
{
|
||||
\"id\": \"EP-001\",
|
||||
\"title\": \"API Contract Specification\",
|
||||
\"affected_roles\": [\"system-architect\", \"api-designer\"],
|
||||
\"category\": \"Architecture\",
|
||||
\"current_state\": \"High-level API descriptions\",
|
||||
\"enhancement\": \"Add detailed contract definitions with request/response schemas\",
|
||||
\"rationale\": \"Enables precise implementation and testing\",
|
||||
\"priority\": \"High\"
|
||||
},
|
||||
...
|
||||
"id": "EP-001",
|
||||
"title": "API Contract Specification",
|
||||
"affected_roles": ["system-architect", "api-designer"],
|
||||
"category": "Architecture",
|
||||
"current_state": "High-level API descriptions",
|
||||
"enhancement": "Add detailed contract definitions",
|
||||
"rationale": "Enables precise implementation",
|
||||
"priority": "High"
|
||||
}
|
||||
]
|
||||
|
||||
"
|
||||
`)
|
||||
```
|
||||
|
||||
### Phase 4: Main Flow User Interaction
|
||||
### Phase 4: User Interaction
|
||||
|
||||
**Main flow handles all user interaction via text output**:
|
||||
**All interactions via AskUserQuestion (Chinese questions)**
|
||||
|
||||
**⚠️ CRITICAL**: ALL questions MUST use Chinese (所有问题必须用中文) for better user understanding
|
||||
#### Step 1: Enhancement Selection
|
||||
|
||||
1. **Present Enhancement Options** (multi-select):
|
||||
```markdown
|
||||
===== Enhancement 选择 =====
|
||||
```javascript
|
||||
// If enhancements > 4, split into multiple rounds
|
||||
const enhancements = [...]; // from Phase 3A
|
||||
const BATCH_SIZE = 4;
|
||||
|
||||
请选择要应用的改进建议(可多选):
|
||||
for (let i = 0; i < enhancements.length; i += BATCH_SIZE) {
|
||||
const batch = enhancements.slice(i, i + BATCH_SIZE);
|
||||
|
||||
a) EP-001: API Contract Specification
|
||||
影响角色:system-architect, api-designer
|
||||
说明:添加详细的请求/响应 schema 定义
|
||||
AskUserQuestion({
|
||||
questions: [{
|
||||
question: `请选择要应用的改进建议 (第${Math.floor(i/BATCH_SIZE)+1}轮)`,
|
||||
header: "改进选择",
|
||||
multiSelect: true,
|
||||
options: batch.map(ep => ({
|
||||
label: `${ep.id}: ${ep.title}`,
|
||||
description: `影响: ${ep.affected_roles.join(', ')} | ${ep.enhancement}`
|
||||
}))
|
||||
}]
|
||||
})
|
||||
|
||||
b) EP-002: User Intent Validation
|
||||
影响角色:product-manager, ux-expert
|
||||
说明:明确用户需求优先级和验收标准
|
||||
// Store selections before next round
|
||||
}
|
||||
|
||||
c) EP-003: Error Handling Strategy
|
||||
影响角色:system-architect
|
||||
说明:统一异常处理和降级方案
|
||||
|
||||
支持格式:1abc 或 1a 1b 1c 或 1a,b,c
|
||||
请输入选择(可跳过输入 skip):
|
||||
// User can also skip: provide "跳过" option
|
||||
```
|
||||
|
||||
2. **Generate Clarification Questions** (based on analysis agent output):
|
||||
- ✅ **ALL questions in Chinese (所有问题必须用中文)**
|
||||
- Use 9-category taxonomy scan results
|
||||
- Prioritize most critical questions (no hard limit)
|
||||
- Each with 2-4 options + descriptions
|
||||
#### Step 2: Clarification Questions
|
||||
|
||||
3. **Interactive Clarification Loop** (max 10 questions per round):
|
||||
```markdown
|
||||
===== Clarification 问题 (第 1/2 轮) =====
|
||||
```javascript
|
||||
// Generate questions based on 9-category taxonomy scan
|
||||
// Categories: User Intent, Requirements, Architecture, UX, Feasibility, Risk, Process, Decisions, Terminology
|
||||
|
||||
【问题1 - 用户意图】MVP 阶段的核心目标是什么?
|
||||
a) 快速验证市场需求
|
||||
说明:最小功能集,快速上线获取反馈
|
||||
b) 建立技术壁垒
|
||||
说明:完善架构,为长期发展打基础
|
||||
c) 实现功能完整性
|
||||
说明:覆盖所有规划功能,延迟上线
|
||||
const clarifications = [...]; // from analysis
|
||||
const BATCH_SIZE = 4;
|
||||
|
||||
【问题2 - 架构决策】技术栈选择的优先考虑因素?
|
||||
a) 团队熟悉度
|
||||
说明:使用现有技术栈,降低学习成本
|
||||
b) 技术先进性
|
||||
说明:采用新技术,提升竞争力
|
||||
c) 生态成熟度
|
||||
说明:选择成熟方案,保证稳定性
|
||||
for (let i = 0; i < clarifications.length; i += BATCH_SIZE) {
|
||||
const batch = clarifications.slice(i, i + BATCH_SIZE);
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1;
|
||||
const totalRounds = Math.ceil(clarifications.length / BATCH_SIZE);
|
||||
|
||||
...(最多10个问题)
|
||||
AskUserQuestion({
|
||||
questions: batch.map(q => ({
|
||||
question: q.question,
|
||||
header: q.category.substring(0, 12),
|
||||
multiSelect: false,
|
||||
options: q.options.map(opt => ({
|
||||
label: opt.label,
|
||||
description: opt.description
|
||||
}))
|
||||
}))
|
||||
})
|
||||
|
||||
请回答 (格式: 1a 2b 3c...):
|
||||
// Store answers before next round
|
||||
}
|
||||
```
|
||||
|
||||
Wait for user input → Parse all answers in batch → Continue to next round if needed
|
||||
### Question Guidelines
|
||||
|
||||
4. **Build Update Plan**:
|
||||
```
|
||||
**Target**: 开发者(理解技术但需要从用户需求出发)
|
||||
|
||||
**Question Structure**: `[跨角色分析发现] + [需要澄清的决策点]`
|
||||
**Option Structure**: `标签:[具体方案] + 说明:[业务影响] + [技术权衡]`
|
||||
|
||||
**9-Category Taxonomy**:
|
||||
|
||||
| Category | Focus | Example Question Pattern |
|
||||
|----------|-------|--------------------------|
|
||||
| User Intent | 用户目标 | "MVP阶段核心目标?" + 验证/壁垒/完整性 |
|
||||
| Requirements | 需求细化 | "功能优先级如何排序?" + 核心/增强/可选 |
|
||||
| Architecture | 架构决策 | "技术栈选择考量?" + 熟悉度/先进性/成熟度 |
|
||||
| UX | 用户体验 | "交互复杂度取舍?" + 简洁/丰富/渐进 |
|
||||
| Feasibility | 可行性 | "资源约束下的范围?" + 最小/标准/完整 |
|
||||
| Risk | 风险管理 | "风险容忍度?" + 保守/平衡/激进 |
|
||||
| Process | 流程规范 | "迭代节奏?" + 快速/稳定/灵活 |
|
||||
| Decisions | 决策确认 | "冲突解决方案?" + 方案A/方案B/折中 |
|
||||
| Terminology | 术语统一 | "统一使用哪个术语?" + 术语A/术语B |
|
||||
|
||||
**Quality Rules**:
|
||||
|
||||
**MUST Include**:
|
||||
- ✅ All questions in Chinese (用中文提问)
|
||||
- ✅ 基于跨角色分析的具体发现
|
||||
- ✅ 选项包含业务影响说明
|
||||
- ✅ 解决实际的模糊点或冲突
|
||||
|
||||
**MUST Avoid**:
|
||||
- ❌ 与角色分析无关的通用问题
|
||||
- ❌ 重复已在 artifacts 阶段确认的内容
|
||||
- ❌ 过于细节的实现级问题
|
||||
|
||||
#### Step 3: Build Update Plan
|
||||
|
||||
```javascript
|
||||
update_plan = {
|
||||
"role1": {
|
||||
"enhancements": [EP-001, EP-003],
|
||||
"enhancements": ["EP-001", "EP-003"],
|
||||
"clarifications": [
|
||||
{"question": "...", "answer": "...", "category": "..."},
|
||||
...
|
||||
{"question": "...", "answer": "...", "category": "..."}
|
||||
]
|
||||
},
|
||||
"role2": {
|
||||
"enhancements": [EP-002],
|
||||
"enhancements": ["EP-002"],
|
||||
"clarifications": [...]
|
||||
},
|
||||
...
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Phase 5: Parallel Document Update Agents
|
||||
|
||||
**Parallel agent calls** (one per role needing updates):
|
||||
**Execute in parallel** (one agent per role):
|
||||
|
||||
```bash
|
||||
# Execute in parallel using single message with multiple Task calls
|
||||
|
||||
Task(conceptual-planning-agent): "
|
||||
```javascript
|
||||
// Single message with multiple Task calls for parallelism
|
||||
Task(conceptual-planning-agent, `
|
||||
## Agent Mission
|
||||
Apply user-confirmed enhancements and clarifications to {role1} analysis document
|
||||
Apply enhancements and clarifications to ${role} analysis
|
||||
|
||||
## Agent Intent
|
||||
- **Goal**: Integrate synthesis results into role-specific analysis
|
||||
- **Scope**: Update ONLY {role1}/analysis.md (isolated, no cross-role dependencies)
|
||||
- **Constraints**: Preserve original insights, add refinements without deletion
|
||||
## Input
|
||||
- role: ${role}
|
||||
- analysis_path: ${brainstorm_dir}/${role}/analysis.md
|
||||
- enhancements: ${role_enhancements}
|
||||
- clarifications: ${role_clarifications}
|
||||
- original_user_intent: ${intent}
|
||||
|
||||
## Input from Main Flow
|
||||
- role: {role1}
|
||||
- analysis_path: {brainstorm_dir}/{role1}/analysis.md
|
||||
- enhancements: [EP-001, EP-003] (user-selected improvements)
|
||||
- clarifications: [{question, answer, category}, ...] (user-confirmed answers)
|
||||
- original_user_intent: {from session metadata}
|
||||
## Flow Control Steps
|
||||
1. load_current_analysis → Read analysis file
|
||||
2. add_clarifications_section → Insert Q&A section
|
||||
3. apply_enhancements → Integrate into relevant sections
|
||||
4. resolve_contradictions → Remove conflicts
|
||||
5. enforce_terminology → Align terminology
|
||||
6. validate_intent → Verify alignment with user intent
|
||||
7. write_updated_file → Save changes
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute these update steps sequentially:
|
||||
|
||||
1. **load_current_analysis**
|
||||
- Action: Load existing role analysis document
|
||||
- Command: Read({brainstorm_dir}/{role1}/analysis.md)
|
||||
- Output: current_analysis_content
|
||||
|
||||
2. **add_clarifications_section**
|
||||
- Action: Insert Clarifications section with Q&A
|
||||
- Format: \"## Clarifications\\n### Session {date}\\n- **Q**: {question} (Category: {category})\\n **A**: {answer}\"
|
||||
- Output: analysis_with_clarifications
|
||||
|
||||
3. **apply_enhancements**
|
||||
- Action: Integrate EP-001, EP-003 into relevant sections
|
||||
- Strategy: Locate section by category (Architecture → Architecture section, UX → User Experience section)
|
||||
- Output: analysis_with_enhancements
|
||||
|
||||
4. **resolve_contradictions**
|
||||
- Action: Remove conflicts between original content and clarifications/enhancements
|
||||
- Output: contradiction_free_analysis
|
||||
|
||||
5. **enforce_terminology_consistency**
|
||||
- Action: Align all terminology with user-confirmed choices from clarifications
|
||||
- Output: terminology_consistent_analysis
|
||||
|
||||
6. **validate_user_intent_alignment**
|
||||
- Action: Verify all updates support original_user_intent
|
||||
- Output: validated_analysis
|
||||
|
||||
7. **write_updated_file**
|
||||
- Action: Save final analysis document
|
||||
- Command: Write({brainstorm_dir}/{role1}/analysis.md, validated_analysis)
|
||||
- Output: File update confirmation
|
||||
|
||||
### Output
|
||||
Updated {role1}/analysis.md with Clarifications section + enhanced content
|
||||
")
|
||||
|
||||
Task(conceptual-planning-agent): "
|
||||
## Agent Mission
|
||||
Apply user-confirmed enhancements and clarifications to {role2} analysis document
|
||||
|
||||
## Agent Intent
|
||||
- **Goal**: Integrate synthesis results into role-specific analysis
|
||||
- **Scope**: Update ONLY {role2}/analysis.md (isolated, no cross-role dependencies)
|
||||
- **Constraints**: Preserve original insights, add refinements without deletion
|
||||
|
||||
## Input from Main Flow
|
||||
- role: {role2}
|
||||
- analysis_path: {brainstorm_dir}/{role2}/analysis.md
|
||||
- enhancements: [EP-002] (user-selected improvements)
|
||||
- clarifications: [{question, answer, category}, ...] (user-confirmed answers)
|
||||
- original_user_intent: {from session metadata}
|
||||
|
||||
## Execution Instructions
|
||||
[FLOW_CONTROL]
|
||||
|
||||
### Flow Control Steps
|
||||
**AGENT RESPONSIBILITY**: Execute same 7 update steps as {role1} agent (load → clarifications → enhancements → contradictions → terminology → validation → write)
|
||||
|
||||
### Output
|
||||
Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
")
|
||||
|
||||
# ... repeat for each role in update_plan
|
||||
## Output
|
||||
Updated ${role}/analysis.md
|
||||
`)
|
||||
```
|
||||
|
||||
**Agent Characteristics**:
|
||||
- **Intent**: Integrate user-confirmed synthesis results (NOT generate new analysis)
|
||||
- **Isolation**: Each agent updates exactly ONE role (parallel execution safe)
|
||||
- **Context**: Minimal - receives only role-specific enhancements + clarifications
|
||||
- **Dependencies**: Zero cross-agent dependencies (full parallelism)
|
||||
- **Isolation**: Each agent updates exactly ONE role (parallel safe)
|
||||
- **Dependencies**: Zero cross-agent dependencies
|
||||
- **Validation**: All updates must align with original_user_intent
|
||||
|
||||
### Phase 6: Completion & Metadata Update
|
||||
### Phase 6: Finalization
|
||||
|
||||
**Main flow finalizes**:
|
||||
#### Step 1: Update Context Package
|
||||
|
||||
```javascript
|
||||
// Sync updated analyses to context-package.json
|
||||
const context_pkg = Read(".workflow/active/WFS-{session}/.process/context-package.json")
|
||||
|
||||
// Update guidance-specification if exists
|
||||
// Update synthesis-specification if exists
|
||||
// Re-read all role analysis files
|
||||
// Update metadata timestamps
|
||||
|
||||
Write(context_pkg_path, JSON.stringify(context_pkg))
|
||||
```
|
||||
|
||||
#### Step 2: Update Session Metadata
|
||||
|
||||
1. Wait for all parallel agents to complete
|
||||
2. Update workflow-session.json:
|
||||
```json
|
||||
{
|
||||
"phases": {
|
||||
@@ -330,15 +323,13 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
"completed_at": "timestamp",
|
||||
"participating_roles": [...],
|
||||
"clarification_results": {
|
||||
"enhancements_applied": ["EP-001", "EP-002", ...],
|
||||
"enhancements_applied": ["EP-001", "EP-002"],
|
||||
"questions_asked": 3,
|
||||
"categories_clarified": ["Architecture", "UX", ...],
|
||||
"roles_updated": ["role1", "role2", ...],
|
||||
"outstanding_items": []
|
||||
"categories_clarified": ["Architecture", "UX"],
|
||||
"roles_updated": ["role1", "role2"]
|
||||
},
|
||||
"quality_metrics": {
|
||||
"user_intent_alignment": "validated",
|
||||
"requirement_coverage": "comprehensive",
|
||||
"ambiguity_resolution": "complete",
|
||||
"terminology_consistency": "enforced"
|
||||
}
|
||||
@@ -347,7 +338,8 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
}
|
||||
```
|
||||
|
||||
3. Generate completion report (show to user):
|
||||
#### Step 3: Completion Report
|
||||
|
||||
```markdown
|
||||
## ✅ Clarification Complete
|
||||
|
||||
@@ -359,9 +351,11 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
✅ PROCEED: `/workflow:plan --session WFS-{session-id}`
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Output
|
||||
|
||||
**Location**: `.workflow/active/WFS-{session}/.brainstorming/[role]/analysis*.md` (in-place updates)
|
||||
**Location**: `.workflow/active/WFS-{session}/.brainstorming/[role]/analysis*.md`
|
||||
|
||||
**Updated Structure**:
|
||||
```markdown
|
||||
@@ -381,116 +375,24 @@ Updated {role2}/analysis.md with Clarifications section + enhanced content
|
||||
- Ambiguities resolved, placeholders removed
|
||||
- Consistent terminology
|
||||
|
||||
### Phase 6: Update Context Package
|
||||
|
||||
**Purpose**: Sync updated role analyses to context-package.json to avoid stale cache
|
||||
|
||||
**Operations**:
|
||||
```bash
|
||||
context_pkg_path = ".workflow/active/WFS-{session}/.process/context-package.json"
|
||||
|
||||
# 1. Read existing package
|
||||
context_pkg = Read(context_pkg_path)
|
||||
|
||||
# 2. Re-read brainstorm artifacts (now with synthesis enhancements)
|
||||
brainstorm_dir = ".workflow/active/WFS-{session}/.brainstorming"
|
||||
|
||||
# 2.1 Update guidance-specification if exists
|
||||
IF exists({brainstorm_dir}/guidance-specification.md):
|
||||
context_pkg.brainstorm_artifacts.guidance_specification.content = Read({brainstorm_dir}/guidance-specification.md)
|
||||
context_pkg.brainstorm_artifacts.guidance_specification.updated_at = NOW()
|
||||
|
||||
# 2.2 Update synthesis-specification if exists
|
||||
IF exists({brainstorm_dir}/synthesis-specification.md):
|
||||
IF context_pkg.brainstorm_artifacts.synthesis_output:
|
||||
context_pkg.brainstorm_artifacts.synthesis_output.content = Read({brainstorm_dir}/synthesis-specification.md)
|
||||
context_pkg.brainstorm_artifacts.synthesis_output.updated_at = NOW()
|
||||
|
||||
# 2.3 Re-read all role analysis files
|
||||
role_analysis_files = Glob({brainstorm_dir}/*/analysis*.md)
|
||||
context_pkg.brainstorm_artifacts.role_analyses = []
|
||||
|
||||
FOR file IN role_analysis_files:
|
||||
role_name = extract_role_from_path(file) # e.g., "ui-designer"
|
||||
relative_path = file.replace({brainstorm_dir}/, "")
|
||||
|
||||
context_pkg.brainstorm_artifacts.role_analyses.push({
|
||||
"role": role_name,
|
||||
"files": [{
|
||||
"path": relative_path,
|
||||
"type": "primary",
|
||||
"content": Read(file),
|
||||
"updated_at": NOW()
|
||||
}]
|
||||
})
|
||||
|
||||
# 3. Update metadata
|
||||
context_pkg.metadata.updated_at = NOW()
|
||||
context_pkg.metadata.synthesis_timestamp = NOW()
|
||||
|
||||
# 4. Write back
|
||||
Write(context_pkg_path, JSON.stringify(context_pkg, indent=2))
|
||||
|
||||
REPORT: "✅ Updated context-package.json with synthesis results"
|
||||
```
|
||||
|
||||
**TodoWrite Update**:
|
||||
```json
|
||||
{"content": "Update context package with synthesis results", "status": "completed", "activeForm": "Updating context package"}
|
||||
```
|
||||
|
||||
## Session Metadata
|
||||
|
||||
Update `workflow-session.json`:
|
||||
|
||||
```json
|
||||
{
|
||||
"phases": {
|
||||
"BRAINSTORM": {
|
||||
"status": "clarification_completed",
|
||||
"clarification_completed": true,
|
||||
"completed_at": "timestamp",
|
||||
"participating_roles": ["product-manager", "system-architect", ...],
|
||||
"clarification_results": {
|
||||
"questions_asked": 3,
|
||||
"categories_clarified": ["Architecture & Design", ...],
|
||||
"roles_updated": ["system-architect", "ui-designer", ...],
|
||||
"outstanding_items": []
|
||||
},
|
||||
"quality_metrics": {
|
||||
"user_intent_alignment": "validated",
|
||||
"requirement_coverage": "comprehensive",
|
||||
"ambiguity_resolution": "complete",
|
||||
"terminology_consistency": "enforced",
|
||||
"decision_transparency": "documented"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
---
|
||||
|
||||
## Quality Checklist
|
||||
|
||||
**Content**:
|
||||
- All role analyses loaded/analyzed
|
||||
- Cross-role analysis (consensus, conflicts, gaps)
|
||||
- 9-category ambiguity scan
|
||||
- Questions prioritized
|
||||
- Clarifications documented
|
||||
- ✅ All role analyses loaded/analyzed
|
||||
- ✅ Cross-role analysis (consensus, conflicts, gaps)
|
||||
- ✅ 9-category ambiguity scan
|
||||
- ✅ Questions prioritized
|
||||
|
||||
**Analysis**:
|
||||
- User intent validated
|
||||
- Cross-role synthesis complete
|
||||
- Ambiguities resolved
|
||||
- Correct roles updated
|
||||
- Terminology consistent
|
||||
- Contradictions removed
|
||||
- ✅ User intent validated
|
||||
- ✅ Cross-role synthesis complete
|
||||
- ✅ Ambiguities resolved
|
||||
- ✅ Terminology consistent
|
||||
|
||||
**Documents**:
|
||||
- Clarifications section formatted
|
||||
- Sections reflect answers
|
||||
- No placeholders (TODO/TBD)
|
||||
- Valid Markdown
|
||||
- Cross-references maintained
|
||||
|
||||
|
||||
- ✅ Clarifications section formatted
|
||||
- ✅ Sections reflect answers
|
||||
- ✅ No placeholders (TODO/TBD)
|
||||
- ✅ Valid Markdown
|
||||
|
||||
@@ -92,7 +92,7 @@ Analyze project for workflow initialization and generate .workflow/project.json.
|
||||
|
||||
## MANDATORY FIRST STEPS
|
||||
1. Execute: cat ~/.claude/workflows/cli-templates/schemas/project-json-schema.json (get schema reference)
|
||||
2. Execute: ~/.claude/scripts/get_modules_by_depth.sh (get project structure)
|
||||
2. Execute: ccw tool exec get_modules_by_depth '{}' (get project structure)
|
||||
|
||||
## Task
|
||||
Generate complete project.json with:
|
||||
@@ -122,7 +122,7 @@ Generate complete project.json with:
|
||||
1. Structural scan: get_modules_by_depth.sh, find, wc -l
|
||||
2. Semantic analysis: Gemini for patterns/architecture
|
||||
3. Synthesis: Merge findings
|
||||
4. ${regenerate ? 'Merge with preserved features/statistics from .workflow/project.json.backup' : ''}
|
||||
4. ${regenerate ? 'Merge with preserved features/development_index/statistics from .workflow/project.json.backup' : ''}
|
||||
5. Write JSON: Write('.workflow/project.json', jsonContent)
|
||||
6. Report: Return brief completion summary
|
||||
|
||||
|
||||
@@ -556,6 +556,58 @@ codex --full-auto exec "[Verify plan acceptance criteria at ${plan.json}]" --ski
|
||||
- `@{plan.json}` → `@${executionContext.session.artifacts.plan}`
|
||||
- `[@{exploration.json}]` → exploration files from artifacts (if exists)
|
||||
|
||||
### Step 6: Update Development Index
|
||||
|
||||
**Trigger**: After all executions complete (regardless of code review)
|
||||
|
||||
**Skip Condition**: Skip if `.workflow/project.json` does not exist
|
||||
|
||||
**Operations**:
|
||||
```javascript
|
||||
const projectJsonPath = '.workflow/project.json'
|
||||
if (!fileExists(projectJsonPath)) return // Silent skip
|
||||
|
||||
const projectJson = JSON.parse(Read(projectJsonPath))
|
||||
|
||||
// Initialize if needed
|
||||
if (!projectJson.development_index) {
|
||||
projectJson.development_index = { feature: [], enhancement: [], bugfix: [], refactor: [], docs: [] }
|
||||
}
|
||||
|
||||
// Detect category from keywords
|
||||
function detectCategory(text) {
|
||||
text = text.toLowerCase()
|
||||
if (/\b(fix|bug|error|issue|crash)\b/.test(text)) return 'bugfix'
|
||||
if (/\b(refactor|cleanup|reorganize)\b/.test(text)) return 'refactor'
|
||||
if (/\b(doc|readme|comment)\b/.test(text)) return 'docs'
|
||||
if (/\b(add|new|create|implement)\b/.test(text)) return 'feature'
|
||||
return 'enhancement'
|
||||
}
|
||||
|
||||
// Detect sub_feature from task file paths
|
||||
function detectSubFeature(tasks) {
|
||||
const dirs = tasks.map(t => t.file?.split('/').slice(-2, -1)[0]).filter(Boolean)
|
||||
const counts = dirs.reduce((a, d) => { a[d] = (a[d] || 0) + 1; return a }, {})
|
||||
return Object.entries(counts).sort((a, b) => b[1] - a[1])[0]?.[0] || 'general'
|
||||
}
|
||||
|
||||
const category = detectCategory(`${planObject.summary} ${planObject.approach}`)
|
||||
const entry = {
|
||||
title: planObject.summary.slice(0, 60),
|
||||
sub_feature: detectSubFeature(planObject.tasks),
|
||||
date: new Date().toISOString().split('T')[0],
|
||||
description: planObject.approach.slice(0, 100),
|
||||
status: previousExecutionResults.every(r => r.status === 'completed') ? 'completed' : 'partial',
|
||||
session_id: executionContext?.session?.id || null
|
||||
}
|
||||
|
||||
projectJson.development_index[category].push(entry)
|
||||
projectJson.statistics.last_updated = new Date().toISOString()
|
||||
Write(projectJsonPath, JSON.stringify(projectJson, null, 2))
|
||||
|
||||
console.log(`✓ Development index: [${category}] ${entry.title}`)
|
||||
```
|
||||
|
||||
## Best Practices
|
||||
|
||||
**Input Modes**: In-memory (lite-plan), prompt (standalone), file (JSON/text)
|
||||
|
||||
@@ -43,11 +43,11 @@ Phase 1: Bug Analysis & Diagnosis
|
||||
|- needsDiagnosis=true -> Launch parallel cli-explore-agents (1-4 based on severity)
|
||||
+- needsDiagnosis=false (hotfix) -> Skip directly to Phase 3 (Fix Planning)
|
||||
|
||||
Phase 2: Clarification (optional)
|
||||
Phase 2: Clarification (optional, multi-round)
|
||||
|- Aggregate clarification_needs from all diagnosis angles
|
||||
|- Deduplicate similar questions
|
||||
+- Decision:
|
||||
|- Has clarifications -> AskUserQuestion (max 4 questions)
|
||||
|- Has clarifications -> AskUserQuestion (max 4 questions per round, multiple rounds allowed)
|
||||
+- No clarifications -> Skip to Phase 3
|
||||
|
||||
Phase 3: Fix Planning (NO CODE EXECUTION - planning only)
|
||||
@@ -71,18 +71,18 @@ Phase 5: Dispatch
|
||||
|
||||
### Phase 1: Intelligent Multi-Angle Diagnosis
|
||||
|
||||
**Session Setup**:
|
||||
**Session Setup** (MANDATORY - follow exactly):
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
const bugSlug = bug_description.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const timestamp = getUtc8ISOString().replace(/[:.]/g, '-')
|
||||
const shortTimestamp = timestamp.substring(0, 19).replace('T', '-')
|
||||
const sessionId = `${bugSlug}-${shortTimestamp}`
|
||||
const dateStr = getUtc8ISOString().substring(0, 10) // Format: 2025-11-29
|
||||
|
||||
const sessionId = `${bugSlug}-${dateStr}` // e.g., "user-avatar-upload-fails-2025-11-29"
|
||||
const sessionFolder = `.workflow/.lite-fix/${sessionId}`
|
||||
|
||||
bash(`mkdir -p ${sessionFolder}`)
|
||||
bash(`mkdir -p ${sessionFolder} && test -d ${sessionFolder} && echo "SUCCESS: ${sessionFolder}" || echo "FAILED: ${sessionFolder}"`)
|
||||
```
|
||||
|
||||
**Diagnosis Decision Logic**:
|
||||
@@ -177,7 +177,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{error_keyword_from_bug}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json (get output schema reference)
|
||||
|
||||
@@ -216,7 +216,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
- fix_hints: Suggested fix approaches from ${angle} viewpoint
|
||||
- dependencies: Dependencies relevant to ${angle} diagnosis
|
||||
- constraints: ${angle}-specific limitations affecting fix
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.diagnosis_angle: "${angle}"
|
||||
- _metadata.diagnosis_index: ${index + 1}
|
||||
|
||||
@@ -228,7 +228,7 @@ Execute **${angle}** diagnosis for bug root cause analysis. Analyze codebase fro
|
||||
- [ ] Fix hints are actionable (specific code changes, not generic advice)
|
||||
- [ ] Reproduction steps are verifiable
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/diagnosis-${angle}.json
|
||||
@@ -287,10 +287,12 @@ Angles diagnosed: ${diagnosisManifest.diagnoses.map(d => d.angle).join(', ')}
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Clarification (Optional)
|
||||
### Phase 2: Clarification (Optional, Multi-Round)
|
||||
|
||||
**Skip if**: No diagnosis or `clarification_needs` is empty across all diagnoses
|
||||
|
||||
**⚠️ CRITICAL**: AskUserQuestion tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
||||
|
||||
**Aggregate clarification needs from all diagnosis angles**:
|
||||
```javascript
|
||||
// Load manifest and all diagnosis files
|
||||
@@ -327,18 +329,35 @@ function deduplicateClarifications(clarifications) {
|
||||
|
||||
const uniqueClarifications = deduplicateClarifications(allClarifications)
|
||||
|
||||
// Multi-round clarification: batch questions (max 4 per round)
|
||||
// ⚠️ MUST execute ALL rounds until uniqueClarifications exhausted
|
||||
if (uniqueClarifications.length > 0) {
|
||||
AskUserQuestion({
|
||||
questions: uniqueClarifications.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map(opt => ({
|
||||
label: opt,
|
||||
description: `Use ${opt} approach`
|
||||
const BATCH_SIZE = 4
|
||||
const totalRounds = Math.ceil(uniqueClarifications.length / BATCH_SIZE)
|
||||
|
||||
for (let i = 0; i < uniqueClarifications.length; i += BATCH_SIZE) {
|
||||
const batch = uniqueClarifications.slice(i, i + BATCH_SIZE)
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1
|
||||
|
||||
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
||||
|
||||
AskUserQuestion({
|
||||
questions: batch.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map((opt, index) => {
|
||||
const isRecommended = need.recommended === index
|
||||
return {
|
||||
label: isRecommended ? `${opt} ★` : opt,
|
||||
description: isRecommended ? `Use ${opt} approach (Recommended)` : `Use ${opt} approach`
|
||||
}
|
||||
})
|
||||
}))
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
// Store batch responses in clarificationContext before next round
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -568,7 +587,7 @@ SlashCommand(command="/workflow:lite-execute --in-memory --mode bugfix")
|
||||
## Session Folder Structure
|
||||
|
||||
```
|
||||
.workflow/.lite-fix/{bug-slug}-{timestamp}/
|
||||
.workflow/.lite-fix/{bug-slug}-{YYYY-MM-DD}/
|
||||
|- diagnosis-{angle1}.json # Diagnosis angle 1
|
||||
|- diagnosis-{angle2}.json # Diagnosis angle 2
|
||||
|- diagnosis-{angle3}.json # Diagnosis angle 3 (if applicable)
|
||||
|
||||
@@ -43,11 +43,11 @@ Phase 1: Task Analysis & Exploration
|
||||
├─ needsExploration=true → Launch parallel cli-explore-agents (1-4 based on complexity)
|
||||
└─ needsExploration=false → Skip to Phase 2/3
|
||||
|
||||
Phase 2: Clarification (optional)
|
||||
Phase 2: Clarification (optional, multi-round)
|
||||
├─ Aggregate clarification_needs from all exploration angles
|
||||
├─ Deduplicate similar questions
|
||||
└─ Decision:
|
||||
├─ Has clarifications → AskUserQuestion (max 4 questions)
|
||||
├─ Has clarifications → AskUserQuestion (max 4 questions per round, multiple rounds allowed)
|
||||
└─ No clarifications → Skip to Phase 3
|
||||
|
||||
Phase 3: Planning (NO CODE EXECUTION - planning only)
|
||||
@@ -71,18 +71,18 @@ Phase 5: Dispatch
|
||||
|
||||
### Phase 1: Intelligent Multi-Angle Exploration
|
||||
|
||||
**Session Setup**:
|
||||
**Session Setup** (MANDATORY - follow exactly):
|
||||
```javascript
|
||||
// Helper: Get UTC+8 (China Standard Time) ISO string
|
||||
const getUtc8ISOString = () => new Date(Date.now() + 8 * 60 * 60 * 1000).toISOString()
|
||||
|
||||
const taskSlug = task_description.toLowerCase().replace(/[^a-z0-9]+/g, '-').substring(0, 40)
|
||||
const timestamp = getUtc8ISOString().replace(/[:.]/g, '-')
|
||||
const shortTimestamp = timestamp.substring(0, 19).replace('T', '-')
|
||||
const sessionId = `${taskSlug}-${shortTimestamp}`
|
||||
const dateStr = getUtc8ISOString().substring(0, 10) // Format: 2025-11-29
|
||||
|
||||
const sessionId = `${taskSlug}-${dateStr}` // e.g., "implement-jwt-refresh-2025-11-29"
|
||||
const sessionFolder = `.workflow/.lite-plan/${sessionId}`
|
||||
|
||||
bash(`mkdir -p ${sessionFolder}`)
|
||||
bash(`mkdir -p ${sessionFolder} && test -d ${sessionFolder} && echo "SUCCESS: ${sessionFolder}" || echo "FAILED: ${sessionFolder}"`)
|
||||
```
|
||||
|
||||
**Exploration Decision Logic**:
|
||||
@@ -170,7 +170,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference)
|
||||
|
||||
@@ -206,7 +206,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- dependencies: Dependencies relevant to ${angle}
|
||||
- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations)
|
||||
- constraints: ${angle}-specific limitations/conventions
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.exploration_angle: "${angle}"
|
||||
|
||||
## Success Criteria
|
||||
@@ -217,7 +217,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- [ ] Integration points include file:line locations
|
||||
- [ ] Constraints are project-specific to ${angle}
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/exploration-${angle}.json
|
||||
@@ -276,10 +276,12 @@ Angles explored: ${explorationManifest.explorations.map(e => e.angle).join(', ')
|
||||
|
||||
---
|
||||
|
||||
### Phase 2: Clarification (Optional)
|
||||
### Phase 2: Clarification (Optional, Multi-Round)
|
||||
|
||||
**Skip if**: No exploration or `clarification_needs` is empty across all explorations
|
||||
|
||||
**⚠️ CRITICAL**: AskUserQuestion tool limits max 4 questions per call. **MUST execute multiple rounds** to exhaust all clarification needs - do NOT stop at round 1.
|
||||
|
||||
**Aggregate clarification needs from all exploration angles**:
|
||||
```javascript
|
||||
// Load manifest and all exploration files
|
||||
@@ -302,32 +304,40 @@ explorations.forEach(exp => {
|
||||
}
|
||||
})
|
||||
|
||||
// Deduplicate by question similarity
|
||||
function deduplicateClarifications(clarifications) {
|
||||
const unique = []
|
||||
clarifications.forEach(c => {
|
||||
const isDuplicate = unique.some(u =>
|
||||
u.question.toLowerCase() === c.question.toLowerCase()
|
||||
)
|
||||
if (!isDuplicate) unique.push(c)
|
||||
})
|
||||
return unique
|
||||
}
|
||||
// Deduplicate exact same questions only
|
||||
const seen = new Set()
|
||||
const dedupedClarifications = allClarifications.filter(c => {
|
||||
const key = c.question.toLowerCase()
|
||||
if (seen.has(key)) return false
|
||||
seen.add(key)
|
||||
return true
|
||||
})
|
||||
|
||||
const uniqueClarifications = deduplicateClarifications(allClarifications)
|
||||
// Multi-round clarification: batch questions (max 4 per round)
|
||||
if (dedupedClarifications.length > 0) {
|
||||
const BATCH_SIZE = 4
|
||||
const totalRounds = Math.ceil(dedupedClarifications.length / BATCH_SIZE)
|
||||
|
||||
if (uniqueClarifications.length > 0) {
|
||||
AskUserQuestion({
|
||||
questions: uniqueClarifications.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle,
|
||||
multiSelect: false,
|
||||
options: need.options.map(opt => ({
|
||||
label: opt,
|
||||
description: `Use ${opt} approach`
|
||||
for (let i = 0; i < dedupedClarifications.length; i += BATCH_SIZE) {
|
||||
const batch = dedupedClarifications.slice(i, i + BATCH_SIZE)
|
||||
const currentRound = Math.floor(i / BATCH_SIZE) + 1
|
||||
|
||||
console.log(`### Clarification Round ${currentRound}/${totalRounds}`)
|
||||
|
||||
AskUserQuestion({
|
||||
questions: batch.map(need => ({
|
||||
question: `[${need.source_angle}] ${need.question}\n\nContext: ${need.context}`,
|
||||
header: need.source_angle.substring(0, 12),
|
||||
multiSelect: false,
|
||||
options: need.options.map((opt, index) => ({
|
||||
label: need.recommended === index ? `${opt} ★` : opt,
|
||||
description: need.recommended === index ? `Recommended` : `Use ${opt}`
|
||||
}))
|
||||
}))
|
||||
}))
|
||||
})
|
||||
})
|
||||
|
||||
// Store batch responses in clarificationContext before next round
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
@@ -552,7 +562,7 @@ SlashCommand(command="/workflow:lite-execute --in-memory")
|
||||
## Session Folder Structure
|
||||
|
||||
```
|
||||
.workflow/.lite-plan/{task-slug}-{timestamp}/
|
||||
.workflow/.lite-plan/{task-slug}-{YYYY-MM-DD}/
|
||||
├── exploration-{angle1}.json # Exploration angle 1
|
||||
├── exploration-{angle2}.json # Exploration angle 2
|
||||
├── exploration-{angle3}.json # Exploration angle 3 (if applicable)
|
||||
|
||||
@@ -46,8 +46,7 @@ Automated fix orchestrator with **two-phase architecture**: AI-powered planning
|
||||
1. **Intelligent Planning**: AI-powered analysis identifies optimal grouping and execution strategy
|
||||
2. **Multi-stage Coordination**: Supports complex parallel + serial execution with dependency management
|
||||
3. **Conservative Safety**: Mandatory test verification with automatic rollback on failure
|
||||
4. **Real-time Visibility**: Dashboard shows planning progress, stage timeline, and active agents
|
||||
5. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
||||
4. **Resume Support**: Checkpoint-based recovery for interrupted sessions
|
||||
|
||||
### Orchestrator Boundary (CRITICAL)
|
||||
- **ONLY command** for automated review finding fixes
|
||||
@@ -59,14 +58,14 @@ Automated fix orchestrator with **two-phase architecture**: AI-powered planning
|
||||
|
||||
```
|
||||
Phase 1: Discovery & Initialization
|
||||
└─ Validate export file, create fix session structure, initialize state files → Generate fix-dashboard.html
|
||||
└─ Validate export file, create fix session structure, initialize state files
|
||||
|
||||
Phase 2: Planning Coordination (@cli-planning-agent)
|
||||
├─ Analyze findings for patterns and dependencies
|
||||
├─ Group by file + dimension + root cause similarity
|
||||
├─ Determine execution strategy (parallel/serial/hybrid)
|
||||
├─ Generate fix timeline with stages
|
||||
└─ Output: fix-plan.json (dashboard auto-polls for status)
|
||||
└─ Output: fix-plan.json
|
||||
|
||||
Phase 3: Execution Orchestration (Stage-based)
|
||||
For each timeline stage:
|
||||
@@ -198,12 +197,10 @@ if (result.passRate < 100%) {
|
||||
- Session creation: Generate fix-session-id (`fix-{timestamp}`)
|
||||
- Directory structure: Create `{review-dir}/fixes/{fix-session-id}/` with subdirectories
|
||||
- State files: Initialize active-fix-session.json (session marker)
|
||||
- Dashboard generation: Create fix-dashboard.html from template (see Dashboard Generation below)
|
||||
- TodoWrite initialization: Set up 4-phase tracking
|
||||
|
||||
**Phase 2: Planning Coordination**
|
||||
- Launch @cli-planning-agent with findings data and project context
|
||||
- Monitor planning progress (dashboard shows "Planning fixes..." indicator)
|
||||
- Validate fix-plan.json output (schema conformance, includes metadata with session status)
|
||||
- Load plan into memory for execution phase
|
||||
- TodoWrite update: Mark planning complete, start execution
|
||||
@@ -216,7 +213,6 @@ if (result.passRate < 100%) {
|
||||
- Assign agent IDs (agents update their fix-progress-{N}.json)
|
||||
- Handle agent failures gracefully (mark group as failed, continue)
|
||||
- Advance to next stage only when current stage complete
|
||||
- Dashboard polls and aggregates fix-progress-{N}.json files for display
|
||||
|
||||
**Phase 4: Completion & Aggregation**
|
||||
- Collect final status from all fix-progress-{N}.json files
|
||||
@@ -224,7 +220,7 @@ if (result.passRate < 100%) {
|
||||
- Update fix-history.json with new session entry
|
||||
- Remove active-fix-session.json
|
||||
- TodoWrite completion: Mark all phases done
|
||||
- Output summary to user with dashboard link
|
||||
- Output summary to user
|
||||
|
||||
**Phase 5: Session Completion (Optional)**
|
||||
- If all findings fixed successfully (no failures):
|
||||
@@ -234,53 +230,12 @@ if (result.passRate < 100%) {
|
||||
- Output: "Some findings failed. Review fix-summary.md before completing session."
|
||||
- Do NOT auto-complete session
|
||||
|
||||
### Dashboard Generation
|
||||
|
||||
**MANDATORY**: Dashboard MUST be generated from template during Phase 1 initialization
|
||||
|
||||
**Template Location**: `~/.claude/templates/fix-dashboard.html`
|
||||
|
||||
**⚠️ POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify fix-dashboard.html after creation
|
||||
|
||||
**Generation Steps**:
|
||||
|
||||
```bash
|
||||
# 1. Copy template to fix session directory
|
||||
cp ~/.claude/templates/fix-dashboard.html ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
||||
|
||||
# 2. Replace SESSION_ID placeholder
|
||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
||||
|
||||
# 3. Replace REVIEW_DIR placeholder
|
||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/fixes/${fixSessionId}/fix-dashboard.html
|
||||
|
||||
# 4. Start local server and output dashboard URL
|
||||
cd ${sessionDir}/fixes/${fixSessionId} && python -m http.server 8766 --bind 127.0.0.1 &
|
||||
echo "🔧 Fix Dashboard: http://127.0.0.1:8766/fix-dashboard.html"
|
||||
echo " (Press Ctrl+C to stop server when done)"
|
||||
```
|
||||
|
||||
**Dashboard Features**:
|
||||
- Real-time progress tracking via JSON polling (3-second interval)
|
||||
- Stage timeline visualization with parallel/serial execution modes
|
||||
- Active groups and agents monitoring
|
||||
- Flow control steps tracking for each agent
|
||||
- Fix history drawer with session summaries
|
||||
- Consumes new JSON structure (fix-plan.json with metadata + fix-progress-{N}.json)
|
||||
|
||||
**JSON Consumption**:
|
||||
- `fix-plan.json`: Reads metadata field for session info, timeline stages, groups configuration
|
||||
- `fix-progress-{N}.json`: Polls all progress files to aggregate real-time status
|
||||
- `active-fix-session.json`: Detects active session on load
|
||||
- `fix-history.json`: Loads historical fix sessions
|
||||
|
||||
### Output File Structure
|
||||
|
||||
```
|
||||
.workflow/active/WFS-{session-id}/.review/
|
||||
├── fix-export-{timestamp}.json # Exported findings (input)
|
||||
└── fixes/{fix-session-id}/
|
||||
├── fix-dashboard.html # Interactive dashboard (generated once, auto-polls JSON)
|
||||
├── fix-plan.json # Planning agent output (execution plan with metadata)
|
||||
├── fix-progress-1.json # Group 1 progress (planning agent init → agent updates)
|
||||
├── fix-progress-2.json # Group 2 progress (planning agent init → agent updates)
|
||||
@@ -291,10 +246,8 @@ echo " (Press Ctrl+C to stop server when done)"
|
||||
```
|
||||
|
||||
**File Producers**:
|
||||
- **Orchestrator**: `fix-dashboard.html` (generated once from template during Phase 1)
|
||||
- **Planning Agent**: `fix-plan.json` (with metadata), all `fix-progress-*.json` (initial state)
|
||||
- **Execution Agents**: Update assigned `fix-progress-{N}.json` in real-time
|
||||
- **Dashboard (Browser)**: Reads `fix-plan.json` + all `fix-progress-*.json`, aggregates in-memory every 3 seconds via JavaScript polling
|
||||
|
||||
|
||||
### Agent Invocation Template
|
||||
@@ -347,7 +300,7 @@ For each group (G1, G2, G3, ...), generate fix-progress-{N}.json following templ
|
||||
- Flow control: Empty implementation_approach array
|
||||
- Errors: Empty array
|
||||
|
||||
**CRITICAL**: Ensure complete template structure for Dashboard consumption - all fields must be present.
|
||||
**CRITICAL**: Ensure complete template structure - all fields must be present.
|
||||
|
||||
## Analysis Requirements
|
||||
|
||||
@@ -419,7 +372,7 @@ Task({
|
||||
description: `Fix ${group.findings.length} issues: ${group.group_name}`,
|
||||
prompt: `
|
||||
## Task Objective
|
||||
Execute fixes for code review findings in group ${group.group_id}. Update progress file in real-time with flow control tracking for dashboard visibility.
|
||||
Execute fixes for code review findings in group ${group.group_id}. Update progress file in real-time with flow control tracking.
|
||||
|
||||
## Assignment
|
||||
- Group ID: ${group.group_id}
|
||||
@@ -549,7 +502,6 @@ When all findings processed:
|
||||
|
||||
### Progress File Updates
|
||||
- **MUST update after every significant action** (before/after each step)
|
||||
- **Dashboard polls every 3 seconds** - ensure writes are atomic
|
||||
- **Always maintain complete structure** - never write partial updates
|
||||
- **Use ISO 8601 timestamps** - e.g., "2025-01-25T14:36:00Z"
|
||||
|
||||
@@ -638,9 +590,17 @@ TodoWrite({
|
||||
1. **Trust AI Planning**: Planning agent's grouping and execution strategy are based on dependency analysis
|
||||
2. **Conservative Approach**: Test verification is mandatory - no fixes kept without passing tests
|
||||
3. **Parallel Efficiency**: Default 3 concurrent agents balances speed and resource usage
|
||||
4. **Monitor Dashboard**: Real-time stage timeline and agent status provide execution visibility
|
||||
5. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
||||
6. **Manual Review**: Always review failed fixes manually - may require architectural changes
|
||||
7. **Incremental Fixing**: Start with small batches (5-10 findings) before large-scale fixes
|
||||
4. **Resume Support**: Fix sessions can resume from checkpoints after interruption
|
||||
5. **Manual Review**: Always review failed fixes manually - may require architectural changes
|
||||
6. **Incremental Fixing**: Start with small batches (5-10 findings) before large-scale fixes
|
||||
|
||||
## Related Commands
|
||||
|
||||
### View Fix Progress
|
||||
Use `ccw view` to open the workflow dashboard in browser:
|
||||
|
||||
```bash
|
||||
ccw view
|
||||
```
|
||||
|
||||
|
||||
|
||||
@@ -51,14 +51,12 @@ Independent multi-dimensional code review orchestrator with **hybrid parallel-it
|
||||
2. **Session-Integrated**: Review results tracked within workflow session for unified management
|
||||
3. **Comprehensive Coverage**: Same 7 specialized dimensions as session review
|
||||
4. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||
5. **Real-time Visibility**: JSON-based progress tracking with interactive HTML dashboard
|
||||
6. **Unified Archive**: Review results archived with session for historical reference
|
||||
5. **Unified Archive**: Review results archived with session for historical reference
|
||||
|
||||
### Orchestrator Boundary (CRITICAL)
|
||||
- **ONLY command** for independent multi-dimensional module review
|
||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
||||
- **⚠️ DASHBOARD CONSTRAINT**: Dashboard is generated ONCE during Phase 1 initialization. After initialization, orchestrator and agents MUST NOT read, write, or modify dashboard.html - it remains static for user interaction only.
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -66,7 +64,7 @@ Independent multi-dimensional code review orchestrator with **hybrid parallel-it
|
||||
|
||||
```
|
||||
Phase 1: Discovery & Initialization
|
||||
└─ Resolve file patterns, validate paths, initialize state, create output structure → Generate dashboard.html
|
||||
└─ Resolve file patterns, validate paths, initialize state, create output structure
|
||||
|
||||
Phase 2: Parallel Reviews (for each dimension)
|
||||
├─ Launch 7 review agents simultaneously
|
||||
@@ -90,7 +88,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
||||
└─ Loop until no critical findings OR max iterations
|
||||
|
||||
Phase 5: Completion
|
||||
└─ Finalize review-progress.json → Output dashboard path
|
||||
└─ Finalize review-progress.json
|
||||
```
|
||||
|
||||
### Agent Roles
|
||||
@@ -188,8 +186,8 @@ const CATEGORIES = {
|
||||
|
||||
**Step 1: Session Creation**
|
||||
```javascript
|
||||
// Create workflow session for this review
|
||||
SlashCommand(command="/workflow:session:start \"Code review for [target_pattern]\"")
|
||||
// Create workflow session for this review (type: review)
|
||||
SlashCommand(command="/workflow:session:start --type review \"Code review for [target_pattern]\"")
|
||||
|
||||
// Parse output
|
||||
const sessionId = output.match(/SESSION_ID: (WFS-[^\s]+)/)[1];
|
||||
@@ -219,37 +217,9 @@ done
|
||||
|
||||
**Step 4: Initialize Review State**
|
||||
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations, resolved_files (merged metadata + state)
|
||||
- Progress tracking: Create `review-progress.json` for dashboard polling
|
||||
- Progress tracking: Create `review-progress.json` for progress tracking
|
||||
|
||||
**Step 5: Dashboard Generation**
|
||||
|
||||
**Constraints**:
|
||||
- **MANDATORY**: Dashboard MUST be generated from template: `~/.claude/templates/review-cycle-dashboard.html`
|
||||
- **PROHIBITED**: Direct creation or custom generation without template
|
||||
- **POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify dashboard.html after creation
|
||||
|
||||
**Generation Commands** (3 independent steps):
|
||||
```bash
|
||||
# Step 1: Copy template to output location
|
||||
cp ~/.claude/templates/review-cycle-dashboard.html ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 2: Replace SESSION_ID placeholder
|
||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 3: Replace REVIEW_TYPE placeholder
|
||||
sed -i "s|{{REVIEW_TYPE}}|module|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 4: Replace REVIEW_DIR placeholder
|
||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Output: Start local server and output dashboard URL
|
||||
# Use Python HTTP server (available on most systems)
|
||||
cd ${sessionDir}/.review && python -m http.server 8765 --bind 127.0.0.1 &
|
||||
echo "📊 Dashboard: http://127.0.0.1:8765/dashboard.html"
|
||||
echo " (Press Ctrl+C to stop server when done)"
|
||||
```
|
||||
|
||||
**Step 6: TodoWrite Initialization**
|
||||
**Step 5: TodoWrite Initialization**
|
||||
- Set up progress tracking with hierarchical structure
|
||||
- Mark Phase 1 completed, Phase 2 in_progress
|
||||
|
||||
@@ -280,7 +250,6 @@ echo " (Press Ctrl+C to stop server when done)"
|
||||
- Finalize review-progress.json with completion statistics
|
||||
- Update review-state.json with completion_time and phase=complete
|
||||
- TodoWrite completion: Mark all tasks done
|
||||
- Output: Dashboard path to user
|
||||
|
||||
|
||||
|
||||
@@ -301,12 +270,11 @@ echo " (Press Ctrl+C to stop server when done)"
|
||||
├── iterations/ # Deep-dive results
|
||||
│ ├── iteration-1-finding-{uuid}.json
|
||||
│ └── iteration-2-finding-{uuid}.json
|
||||
├── reports/ # Human-readable reports
|
||||
│ ├── security-analysis.md
|
||||
│ ├── security-cli-output.txt
|
||||
│ ├── deep-dive-1-{uuid}.md
|
||||
│ └── ...
|
||||
└── dashboard.html # Interactive dashboard (primary output)
|
||||
└── reports/ # Human-readable reports
|
||||
├── security-analysis.md
|
||||
├── security-cli-output.txt
|
||||
├── deep-dive-1-{uuid}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Session Context**:
|
||||
@@ -772,23 +740,25 @@ TodoWrite({
|
||||
3. **Use Glob Wisely**: `src/auth/**` is more efficient than `src/**` with lots of irrelevant files
|
||||
4. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
||||
5. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
||||
6. **Dashboard Polling**: Refresh every 5 seconds for real-time updates
|
||||
7. **Export Results**: Use dashboard export for external tracking tools
|
||||
|
||||
## Related Commands
|
||||
|
||||
### View Review Progress
|
||||
Use `ccw view` to open the review dashboard in browser:
|
||||
|
||||
```bash
|
||||
ccw view
|
||||
```
|
||||
|
||||
### Automated Fix Workflow
|
||||
After completing a module review, use the dashboard to select findings and export them for automated fixing:
|
||||
After completing a module review, use the generated findings JSON for automated fixing:
|
||||
|
||||
```bash
|
||||
# Step 1: Complete review (this command)
|
||||
/workflow:review-module-cycle src/auth/**
|
||||
|
||||
# Step 2: Open dashboard, select findings, and export
|
||||
# Dashboard generates: fix-export-{timestamp}.json
|
||||
|
||||
# Step 3: Run automated fixes
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
||||
# Step 2: Run automated fixes using dimension findings
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||
```
|
||||
|
||||
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
||||
|
||||
@@ -45,13 +45,11 @@ Session-based multi-dimensional code review orchestrator with **hybrid parallel-
|
||||
1. **Comprehensive Coverage**: 7 specialized dimensions analyze all quality aspects simultaneously
|
||||
2. **Intelligent Prioritization**: Automatic identification of critical issues and cross-cutting concerns
|
||||
3. **Actionable Insights**: Deep-dive iterations provide step-by-step remediation plans
|
||||
4. **Real-time Visibility**: JSON-based progress tracking with interactive HTML dashboard
|
||||
|
||||
### Orchestrator Boundary (CRITICAL)
|
||||
- **ONLY command** for comprehensive multi-dimensional review
|
||||
- Manages: dimension coordination, aggregation, iteration control, progress tracking
|
||||
- Delegates: Code exploration and analysis to @cli-explore-agent, dimension-specific reviews via Deep Scan mode
|
||||
- **⚠️ DASHBOARD CONSTRAINT**: Dashboard is generated ONCE during Phase 1 initialization. After initialization, orchestrator and agents MUST NOT read, write, or modify dashboard.html - it remains static for user interaction only.
|
||||
|
||||
## How It Works
|
||||
|
||||
@@ -59,7 +57,7 @@ Session-based multi-dimensional code review orchestrator with **hybrid parallel-
|
||||
|
||||
```
|
||||
Phase 1: Discovery & Initialization
|
||||
└─ Validate session, initialize state, create output structure → Generate dashboard.html
|
||||
└─ Validate session, initialize state, create output structure
|
||||
|
||||
Phase 2: Parallel Reviews (for each dimension)
|
||||
├─ Launch 7 review agents simultaneously
|
||||
@@ -83,7 +81,7 @@ Phase 4: Iterative Deep-Dive (optional)
|
||||
└─ Loop until no critical findings OR max iterations
|
||||
|
||||
Phase 5: Completion
|
||||
└─ Finalize review-progress.json → Output dashboard path
|
||||
└─ Finalize review-progress.json
|
||||
```
|
||||
|
||||
### Agent Roles
|
||||
@@ -199,36 +197,9 @@ git log --since="${sessionCreatedAt}" --name-only --pretty=format: | sort -u
|
||||
|
||||
**Step 5: Initialize Review State**
|
||||
- State initialization: Create `review-state.json` with metadata, dimensions, max_iterations (merged metadata + state)
|
||||
- Progress tracking: Create `review-progress.json` for dashboard polling
|
||||
- Progress tracking: Create `review-progress.json` for progress tracking
|
||||
|
||||
**Step 6: Dashboard Generation**
|
||||
|
||||
**Constraints**:
|
||||
- **MANDATORY**: Dashboard MUST be generated from template: `~/.claude/templates/review-cycle-dashboard.html`
|
||||
- **PROHIBITED**: Direct creation or custom generation without template
|
||||
- **POST-GENERATION**: Orchestrator and agents MUST NOT read/write/modify dashboard.html after creation
|
||||
|
||||
**Generation Commands** (3 independent steps):
|
||||
```bash
|
||||
# Step 1: Copy template to output location
|
||||
cp ~/.claude/templates/review-cycle-dashboard.html ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 2: Replace SESSION_ID placeholder
|
||||
sed -i "s|{{SESSION_ID}}|${sessionId}|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 3: Replace REVIEW_TYPE placeholder
|
||||
sed -i "s|{{REVIEW_TYPE}}|session|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Step 4: Replace REVIEW_DIR placeholder
|
||||
sed -i "s|{{REVIEW_DIR}}|${reviewDir}|g" ${sessionDir}/.review/dashboard.html
|
||||
|
||||
# Output: Start local server and output dashboard URL
|
||||
cd ${sessionDir}/.review && python -m http.server 8765 --bind 127.0.0.1 &
|
||||
echo "📊 Dashboard: http://127.0.0.1:8765/dashboard.html"
|
||||
echo " (Press Ctrl+C to stop server when done)"
|
||||
```
|
||||
|
||||
**Step 7: TodoWrite Initialization**
|
||||
**Step 6: TodoWrite Initialization**
|
||||
- Set up progress tracking with hierarchical structure
|
||||
- Mark Phase 1 completed, Phase 2 in_progress
|
||||
|
||||
@@ -259,7 +230,6 @@ echo " (Press Ctrl+C to stop server when done)"
|
||||
- Finalize review-progress.json with completion statistics
|
||||
- Update review-state.json with completion_time and phase=complete
|
||||
- TodoWrite completion: Mark all tasks done
|
||||
- Output: Dashboard path to user
|
||||
|
||||
|
||||
|
||||
@@ -280,12 +250,11 @@ echo " (Press Ctrl+C to stop server when done)"
|
||||
├── iterations/ # Deep-dive results
|
||||
│ ├── iteration-1-finding-{uuid}.json
|
||||
│ └── iteration-2-finding-{uuid}.json
|
||||
├── reports/ # Human-readable reports
|
||||
│ ├── security-analysis.md
|
||||
│ ├── security-cli-output.txt
|
||||
│ ├── deep-dive-1-{uuid}.md
|
||||
│ └── ...
|
||||
└── dashboard.html # Interactive dashboard (primary output)
|
||||
└── reports/ # Human-readable reports
|
||||
├── security-analysis.md
|
||||
├── security-cli-output.txt
|
||||
├── deep-dive-1-{uuid}.md
|
||||
└── ...
|
||||
```
|
||||
|
||||
**Session Context**:
|
||||
@@ -782,23 +751,25 @@ TodoWrite({
|
||||
2. **Parallel Execution**: ~60 minutes for full initial review (7 dimensions)
|
||||
3. **Trust Aggregation Logic**: Auto-selection based on proven heuristics
|
||||
4. **Monitor Logs**: Check reports/ directory for CLI analysis insights
|
||||
5. **Dashboard Polling**: Refresh every 5 seconds for real-time updates
|
||||
6. **Export Results**: Use dashboard export for external tracking tools
|
||||
|
||||
## Related Commands
|
||||
|
||||
### View Review Progress
|
||||
Use `ccw view` to open the review dashboard in browser:
|
||||
|
||||
```bash
|
||||
ccw view
|
||||
```
|
||||
|
||||
### Automated Fix Workflow
|
||||
After completing a review, use the dashboard to select findings and export them for automated fixing:
|
||||
After completing a review, use the generated findings JSON for automated fixing:
|
||||
|
||||
```bash
|
||||
# Step 1: Complete review (this command)
|
||||
/workflow:review-session-cycle
|
||||
|
||||
# Step 2: Open dashboard, select findings, and export
|
||||
# Dashboard generates: fix-export-{timestamp}.json
|
||||
|
||||
# Step 3: Run automated fixes
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/fix-export-{timestamp}.json
|
||||
# Step 2: Run automated fixes using dimension findings
|
||||
/workflow:review-fix .workflow/active/WFS-{session-id}/.review/
|
||||
```
|
||||
|
||||
See `/workflow:review-fix` for automated fixing with smart grouping, parallel execution, and test verification.
|
||||
|
||||
@@ -1,11 +1,13 @@
|
||||
---
|
||||
name: start
|
||||
description: Discover existing sessions or start new workflow session with intelligent session management and conflict detection
|
||||
argument-hint: [--auto|--new] [optional: task description for new session]
|
||||
argument-hint: [--type <workflow|review|tdd|test|docs>] [--auto|--new] [optional: task description for new session]
|
||||
examples:
|
||||
- /workflow:session:start
|
||||
- /workflow:session:start --auto "implement OAuth2 authentication"
|
||||
- /workflow:session:start --new "fix login bug"
|
||||
- /workflow:session:start --type review "Code review for auth module"
|
||||
- /workflow:session:start --type tdd --auto "implement user authentication"
|
||||
- /workflow:session:start --type test --new "test payment flow"
|
||||
---
|
||||
|
||||
# Start Workflow Session (/workflow:session:start)
|
||||
@@ -17,6 +19,23 @@ Manages workflow sessions with three operation modes: discovery (manual), auto (
|
||||
1. **Project-level initialization** (first-time only): Creates `.workflow/project.json` for feature registry
|
||||
2. **Session-level initialization** (always): Creates session directory structure
|
||||
|
||||
## Session Types
|
||||
|
||||
The `--type` parameter classifies sessions for CCW dashboard organization:
|
||||
|
||||
| Type | Description | Default For |
|
||||
|------|-------------|-------------|
|
||||
| `workflow` | Standard implementation (default) | `/workflow:plan` |
|
||||
| `review` | Code review sessions | `/workflow:review-module-cycle` |
|
||||
| `tdd` | TDD-based development | `/workflow:tdd-plan` |
|
||||
| `test` | Test generation/fix sessions | `/workflow:test-fix-gen` |
|
||||
| `docs` | Documentation sessions | `/memory:docs` |
|
||||
|
||||
**Validation**: If `--type` is provided with invalid value, return error:
|
||||
```
|
||||
ERROR: Invalid session type. Valid types: workflow, review, tdd, test, docs
|
||||
```
|
||||
|
||||
## Step 0: Initialize Project State (First-time Only)
|
||||
|
||||
**Executed before all modes** - Ensures project-level state file exists by calling `/workflow:init`.
|
||||
@@ -86,8 +105,8 @@ bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.process)
|
||||
bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.task)
|
||||
bash(mkdir -p .workflow/active/WFS-implement-oauth2-auth/.summaries)
|
||||
|
||||
# Create metadata
|
||||
bash(echo '{"session_id":"WFS-implement-oauth2-auth","project":"implement OAuth2 auth","status":"planning"}' > .workflow/active/WFS-implement-oauth2-auth/workflow-session.json)
|
||||
# Create metadata (include type field, default to "workflow" if not specified)
|
||||
bash(echo '{"session_id":"WFS-implement-oauth2-auth","project":"implement OAuth2 auth","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-implement-oauth2-auth/workflow-session.json)
|
||||
```
|
||||
|
||||
**Output**: `SESSION_ID: WFS-implement-oauth2-auth`
|
||||
@@ -143,7 +162,8 @@ bash(mkdir -p .workflow/active/WFS-fix-login-bug/.summaries)
|
||||
|
||||
### Step 3: Create Metadata
|
||||
```bash
|
||||
bash(echo '{"session_id":"WFS-fix-login-bug","project":"fix login bug","status":"planning"}' > .workflow/active/WFS-fix-login-bug/workflow-session.json)
|
||||
# Include type field from --type parameter (default: "workflow")
|
||||
bash(echo '{"session_id":"WFS-fix-login-bug","project":"fix login bug","status":"planning","type":"workflow","created_at":"2024-12-04T08:00:00Z"}' > .workflow/active/WFS-fix-login-bug/workflow-session.json)
|
||||
```
|
||||
|
||||
**Output**: `SESSION_ID: WFS-fix-login-bug`
|
||||
|
||||
@@ -1,352 +0,0 @@
|
||||
---
|
||||
name: workflow:status
|
||||
description: Generate on-demand views for project overview and workflow tasks with optional task-id filtering for detailed view
|
||||
argument-hint: "[optional: --project|task-id|--validate|--dashboard]"
|
||||
---
|
||||
|
||||
# Workflow Status Command (/workflow:status)
|
||||
|
||||
## Overview
|
||||
Generates on-demand views from project and session data. Supports multiple modes:
|
||||
1. **Project Overview** (`--project`): Shows completed features and project statistics
|
||||
2. **Workflow Tasks** (default): Shows current session task progress
|
||||
3. **HTML Dashboard** (`--dashboard`): Generates interactive HTML task board with active and archived sessions
|
||||
|
||||
No synchronization needed - all views are calculated from current JSON state.
|
||||
|
||||
## Usage
|
||||
```bash
|
||||
/workflow:status # Show current workflow session overview
|
||||
/workflow:status --project # Show project-level feature registry
|
||||
/workflow:status impl-1 # Show specific task details
|
||||
/workflow:status --validate # Validate workflow integrity
|
||||
/workflow:status --dashboard # Generate HTML dashboard board
|
||||
```
|
||||
|
||||
## Execution Process
|
||||
|
||||
```
|
||||
Input Parsing:
|
||||
└─ Decision (mode detection):
|
||||
├─ --project flag → Project Overview Mode
|
||||
├─ --dashboard flag → Dashboard Mode
|
||||
├─ task-id argument → Task Details Mode
|
||||
└─ No flags → Workflow Session Mode (default)
|
||||
|
||||
Project Overview Mode:
|
||||
├─ Check project.json exists
|
||||
├─ Read project data
|
||||
├─ Parse and display overview + features
|
||||
└─ Show recent archived sessions
|
||||
|
||||
Workflow Session Mode (default):
|
||||
├─ Find active session
|
||||
├─ Load session data
|
||||
├─ Scan task files
|
||||
└─ Display task progress
|
||||
|
||||
Dashboard Mode:
|
||||
├─ Collect active sessions
|
||||
├─ Collect archived sessions
|
||||
├─ Generate HTML from template
|
||||
└─ Write dashboard.html
|
||||
```
|
||||
|
||||
## Implementation Flow
|
||||
|
||||
### Mode Selection
|
||||
|
||||
**Check for --project flag**:
|
||||
- If `--project` flag present → Execute **Project Overview Mode**
|
||||
- Otherwise → Execute **Workflow Session Mode** (default)
|
||||
|
||||
## Project Overview Mode
|
||||
|
||||
### Step 1: Check Project State
|
||||
```bash
|
||||
bash(test -f .workflow/project.json && echo "EXISTS" || echo "NOT_FOUND")
|
||||
```
|
||||
|
||||
**If NOT_FOUND**:
|
||||
```
|
||||
No project state found.
|
||||
Run /workflow:session:start to initialize project.
|
||||
```
|
||||
|
||||
### Step 2: Read Project Data
|
||||
```bash
|
||||
bash(cat .workflow/project.json)
|
||||
```
|
||||
|
||||
### Step 3: Parse and Display
|
||||
|
||||
**Data Processing**:
|
||||
```javascript
|
||||
const projectData = JSON.parse(Read('.workflow/project.json'));
|
||||
const features = projectData.features || [];
|
||||
const stats = projectData.statistics || {};
|
||||
const overview = projectData.overview || null;
|
||||
|
||||
// Sort features by implementation date (newest first)
|
||||
const sortedFeatures = features.sort((a, b) =>
|
||||
new Date(b.implemented_at) - new Date(a.implemented_at)
|
||||
);
|
||||
```
|
||||
|
||||
**Output Format** (with extended overview):
|
||||
```
|
||||
## Project: ${projectData.project_name}
|
||||
Initialized: ${projectData.initialized_at}
|
||||
|
||||
${overview ? `
|
||||
### Overview
|
||||
${overview.description}
|
||||
|
||||
**Technology Stack**:
|
||||
${overview.technology_stack.languages.map(l => `- ${l.name}${l.primary ? ' (primary)' : ''}: ${l.file_count} files`).join('\n')}
|
||||
Frameworks: ${overview.technology_stack.frameworks.join(', ')}
|
||||
|
||||
**Architecture**:
|
||||
Style: ${overview.architecture.style}
|
||||
Patterns: ${overview.architecture.patterns.join(', ')}
|
||||
|
||||
**Key Components** (${overview.key_components.length}):
|
||||
${overview.key_components.map(c => `- ${c.name} (${c.path})\n ${c.description}`).join('\n')}
|
||||
|
||||
---
|
||||
` : ''}
|
||||
|
||||
### Completed Features (${stats.total_features})
|
||||
|
||||
${sortedFeatures.map(f => `
|
||||
- ${f.title} (${f.timeline?.implemented_at || f.implemented_at})
|
||||
${f.description}
|
||||
Tags: ${f.tags?.join(', ') || 'none'}
|
||||
Session: ${f.traceability?.session_id || f.session_id}
|
||||
Archive: ${f.traceability?.archive_path || 'unknown'}
|
||||
${f.traceability?.commit_hash ? `Commit: ${f.traceability.commit_hash}` : ''}
|
||||
`).join('\n')}
|
||||
|
||||
### Project Statistics
|
||||
- Total Features: ${stats.total_features}
|
||||
- Total Sessions: ${stats.total_sessions}
|
||||
- Last Updated: ${stats.last_updated}
|
||||
|
||||
### Quick Access
|
||||
- View session details: /workflow:status
|
||||
- Archive query: jq '.archives[] | select(.session_id == "SESSION_ID")' .workflow/archives/manifest.json
|
||||
- Documentation: .workflow/docs/${projectData.project_name}/
|
||||
|
||||
### Query Commands
|
||||
# Find by tag
|
||||
cat .workflow/project.json | jq '.features[] | select(.tags[] == "auth")'
|
||||
|
||||
# View archive
|
||||
cat ${feature.traceability.archive_path}/IMPL_PLAN.md
|
||||
|
||||
# List all tags
|
||||
cat .workflow/project.json | jq -r '.features[].tags[]' | sort -u
|
||||
```
|
||||
|
||||
**Empty State**:
|
||||
```
|
||||
## Project: ${projectData.project_name}
|
||||
Initialized: ${projectData.initialized_at}
|
||||
|
||||
No features completed yet.
|
||||
|
||||
Complete your first workflow session to add features:
|
||||
1. /workflow:plan "feature description"
|
||||
2. /workflow:execute
|
||||
3. /workflow:session:complete
|
||||
```
|
||||
|
||||
### Step 4: Show Recent Sessions (Optional)
|
||||
|
||||
```bash
|
||||
# List 5 most recent archived sessions
|
||||
bash(ls -1t .workflow/archives/WFS-* 2>/dev/null | head -5 | xargs -I {} basename {})
|
||||
```
|
||||
|
||||
**Output**:
|
||||
```
|
||||
### Recent Sessions
|
||||
- WFS-auth-system (archived)
|
||||
- WFS-payment-flow (archived)
|
||||
- WFS-user-dashboard (archived)
|
||||
|
||||
Use /workflow:session:complete to archive current session.
|
||||
```
|
||||
|
||||
## Workflow Session Mode (Default)
|
||||
|
||||
### Step 1: Find Active Session
|
||||
```bash
|
||||
find .workflow/active/ -name "WFS-*" -type d 2>/dev/null | head -1
|
||||
```
|
||||
|
||||
### Step 2: Load Session Data
|
||||
```bash
|
||||
cat .workflow/active/WFS-session/workflow-session.json
|
||||
```
|
||||
|
||||
### Step 3: Scan Task Files
|
||||
```bash
|
||||
find .workflow/active/WFS-session/.task/ -name "*.json" -type f 2>/dev/null
|
||||
```
|
||||
|
||||
### Step 4: Generate Task Status
|
||||
```bash
|
||||
cat .workflow/active/WFS-session/.task/impl-1.json | jq -r '.status'
|
||||
```
|
||||
|
||||
### Step 5: Count Task Progress
|
||||
```bash
|
||||
find .workflow/active/WFS-session/.task/ -name "*.json" -type f | wc -l
|
||||
find .workflow/active/WFS-session/.summaries/ -name "*.md" -type f 2>/dev/null | wc -l
|
||||
```
|
||||
|
||||
### Step 6: Display Overview
|
||||
```markdown
|
||||
# Workflow Overview
|
||||
**Session**: WFS-session-name
|
||||
**Progress**: 3/8 tasks completed
|
||||
|
||||
## Active Tasks
|
||||
- [IN PROGRESS] impl-1: Current task in progress
|
||||
- [ ] impl-2: Next pending task
|
||||
|
||||
## Completed Tasks
|
||||
- [COMPLETED] impl-0: Setup completed
|
||||
```
|
||||
|
||||
## Dashboard Mode (HTML Board)
|
||||
|
||||
### Step 1: Check for --dashboard flag
|
||||
```bash
|
||||
# If --dashboard flag present → Execute Dashboard Mode
|
||||
```
|
||||
|
||||
### Step 2: Collect Workflow Data
|
||||
|
||||
**Collect Active Sessions**:
|
||||
```bash
|
||||
# Find all active sessions
|
||||
find .workflow/active/ -name "WFS-*" -type d 2>/dev/null
|
||||
|
||||
# For each active session, read metadata and tasks
|
||||
for session in $(find .workflow/active/ -name "WFS-*" -type d 2>/dev/null); do
|
||||
cat "$session/workflow-session.json"
|
||||
find "$session/.task/" -name "*.json" -type f 2>/dev/null
|
||||
done
|
||||
```
|
||||
|
||||
**Collect Archived Sessions**:
|
||||
```bash
|
||||
# Find all archived sessions
|
||||
find .workflow/archives/ -name "WFS-*" -type d 2>/dev/null
|
||||
|
||||
# Read manifest if exists
|
||||
cat .workflow/archives/manifest.json 2>/dev/null
|
||||
|
||||
# For each archived session, read metadata
|
||||
for archive in $(find .workflow/archives/ -name "WFS-*" -type d 2>/dev/null); do
|
||||
cat "$archive/workflow-session.json" 2>/dev/null
|
||||
# Count completed tasks
|
||||
find "$archive/.task/" -name "*.json" -type f 2>/dev/null | wc -l
|
||||
done
|
||||
```
|
||||
|
||||
### Step 3: Process and Structure Data
|
||||
|
||||
**Build data structure for dashboard**:
|
||||
```javascript
|
||||
const dashboardData = {
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
generatedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
// Process active sessions
|
||||
for each active_session in active_sessions:
|
||||
const sessionData = JSON.parse(Read(active_session/workflow-session.json));
|
||||
const tasks = [];
|
||||
|
||||
// Load all tasks for this session
|
||||
for each task_file in find(active_session/.task/*.json):
|
||||
const taskData = JSON.parse(Read(task_file));
|
||||
tasks.push({
|
||||
task_id: taskData.task_id,
|
||||
title: taskData.title,
|
||||
status: taskData.status,
|
||||
type: taskData.type
|
||||
});
|
||||
|
||||
dashboardData.activeSessions.push({
|
||||
session_id: sessionData.session_id,
|
||||
project: sessionData.project,
|
||||
status: sessionData.status,
|
||||
created_at: sessionData.created_at || sessionData.initialized_at,
|
||||
tasks: tasks
|
||||
});
|
||||
|
||||
// Process archived sessions
|
||||
for each archived_session in archived_sessions:
|
||||
const sessionData = JSON.parse(Read(archived_session/workflow-session.json));
|
||||
const taskCount = bash(find archived_session/.task/*.json | wc -l);
|
||||
|
||||
dashboardData.archivedSessions.push({
|
||||
session_id: sessionData.session_id,
|
||||
project: sessionData.project,
|
||||
archived_at: sessionData.completed_at || sessionData.archived_at,
|
||||
taskCount: parseInt(taskCount),
|
||||
archive_path: archived_session
|
||||
});
|
||||
```
|
||||
|
||||
### Step 4: Generate HTML from Template
|
||||
|
||||
**Load template and inject data**:
|
||||
```javascript
|
||||
// Read the HTML template
|
||||
const template = Read("~/.claude/templates/workflow-dashboard.html");
|
||||
|
||||
// Prepare data for injection
|
||||
const dataJson = JSON.stringify(dashboardData, null, 2);
|
||||
|
||||
// Replace placeholder with actual data
|
||||
const htmlContent = template.replace('{{WORKFLOW_DATA}}', dataJson);
|
||||
|
||||
// Ensure .workflow directory exists
|
||||
bash(mkdir -p .workflow);
|
||||
```
|
||||
|
||||
### Step 5: Write HTML File
|
||||
|
||||
```bash
|
||||
# Write the generated HTML to .workflow/dashboard.html
|
||||
Write({
|
||||
file_path: ".workflow/dashboard.html",
|
||||
content: htmlContent
|
||||
})
|
||||
```
|
||||
|
||||
### Step 6: Display Success Message
|
||||
|
||||
```markdown
|
||||
Dashboard generated successfully!
|
||||
|
||||
Location: .workflow/dashboard.html
|
||||
|
||||
Open in browser:
|
||||
file://$(pwd)/.workflow/dashboard.html
|
||||
|
||||
Features:
|
||||
- 📊 Active sessions overview
|
||||
- 📦 Archived sessions history
|
||||
- 🔍 Search and filter
|
||||
- 📈 Progress tracking
|
||||
- 🎨 Dark/light theme
|
||||
|
||||
Refresh data: Re-run /workflow:status --dashboard
|
||||
```
|
||||
@@ -44,7 +44,7 @@ allowed-tools: SlashCommand(*), TodoWrite(*), Read(*), Bash(*)
|
||||
**Step 1.1: Dispatch** - Session discovery and initialization
|
||||
|
||||
```javascript
|
||||
SlashCommand(command="/workflow:session:start --auto \"TDD: [structured-description]\"")
|
||||
SlashCommand(command="/workflow:session:start --type tdd --auto \"TDD: [structured-description]\"")
|
||||
```
|
||||
|
||||
**TDD Structured Format**:
|
||||
|
||||
@@ -159,19 +159,19 @@ Read(".workflow/active/[sourceSessionId]/.process/context-package.json")
|
||||
|
||||
```javascript
|
||||
// Session Mode - Include original task description to enable semantic CLI selection
|
||||
SlashCommand(command="/workflow:session:start --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
SlashCommand(command="/workflow:session:start --type test --new \"Test validation for [sourceSessionId]: [originalTaskDescription]\"")
|
||||
|
||||
// Prompt Mode - User's description already contains their intent
|
||||
SlashCommand(command="/workflow:session:start --new \"Test generation for: [description]\"")
|
||||
SlashCommand(command="/workflow:session:start --type test --new \"Test generation for: [description]\"")
|
||||
```
|
||||
|
||||
**Input**: User argument (session ID, description, or file path)
|
||||
|
||||
**Expected Behavior**:
|
||||
- Creates new session: `WFS-test-[slug]`
|
||||
- Writes `workflow-session.json` metadata:
|
||||
- **Session Mode**: Includes `workflow_type: "test_session"`, `source_session_id: "[sourceId]"`, description with original user intent
|
||||
- **Prompt Mode**: Includes `workflow_type: "test_session"` only (user's description already contains intent)
|
||||
- Writes `workflow-session.json` metadata with `type: "test"`
|
||||
- **Session Mode**: Additionally includes `source_session_id: "[sourceId]"`, description with original user intent
|
||||
- **Prompt Mode**: Uses user's description (already contains intent)
|
||||
- Returns new session ID
|
||||
|
||||
**Parse Output**:
|
||||
@@ -579,11 +579,11 @@ WFS-test-[session]/
|
||||
**File**: `workflow-session.json`
|
||||
|
||||
**Session Mode** includes:
|
||||
- `workflow_type: "test_session"`
|
||||
- `type: "test"` (set by session:start --type test)
|
||||
- `source_session_id: "[sourceSessionId]"` (enables automatic cross-session context)
|
||||
|
||||
**Prompt Mode** includes:
|
||||
- `workflow_type: "test_session"`
|
||||
- `type: "test"` (set by session:start --type test)
|
||||
- No `source_session_id` field
|
||||
|
||||
### Execution Flow Diagram
|
||||
|
||||
@@ -135,7 +135,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
|
||||
## MANDATORY FIRST STEPS (Execute by Agent)
|
||||
**You (cli-explore-agent) MUST execute these steps in order:**
|
||||
1. Run: ~/.claude/scripts/get_modules_by_depth.sh (project structure)
|
||||
1. Run: ccw tool exec get_modules_by_depth '{}' (project structure)
|
||||
2. Run: rg -l "{keyword_from_task}" --type ts (locate relevant files)
|
||||
3. Execute: cat ~/.claude/workflows/cli-templates/schemas/explore-json-schema.json (get output schema reference)
|
||||
|
||||
@@ -171,7 +171,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- dependencies: Dependencies relevant to ${angle}
|
||||
- integration_points: Where to integrate from ${angle} viewpoint (include file:line locations)
|
||||
- constraints: ${angle}-specific limitations/conventions
|
||||
- clarification_needs: ${angle}-related ambiguities (with options array)
|
||||
- clarification_needs: ${angle}-related ambiguities (options array + recommended index)
|
||||
- _metadata.exploration_angle: "${angle}"
|
||||
|
||||
## Success Criteria
|
||||
@@ -182,7 +182,7 @@ Execute **${angle}** exploration for task planning context. Analyze codebase fro
|
||||
- [ ] Integration points include file:line locations
|
||||
- [ ] Constraints are project-specific to ${angle}
|
||||
- [ ] JSON output follows schema exactly
|
||||
- [ ] clarification_needs includes options array
|
||||
- [ ] clarification_needs includes options + recommended
|
||||
|
||||
## Output
|
||||
Write: ${sessionFolder}/exploration-${angle}.json
|
||||
@@ -251,7 +251,7 @@ Execute all discovery tracks:
|
||||
### Phase 3: Synthesis, Assessment & Packaging
|
||||
1. Apply relevance scoring and build dependency graph
|
||||
2. **Synthesize 4-source data**: Merge findings from all sources (archive > docs > code > web). **Prioritize the context from `project.json`** for architecture and tech stack unless code analysis reveals it's outdated.
|
||||
3. **Populate `project_context`**: Directly use the `overview` from `project.json` to fill the `project_context` section of the output `context-package.json`. Include technology_stack, architecture, key_components, and entry_points.
|
||||
3. **Populate `project_context`**: Directly use the `overview` from `project.json` to fill the `project_context` section of the output `context-package.json`. Include description, technology_stack, architecture, and key_components.
|
||||
4. Integrate brainstorm artifacts (if .brainstorming/ exists, read content)
|
||||
5. Perform conflict detection with risk assessment
|
||||
6. **Inject historical conflicts** from archive analysis into conflict_detection
|
||||
@@ -260,7 +260,7 @@ Execute all discovery tracks:
|
||||
## Output Requirements
|
||||
Complete context-package.json with:
|
||||
- **metadata**: task_description, keywords, complexity, tech_stack, session_id
|
||||
- **project_context**: architecture_patterns, coding_conventions, tech_stack (sourced from `project.json` overview)
|
||||
- **project_context**: description, technology_stack, architecture, key_components (sourced from `project.json` overview)
|
||||
- **assets**: {documentation[], source_code[], config[], tests[]} with relevance scores
|
||||
- **dependencies**: {internal[], external[]} with dependency graph
|
||||
- **brainstorm_artifacts**: {guidance_specification, role_analyses[], synthesis_output} with content
|
||||
|
||||
@@ -14,8 +14,8 @@ Generate implementation planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.
|
||||
## Core Philosophy
|
||||
- **Planning Only**: Generate planning documents (IMPL_PLAN.md, task JSONs, TODO_LIST.md) - does NOT implement code
|
||||
- **Agent-Driven Document Generation**: Delegate plan generation to action-planning-agent
|
||||
- **N+1 Parallel Planning**: Auto-detect multi-module projects, enable parallel planning (2+1 or 3+1 mode)
|
||||
- **Progressive Loading**: Load context incrementally (Core → Selective → On-Demand) due to analysis.md file size
|
||||
- **Two-Phase Flow**: Discovery (context gathering) → Output (planning document generation)
|
||||
- **Memory-First**: Reuse loaded documents from conversation memory
|
||||
- **Smart Selection**: Load synthesis_output OR guidance + relevant role analyses, NOT all role analyses
|
||||
- **MCP-Enhanced**: Use MCP tools for advanced code analysis and research
|
||||
@@ -28,22 +28,38 @@ Input Parsing:
|
||||
├─ Parse flags: --session
|
||||
└─ Validation: session_id REQUIRED
|
||||
|
||||
Phase 1: Context Preparation (Command)
|
||||
Phase 1: Context Preparation & Module Detection (Command)
|
||||
├─ Assemble session paths (metadata, context package, output dirs)
|
||||
└─ Provide metadata (session_id, execution_mode, mcp_capabilities)
|
||||
├─ Provide metadata (session_id, execution_mode, mcp_capabilities)
|
||||
├─ Auto-detect modules from context-package + directory structure
|
||||
└─ Decision:
|
||||
├─ modules.length == 1 → Single Agent Mode (Phase 2A)
|
||||
└─ modules.length >= 2 → Parallel Mode (Phase 2B + Phase 3)
|
||||
|
||||
Phase 2: Planning Document Generation (Agent)
|
||||
Phase 2A: Single Agent Planning (Original Flow)
|
||||
├─ Load context package (progressive loading strategy)
|
||||
├─ Generate Task JSON Files (.task/IMPL-*.json)
|
||||
├─ Create IMPL_PLAN.md
|
||||
└─ Generate TODO_LIST.md
|
||||
|
||||
Phase 2B: N Parallel Planning (Multi-Module)
|
||||
├─ Launch N action-planning-agents simultaneously (one per module)
|
||||
├─ Each agent generates module-scoped tasks (IMPL-{prefix}{seq}.json)
|
||||
├─ Task ID format: IMPL-A1, IMPL-A2... / IMPL-B1, IMPL-B2...
|
||||
└─ Each module limited to ≤9 tasks
|
||||
|
||||
Phase 3: Integration (+1 Coordinator, Multi-Module Only)
|
||||
├─ Collect all module task JSONs
|
||||
├─ Resolve cross-module dependencies (CROSS::{module}::{pattern} → actual ID)
|
||||
├─ Generate unified IMPL_PLAN.md (grouped by module)
|
||||
└─ Generate TODO_LIST.md (hierarchical: module → tasks)
|
||||
```
|
||||
|
||||
## Document Generation Lifecycle
|
||||
|
||||
### Phase 1: Context Preparation (Command Responsibility)
|
||||
### Phase 1: Context Preparation & Module Detection (Command Responsibility)
|
||||
|
||||
**Command prepares session paths and metadata for planning document generation.**
|
||||
**Command prepares session paths, metadata, and detects module structure.**
|
||||
|
||||
**Session Path Structure**:
|
||||
```
|
||||
@@ -52,8 +68,12 @@ Phase 2: Planning Document Generation (Agent)
|
||||
├── .process/
|
||||
│ └── context-package.json # Context package with artifact catalog
|
||||
├── .task/ # Output: Task JSON files
|
||||
├── IMPL_PLAN.md # Output: Implementation plan
|
||||
└── TODO_LIST.md # Output: TODO list
|
||||
│ ├── IMPL-A1.json # Multi-module: prefixed by module
|
||||
│ ├── IMPL-A2.json
|
||||
│ ├── IMPL-B1.json
|
||||
│ └── ...
|
||||
├── IMPL_PLAN.md # Output: Implementation plan (grouped by module)
|
||||
└── TODO_LIST.md # Output: TODO list (hierarchical)
|
||||
```
|
||||
|
||||
**Command Preparation**:
|
||||
@@ -66,9 +86,40 @@ Phase 2: Planning Document Generation (Agent)
|
||||
- `session_id`
|
||||
- `mcp_capabilities` (available MCP tools)
|
||||
|
||||
3. **Auto Module Detection** (determines single vs parallel mode):
|
||||
```javascript
|
||||
function autoDetectModules(contextPackage, projectRoot) {
|
||||
// Priority 1: Explicit frontend/backend separation
|
||||
if (exists('src/frontend') && exists('src/backend')) {
|
||||
return [
|
||||
{ name: 'frontend', prefix: 'A', paths: ['src/frontend'] },
|
||||
{ name: 'backend', prefix: 'B', paths: ['src/backend'] }
|
||||
];
|
||||
}
|
||||
|
||||
// Priority 2: Monorepo structure
|
||||
if (exists('packages/*') || exists('apps/*')) {
|
||||
return detectMonorepoModules(); // Returns 2-3 main packages
|
||||
}
|
||||
|
||||
// Priority 3: Context-package dependency clustering
|
||||
const modules = clusterByDependencies(contextPackage.dependencies?.internal);
|
||||
if (modules.length >= 2) return modules.slice(0, 3);
|
||||
|
||||
// Default: Single module (original flow)
|
||||
return [{ name: 'main', prefix: '', paths: ['.'] }];
|
||||
}
|
||||
```
|
||||
|
||||
**Decision Logic**:
|
||||
- `modules.length == 1` → Phase 2A (Single Agent, original flow)
|
||||
- `modules.length >= 2` → Phase 2B + Phase 3 (N+1 Parallel)
|
||||
|
||||
**Note**: CLI tool usage is now determined semantically by action-planning-agent based on user's task description, not by flags.
|
||||
|
||||
### Phase 2: Planning Document Generation (Agent Responsibility)
|
||||
### Phase 2A: Single Agent Planning (Original Flow)
|
||||
|
||||
**Condition**: `modules.length == 1` (no multi-module detected)
|
||||
|
||||
**Purpose**: Generate IMPL_PLAN.md, task JSONs, and TODO_LIST.md - planning documents only, NOT code implementation.
|
||||
|
||||
@@ -132,7 +183,7 @@ Determine CLI tool usage per-step based on user's task description:
|
||||
|
||||
## QUALITY STANDARDS
|
||||
Hard Constraints:
|
||||
- Task count <= 12 (hard limit - request re-scope if exceeded)
|
||||
- Task count <= 18 (hard limit - request re-scope if exceeded)
|
||||
- All requirements quantified (explicit counts and enumerated lists)
|
||||
- Acceptance criteria measurable (include verification commands)
|
||||
- Artifact references mapped from context package
|
||||
@@ -148,4 +199,93 @@ Hard Constraints:
|
||||
)
|
||||
```
|
||||
|
||||
、
|
||||
### Phase 2B: N Parallel Planning (Multi-Module)
|
||||
|
||||
**Condition**: `modules.length >= 2` (multi-module detected)
|
||||
|
||||
**Purpose**: Launch N action-planning-agents simultaneously, one per module, for parallel task generation.
|
||||
|
||||
**Parallel Agent Invocation**:
|
||||
```javascript
|
||||
// Launch N agents in parallel (one per module)
|
||||
const planningTasks = modules.map(module =>
|
||||
Task(
|
||||
subagent_type="action-planning-agent",
|
||||
description=`Plan ${module.name} module`,
|
||||
prompt=`
|
||||
## SCOPE
|
||||
- Module: ${module.name} (${module.type})
|
||||
- Focus Paths: ${module.paths.join(', ')}
|
||||
- Task ID Prefix: IMPL-${module.prefix}
|
||||
- Task Limit: ≤9 tasks
|
||||
- Other Modules: ${otherModules.join(', ')}
|
||||
- Cross-module deps format: "CROSS::{module}::{pattern}"
|
||||
|
||||
## SESSION PATHS
|
||||
Input:
|
||||
- Context Package: .workflow/active/{session-id}/.process/context-package.json
|
||||
Output:
|
||||
- Task Dir: .workflow/active/{session-id}/.task/
|
||||
|
||||
## INSTRUCTIONS
|
||||
- Generate tasks ONLY for ${module.name} module
|
||||
- Use task ID format: IMPL-${module.prefix}1, IMPL-${module.prefix}2, ...
|
||||
- For cross-module dependencies, use: depends_on: ["CROSS::B::api-endpoint"]
|
||||
- Maximum 9 tasks per module
|
||||
`
|
||||
)
|
||||
);
|
||||
|
||||
// Execute all in parallel
|
||||
await Promise.all(planningTasks);
|
||||
```
|
||||
|
||||
**Output Structure** (direct to .task/):
|
||||
```
|
||||
.task/
|
||||
├── IMPL-A1.json # Module A (e.g., frontend)
|
||||
├── IMPL-A2.json
|
||||
├── IMPL-B1.json # Module B (e.g., backend)
|
||||
├── IMPL-B2.json
|
||||
└── IMPL-C1.json # Module C (e.g., shared)
|
||||
```
|
||||
|
||||
**Task ID Naming**:
|
||||
- Format: `IMPL-{prefix}{seq}.json`
|
||||
- Prefix: A, B, C... (assigned by detection order)
|
||||
- Sequence: 1, 2, 3... (per-module increment)
|
||||
|
||||
### Phase 3: Integration (+1 Coordinator, Multi-Module Only)
|
||||
|
||||
**Condition**: Only executed when `modules.length >= 2`
|
||||
|
||||
**Purpose**: Collect all module tasks, resolve cross-module dependencies, generate unified documents.
|
||||
|
||||
**Integration Logic**:
|
||||
```javascript
|
||||
// 1. Collect all module task JSONs
|
||||
const allTasks = glob('.task/IMPL-*.json').map(loadJson);
|
||||
|
||||
// 2. Resolve cross-module dependencies
|
||||
for (const task of allTasks) {
|
||||
if (task.depends_on) {
|
||||
task.depends_on = task.depends_on.map(dep => {
|
||||
if (dep.startsWith('CROSS::')) {
|
||||
// CROSS::B::api-endpoint → find matching IMPL-B* task
|
||||
const [, targetModule, pattern] = dep.match(/CROSS::(\w+)::(.+)/);
|
||||
return findTaskByModuleAndPattern(allTasks, targetModule, pattern);
|
||||
}
|
||||
return dep;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Generate unified IMPL_PLAN.md (grouped by module)
|
||||
generateIMPL_PLAN(allTasks, modules);
|
||||
|
||||
// 4. Generate TODO_LIST.md (hierarchical structure)
|
||||
generateTODO_LIST(allTasks, modules);
|
||||
```
|
||||
|
||||
**Note**: IMPL_PLAN.md and TODO_LIST.md structure definitions are in `action-planning-agent.md`.
|
||||
|
||||
|
||||
@@ -42,10 +42,10 @@ Autonomous TDD task JSON and IMPL_PLAN.md generation using action-planning-agent
|
||||
- Different tech stacks or domains within feature
|
||||
|
||||
### Task Limits
|
||||
- **Maximum 10 tasks** (hard limit for TDD workflows)
|
||||
- **Maximum 18 tasks** (hard limit for TDD workflows)
|
||||
- **Feature-based**: Complete functional units with internal TDD cycles
|
||||
- **Hierarchy**: Flat (≤5 simple features) | Two-level (6-10 for complex features with sub-features)
|
||||
- **Re-scope**: If >10 tasks needed, break project into multiple TDD workflow sessions
|
||||
- **Re-scope**: If >18 tasks needed, break project into multiple TDD workflow sessions
|
||||
|
||||
### TDD Cycle Mapping
|
||||
- **Old approach**: 1 feature = 3 tasks (TEST-N.M, IMPL-N.M, REFACTOR-N.M)
|
||||
@@ -249,7 +249,7 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
- Each task executes Red-Green-Refactor phases sequentially
|
||||
- Task count = Feature count (typically 5 features = 5 tasks)
|
||||
- Subtasks only when complexity >2500 lines or >6 files per cycle
|
||||
- **Maximum 10 tasks** (hard limit for TDD workflows)
|
||||
- **Maximum 18 tasks** (hard limit for TDD workflows)
|
||||
|
||||
#### TDD Cycle Mapping
|
||||
- **Simple features**: IMPL-N with internal Red-Green-Refactor phases
|
||||
@@ -318,7 +318,7 @@ Refer to: @.claude/agents/action-planning-agent.md for:
|
||||
|
||||
**Quality Gates** (Full checklist in action-planning-agent.md):
|
||||
- ✓ Quantification requirements enforced (explicit counts, measurable acceptance, exact targets)
|
||||
- ✓ Task count ≤10 (hard limit)
|
||||
- ✓ Task count ≤18 (hard limit)
|
||||
- ✓ Each task has meta.tdd_workflow: true
|
||||
- ✓ Each task has exactly 3 implementation steps with tdd_phase field
|
||||
- ✓ Green phase includes test-fix cycle logic
|
||||
|
||||
@@ -212,7 +212,7 @@ PRIMARY requirements source - extract and map to task JSONs:
|
||||
|
||||
## QUALITY STANDARDS
|
||||
Hard Constraints:
|
||||
- Task count: minimum 2, maximum 12
|
||||
- Task count: minimum 2, maximum 18
|
||||
- All requirements quantified from TEST_ANALYSIS_RESULTS.md
|
||||
- Test framework matches existing project framework
|
||||
- flow_control includes reusable_test_tools and test_commands from project
|
||||
|
||||
@@ -320,7 +320,7 @@ Read({base_path}/prototypes/{target}-style-{style_id}-layout-{layout_id}.html)
|
||||
|
||||
### Step 1: Run Preview Generation Script
|
||||
```bash
|
||||
bash(~/.claude/scripts/ui-generate-preview.sh "{base_path}/prototypes")
|
||||
bash(ccw tool exec ui_generate_preview '{"prototypesDir":"{base_path}/prototypes"}')
|
||||
```
|
||||
|
||||
**Script generates**:
|
||||
@@ -432,7 +432,7 @@ bash(test -f {base_path}/prototypes/compare.html && echo "exists")
|
||||
bash(mkdir -p {base_path}/prototypes)
|
||||
|
||||
# Run preview script
|
||||
bash(~/.claude/scripts/ui-generate-preview.sh "{base_path}/prototypes")
|
||||
bash(ccw tool exec ui_generate_preview '{"prototypesDir":"{base_path}/prototypes"}')
|
||||
```
|
||||
|
||||
## Output Structure
|
||||
@@ -467,7 +467,7 @@ ERROR: Agent assembly failed
|
||||
→ Check inputs exist, validate JSON structure
|
||||
|
||||
ERROR: Script permission denied
|
||||
→ chmod +x ~/.claude/scripts/ui-generate-preview.sh
|
||||
→ Verify ccw tool is available: ccw tool list
|
||||
```
|
||||
|
||||
### Recovery Strategies
|
||||
|
||||
@@ -106,7 +106,7 @@ echo " Output: $base_path"
|
||||
|
||||
# 3. Discover files using script
|
||||
discovery_file="${intermediates_dir}/discovered-files.json"
|
||||
~/.claude/scripts/discover-design-files.sh "$source" "$discovery_file"
|
||||
ccw tool exec discover_design_files '{"sourceDir":"'"$source"'","outputPath":"'"$discovery_file"'"}'
|
||||
|
||||
echo " Output: $discovery_file"
|
||||
```
|
||||
|
||||
@@ -52,7 +52,7 @@ RULES: Focus on direct relevance to task requirements
|
||||
"
|
||||
|
||||
# Program Architecture (MANDATORY before planning)
|
||||
~/.claude/scripts/get_modules_by_depth.sh
|
||||
ccw tool exec get_modules_by_depth '{}'
|
||||
|
||||
# Content Search (rg preferred)
|
||||
rg "pattern" --type js -n # Search JS files with line numbers
|
||||
|
||||
@@ -10,62 +10,81 @@
|
||||
- Complex API research → Exa Code Context
|
||||
- Real-time information needs → Exa Web Search
|
||||
|
||||
## ⚡ Bash Text Processing (sed/awk)
|
||||
## ⚡ CCW edit_file Tool (AI-Powered Editing)
|
||||
|
||||
**When to Use**: Edit tool fails 2+ times on same file
|
||||
|
||||
### sed Quick Reference
|
||||
### update Mode (Default)
|
||||
|
||||
**Best for**: Code block replacements, function rewrites, multi-line changes
|
||||
|
||||
```bash
|
||||
# Replace first occurrence per line
|
||||
sed 's/old/new/' file.txt
|
||||
|
||||
# Replace all occurrences (global)
|
||||
sed 's/old/new/g' file.txt
|
||||
|
||||
# In-place edit (modify file directly)
|
||||
sed -i 's/old/new/g' file.txt
|
||||
|
||||
# Delete lines matching pattern
|
||||
sed '/pattern/d' file.txt
|
||||
|
||||
# Insert line before match
|
||||
sed '/pattern/i\new line' file.txt
|
||||
|
||||
# Insert line after match
|
||||
sed '/pattern/a\new line' file.txt
|
||||
|
||||
# Replace on specific line number
|
||||
sed '5s/old/new/' file.txt
|
||||
|
||||
# Multi-line replacement (escape newlines)
|
||||
sed ':a;N;$!ba;s/old\npattern/new\ntext/g' file.txt
|
||||
ccw tool exec edit_file '{
|
||||
"path": "file.py",
|
||||
"oldText": "def old():\n pass",
|
||||
"newText": "def new():\n return True"
|
||||
}'
|
||||
```
|
||||
|
||||
### awk Quick Reference
|
||||
**Features**:
|
||||
- ✅ Exact text matching (precise and predictable)
|
||||
- ✅ Auto line ending adaptation (CRLF/LF)
|
||||
- ✅ Simple `oldText` → `newText` replacement
|
||||
- ✅ No special markers needed
|
||||
|
||||
### line Mode (Precise Line Operations)
|
||||
|
||||
**Best for**: Config files, line insertions/deletions, precise line number control
|
||||
|
||||
```bash
|
||||
# Print specific column
|
||||
awk '{print $1}' file.txt
|
||||
# Insert after specific line
|
||||
ccw tool exec edit_file '{
|
||||
"path": "config.txt",
|
||||
"mode": "line",
|
||||
"operation": "insert_after",
|
||||
"line": 10,
|
||||
"text": "new config line"
|
||||
}'
|
||||
|
||||
# Print lines matching pattern
|
||||
awk '/pattern/' file.txt
|
||||
# Delete line range
|
||||
ccw tool exec edit_file '{
|
||||
"path": "log.txt",
|
||||
"mode": "line",
|
||||
"operation": "delete",
|
||||
"line": 5,
|
||||
"end_line": 8
|
||||
}'
|
||||
|
||||
# Replace field value
|
||||
awk '{$2="new"; print}' file.txt
|
||||
|
||||
# Conditional replacement
|
||||
awk '/pattern/{gsub(/old/,"new")}1' file.txt
|
||||
|
||||
# Insert line after match
|
||||
awk '/pattern/{print; print "new line"; next}1' file.txt
|
||||
|
||||
# Multi-field operations
|
||||
awk -F',' '{print $1, $3}' file.csv
|
||||
# Replace specific line
|
||||
ccw tool exec edit_file '{
|
||||
"path": "script.sh",
|
||||
"mode": "line",
|
||||
"operation": "replace",
|
||||
"line": 3,
|
||||
"text": "#!/bin/bash"
|
||||
}'
|
||||
```
|
||||
|
||||
**Operations**:
|
||||
- `insert_before`: Insert text before specified line
|
||||
- `insert_after`: Insert text after specified line
|
||||
- `replace`: Replace line or line range
|
||||
- `delete`: Delete line or line range
|
||||
|
||||
### Mode Selection Guide
|
||||
|
||||
| Scenario | Mode | Reason |
|
||||
|----------|------|--------|
|
||||
| Code refactoring | update | Content-driven replacement |
|
||||
| Function rewrite | update | Simple oldText/newText |
|
||||
| Config line change | line | Precise line number control |
|
||||
| Insert at specific position | line | Exact line number needed |
|
||||
| Delete line range | line | Line-based operation |
|
||||
|
||||
### Fallback Strategy
|
||||
|
||||
1. **Edit fails 2+ times** → Try sed for simple replacements
|
||||
2. **sed fails** → Try awk for complex patterns
|
||||
3. **awk fails** → Use Write to recreate file
|
||||
1. **Edit fails 1+ times** → Use `ccw tool exec edit_file` (update mode)
|
||||
2. **update mode fails** → Try line mode with precise line numbers
|
||||
3. **All fails** → Use Write to recreate file
|
||||
|
||||
**Default mode**: update (exact matching with line ending adaptation)
|
||||
|
||||
@@ -433,7 +433,7 @@ The `[FLOW_CONTROL]` marker indicates that a task or prompt contains flow contro
|
||||
"step": "load_context",
|
||||
"action": "Load project context and patterns",
|
||||
"commands": [
|
||||
"bash(~/.claude/scripts/get_modules_by_depth.sh)",
|
||||
"bash(ccw tool exec get_modules_by_depth '{}')",
|
||||
"Read(CLAUDE.md)"
|
||||
],
|
||||
"output_to": "project_structure",
|
||||
|
||||
12
README.md
12
README.md
@@ -2,7 +2,7 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://www.npmjs.com/package/claude-code-workflow)
|
||||
[](LICENSE)
|
||||
[]()
|
||||
@@ -15,13 +15,13 @@
|
||||
|
||||
**Claude Code Workflow (CCW)** is a JSON-driven multi-agent development framework with intelligent CLI orchestration (Gemini/Qwen/Codex), context-first architecture, and automated workflow execution. It transforms AI development from simple prompt chaining into a powerful orchestration system.
|
||||
|
||||
> **🎉 Version 6.0.0: npm Package & Simplified Installation**
|
||||
> **🎉 Version 6.1.0: Dashboard Icon Unification & CCW Tool System**
|
||||
>
|
||||
> **Core Improvements**:
|
||||
> - 📦 **npm Package**: Now available as `claude-code-workflow` on npm for simplified global installation
|
||||
> - 🖥️ **CCW CLI Tool**: New `ccw` command with dashboard viewer, installation management, and workflow visualization
|
||||
> - 🎯 **Simplified Install Flow**: Unified installation via npm with local-only operation (no GitHub API dependency)
|
||||
> - ✨ **Enhanced Dashboard**: MCP manager, review session improvements, and UI enhancements
|
||||
> - 🎨 **Dashboard Icon Unification**: Complete migration to Lucide Icons library across all views
|
||||
> - 🛠️ **CCW Tool Exec System**: New `ccw tool exec` command for executing tools with JSON parameters
|
||||
> - 🚀 **Explorer Enhancements**: Async task execution, CLI selector improvements, WebSocket frame handling
|
||||
> - ✨ **Smart Server Recognition**: Intelligent workspace switching and MCP multi-source configuration
|
||||
>
|
||||
> See [CHANGELOG.md](CHANGELOG.md) for complete details.
|
||||
|
||||
|
||||
14
README_CN.md
14
README_CN.md
@@ -2,7 +2,8 @@
|
||||
|
||||
<div align="center">
|
||||
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://github.com/catlog22/Claude-Code-Workflow/releases)
|
||||
[](https://www.npmjs.com/package/claude-code-workflow)
|
||||
[](LICENSE)
|
||||
[]()
|
||||
|
||||
@@ -14,14 +15,13 @@
|
||||
|
||||
**Claude Code Workflow (CCW)** 将 AI 开发从简单的提示词链接转变为一个强大的、上下文优先的编排系统。它通过结构化规划、确定性执行和智能多模型编排,解决了执行不确定性和误差累积的问题。
|
||||
|
||||
> **🎉 版本 5.9.6: 审查周期增强与仪表盘自动化**
|
||||
> **🎉 版本 6.1.0: 仪表盘图标统一 & CCW 工具系统**
|
||||
>
|
||||
> **核心改进**:
|
||||
> - ✨ **增强的审查仪表盘** - `review-cycle` 仪表盘支持实时进度跟踪和高级过滤
|
||||
> - 🎯 **新修复追踪仪表盘** - 新增独立的 `fix-dashboard.html` 监控 Bug 修复进度
|
||||
> - 🚀 **`lite-fix` 工作流** - 新增智能化、流程化的 Bug 诊断和修复命令
|
||||
> - 🛠️ **`lite-plan` 优化** - 成本感知并行执行、智能复杂度分析、健壮的上下文保护
|
||||
> - 🧠 **智能测试周期** - 改进 `test-cycle-execute` 智能迭代策略和通用 `@test-fix-agent`
|
||||
> - 🎨 **仪表盘图标统一** - 全面迁移至 Lucide Icons 图标库
|
||||
> - 🛠️ **CCW Tool Exec 系统** - 新增 `ccw tool exec` 命令,支持 JSON 参数执行工具
|
||||
> - 🚀 **Explorer 增强** - 异步任务执行、CLI 选择器改进、WebSocket 帧处理
|
||||
> - ✨ **智能服务器识别** - 智能工作空间切换和 MCP 多源配置
|
||||
>
|
||||
> 详见 [CHANGELOG.md](CHANGELOG.md)。
|
||||
|
||||
|
||||
@@ -24,15 +24,15 @@
|
||||
"node": ">=16.0.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"commander": "^11.0.0",
|
||||
"open": "^9.1.0",
|
||||
"chalk": "^5.3.0",
|
||||
"glob": "^10.3.0",
|
||||
"inquirer": "^9.2.0",
|
||||
"ora": "^7.0.0",
|
||||
"figlet": "^1.7.0",
|
||||
"boxen": "^7.1.0",
|
||||
"gradient-string": "^2.0.2"
|
||||
"chalk": "^5.3.0",
|
||||
"commander": "^11.0.0",
|
||||
"figlet": "^1.7.0",
|
||||
"glob": "^10.3.0",
|
||||
"gradient-string": "^2.0.2",
|
||||
"inquirer": "^9.2.0",
|
||||
"open": "^9.1.0",
|
||||
"ora": "^7.0.0"
|
||||
},
|
||||
"files": [
|
||||
"bin/",
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
import { Command } from 'commander';
|
||||
import { viewCommand } from './commands/view.js';
|
||||
import { serveCommand } from './commands/serve.js';
|
||||
import { stopCommand } from './commands/stop.js';
|
||||
import { installCommand } from './commands/install.js';
|
||||
import { uninstallCommand } from './commands/uninstall.js';
|
||||
import { upgradeCommand } from './commands/upgrade.js';
|
||||
import { listCommand } from './commands/list.js';
|
||||
import { toolCommand } from './commands/tool.js';
|
||||
import { readFileSync, existsSync } from 'fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
import { dirname, join } from 'path';
|
||||
@@ -68,6 +70,14 @@ export function run(argv) {
|
||||
.option('--no-browser', 'Start server without opening browser')
|
||||
.action(serveCommand);
|
||||
|
||||
// Stop command
|
||||
program
|
||||
.command('stop')
|
||||
.description('Stop the running CCW dashboard server')
|
||||
.option('--port <port>', 'Server port', '3456')
|
||||
.option('-f, --force', 'Force kill process on the port')
|
||||
.action(stopCommand);
|
||||
|
||||
// Install command
|
||||
program
|
||||
.command('install')
|
||||
@@ -96,5 +106,11 @@ export function run(argv) {
|
||||
.description('List all installed Claude Code Workflow instances')
|
||||
.action(listCommand);
|
||||
|
||||
// Tool command
|
||||
program
|
||||
.command('tool [subcommand] [args] [json]')
|
||||
.description('Execute CCW tools')
|
||||
.action((subcommand, args, json) => toolCommand(subcommand, args, { json }));
|
||||
|
||||
program.parse(argv);
|
||||
}
|
||||
|
||||
101
ccw/src/commands/stop.js
Normal file
101
ccw/src/commands/stop.js
Normal file
@@ -0,0 +1,101 @@
|
||||
import chalk from 'chalk';
|
||||
import { exec } from 'child_process';
|
||||
import { promisify } from 'util';
|
||||
|
||||
const execAsync = promisify(exec);
|
||||
|
||||
/**
|
||||
* Find process using a specific port (Windows)
|
||||
* @param {number} port - Port number
|
||||
* @returns {Promise<string|null>} PID or null
|
||||
*/
|
||||
async function findProcessOnPort(port) {
|
||||
try {
|
||||
const { stdout } = await execAsync(`netstat -ano | findstr :${port} | findstr LISTENING`);
|
||||
const lines = stdout.trim().split('\n');
|
||||
if (lines.length > 0) {
|
||||
const parts = lines[0].trim().split(/\s+/);
|
||||
return parts[parts.length - 1]; // PID is the last column
|
||||
}
|
||||
} catch {
|
||||
// No process found
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Kill process by PID (Windows)
|
||||
* @param {string} pid - Process ID
|
||||
* @returns {Promise<boolean>} Success status
|
||||
*/
|
||||
async function killProcess(pid) {
|
||||
try {
|
||||
await execAsync(`taskkill /PID ${pid} /F`);
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Stop command handler - stops the running CCW dashboard server
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function stopCommand(options) {
|
||||
const port = options.port || 3456;
|
||||
const force = options.force || false;
|
||||
|
||||
console.log(chalk.blue.bold('\n CCW Dashboard\n'));
|
||||
console.log(chalk.gray(` Checking server on port ${port}...`));
|
||||
|
||||
try {
|
||||
// Try graceful shutdown via API first
|
||||
const healthCheck = await fetch(`http://localhost:${port}/api/health`, {
|
||||
signal: AbortSignal.timeout(2000)
|
||||
}).catch(() => null);
|
||||
|
||||
if (healthCheck && healthCheck.ok) {
|
||||
// CCW server is running - send shutdown signal
|
||||
console.log(chalk.cyan(' CCW server found, sending shutdown signal...'));
|
||||
|
||||
await fetch(`http://localhost:${port}/api/shutdown`, {
|
||||
method: 'POST',
|
||||
signal: AbortSignal.timeout(5000)
|
||||
}).catch(() => null);
|
||||
|
||||
// Wait a moment for shutdown
|
||||
await new Promise(resolve => setTimeout(resolve, 500));
|
||||
|
||||
console.log(chalk.green.bold('\n Server stopped successfully!\n'));
|
||||
return;
|
||||
}
|
||||
|
||||
// No CCW server responding, check if port is in use
|
||||
const pid = await findProcessOnPort(port);
|
||||
|
||||
if (!pid) {
|
||||
console.log(chalk.yellow(` No server running on port ${port}\n`));
|
||||
return;
|
||||
}
|
||||
|
||||
// Port is in use by another process
|
||||
console.log(chalk.yellow(` Port ${port} is in use by process PID: ${pid}`));
|
||||
|
||||
if (force) {
|
||||
console.log(chalk.cyan(' Force killing process...'));
|
||||
const killed = await killProcess(pid);
|
||||
|
||||
if (killed) {
|
||||
console.log(chalk.green.bold('\n Process killed successfully!\n'));
|
||||
} else {
|
||||
console.log(chalk.red('\n Failed to kill process. Try running as administrator.\n'));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.gray(`\n This is not a CCW server. Use --force to kill it:`));
|
||||
console.log(chalk.white(` ccw stop --force\n`));
|
||||
}
|
||||
|
||||
} catch (err) {
|
||||
console.error(chalk.red(`\n Error: ${err.message}\n`));
|
||||
}
|
||||
}
|
||||
217
ccw/src/commands/tool.js
Normal file
217
ccw/src/commands/tool.js
Normal file
@@ -0,0 +1,217 @@
|
||||
/**
|
||||
* Tool Command - Execute and manage CCW tools
|
||||
*/
|
||||
|
||||
import chalk from 'chalk';
|
||||
import { listTools, executeTool, getTool, getAllToolSchemas } from '../tools/index.js';
|
||||
|
||||
/**
|
||||
* List all available tools
|
||||
*/
|
||||
async function listAction() {
|
||||
const tools = listTools();
|
||||
|
||||
if (tools.length === 0) {
|
||||
console.log(chalk.yellow('No tools registered'));
|
||||
return;
|
||||
}
|
||||
|
||||
console.log(chalk.bold.cyan('\nAvailable Tools:\n'));
|
||||
|
||||
for (const tool of tools) {
|
||||
console.log(chalk.bold.white(` ${tool.name}`));
|
||||
console.log(chalk.gray(` ${tool.description}`));
|
||||
|
||||
if (tool.parameters?.properties) {
|
||||
const props = tool.parameters.properties;
|
||||
const required = tool.parameters.required || [];
|
||||
|
||||
console.log(chalk.gray(' Parameters:'));
|
||||
for (const [name, schema] of Object.entries(props)) {
|
||||
const req = required.includes(name) ? chalk.red('*') : '';
|
||||
const defaultVal = schema.default !== undefined ? chalk.gray(` (default: ${schema.default})`) : '';
|
||||
console.log(chalk.gray(` - ${name}${req}: ${schema.description}${defaultVal}`));
|
||||
}
|
||||
}
|
||||
console.log();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show tool schema in MCP-compatible JSON format
|
||||
*/
|
||||
async function schemaAction(options) {
|
||||
const { name } = options;
|
||||
|
||||
if (name) {
|
||||
const tool = getTool(name);
|
||||
if (!tool) {
|
||||
console.error(chalk.red(`Tool not found: ${name}`));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const schema = {
|
||||
name: tool.name,
|
||||
description: tool.description,
|
||||
inputSchema: {
|
||||
type: 'object',
|
||||
properties: tool.parameters?.properties || {},
|
||||
required: tool.parameters?.required || []
|
||||
}
|
||||
};
|
||||
console.log(JSON.stringify(schema, null, 2));
|
||||
} else {
|
||||
const schemas = getAllToolSchemas();
|
||||
console.log(JSON.stringify({ tools: schemas }, null, 2));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read from stdin if available
|
||||
*/
|
||||
async function readStdin() {
|
||||
// Check if stdin is a TTY (interactive terminal)
|
||||
if (process.stdin.isTTY) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
let data = '';
|
||||
|
||||
process.stdin.setEncoding('utf8');
|
||||
|
||||
process.stdin.on('readable', () => {
|
||||
let chunk;
|
||||
while ((chunk = process.stdin.read()) !== null) {
|
||||
data += chunk;
|
||||
}
|
||||
});
|
||||
|
||||
process.stdin.on('end', () => {
|
||||
resolve(data.trim() || null);
|
||||
});
|
||||
|
||||
process.stdin.on('error', (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Smart JSON parser with Windows path handling
|
||||
*/
|
||||
function parseJsonWithPathFix(jsonString) {
|
||||
try {
|
||||
// Try normal parse first
|
||||
return JSON.parse(jsonString);
|
||||
} catch (firstError) {
|
||||
// If parsing fails, try to fix Windows paths
|
||||
try {
|
||||
// Pattern: "path": "X:\..." or "path":"X:\..."
|
||||
const fixedJson = jsonString.replace(
|
||||
/("(?:path|file|target|source|dest|destination)":\s*")([A-Za-z]:[^"]+)"/g,
|
||||
(match, prefix, path) => {
|
||||
// Convert backslashes to forward slashes (universal)
|
||||
const fixedPath = path.replace(/\\/g, '/');
|
||||
return `${prefix}${fixedPath}"`;
|
||||
}
|
||||
);
|
||||
|
||||
return JSON.parse(fixedJson);
|
||||
} catch (secondError) {
|
||||
// If still fails, throw original error with helpful message
|
||||
const errorMsg = firstError.message;
|
||||
const hint = errorMsg.includes('escaped character') || errorMsg.includes('position')
|
||||
? '\n\n' + chalk.yellow('Hint: Windows paths in JSON need forward slashes or double backslashes:') +
|
||||
'\n ' + chalk.green('✓ "D:/Claude_dms3/file.md"') +
|
||||
'\n ' + chalk.green('✓ "D:\\\\Claude_dms3\\\\file.md"') +
|
||||
'\n ' + chalk.red('✗ "D:\\Claude_dms3\\file.md"')
|
||||
: '';
|
||||
|
||||
throw new Error(errorMsg + hint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a tool with given parameters
|
||||
*/
|
||||
async function execAction(toolName, jsonInput, options) {
|
||||
if (!toolName) {
|
||||
console.error(chalk.red('Tool name is required'));
|
||||
console.error(chalk.gray('Usage: ccw tool exec <tool-name> \'{"param": "value"}\''));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const tool = getTool(toolName);
|
||||
if (!tool) {
|
||||
console.error(chalk.red(`Tool not found: ${toolName}`));
|
||||
console.error(chalk.gray('Use "ccw tool list" to see available tools'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
// Parse JSON input (default format)
|
||||
let params = {};
|
||||
|
||||
if (jsonInput) {
|
||||
try {
|
||||
params = parseJsonWithPathFix(jsonInput);
|
||||
} catch (error) {
|
||||
console.error(chalk.red(`Invalid JSON: ${error.message}`));
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Check for stdin input (for piped commands)
|
||||
const stdinData = await readStdin();
|
||||
if (stdinData) {
|
||||
// If tool has an 'input' parameter, use it
|
||||
// Otherwise, try to parse stdin as JSON and merge with params
|
||||
if (tool.parameters?.properties?.input) {
|
||||
params.input = stdinData;
|
||||
} else {
|
||||
try {
|
||||
const stdinJson = JSON.parse(stdinData);
|
||||
params = { ...stdinJson, ...params };
|
||||
} catch {
|
||||
// If not JSON, store as 'input' anyway
|
||||
params.input = stdinData;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Execute tool
|
||||
const result = await executeTool(toolName, params);
|
||||
|
||||
// Always output JSON
|
||||
console.log(JSON.stringify(result, null, 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tool command entry point
|
||||
*/
|
||||
export async function toolCommand(subcommand, args, options) {
|
||||
// Handle subcommands
|
||||
switch (subcommand) {
|
||||
case 'list':
|
||||
await listAction();
|
||||
break;
|
||||
case 'schema':
|
||||
await schemaAction({ name: args });
|
||||
break;
|
||||
case 'exec':
|
||||
await execAction(args, options.json, options);
|
||||
break;
|
||||
default:
|
||||
console.log(chalk.bold.cyan('\nCCW Tool System\n'));
|
||||
console.log('Subcommands:');
|
||||
console.log(chalk.gray(' list List all available tools'));
|
||||
console.log(chalk.gray(' schema [name] Show tool schema (JSON)'));
|
||||
console.log(chalk.gray(' exec <name> Execute a tool'));
|
||||
console.log();
|
||||
console.log('Examples:');
|
||||
console.log(chalk.gray(' ccw tool list'));
|
||||
console.log(chalk.gray(' ccw tool schema edit_file'));
|
||||
console.log(chalk.gray(' ccw tool exec edit_file \'{"path":"file.txt","oldText":"old","newText":"new"}\''));
|
||||
}
|
||||
}
|
||||
@@ -1,14 +1,105 @@
|
||||
import { serveCommand } from './serve.js';
|
||||
import { launchBrowser } from '../utils/browser-launcher.js';
|
||||
import { validatePath } from '../utils/path-resolver.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* View command handler - starts dashboard server (unified with serve mode)
|
||||
* Check if server is already running on the specified port
|
||||
* @param {number} port - Port to check
|
||||
* @returns {Promise<boolean>} True if server is running
|
||||
*/
|
||||
async function isServerRunning(port) {
|
||||
try {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 1000);
|
||||
|
||||
const response = await fetch(`http://localhost:${port}/api/health`, {
|
||||
signal: controller.signal
|
||||
});
|
||||
clearTimeout(timeoutId);
|
||||
|
||||
return response.ok;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch workspace on running server
|
||||
* @param {number} port - Server port
|
||||
* @param {string} path - New workspace path
|
||||
* @returns {Promise<Object>} Result with success status
|
||||
*/
|
||||
async function switchWorkspace(port, path) {
|
||||
try {
|
||||
const response = await fetch(
|
||||
`http://localhost:${port}/api/switch-path?path=${encodeURIComponent(path)}`
|
||||
);
|
||||
return await response.json();
|
||||
} catch (err) {
|
||||
return { success: false, error: err.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* View command handler - opens dashboard for current workspace
|
||||
* If server is already running, switches workspace and opens browser
|
||||
* If not running, starts a new server
|
||||
* @param {Object} options - Command options
|
||||
*/
|
||||
export async function viewCommand(options) {
|
||||
// Forward to serve command with same options
|
||||
await serveCommand({
|
||||
path: options.path,
|
||||
port: options.port || 3456,
|
||||
browser: options.browser
|
||||
});
|
||||
const port = options.port || 3456;
|
||||
|
||||
// Resolve workspace path
|
||||
let workspacePath = process.cwd();
|
||||
if (options.path) {
|
||||
const pathValidation = validatePath(options.path, { mustExist: true });
|
||||
if (!pathValidation.valid) {
|
||||
console.error(chalk.red(`\n Error: ${pathValidation.error}\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
workspacePath = pathValidation.path;
|
||||
}
|
||||
|
||||
// Check if server is already running
|
||||
const serverRunning = await isServerRunning(port);
|
||||
|
||||
if (serverRunning) {
|
||||
// Server is running - switch workspace and open browser
|
||||
console.log(chalk.blue.bold('\n CCW Dashboard\n'));
|
||||
console.log(chalk.gray(` Server already running on port ${port}`));
|
||||
console.log(chalk.cyan(` Switching workspace to: ${workspacePath}`));
|
||||
|
||||
const result = await switchWorkspace(port, workspacePath);
|
||||
|
||||
if (result.success) {
|
||||
console.log(chalk.green(` Workspace switched successfully`));
|
||||
|
||||
// Open browser with the new path
|
||||
const url = `http://localhost:${port}/?path=${encodeURIComponent(result.path)}`;
|
||||
|
||||
if (options.browser !== false) {
|
||||
console.log(chalk.cyan(' Opening in browser...'));
|
||||
try {
|
||||
await launchBrowser(url);
|
||||
console.log(chalk.green.bold('\n Dashboard opened!\n'));
|
||||
} catch (err) {
|
||||
console.log(chalk.yellow(`\n Could not open browser: ${err.message}`));
|
||||
console.log(chalk.gray(` Open manually: ${url}\n`));
|
||||
}
|
||||
} else {
|
||||
console.log(chalk.gray(`\n URL: ${url}\n`));
|
||||
}
|
||||
} else {
|
||||
console.error(chalk.red(`\n Failed to switch workspace: ${result.error}\n`));
|
||||
process.exit(1);
|
||||
}
|
||||
} else {
|
||||
// Server not running - start new server
|
||||
await serveCommand({
|
||||
path: workspacePath,
|
||||
port: port,
|
||||
browser: options.browser
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,10 +8,22 @@ const __dirname = dirname(__filename);
|
||||
// Bundled template paths
|
||||
const UNIFIED_TEMPLATE = join(__dirname, '../templates/dashboard.html');
|
||||
const JS_FILE = join(__dirname, '../templates/dashboard.js');
|
||||
const CSS_FILE = join(__dirname, '../templates/dashboard.css');
|
||||
const MODULE_CSS_DIR = join(__dirname, '../templates/dashboard-css');
|
||||
const WORKFLOW_TEMPLATE = join(__dirname, '../templates/workflow-dashboard.html');
|
||||
const REVIEW_TEMPLATE = join(__dirname, '../templates/review-cycle-dashboard.html');
|
||||
|
||||
// Modular CSS files in load order
|
||||
const MODULE_CSS_FILES = [
|
||||
'01-base.css',
|
||||
'02-session.css',
|
||||
'03-tasks.css',
|
||||
'04-lite-tasks.css',
|
||||
'05-context.css',
|
||||
'06-cards.css',
|
||||
'07-managers.css',
|
||||
'08-review.css'
|
||||
];
|
||||
|
||||
const MODULE_FILES = [
|
||||
'utils.js',
|
||||
'state.js',
|
||||
@@ -63,8 +75,11 @@ export async function generateDashboard(data) {
|
||||
function generateFromUnifiedTemplate(data) {
|
||||
let html = readFileSync(UNIFIED_TEMPLATE, 'utf8');
|
||||
|
||||
// Read CSS file
|
||||
let cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
// Read and concatenate modular CSS files in load order
|
||||
let cssContent = MODULE_CSS_FILES.map(file => {
|
||||
const filePath = join(MODULE_CSS_DIR, file);
|
||||
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
||||
}).join('\n\n');
|
||||
|
||||
// Read JS content
|
||||
let jsContent = '';
|
||||
|
||||
@@ -54,20 +54,36 @@ function scanLiteDir(dir, type) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Load plan.json from session directory
|
||||
* Load plan.json or fix-plan.json from session directory
|
||||
* @param {string} sessionPath - Session directory path
|
||||
* @returns {Object|null} - Plan data or null
|
||||
*/
|
||||
function loadPlanJson(sessionPath) {
|
||||
// Try fix-plan.json first (for lite-fix), then plan.json (for lite-plan)
|
||||
const fixPlanPath = join(sessionPath, 'fix-plan.json');
|
||||
const planPath = join(sessionPath, 'plan.json');
|
||||
if (!existsSync(planPath)) return null;
|
||||
|
||||
try {
|
||||
const content = readFileSync(planPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
// Try fix-plan.json first
|
||||
if (existsSync(fixPlanPath)) {
|
||||
try {
|
||||
const content = readFileSync(fixPlanPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
// Continue to try plan.json
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback to plan.json
|
||||
if (existsSync(planPath)) {
|
||||
try {
|
||||
const content = readFileSync(planPath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -91,6 +107,7 @@ function loadTaskJsons(sessionPath) {
|
||||
f.startsWith('IMPL-') ||
|
||||
f.startsWith('TASK-') ||
|
||||
f.startsWith('task-') ||
|
||||
f.startsWith('diagnosis-') ||
|
||||
/^T\d+\.json$/i.test(f)
|
||||
))
|
||||
.map(f => {
|
||||
@@ -109,12 +126,18 @@ function loadTaskJsons(sessionPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Method 2: Check plan.json for embedded tasks array
|
||||
// Method 2: Check plan.json or fix-plan.json for embedded tasks array
|
||||
if (tasks.length === 0) {
|
||||
// Try fix-plan.json first (for lite-fix), then plan.json (for lite-plan)
|
||||
const fixPlanPath = join(sessionPath, 'fix-plan.json');
|
||||
const planPath = join(sessionPath, 'plan.json');
|
||||
if (existsSync(planPath)) {
|
||||
|
||||
const planFile = existsSync(fixPlanPath) ? fixPlanPath :
|
||||
existsSync(planPath) ? planPath : null;
|
||||
|
||||
if (planFile) {
|
||||
try {
|
||||
const plan = JSON.parse(readFileSync(planPath, 'utf8'));
|
||||
const plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
||||
if (Array.isArray(plan.tasks)) {
|
||||
tasks = plan.tasks.map(t => normalizeTask(t));
|
||||
}
|
||||
@@ -124,13 +147,14 @@ function loadTaskJsons(sessionPath) {
|
||||
}
|
||||
}
|
||||
|
||||
// Method 3: Check for task-*.json files in session root
|
||||
// Method 3: Check for task-*.json and diagnosis-*.json files in session root
|
||||
if (tasks.length === 0) {
|
||||
try {
|
||||
const rootTasks = readdirSync(sessionPath)
|
||||
.filter(f => f.endsWith('.json') && (
|
||||
f.startsWith('task-') ||
|
||||
f.startsWith('TASK-') ||
|
||||
f.startsWith('diagnosis-') ||
|
||||
/^T\d+\.json$/i.test(f)
|
||||
))
|
||||
.map(f => {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, promises as fsPromises } from 'fs';
|
||||
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync, statSync, promises as fsPromises } from 'fs';
|
||||
import { join, dirname } from 'path';
|
||||
import { homedir } from 'os';
|
||||
import { createHash } from 'crypto';
|
||||
@@ -8,17 +8,46 @@ import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
// Claude config file path
|
||||
// Claude config file paths
|
||||
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
||||
const CLAUDE_SETTINGS_DIR = join(homedir(), '.claude');
|
||||
const CLAUDE_GLOBAL_SETTINGS = join(CLAUDE_SETTINGS_DIR, 'settings.json');
|
||||
const CLAUDE_GLOBAL_SETTINGS_LOCAL = join(CLAUDE_SETTINGS_DIR, 'settings.local.json');
|
||||
|
||||
// Enterprise managed MCP paths (platform-specific)
|
||||
function getEnterpriseMcpPath() {
|
||||
const platform = process.platform;
|
||||
if (platform === 'darwin') {
|
||||
return '/Library/Application Support/ClaudeCode/managed-mcp.json';
|
||||
} else if (platform === 'win32') {
|
||||
return 'C:\\Program Files\\ClaudeCode\\managed-mcp.json';
|
||||
} else {
|
||||
// Linux and WSL
|
||||
return '/etc/claude-code/managed-mcp.json';
|
||||
}
|
||||
}
|
||||
|
||||
// WebSocket clients for real-time notifications
|
||||
const wsClients = new Set();
|
||||
|
||||
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
||||
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
||||
const MODULE_CSS_DIR = join(import.meta.dirname, '../templates/dashboard-css');
|
||||
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
||||
const MODULE_JS_DIR = join(import.meta.dirname, '../templates/dashboard-js');
|
||||
|
||||
// Modular CSS files in load order
|
||||
const MODULE_CSS_FILES = [
|
||||
'01-base.css',
|
||||
'02-session.css',
|
||||
'03-tasks.css',
|
||||
'04-lite-tasks.css',
|
||||
'05-context.css',
|
||||
'06-cards.css',
|
||||
'07-managers.css',
|
||||
'08-review.css',
|
||||
'09-explorer.css'
|
||||
];
|
||||
|
||||
/**
|
||||
* Handle POST request with JSON body
|
||||
*/
|
||||
@@ -56,6 +85,7 @@ const MODULE_FILES = [
|
||||
'components/sidebar.js',
|
||||
'components/carousel.js',
|
||||
'components/notifications.js',
|
||||
'components/global-notifications.js',
|
||||
'components/mcp-manager.js',
|
||||
'components/hook-manager.js',
|
||||
'components/_exp_helpers.js',
|
||||
@@ -74,6 +104,7 @@ const MODULE_FILES = [
|
||||
'views/fix-session.js',
|
||||
'views/mcp-manager.js',
|
||||
'views/hook-manager.js',
|
||||
'views/explorer.js',
|
||||
'main.js'
|
||||
];
|
||||
/**
|
||||
@@ -126,6 +157,58 @@ export async function startServer(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Switch workspace path (for ccw view command)
|
||||
if (pathname === '/api/switch-path') {
|
||||
const newPath = url.searchParams.get('path');
|
||||
if (!newPath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Path is required' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const resolved = resolvePath(newPath);
|
||||
if (!existsSync(resolved)) {
|
||||
res.writeHead(404, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Path does not exist' }));
|
||||
return;
|
||||
}
|
||||
|
||||
// Track the path and return success
|
||||
trackRecentPath(resolved);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({
|
||||
success: true,
|
||||
path: resolved,
|
||||
recentPaths: getRecentPaths()
|
||||
}));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Health check (for ccw view to detect running server)
|
||||
if (pathname === '/api/health') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'ok', timestamp: Date.now() }));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Shutdown server (for ccw stop command)
|
||||
if (pathname === '/api/shutdown' && req.method === 'POST') {
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ status: 'shutting_down' }));
|
||||
|
||||
// Graceful shutdown
|
||||
console.log('\n Received shutdown signal...');
|
||||
setTimeout(() => {
|
||||
server.close(() => {
|
||||
console.log(' Server stopped.\n');
|
||||
process.exit(0);
|
||||
});
|
||||
// Force exit after 3 seconds if graceful shutdown fails
|
||||
setTimeout(() => process.exit(0), 3000);
|
||||
}, 100);
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Remove a recent path
|
||||
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
@@ -319,6 +402,41 @@ export async function startServer(options = {}) {
|
||||
return;
|
||||
}
|
||||
|
||||
// API: List directory files with .gitignore filtering (Explorer view)
|
||||
if (pathname === '/api/files') {
|
||||
const dirPath = url.searchParams.get('path') || initialPath;
|
||||
const filesData = await listDirectoryFiles(dirPath);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(filesData));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get file content for preview (Explorer view)
|
||||
if (pathname === '/api/file-content') {
|
||||
const filePath = url.searchParams.get('path');
|
||||
if (!filePath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'File path is required' }));
|
||||
return;
|
||||
}
|
||||
const fileData = await getFileContent(filePath);
|
||||
res.writeHead(fileData.error ? 404 : 200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(fileData));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Update CLAUDE.md using CLI tools (Explorer view)
|
||||
if (pathname === '/api/update-claude-md' && req.method === 'POST') {
|
||||
handlePostRequest(req, res, async (body) => {
|
||||
const { path: targetPath, tool = 'gemini', strategy = 'single-layer' } = body;
|
||||
if (!targetPath) {
|
||||
return { error: 'path is required', status: 400 };
|
||||
}
|
||||
return await triggerUpdateClaudeMd(targetPath, tool, strategy);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve dashboard HTML
|
||||
if (pathname === '/' || pathname === '/index.html') {
|
||||
const html = generateServerDashboard(initialPath);
|
||||
@@ -389,9 +507,31 @@ function handleWebSocketUpgrade(req, socket, head) {
|
||||
// Handle incoming messages
|
||||
socket.on('data', (buffer) => {
|
||||
try {
|
||||
const message = parseWebSocketFrame(buffer);
|
||||
if (message) {
|
||||
console.log('[WS] Received:', message);
|
||||
const frame = parseWebSocketFrame(buffer);
|
||||
if (!frame) return;
|
||||
|
||||
const { opcode, payload } = frame;
|
||||
|
||||
switch (opcode) {
|
||||
case 0x1: // Text frame
|
||||
if (payload) {
|
||||
console.log('[WS] Received:', payload);
|
||||
}
|
||||
break;
|
||||
case 0x8: // Close frame
|
||||
socket.end();
|
||||
break;
|
||||
case 0x9: // Ping frame - respond with Pong
|
||||
const pongFrame = Buffer.alloc(2);
|
||||
pongFrame[0] = 0x8A; // Pong opcode with FIN bit
|
||||
pongFrame[1] = 0x00; // No payload
|
||||
socket.write(pongFrame);
|
||||
break;
|
||||
case 0xA: // Pong frame - ignore
|
||||
break;
|
||||
default:
|
||||
// Ignore other frame types (binary, continuation)
|
||||
break;
|
||||
}
|
||||
} catch (e) {
|
||||
// Ignore parse errors
|
||||
@@ -411,10 +551,18 @@ function handleWebSocketUpgrade(req, socket, head) {
|
||||
|
||||
/**
|
||||
* Parse WebSocket frame (simplified)
|
||||
* Returns { opcode, payload } or null
|
||||
*/
|
||||
function parseWebSocketFrame(buffer) {
|
||||
if (buffer.length < 2) return null;
|
||||
|
||||
const firstByte = buffer[0];
|
||||
const opcode = firstByte & 0x0f; // Extract opcode (bits 0-3)
|
||||
|
||||
// Opcode types:
|
||||
// 0x0 = continuation, 0x1 = text, 0x2 = binary
|
||||
// 0x8 = close, 0x9 = ping, 0xA = pong
|
||||
|
||||
const secondByte = buffer[1];
|
||||
const isMasked = (secondByte & 0x80) !== 0;
|
||||
let payloadLength = secondByte & 0x7f;
|
||||
@@ -442,7 +590,7 @@ function parseWebSocketFrame(buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
return payload.toString('utf8');
|
||||
return { opcode, payload: payload.toString('utf8') };
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -897,8 +1045,11 @@ async function updateTaskStatus(sessionPath, taskId, newStatus) {
|
||||
function generateServerDashboard(initialPath) {
|
||||
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
||||
|
||||
// Read CSS file
|
||||
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
// Read and concatenate modular CSS files in load order
|
||||
const cssContent = MODULE_CSS_FILES.map(file => {
|
||||
const filePath = join(MODULE_CSS_DIR, file);
|
||||
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
||||
}).join('\n\n');
|
||||
|
||||
// Read and concatenate modular JS files in dependency order
|
||||
let jsContent = MODULE_FILES.map(file => {
|
||||
@@ -972,22 +1123,114 @@ async function loadRecentPaths() {
|
||||
// ========================================
|
||||
|
||||
/**
|
||||
* Get MCP configuration from .claude.json
|
||||
* Safely read and parse JSON file
|
||||
* @param {string} filePath
|
||||
* @returns {Object|null}
|
||||
*/
|
||||
function safeReadJson(filePath) {
|
||||
try {
|
||||
if (!existsSync(filePath)) return null;
|
||||
const content = readFileSync(filePath, 'utf8');
|
||||
return JSON.parse(content);
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MCP servers from a JSON file (expects mcpServers key at top level)
|
||||
* @param {string} filePath
|
||||
* @returns {Object} mcpServers object or empty object
|
||||
*/
|
||||
function getMcpServersFromFile(filePath) {
|
||||
const config = safeReadJson(filePath);
|
||||
if (!config) return {};
|
||||
return config.mcpServers || {};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get MCP configuration from multiple sources (per official Claude Code docs):
|
||||
*
|
||||
* Priority (highest to lowest):
|
||||
* 1. Enterprise managed-mcp.json (cannot be overridden)
|
||||
* 2. Local scope (project-specific private in ~/.claude.json)
|
||||
* 3. Project scope (.mcp.json in project root)
|
||||
* 4. User scope (mcpServers in ~/.claude.json)
|
||||
*
|
||||
* Note: ~/.claude/settings.json is for MCP PERMISSIONS, NOT definitions!
|
||||
*
|
||||
* @returns {Object}
|
||||
*/
|
||||
function getMcpConfig() {
|
||||
try {
|
||||
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
||||
return { projects: {} };
|
||||
}
|
||||
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
||||
const config = JSON.parse(content);
|
||||
return {
|
||||
projects: config.projects || {}
|
||||
const result = {
|
||||
projects: {},
|
||||
userServers: {}, // User-level servers from ~/.claude.json mcpServers
|
||||
enterpriseServers: {}, // Enterprise managed servers (highest priority)
|
||||
configSources: [] // Track where configs came from for debugging
|
||||
};
|
||||
|
||||
// 1. Read Enterprise managed MCP servers (highest priority)
|
||||
const enterprisePath = getEnterpriseMcpPath();
|
||||
if (existsSync(enterprisePath)) {
|
||||
const enterpriseConfig = safeReadJson(enterprisePath);
|
||||
if (enterpriseConfig?.mcpServers) {
|
||||
result.enterpriseServers = enterpriseConfig.mcpServers;
|
||||
result.configSources.push({ type: 'enterprise', path: enterprisePath, count: Object.keys(enterpriseConfig.mcpServers).length });
|
||||
}
|
||||
}
|
||||
|
||||
// 2. Read from ~/.claude.json
|
||||
if (existsSync(CLAUDE_CONFIG_PATH)) {
|
||||
const claudeConfig = safeReadJson(CLAUDE_CONFIG_PATH);
|
||||
if (claudeConfig) {
|
||||
// 2a. User-level mcpServers (top-level mcpServers key)
|
||||
if (claudeConfig.mcpServers) {
|
||||
result.userServers = claudeConfig.mcpServers;
|
||||
result.configSources.push({ type: 'user', path: CLAUDE_CONFIG_PATH, count: Object.keys(claudeConfig.mcpServers).length });
|
||||
}
|
||||
|
||||
// 2b. Project-specific configurations (projects[path].mcpServers)
|
||||
if (claudeConfig.projects) {
|
||||
result.projects = claudeConfig.projects;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. For each known project, check for .mcp.json (project-level config)
|
||||
const projectPaths = Object.keys(result.projects);
|
||||
for (const projectPath of projectPaths) {
|
||||
const mcpJsonPath = join(projectPath, '.mcp.json');
|
||||
if (existsSync(mcpJsonPath)) {
|
||||
const mcpJsonConfig = safeReadJson(mcpJsonPath);
|
||||
if (mcpJsonConfig?.mcpServers) {
|
||||
// Merge .mcp.json servers into project config
|
||||
// Project's .mcp.json has lower priority than ~/.claude.json projects[path].mcpServers
|
||||
const existingServers = result.projects[projectPath]?.mcpServers || {};
|
||||
result.projects[projectPath] = {
|
||||
...result.projects[projectPath],
|
||||
mcpServers: {
|
||||
...mcpJsonConfig.mcpServers, // .mcp.json (lower priority)
|
||||
...existingServers // ~/.claude.json projects[path] (higher priority)
|
||||
},
|
||||
mcpJsonPath: mcpJsonPath // Track source for debugging
|
||||
};
|
||||
result.configSources.push({ type: 'project-mcp-json', path: mcpJsonPath, count: Object.keys(mcpJsonConfig.mcpServers).length });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Build globalServers by merging user and enterprise servers
|
||||
// Enterprise servers override user servers
|
||||
result.globalServers = {
|
||||
...result.userServers,
|
||||
...result.enterpriseServers
|
||||
};
|
||||
|
||||
return result;
|
||||
} catch (error) {
|
||||
console.error('Error reading MCP config:', error);
|
||||
return { projects: {}, error: error.message };
|
||||
return { projects: {}, globalServers: {}, userServers: {}, enterpriseServers: {}, configSources: [], error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1346,3 +1589,360 @@ function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// ========================================
|
||||
// Explorer View Functions
|
||||
// ========================================
|
||||
|
||||
// Directories to always exclude from file tree
|
||||
const EXPLORER_EXCLUDE_DIRS = [
|
||||
'.git', '__pycache__', 'node_modules', '.venv', 'venv', 'env',
|
||||
'dist', 'build', '.cache', '.pytest_cache', '.mypy_cache',
|
||||
'coverage', '.nyc_output', 'logs', 'tmp', 'temp', '.next',
|
||||
'.nuxt', '.output', '.turbo', '.parcel-cache'
|
||||
];
|
||||
|
||||
// File extensions to language mapping for syntax highlighting
|
||||
const EXT_TO_LANGUAGE = {
|
||||
'.js': 'javascript',
|
||||
'.jsx': 'javascript',
|
||||
'.ts': 'typescript',
|
||||
'.tsx': 'typescript',
|
||||
'.py': 'python',
|
||||
'.rb': 'ruby',
|
||||
'.java': 'java',
|
||||
'.go': 'go',
|
||||
'.rs': 'rust',
|
||||
'.c': 'c',
|
||||
'.cpp': 'cpp',
|
||||
'.h': 'c',
|
||||
'.hpp': 'cpp',
|
||||
'.cs': 'csharp',
|
||||
'.php': 'php',
|
||||
'.swift': 'swift',
|
||||
'.kt': 'kotlin',
|
||||
'.scala': 'scala',
|
||||
'.sh': 'bash',
|
||||
'.bash': 'bash',
|
||||
'.zsh': 'bash',
|
||||
'.ps1': 'powershell',
|
||||
'.sql': 'sql',
|
||||
'.html': 'html',
|
||||
'.htm': 'html',
|
||||
'.css': 'css',
|
||||
'.scss': 'scss',
|
||||
'.sass': 'sass',
|
||||
'.less': 'less',
|
||||
'.json': 'json',
|
||||
'.xml': 'xml',
|
||||
'.yaml': 'yaml',
|
||||
'.yml': 'yaml',
|
||||
'.toml': 'toml',
|
||||
'.ini': 'ini',
|
||||
'.cfg': 'ini',
|
||||
'.conf': 'nginx',
|
||||
'.md': 'markdown',
|
||||
'.markdown': 'markdown',
|
||||
'.txt': 'plaintext',
|
||||
'.log': 'plaintext',
|
||||
'.env': 'bash',
|
||||
'.dockerfile': 'dockerfile',
|
||||
'.vue': 'html',
|
||||
'.svelte': 'html'
|
||||
};
|
||||
|
||||
/**
|
||||
* Parse .gitignore file and return patterns
|
||||
* @param {string} gitignorePath - Path to .gitignore file
|
||||
* @returns {string[]} Array of gitignore patterns
|
||||
*/
|
||||
function parseGitignore(gitignorePath) {
|
||||
try {
|
||||
if (!existsSync(gitignorePath)) return [];
|
||||
const content = readFileSync(gitignorePath, 'utf8');
|
||||
return content
|
||||
.split('\n')
|
||||
.map(line => line.trim())
|
||||
.filter(line => line && !line.startsWith('#'));
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a file/directory should be ignored based on gitignore patterns
|
||||
* Simple pattern matching (supports basic glob patterns)
|
||||
* @param {string} name - File or directory name
|
||||
* @param {string[]} patterns - Gitignore patterns
|
||||
* @param {boolean} isDirectory - Whether the entry is a directory
|
||||
* @returns {boolean}
|
||||
*/
|
||||
function shouldIgnore(name, patterns, isDirectory) {
|
||||
// Always exclude certain directories
|
||||
if (isDirectory && EXPLORER_EXCLUDE_DIRS.includes(name)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Skip hidden files/directories (starting with .)
|
||||
if (name.startsWith('.') && name !== '.claude' && name !== '.workflow') {
|
||||
return true;
|
||||
}
|
||||
|
||||
for (const pattern of patterns) {
|
||||
let p = pattern;
|
||||
|
||||
// Handle negation patterns (we skip them for simplicity)
|
||||
if (p.startsWith('!')) continue;
|
||||
|
||||
// Handle directory-only patterns
|
||||
if (p.endsWith('/')) {
|
||||
if (!isDirectory) continue;
|
||||
p = p.slice(0, -1);
|
||||
}
|
||||
|
||||
// Simple pattern matching
|
||||
if (p === name) return true;
|
||||
|
||||
// Handle wildcard patterns
|
||||
if (p.includes('*')) {
|
||||
const regex = new RegExp('^' + p.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$');
|
||||
if (regex.test(name)) return true;
|
||||
}
|
||||
|
||||
// Handle extension patterns like *.log
|
||||
if (p.startsWith('*.')) {
|
||||
const ext = p.slice(1);
|
||||
if (name.endsWith(ext)) return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* List directory files with .gitignore filtering
|
||||
* @param {string} dirPath - Directory path to list
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function listDirectoryFiles(dirPath) {
|
||||
try {
|
||||
// Normalize path
|
||||
let normalizedPath = dirPath.replace(/\\/g, '/');
|
||||
if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
|
||||
normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
|
||||
}
|
||||
|
||||
if (!existsSync(normalizedPath)) {
|
||||
return { error: 'Directory not found', files: [] };
|
||||
}
|
||||
|
||||
if (!statSync(normalizedPath).isDirectory()) {
|
||||
return { error: 'Not a directory', files: [] };
|
||||
}
|
||||
|
||||
// Parse .gitignore patterns
|
||||
const gitignorePath = join(normalizedPath, '.gitignore');
|
||||
const gitignorePatterns = parseGitignore(gitignorePath);
|
||||
|
||||
// Read directory entries
|
||||
const entries = readdirSync(normalizedPath, { withFileTypes: true });
|
||||
|
||||
const files = [];
|
||||
for (const entry of entries) {
|
||||
const isDirectory = entry.isDirectory();
|
||||
|
||||
// Check if should be ignored
|
||||
if (shouldIgnore(entry.name, gitignorePatterns, isDirectory)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const entryPath = join(normalizedPath, entry.name);
|
||||
const fileInfo = {
|
||||
name: entry.name,
|
||||
type: isDirectory ? 'directory' : 'file',
|
||||
path: entryPath.replace(/\\/g, '/')
|
||||
};
|
||||
|
||||
// Check if directory has CLAUDE.md
|
||||
if (isDirectory) {
|
||||
const claudeMdPath = join(entryPath, 'CLAUDE.md');
|
||||
fileInfo.hasClaudeMd = existsSync(claudeMdPath);
|
||||
}
|
||||
|
||||
files.push(fileInfo);
|
||||
}
|
||||
|
||||
// Sort: directories first, then alphabetically
|
||||
files.sort((a, b) => {
|
||||
if (a.type === 'directory' && b.type !== 'directory') return -1;
|
||||
if (a.type !== 'directory' && b.type === 'directory') return 1;
|
||||
return a.name.localeCompare(b.name);
|
||||
});
|
||||
|
||||
return {
|
||||
path: normalizedPath.replace(/\\/g, '/'),
|
||||
files,
|
||||
gitignorePatterns
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error listing directory:', error);
|
||||
return { error: error.message, files: [] };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file content for preview
|
||||
* @param {string} filePath - Path to file
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getFileContent(filePath) {
|
||||
try {
|
||||
// Normalize path
|
||||
let normalizedPath = filePath.replace(/\\/g, '/');
|
||||
if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
|
||||
normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
|
||||
}
|
||||
|
||||
if (!existsSync(normalizedPath)) {
|
||||
return { error: 'File not found' };
|
||||
}
|
||||
|
||||
const stats = statSync(normalizedPath);
|
||||
if (stats.isDirectory()) {
|
||||
return { error: 'Cannot read directory' };
|
||||
}
|
||||
|
||||
// Check file size (limit to 1MB for preview)
|
||||
if (stats.size > 1024 * 1024) {
|
||||
return { error: 'File too large for preview (max 1MB)', size: stats.size };
|
||||
}
|
||||
|
||||
// Read file content
|
||||
const content = readFileSync(normalizedPath, 'utf8');
|
||||
const ext = normalizedPath.substring(normalizedPath.lastIndexOf('.')).toLowerCase();
|
||||
const language = EXT_TO_LANGUAGE[ext] || 'plaintext';
|
||||
const isMarkdown = ext === '.md' || ext === '.markdown';
|
||||
const fileName = normalizedPath.split('/').pop();
|
||||
|
||||
return {
|
||||
content,
|
||||
language,
|
||||
isMarkdown,
|
||||
fileName,
|
||||
path: normalizedPath,
|
||||
size: stats.size,
|
||||
lines: content.split('\n').length
|
||||
};
|
||||
} catch (error) {
|
||||
console.error('Error reading file:', error);
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger update-module-claude tool (async execution)
|
||||
* @param {string} targetPath - Directory path to update
|
||||
* @param {string} tool - CLI tool to use (gemini, qwen, codex)
|
||||
* @param {string} strategy - Update strategy (single-layer, multi-layer)
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function triggerUpdateClaudeMd(targetPath, tool, strategy) {
|
||||
const { spawn } = await import('child_process');
|
||||
|
||||
// Normalize path
|
||||
let normalizedPath = targetPath.replace(/\\/g, '/');
|
||||
if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
|
||||
normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
|
||||
}
|
||||
|
||||
if (!existsSync(normalizedPath)) {
|
||||
return { error: 'Directory not found' };
|
||||
}
|
||||
|
||||
if (!statSync(normalizedPath).isDirectory()) {
|
||||
return { error: 'Not a directory' };
|
||||
}
|
||||
|
||||
// Build ccw tool command with JSON parameters
|
||||
const params = JSON.stringify({
|
||||
strategy,
|
||||
path: normalizedPath,
|
||||
tool
|
||||
});
|
||||
|
||||
console.log(`[Explorer] Running async: ccw tool exec update_module_claude with ${tool} (${strategy})`);
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const isWindows = process.platform === 'win32';
|
||||
|
||||
// Spawn the process
|
||||
const child = spawn('ccw', ['tool', 'exec', 'update_module_claude', params], {
|
||||
cwd: normalizedPath,
|
||||
shell: isWindows,
|
||||
stdio: ['ignore', 'pipe', 'pipe']
|
||||
});
|
||||
|
||||
let stdout = '';
|
||||
let stderr = '';
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdout += data.toString();
|
||||
});
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderr += data.toString();
|
||||
});
|
||||
|
||||
child.on('close', (code) => {
|
||||
if (code === 0) {
|
||||
// Parse the JSON output from the tool
|
||||
let result;
|
||||
try {
|
||||
result = JSON.parse(stdout);
|
||||
} catch {
|
||||
result = { output: stdout };
|
||||
}
|
||||
|
||||
if (result.success === false || result.error) {
|
||||
resolve({
|
||||
success: false,
|
||||
error: result.error || result.message || 'Update failed',
|
||||
output: stdout
|
||||
});
|
||||
} else {
|
||||
resolve({
|
||||
success: true,
|
||||
message: result.message || `CLAUDE.md updated successfully using ${tool} (${strategy})`,
|
||||
output: stdout,
|
||||
path: normalizedPath
|
||||
});
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
success: false,
|
||||
error: stderr || `Process exited with code ${code}`,
|
||||
output: stdout + stderr
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
child.on('error', (error) => {
|
||||
console.error('Error spawning process:', error);
|
||||
resolve({
|
||||
success: false,
|
||||
error: error.message,
|
||||
output: ''
|
||||
});
|
||||
});
|
||||
|
||||
// Timeout after 5 minutes
|
||||
setTimeout(() => {
|
||||
child.kill();
|
||||
resolve({
|
||||
success: false,
|
||||
error: 'Timeout: Process took longer than 5 minutes',
|
||||
output: stdout
|
||||
});
|
||||
}, 300000);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,385 +0,0 @@
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { readFileSync, existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
||||
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
||||
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
||||
|
||||
/**
|
||||
* Create and start the dashboard server
|
||||
* @param {Object} options - Server options
|
||||
* @param {number} options.port - Port to listen on (default: 3456)
|
||||
* @param {string} options.initialPath - Initial project path
|
||||
* @returns {Promise<http.Server>}
|
||||
*/
|
||||
export async function startServer(options = {}) {
|
||||
const port = options.port || 3456;
|
||||
const initialPath = options.initialPath || process.cwd();
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${port}`);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// CORS headers for API requests
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// API: Get workflow data for a path
|
||||
if (pathname === '/api/data') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const data = await getWorkflowData(projectPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get recent paths
|
||||
if (pathname === '/api/recent-paths') {
|
||||
const paths = getRecentPaths();
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ paths }));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||
if (pathname === '/api/session-detail') {
|
||||
const sessionPath = url.searchParams.get('path');
|
||||
const dataType = url.searchParams.get('type') || 'all';
|
||||
|
||||
if (!sessionPath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Session path is required' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = await getSessionDetailData(sessionPath, dataType);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(detail));
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve dashboard HTML
|
||||
if (pathname === '/' || pathname === '/index.html') {
|
||||
const html = generateServerDashboard(initialPath);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(html);
|
||||
return;
|
||||
}
|
||||
|
||||
// 404
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(port, () => {
|
||||
console.log(`Dashboard server running at http://localhost:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow data for a project path
|
||||
* @param {string} projectPath
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getWorkflowData(projectPath) {
|
||||
const resolvedPath = resolvePath(projectPath);
|
||||
const workflowDir = join(resolvedPath, '.workflow');
|
||||
|
||||
// Track this path
|
||||
trackRecentPath(resolvedPath);
|
||||
|
||||
// Check if .workflow exists
|
||||
if (!existsSync(workflowDir)) {
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
},
|
||||
projectPath: normalizePathForDisplay(resolvedPath),
|
||||
recentPaths: getRecentPaths()
|
||||
};
|
||||
}
|
||||
|
||||
// Scan and aggregate data
|
||||
const sessions = await scanSessions(workflowDir);
|
||||
const data = await aggregateData(sessions, workflowDir);
|
||||
|
||||
data.projectPath = normalizePathForDisplay(resolvedPath);
|
||||
data.recentPaths = getRecentPaths();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session detail data (context, summaries, impl-plan, review)
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getSessionDetailData(sessionPath, dataType) {
|
||||
const result = {};
|
||||
|
||||
// Normalize path
|
||||
const normalizedPath = sessionPath.replace(/\\/g, '/');
|
||||
|
||||
try {
|
||||
// Load context-package.json (in .process/ subfolder)
|
||||
if (dataType === 'context' || dataType === 'all') {
|
||||
// Try .process/context-package.json first (common location)
|
||||
let contextFile = join(normalizedPath, '.process', 'context-package.json');
|
||||
if (!existsSync(contextFile)) {
|
||||
// Fallback to session root
|
||||
contextFile = join(normalizedPath, 'context-package.json');
|
||||
}
|
||||
if (existsSync(contextFile)) {
|
||||
try {
|
||||
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load task JSONs from .task/ folder
|
||||
if (dataType === 'tasks' || dataType === 'all') {
|
||||
const taskDir = join(normalizedPath, '.task');
|
||||
result.tasks = [];
|
||||
if (existsSync(taskDir)) {
|
||||
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
|
||||
result.tasks.push({
|
||||
filename: file,
|
||||
task_id: file.replace('.json', ''),
|
||||
...content
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
// Sort by task ID
|
||||
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Load summaries from .summaries/
|
||||
if (dataType === 'summary' || dataType === 'all') {
|
||||
const summariesDir = join(normalizedPath, '.summaries');
|
||||
result.summaries = [];
|
||||
if (existsSync(summariesDir)) {
|
||||
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = readFileSync(join(summariesDir, file), 'utf8');
|
||||
result.summaries.push({ name: file.replace('.md', ''), content });
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load plan.json (for lite tasks)
|
||||
if (dataType === 'plan' || dataType === 'all') {
|
||||
const planFile = join(normalizedPath, 'plan.json');
|
||||
if (existsSync(planFile)) {
|
||||
try {
|
||||
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.plan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load IMPL_PLAN.md
|
||||
if (dataType === 'impl-plan' || dataType === 'all') {
|
||||
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
|
||||
if (existsSync(implPlanFile)) {
|
||||
try {
|
||||
result.implPlan = readFileSync(implPlanFile, 'utf8');
|
||||
} catch (e) {
|
||||
result.implPlan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load review data from .review/
|
||||
if (dataType === 'review' || dataType === 'all') {
|
||||
const reviewDir = join(normalizedPath, '.review');
|
||||
result.review = {
|
||||
state: null,
|
||||
dimensions: [],
|
||||
severityDistribution: null,
|
||||
totalFindings: 0
|
||||
};
|
||||
|
||||
if (existsSync(reviewDir)) {
|
||||
// Load review-state.json
|
||||
const stateFile = join(reviewDir, 'review-state.json');
|
||||
if (existsSync(stateFile)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
||||
result.review.state = state;
|
||||
result.review.severityDistribution = state.severity_distribution || {};
|
||||
result.review.totalFindings = state.total_findings || 0;
|
||||
result.review.phase = state.phase || 'unknown';
|
||||
result.review.dimensionSummaries = state.dimension_summaries || {};
|
||||
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
|
||||
result.review.criticalFiles = state.critical_files || [];
|
||||
} catch (e) {
|
||||
// Skip unreadable state
|
||||
}
|
||||
}
|
||||
|
||||
// Load dimension findings
|
||||
const dimensionsDir = join(reviewDir, 'dimensions');
|
||||
if (existsSync(dimensionsDir)) {
|
||||
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const dimName = file.replace('.json', '');
|
||||
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
||||
|
||||
// Handle array structure: [ { findings: [...] } ]
|
||||
let findings = [];
|
||||
let summary = null;
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const dimData = data[0];
|
||||
findings = dimData.findings || [];
|
||||
summary = dimData.summary || null;
|
||||
} else if (data.findings) {
|
||||
findings = data.findings;
|
||||
summary = data.summary || null;
|
||||
}
|
||||
|
||||
result.review.dimensions.push({
|
||||
name: dimName,
|
||||
findings: findings,
|
||||
summary: summary,
|
||||
count: findings.length
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading session detail:', error);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard HTML for server mode
|
||||
* @param {string} initialPath
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateServerDashboard(initialPath) {
|
||||
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
||||
|
||||
// Read CSS and JS files
|
||||
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
|
||||
|
||||
// Inject CSS content
|
||||
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
||||
|
||||
// Prepare JS content with empty initial data (will be loaded dynamically)
|
||||
const emptyData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
||||
};
|
||||
|
||||
// Replace JS placeholders
|
||||
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
|
||||
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
|
||||
|
||||
// Add server mode flag and dynamic loading functions at the start of JS
|
||||
const serverModeScript = `
|
||||
// Server mode - load data dynamically
|
||||
window.SERVER_MODE = true;
|
||||
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
|
||||
|
||||
async function loadDashboardData(path) {
|
||||
try {
|
||||
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
|
||||
if (!res.ok) throw new Error('Failed to load data');
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('Error loading data:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentPaths() {
|
||||
try {
|
||||
const res = await fetch('/api/recent-paths');
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.paths || [];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// Prepend server mode script to JS content
|
||||
jsContent = serverModeScript + jsContent;
|
||||
|
||||
// Inject JS content
|
||||
html = html.replace('{{JS_CONTENT}}', jsContent);
|
||||
|
||||
// Replace any remaining placeholders in HTML
|
||||
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
|
||||
return html;
|
||||
}
|
||||
@@ -1,385 +0,0 @@
|
||||
import http from 'http';
|
||||
import { URL } from 'url';
|
||||
import { readFileSync, existsSync, readdirSync } from 'fs';
|
||||
import { join } from 'path';
|
||||
import { scanSessions } from './session-scanner.js';
|
||||
import { aggregateData } from './data-aggregator.js';
|
||||
import { resolvePath, getRecentPaths, trackRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
||||
|
||||
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
||||
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
||||
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
||||
|
||||
/**
|
||||
* Create and start the dashboard server
|
||||
* @param {Object} options - Server options
|
||||
* @param {number} options.port - Port to listen on (default: 3456)
|
||||
* @param {string} options.initialPath - Initial project path
|
||||
* @returns {Promise<http.Server>}
|
||||
*/
|
||||
export async function startServer(options = {}) {
|
||||
const port = options.port || 3456;
|
||||
const initialPath = options.initialPath || process.cwd();
|
||||
|
||||
const server = http.createServer(async (req, res) => {
|
||||
const url = new URL(req.url, `http://localhost:${port}`);
|
||||
const pathname = url.pathname;
|
||||
|
||||
// CORS headers for API requests
|
||||
res.setHeader('Access-Control-Allow-Origin', '*');
|
||||
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
||||
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
||||
|
||||
if (req.method === 'OPTIONS') {
|
||||
res.writeHead(200);
|
||||
res.end();
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// API: Get workflow data for a path
|
||||
if (pathname === '/api/data') {
|
||||
const projectPath = url.searchParams.get('path') || initialPath;
|
||||
const data = await getWorkflowData(projectPath);
|
||||
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(data));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get recent paths
|
||||
if (pathname === '/api/recent-paths') {
|
||||
const paths = getRecentPaths();
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ paths }));
|
||||
return;
|
||||
}
|
||||
|
||||
// API: Get session detail data (context, summaries, impl-plan, review)
|
||||
if (pathname === '/api/session-detail') {
|
||||
const sessionPath = url.searchParams.get('path');
|
||||
const dataType = url.searchParams.get('type') || 'all';
|
||||
|
||||
if (!sessionPath) {
|
||||
res.writeHead(400, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: 'Session path is required' }));
|
||||
return;
|
||||
}
|
||||
|
||||
const detail = await getSessionDetailData(sessionPath, dataType);
|
||||
res.writeHead(200, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify(detail));
|
||||
return;
|
||||
}
|
||||
|
||||
// Serve dashboard HTML
|
||||
if (pathname === '/' || pathname === '/index.html') {
|
||||
const html = generateServerDashboard(initialPath);
|
||||
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
||||
res.end(html);
|
||||
return;
|
||||
}
|
||||
|
||||
// 404
|
||||
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
||||
res.end('Not Found');
|
||||
|
||||
} catch (error) {
|
||||
console.error('Server error:', error);
|
||||
res.writeHead(500, { 'Content-Type': 'application/json' });
|
||||
res.end(JSON.stringify({ error: error.message }));
|
||||
}
|
||||
});
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
server.listen(port, () => {
|
||||
console.log(`Dashboard server running at http://localhost:${port}`);
|
||||
resolve(server);
|
||||
});
|
||||
server.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Get workflow data for a project path
|
||||
* @param {string} projectPath
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getWorkflowData(projectPath) {
|
||||
const resolvedPath = resolvePath(projectPath);
|
||||
const workflowDir = join(resolvedPath, '.workflow');
|
||||
|
||||
// Track this path
|
||||
trackRecentPath(resolvedPath);
|
||||
|
||||
// Check if .workflow exists
|
||||
if (!existsSync(workflowDir)) {
|
||||
return {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: {
|
||||
totalSessions: 0,
|
||||
activeSessions: 0,
|
||||
totalTasks: 0,
|
||||
completedTasks: 0,
|
||||
reviewFindings: 0,
|
||||
litePlanCount: 0,
|
||||
liteFixCount: 0
|
||||
},
|
||||
projectPath: normalizePathForDisplay(resolvedPath),
|
||||
recentPaths: getRecentPaths()
|
||||
};
|
||||
}
|
||||
|
||||
// Scan and aggregate data
|
||||
const sessions = await scanSessions(workflowDir);
|
||||
const data = await aggregateData(sessions, workflowDir);
|
||||
|
||||
data.projectPath = normalizePathForDisplay(resolvedPath);
|
||||
data.recentPaths = getRecentPaths();
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get session detail data (context, summaries, impl-plan, review)
|
||||
* @param {string} sessionPath - Path to session directory
|
||||
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
|
||||
* @returns {Promise<Object>}
|
||||
*/
|
||||
async function getSessionDetailData(sessionPath, dataType) {
|
||||
const result = {};
|
||||
|
||||
// Normalize path
|
||||
const normalizedPath = sessionPath.replace(/\\/g, '/');
|
||||
|
||||
try {
|
||||
// Load context-package.json (in .process/ subfolder)
|
||||
if (dataType === 'context' || dataType === 'all') {
|
||||
// Try .process/context-package.json first (common location)
|
||||
let contextFile = join(normalizedPath, '.process', 'context-package.json');
|
||||
if (!existsSync(contextFile)) {
|
||||
// Fallback to session root
|
||||
contextFile = join(normalizedPath, 'context-package.json');
|
||||
}
|
||||
if (existsSync(contextFile)) {
|
||||
try {
|
||||
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.context = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load task JSONs from .task/ folder
|
||||
if (dataType === 'tasks' || dataType === 'all') {
|
||||
const taskDir = join(normalizedPath, '.task');
|
||||
result.tasks = [];
|
||||
if (existsSync(taskDir)) {
|
||||
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
|
||||
result.tasks.push({
|
||||
filename: file,
|
||||
task_id: file.replace('.json', ''),
|
||||
...content
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
// Sort by task ID
|
||||
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
||||
}
|
||||
}
|
||||
|
||||
// Load summaries from .summaries/
|
||||
if (dataType === 'summary' || dataType === 'all') {
|
||||
const summariesDir = join(normalizedPath, '.summaries');
|
||||
result.summaries = [];
|
||||
if (existsSync(summariesDir)) {
|
||||
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const content = readFileSync(join(summariesDir, file), 'utf8');
|
||||
result.summaries.push({ name: file.replace('.md', ''), content });
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load plan.json (for lite tasks)
|
||||
if (dataType === 'plan' || dataType === 'all') {
|
||||
const planFile = join(normalizedPath, 'plan.json');
|
||||
if (existsSync(planFile)) {
|
||||
try {
|
||||
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
||||
} catch (e) {
|
||||
result.plan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load IMPL_PLAN.md
|
||||
if (dataType === 'impl-plan' || dataType === 'all') {
|
||||
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
|
||||
if (existsSync(implPlanFile)) {
|
||||
try {
|
||||
result.implPlan = readFileSync(implPlanFile, 'utf8');
|
||||
} catch (e) {
|
||||
result.implPlan = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Load review data from .review/
|
||||
if (dataType === 'review' || dataType === 'all') {
|
||||
const reviewDir = join(normalizedPath, '.review');
|
||||
result.review = {
|
||||
state: null,
|
||||
dimensions: [],
|
||||
severityDistribution: null,
|
||||
totalFindings: 0
|
||||
};
|
||||
|
||||
if (existsSync(reviewDir)) {
|
||||
// Load review-state.json
|
||||
const stateFile = join(reviewDir, 'review-state.json');
|
||||
if (existsSync(stateFile)) {
|
||||
try {
|
||||
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
||||
result.review.state = state;
|
||||
result.review.severityDistribution = state.severity_distribution || {};
|
||||
result.review.totalFindings = state.total_findings || 0;
|
||||
result.review.phase = state.phase || 'unknown';
|
||||
result.review.dimensionSummaries = state.dimension_summaries || {};
|
||||
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
|
||||
result.review.criticalFiles = state.critical_files || [];
|
||||
} catch (e) {
|
||||
// Skip unreadable state
|
||||
}
|
||||
}
|
||||
|
||||
// Load dimension findings
|
||||
const dimensionsDir = join(reviewDir, 'dimensions');
|
||||
if (existsSync(dimensionsDir)) {
|
||||
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
|
||||
for (const file of files) {
|
||||
try {
|
||||
const dimName = file.replace('.json', '');
|
||||
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
||||
|
||||
// Handle array structure: [ { findings: [...] } ]
|
||||
let findings = [];
|
||||
let summary = null;
|
||||
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
const dimData = data[0];
|
||||
findings = dimData.findings || [];
|
||||
summary = dimData.summary || null;
|
||||
} else if (data.findings) {
|
||||
findings = data.findings;
|
||||
summary = data.summary || null;
|
||||
}
|
||||
|
||||
result.review.dimensions.push({
|
||||
name: dimName,
|
||||
findings: findings,
|
||||
summary: summary,
|
||||
count: findings.length
|
||||
});
|
||||
} catch (e) {
|
||||
// Skip unreadable files
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error('Error loading session detail:', error);
|
||||
result.error = error.message;
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate dashboard HTML for server mode
|
||||
* @param {string} initialPath
|
||||
* @returns {string}
|
||||
*/
|
||||
function generateServerDashboard(initialPath) {
|
||||
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
||||
|
||||
// Read CSS and JS files
|
||||
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
||||
let jsContent = existsSync(JS_FILE) ? readFileSync(JS_FILE, 'utf8') : '';
|
||||
|
||||
// Inject CSS content
|
||||
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
||||
|
||||
// Prepare JS content with empty initial data (will be loaded dynamically)
|
||||
const emptyData = {
|
||||
generatedAt: new Date().toISOString(),
|
||||
activeSessions: [],
|
||||
archivedSessions: [],
|
||||
liteTasks: { litePlan: [], liteFix: [] },
|
||||
reviewData: { dimensions: {} },
|
||||
projectOverview: null,
|
||||
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
||||
};
|
||||
|
||||
// Replace JS placeholders
|
||||
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
|
||||
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
|
||||
|
||||
// Add server mode flag and dynamic loading functions at the start of JS
|
||||
const serverModeScript = `
|
||||
// Server mode - load data dynamically
|
||||
window.SERVER_MODE = true;
|
||||
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
|
||||
|
||||
async function loadDashboardData(path) {
|
||||
try {
|
||||
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
|
||||
if (!res.ok) throw new Error('Failed to load data');
|
||||
return await res.json();
|
||||
} catch (err) {
|
||||
console.error('Error loading data:', err);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadRecentPaths() {
|
||||
try {
|
||||
const res = await fetch('/api/recent-paths');
|
||||
if (!res.ok) return [];
|
||||
const data = await res.json();
|
||||
return data.paths || [];
|
||||
} catch (err) {
|
||||
return [];
|
||||
}
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
// Prepend server mode script to JS content
|
||||
jsContent = serverModeScript + jsContent;
|
||||
|
||||
// Inject JS content
|
||||
html = html.replace('{{JS_CONTENT}}', jsContent);
|
||||
|
||||
// Replace any remaining placeholders in HTML
|
||||
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
||||
|
||||
return html;
|
||||
}
|
||||
161
ccw/src/templates/dashboard-css/01-base.css
Normal file
161
ccw/src/templates/dashboard-css/01-base.css
Normal file
@@ -0,0 +1,161 @@
|
||||
/* ===================================
|
||||
Dashboard - Complementary Styles
|
||||
================================== */
|
||||
|
||||
/* This file contains only essential CSS that cannot be achieved
|
||||
with Tailwind utilities. All layout, colors, and basic styling
|
||||
are handled by Tailwind classes in dashboard.html.
|
||||
|
||||
CSS variables are defined inline in dashboard.html <style> block. */
|
||||
|
||||
/* Font Family Definitions */
|
||||
:root {
|
||||
--font-sans: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
--font-mono: 'SF Mono', 'Consolas', 'Monaco', 'Liberation Mono', 'Courier New', monospace;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: var(--font-sans);
|
||||
}
|
||||
|
||||
/* Scrollbar styling (cannot be done in Tailwind) */
|
||||
::-webkit-scrollbar {
|
||||
width: 6px;
|
||||
height: 6px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-track {
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb {
|
||||
background: hsl(var(--border));
|
||||
border-radius: 3px;
|
||||
}
|
||||
|
||||
::-webkit-scrollbar-thumb:hover {
|
||||
background: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Sidebar collapse state (JavaScript-toggled class) */
|
||||
.sidebar.collapsed {
|
||||
width: 60px;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-text,
|
||||
.sidebar.collapsed .nav-section-title,
|
||||
.sidebar.collapsed .badge,
|
||||
.sidebar.collapsed .toggle-text {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-section-header {
|
||||
justify-content: center;
|
||||
padding: 12px 0;
|
||||
}
|
||||
|
||||
.sidebar.collapsed .nav-item {
|
||||
justify-content: center;
|
||||
padding: 10px 0;
|
||||
}
|
||||
|
||||
/* Nav item active state */
|
||||
.nav-item.active {
|
||||
background-color: hsl(var(--accent));
|
||||
color: hsl(var(--primary));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.nav-item.active .nav-icon {
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.nav-item.active .nav-count {
|
||||
background-color: hsl(var(--primary));
|
||||
color: white;
|
||||
}
|
||||
|
||||
/* Nav item specific color active states */
|
||||
.nav-item[data-filter="active"].active {
|
||||
background-color: hsl(var(--success-light));
|
||||
}
|
||||
|
||||
.nav-item[data-filter="active"].active .nav-icon {
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.nav-item[data-filter="archived"].active {
|
||||
background-color: hsl(var(--info-light));
|
||||
}
|
||||
|
||||
.nav-item[data-filter="archived"].active .nav-icon {
|
||||
color: hsl(var(--info));
|
||||
}
|
||||
|
||||
.nav-item[data-lite="lite-plan"].active {
|
||||
background-color: hsl(var(--indigo-light));
|
||||
}
|
||||
|
||||
.nav-item[data-lite="lite-plan"].active .nav-icon {
|
||||
color: hsl(var(--indigo));
|
||||
}
|
||||
|
||||
.nav-item[data-lite="lite-fix"].active {
|
||||
background-color: hsl(var(--orange-light));
|
||||
}
|
||||
|
||||
.nav-item[data-lite="lite-fix"].active .nav-icon {
|
||||
color: hsl(var(--orange));
|
||||
}
|
||||
|
||||
.sidebar.collapsed .toggle-icon {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
|
||||
/* Path menu open state (JavaScript-toggled class) */
|
||||
.path-menu.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* Mobile sidebar (responsive behavior beyond Tailwind) */
|
||||
@media (max-width: 768px) {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
left: -260px;
|
||||
top: 56px;
|
||||
height: calc(100vh - 56px);
|
||||
z-index: 200;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
|
||||
}
|
||||
|
||||
.sidebar.open {
|
||||
left: 0;
|
||||
}
|
||||
|
||||
.sidebar-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.sidebar-overlay.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.menu-toggle-btn {
|
||||
display: block !important;
|
||||
}
|
||||
}
|
||||
|
||||
/* Task detail drawer (complex transform animation) */
|
||||
.task-detail-drawer {
|
||||
transform: translateX(100%);
|
||||
transition: transform 0.3s ease;
|
||||
}
|
||||
|
||||
.task-detail-drawer.open {
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.drawer-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
726
ccw/src/templates/dashboard-css/02-session.css
Normal file
726
ccw/src/templates/dashboard-css/02-session.css
Normal file
@@ -0,0 +1,726 @@
|
||||
/* ===================================
|
||||
Session Cards (used by dashboard.js)
|
||||
=================================== */
|
||||
|
||||
.sessions-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.session-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.session-card:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
|
||||
transform: translateY(-2px);
|
||||
}
|
||||
|
||||
.session-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.session-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--foreground));
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.session-badges {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.session-status {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 600;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.session-status.active {
|
||||
background: hsl(var(--success-light));
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.session-status.archived {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.session-type-badge {
|
||||
font-size: 0.65rem;
|
||||
font-weight: 500;
|
||||
padding: 0.2rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
background: hsl(var(--accent));
|
||||
color: hsl(var(--accent-foreground));
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.session-type-badge.review {
|
||||
background: hsl(var(--warning-light));
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.session-type-badge.test {
|
||||
background: hsl(220 80% 90%);
|
||||
color: hsl(220 80% 40%);
|
||||
}
|
||||
|
||||
.session-body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.session-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.session-meta-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.progress-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.progress-label {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.progress-bar-wrapper {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.progress-bar {
|
||||
flex: 1;
|
||||
height: 6px;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 3px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.progress-fill {
|
||||
height: 100%;
|
||||
background: linear-gradient(90deg, hsl(var(--primary)), hsl(var(--success)));
|
||||
border-radius: 3px;
|
||||
transition: width 0.3s ease;
|
||||
}
|
||||
|
||||
.progress-text {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
/* Empty state */
|
||||
.empty-state {
|
||||
text-align: center;
|
||||
padding: 3rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.empty-state-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
/* Session detail page */
|
||||
.session-detail-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.session-detail-title {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.session-detail-meta {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.task-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.task-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
cursor: pointer;
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.task-item:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.task-item.completed {
|
||||
border-left: 3px solid hsl(var(--success));
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.task-item.in_progress {
|
||||
border-left: 3px solid hsl(var(--warning));
|
||||
}
|
||||
|
||||
.task-item.pending {
|
||||
border-left: 3px solid hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.task-checkbox {
|
||||
width: 1.25rem;
|
||||
height: 1.25rem;
|
||||
border-radius: 50%;
|
||||
border: 2px solid hsl(var(--border));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 0.7rem;
|
||||
font-weight: bold;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-item.completed .task-checkbox {
|
||||
background: hsl(var(--success));
|
||||
border-color: hsl(var(--success));
|
||||
color: white;
|
||||
}
|
||||
|
||||
.task-item.completed .task-checkbox::after {
|
||||
content: '✓';
|
||||
}
|
||||
|
||||
.task-item.in_progress .task-checkbox {
|
||||
border-color: hsl(var(--warning));
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.task-item.in_progress .task-checkbox::after {
|
||||
content: '⟳';
|
||||
}
|
||||
|
||||
.task-title {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.task-id {
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Back button */
|
||||
.back-button {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.back-button:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Path Dropdown Menu
|
||||
=================================== */
|
||||
|
||||
.path-menu {
|
||||
min-width: 320px;
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.path-menu .path-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.path-menu .path-item:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.path-menu .path-item:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
|
||||
.path-menu .path-icon {
|
||||
font-size: 1.25rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.path-menu .path-text {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
word-break: break-all;
|
||||
}
|
||||
|
||||
.path-menu .path-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.path-menu .path-text {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.path-delete-btn {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
border: none;
|
||||
background: transparent;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
opacity: 0;
|
||||
transition: opacity 0.15s, background 0.15s, color 0.15s;
|
||||
flex-shrink: 0;
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
.path-menu .path-item:hover .path-delete-btn {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.path-delete-btn:hover {
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Session Detail Page
|
||||
=================================== */
|
||||
|
||||
.session-detail-page {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1.5rem;
|
||||
}
|
||||
|
||||
.detail-header {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.btn-back {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
width: fit-content;
|
||||
}
|
||||
|
||||
.btn-back:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.back-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.detail-title-row {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.detail-session-id {
|
||||
font-size: 1.5rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
.detail-badges {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.detail-info-bar {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 1.5rem;
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.info-item {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.info-label {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.info-value {
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Detail Tabs */
|
||||
.detail-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.detail-tab {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.detail-tab:hover {
|
||||
color: hsl(var(--foreground));
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.detail-tab.active {
|
||||
color: hsl(var(--primary));
|
||||
border-bottom-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.tab-icon {
|
||||
font-size: 1rem;
|
||||
}
|
||||
|
||||
.tab-count {
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 9999px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.detail-tab.active .tab-count {
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
}
|
||||
|
||||
.detail-tab-content {
|
||||
min-height: 300px;
|
||||
}
|
||||
|
||||
/* Task Stats */
|
||||
.task-stats {
|
||||
display: flex;
|
||||
gap: 1rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.task-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.task-stat.completed { border-left: 3px solid hsl(var(--success)); }
|
||||
.task-stat.in-progress { border-left: 3px solid hsl(var(--warning)); }
|
||||
.task-stat.pending { border-left: 3px solid hsl(var(--muted-foreground)); }
|
||||
|
||||
.stat-count {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.stat-label {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Tab Loading */
|
||||
.tab-loading {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 3rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* Context/Summary Content */
|
||||
.context-content,
|
||||
.summary-content,
|
||||
.impl-plan-content,
|
||||
.review-content {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.context-section,
|
||||
.summary-section,
|
||||
.plan-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.context-section:last-child,
|
||||
.summary-section:last-child,
|
||||
.plan-section:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
/* Plan Tab Styles */
|
||||
.plan-tab-content {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.plan-section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plan-summary-text,
|
||||
.plan-approach-text {
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.6;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid hsl(var(--primary));
|
||||
}
|
||||
|
||||
.plan-meta-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.plan-meta-grid .meta-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.plan-meta-grid .meta-label {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Lite Task Detail Page */
|
||||
.lite-task-detail-page .detail-header {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.lite-task-detail-page .task-toolbar {
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.lite-task-detail-page .task-stat {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
/* Context Tab Content */
|
||||
.context-tab-content {
|
||||
padding: 1rem 0;
|
||||
}
|
||||
|
||||
.context-tab-content .context-section h4 {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.context-tab-content .context-section p {
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.75rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.file-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Code blocks */
|
||||
pre, code {
|
||||
font-family: var(--font-mono);
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
pre {
|
||||
padding: 1rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
code {
|
||||
padding: 0.125rem 0.25rem;
|
||||
font-size: 0.85em;
|
||||
}
|
||||
|
||||
/* Review Findings */
|
||||
.findings-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.finding-item {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.finding-item.critical { border-left-color: hsl(0 70% 50%); }
|
||||
.finding-item.high { border-left-color: hsl(25 90% 55%); }
|
||||
.finding-item.medium { border-left-color: hsl(45 90% 50%); }
|
||||
.finding-item.low { border-left-color: hsl(var(--success)); }
|
||||
|
||||
.finding-header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.finding-title {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.severity-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.severity-badge.critical { background: hsl(0 70% 90%); color: hsl(0 70% 40%); }
|
||||
.severity-badge.high { background: hsl(25 90% 90%); color: hsl(25 90% 35%); }
|
||||
.severity-badge.medium { background: hsl(45 90% 90%); color: hsl(45 90% 30%); }
|
||||
.severity-badge.low { background: hsl(var(--success-light)); color: hsl(var(--success)); }
|
||||
|
||||
.finding-description {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.finding-location {
|
||||
margin-top: 0.5rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
512
ccw/src/templates/dashboard-css/03-tasks.css
Normal file
512
ccw/src/templates/dashboard-css/03-tasks.css
Normal file
@@ -0,0 +1,512 @@
|
||||
/* ===================================
|
||||
Tasks Tab Content
|
||||
=================================== */
|
||||
|
||||
.tasks-tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
/* Task Toolbar - Combined Stats & Actions */
|
||||
.task-toolbar {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted) / 0.2);
|
||||
border: 1px solid hsl(var(--border) / 0.5);
|
||||
border-radius: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.toolbar-divider {
|
||||
width: 1px;
|
||||
height: 1.5rem;
|
||||
background: hsl(var(--border));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-stats-bar {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.task-stat {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 9999px;
|
||||
font-size: 0.8rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.task-stat.completed {
|
||||
border-color: hsl(142 76% 36% / 0.4);
|
||||
background: hsl(142 76% 36% / 0.1);
|
||||
color: hsl(142 76% 30%);
|
||||
}
|
||||
|
||||
.task-stat.in-progress {
|
||||
border-color: hsl(38 92% 50% / 0.4);
|
||||
background: hsl(38 92% 50% / 0.1);
|
||||
color: hsl(38 92% 40%);
|
||||
}
|
||||
|
||||
.task-stat.pending {
|
||||
border-color: hsl(var(--border));
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Responsive: Stack toolbar on smaller screens */
|
||||
@media (max-width: 768px) {
|
||||
.task-toolbar {
|
||||
flex-direction: column;
|
||||
align-items: stretch;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.toolbar-divider {
|
||||
width: 100%;
|
||||
height: 1px;
|
||||
}
|
||||
|
||||
.task-stats-bar {
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.task-bulk-actions {
|
||||
justify-content: center;
|
||||
}
|
||||
}
|
||||
|
||||
.tasks-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
/* Task Item */
|
||||
.detail-task-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 0.875rem 1rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid hsl(var(--muted-foreground));
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.detail-task-item:hover {
|
||||
background: hsl(var(--hover));
|
||||
box-shadow: 0 2px 8px rgb(0 0 0 / 0.08);
|
||||
}
|
||||
|
||||
.detail-task-item.completed {
|
||||
border-left-color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.detail-task-item.in_progress {
|
||||
border-left-color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.detail-task-item.pending {
|
||||
border-left-color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Status-based background colors for task cards */
|
||||
.detail-task-item.status-completed {
|
||||
background: hsl(var(--success) / 0.08);
|
||||
}
|
||||
|
||||
.detail-task-item.status-completed:hover {
|
||||
background: hsl(var(--success) / 0.12);
|
||||
}
|
||||
|
||||
.detail-task-item.status-in_progress {
|
||||
background: hsl(var(--warning) / 0.08);
|
||||
}
|
||||
|
||||
.detail-task-item.status-in_progress:hover {
|
||||
background: hsl(var(--warning) / 0.12);
|
||||
}
|
||||
|
||||
.detail-task-item.status-pending {
|
||||
background: hsl(var(--muted-foreground) / 0.05);
|
||||
}
|
||||
|
||||
.detail-task-item.status-pending:hover {
|
||||
background: hsl(var(--muted-foreground) / 0.08);
|
||||
}
|
||||
|
||||
.task-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
}
|
||||
|
||||
.task-status-icon {
|
||||
font-size: 1rem;
|
||||
width: 1.5rem;
|
||||
text-align: center;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.detail-task-item.completed .task-status-icon {
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.detail-task-item.in_progress .task-status-icon {
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.task-id-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
font-family: var(--font-mono);
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-item-header .task-title {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.task-status-badge {
|
||||
padding: 0.125rem 0.5rem;
|
||||
border-radius: 9999px;
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
text-transform: uppercase;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.task-status-badge.completed {
|
||||
background: hsl(var(--success-light));
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.task-status-badge.in_progress {
|
||||
background: hsl(var(--warning-light));
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.task-status-badge.pending {
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Full Task Item (expanded view) */
|
||||
.detail-task-item-full {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
border-left: 3px solid hsl(var(--muted-foreground));
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.detail-task-item-full.completed {
|
||||
border-left-color: hsl(var(--success));
|
||||
}
|
||||
|
||||
.detail-task-item-full.in_progress {
|
||||
border-left-color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.task-item-header-full {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.task-item-header-full:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
/* Empty State */
|
||||
.tab-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 4rem 2rem;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.empty-icon {
|
||||
font-size: 3rem;
|
||||
margin-bottom: 1rem;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.empty-title {
|
||||
font-size: 1.25rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-text {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
/* Tab Error */
|
||||
.tab-error {
|
||||
padding: 2rem;
|
||||
text-align: center;
|
||||
color: hsl(var(--destructive));
|
||||
background: hsl(var(--destructive) / 0.1);
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
/* Collapsible Sections */
|
||||
.collapsible-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--muted));
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.collapsible-icon {
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.collapsible-header.open .collapsible-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
padding: 1rem;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.collapsible-content.open {
|
||||
display: block;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Task Drawer (Sidebar Panel)
|
||||
=================================== */
|
||||
|
||||
.drawer-overlay {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drawer-overlay.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.drawer-task-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 1rem;
|
||||
padding-bottom: 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.drawer-tabs {
|
||||
display: flex;
|
||||
gap: 0.25rem;
|
||||
margin-bottom: 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.drawer-tab {
|
||||
padding: 0.625rem 1rem;
|
||||
background: none;
|
||||
border: none;
|
||||
border-bottom: 2px solid transparent;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.drawer-tab:hover {
|
||||
color: hsl(var(--foreground));
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.drawer-tab.active {
|
||||
color: hsl(var(--primary));
|
||||
border-bottom-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.drawer-tab-content {
|
||||
flex: 1;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.drawer-panel {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.drawer-panel.active {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.drawer-section {
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
.drawer-section-title {
|
||||
font-size: 0.875rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0 0 0.75rem 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.empty-section {
|
||||
padding: 1rem;
|
||||
text-align: center;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.875rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.375rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
/* Steps List */
|
||||
.steps-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.step-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 0.75rem;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
width: 1.5rem;
|
||||
height: 1.5rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border-radius: 50%;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.step-content {
|
||||
flex: 1;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.step-description {
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
/* Files List */
|
||||
.files-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.file-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.file-icon {
|
||||
font-size: 0.875rem;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
/* Test Commands */
|
||||
.test-commands {
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
.command-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem 0.75rem;
|
||||
background: hsl(220 13% 18%);
|
||||
color: hsl(142 71% 60%);
|
||||
border-radius: 0.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
margin-bottom: 0.375rem;
|
||||
}
|
||||
|
||||
/* Flowchart Container */
|
||||
.flowchart-container {
|
||||
min-height: 300px;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
.flowchart-container svg {
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* JSON View */
|
||||
.json-view {
|
||||
background: hsl(var(--muted));
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
|
||||
843
ccw/src/templates/dashboard-css/04-lite-tasks.css
Normal file
843
ccw/src/templates/dashboard-css/04-lite-tasks.css
Normal file
@@ -0,0 +1,843 @@
|
||||
/* ===================================
|
||||
Lite Task Detail Page Additions
|
||||
=================================== */
|
||||
|
||||
/* Path Tags */
|
||||
.path-tags {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.path-tag {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-family: var(--font-mono);
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* JSON Content */
|
||||
.json-content {
|
||||
background: hsl(var(--muted));
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
max-height: 500px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
/* Button View JSON */
|
||||
.btn-view-json {
|
||||
padding: 0.25rem 0.5rem;
|
||||
background: hsl(var(--muted));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
font-family: var(--font-mono);
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.btn-view-json:hover {
|
||||
background: hsl(var(--hover));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Context Fields */
|
||||
.context-fields {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.context-section {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
}
|
||||
|
||||
.context-section-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin-bottom: 0.75rem;
|
||||
padding-bottom: 0.5rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.context-field {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.context-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.025em;
|
||||
}
|
||||
|
||||
.context-value {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.context-field label {
|
||||
display: block;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.context-field p {
|
||||
margin: 0;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.context-field ul {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.context-field ul li {
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Modification Points */
|
||||
.mod-points {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.mod-point {
|
||||
padding: 0.5rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.mod-target {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-left: 0.5rem;
|
||||
}
|
||||
|
||||
.mod-change {
|
||||
margin: 0.5rem 0 0 0;
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Implementation Steps */
|
||||
.impl-steps {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.impl-steps li {
|
||||
margin-bottom: 0.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
/* Implementation Steps List (Drawer) */
|
||||
.impl-steps-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.impl-step-item {
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
transition: border-color 0.2s;
|
||||
}
|
||||
|
||||
.impl-step-item:hover {
|
||||
border-color: hsl(var(--primary) / 0.5);
|
||||
}
|
||||
|
||||
.impl-step-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.75rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.impl-step-number {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 1.75rem;
|
||||
height: 1.75rem;
|
||||
padding: 0 0.5rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.impl-step-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.9rem;
|
||||
color: hsl(var(--foreground));
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.impl-step-desc {
|
||||
font-size: 0.85rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.5;
|
||||
margin-bottom: 0.75rem;
|
||||
padding-left: 2.5rem;
|
||||
}
|
||||
|
||||
.impl-step-columns {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr 1fr;
|
||||
gap: 1rem;
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.75rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.impl-step-mods,
|
||||
.impl-step-flow {
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.impl-step-mods strong,
|
||||
.impl-step-flow strong {
|
||||
display: block;
|
||||
font-size: 0.7rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.05em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
.impl-step-mods ul,
|
||||
.impl-step-flow ol {
|
||||
margin: 0;
|
||||
padding-left: 1.25rem;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.impl-step-mods li,
|
||||
.impl-step-flow li {
|
||||
margin-bottom: 0.375rem;
|
||||
line-height: 1.4;
|
||||
}
|
||||
|
||||
.impl-step-mods code {
|
||||
font-size: 0.75rem;
|
||||
padding: 0.125rem 0.375rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.impl-step-deps {
|
||||
margin-top: 0.75rem;
|
||||
padding-top: 0.5rem;
|
||||
border-top: 1px dashed hsl(var(--border));
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.dep-badge {
|
||||
display: inline-block;
|
||||
padding: 0.125rem 0.5rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.7rem;
|
||||
margin-left: 0.25rem;
|
||||
}
|
||||
|
||||
/* Field Groups */
|
||||
.field-group {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.field-row {
|
||||
display: flex;
|
||||
gap: 0.5rem;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--muted-foreground));
|
||||
min-width: 100px;
|
||||
}
|
||||
|
||||
.field-value {
|
||||
color: hsl(var(--foreground));
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.json-value-null {
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.json-value-boolean {
|
||||
color: hsl(220 80% 60%);
|
||||
}
|
||||
|
||||
.json-value-number {
|
||||
color: hsl(142 71% 45%);
|
||||
}
|
||||
|
||||
.json-value-string {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Array Items */
|
||||
.array-value {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 0.375rem;
|
||||
}
|
||||
|
||||
.array-item {
|
||||
padding: 0.125rem 0.375rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.8rem;
|
||||
}
|
||||
|
||||
.array-item.path-item {
|
||||
font-family: var(--font-mono);
|
||||
background: hsl(var(--accent));
|
||||
}
|
||||
|
||||
/* Nested Array */
|
||||
.nested-array {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.5rem;
|
||||
margin-top: 0.25rem;
|
||||
}
|
||||
|
||||
.array-object {
|
||||
padding: 0.5rem;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.array-object-header {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
/* Collapsible Sections */
|
||||
.collapsible-section {
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.collapsible-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.collapsible-header:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.collapse-icon {
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
|
||||
.collapsible-header.expanded .collapse-icon {
|
||||
transform: rotate(90deg);
|
||||
}
|
||||
|
||||
.section-label {
|
||||
font-size: 0.75rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.section-preview {
|
||||
flex: 1;
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-align: right;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.collapsible-content {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--muted));
|
||||
}
|
||||
|
||||
.collapsible-content.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Summary Tab */
|
||||
.summary-tab-content {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.summary-item-collapsible {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.summary-collapsible-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.75rem 1rem;
|
||||
background: hsl(var(--card));
|
||||
cursor: pointer;
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.summary-collapsible-header:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.summary-name {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.summary-preview {
|
||||
flex: 1;
|
||||
font-size: 0.75rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.summary-collapsible-content {
|
||||
padding: 1rem;
|
||||
background: hsl(var(--muted));
|
||||
}
|
||||
|
||||
.summary-collapsible-content.collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.summary-content-pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
/* Summary Item Direct (No collapse) */
|
||||
.summary-item-direct {
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
background: hsl(var(--card));
|
||||
}
|
||||
|
||||
.summary-item-direct .summary-content-pre {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 0.375rem;
|
||||
}
|
||||
|
||||
.markdown-content {
|
||||
background: hsl(var(--muted));
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
font-size: 0.8rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
max-height: 600px;
|
||||
overflow-y: auto;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
/* JSON Modal */
|
||||
.json-modal-overlay {
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
background: rgba(0, 0, 0, 0.5);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
z-index: 100;
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s;
|
||||
}
|
||||
|
||||
.json-modal-overlay.active {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.json-modal {
|
||||
background: hsl(var(--card));
|
||||
border-radius: 0.5rem;
|
||||
width: 90%;
|
||||
max-width: 700px;
|
||||
max-height: 80vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 0.2);
|
||||
}
|
||||
|
||||
.json-modal-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
padding: 1rem;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.json-modal-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.json-modal-close {
|
||||
width: 2rem;
|
||||
height: 2rem;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: none;
|
||||
border: none;
|
||||
font-size: 1.5rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
cursor: pointer;
|
||||
border-radius: 0.25rem;
|
||||
}
|
||||
|
||||
.json-modal-close:hover {
|
||||
background: hsl(var(--hover));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.json-modal-body {
|
||||
flex: 1;
|
||||
overflow: auto;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.json-modal-content {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.6;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.json-modal-footer {
|
||||
padding: 1rem;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.btn-copy-json {
|
||||
padding: 0.5rem 1rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.875rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.btn-copy-json:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
/* Flowchart Fallback */
|
||||
.flowchart-fallback {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 200px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
/* ===================================
|
||||
Markdown Modal
|
||||
=================================== */
|
||||
.markdown-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.md-tab-btn {
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.md-tab-btn.active {
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.md-tab-btn:hover:not(.active) {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Markdown Preview Prose Styles */
|
||||
.markdown-preview {
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.7;
|
||||
}
|
||||
|
||||
.markdown-preview h1,
|
||||
.markdown-preview h2,
|
||||
.markdown-preview h3,
|
||||
.markdown-preview h4,
|
||||
.markdown-preview h5,
|
||||
.markdown-preview h6 {
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 600;
|
||||
margin-top: 1.5em;
|
||||
margin-bottom: 0.5em;
|
||||
line-height: 1.3;
|
||||
}
|
||||
|
||||
.markdown-preview h1 { font-size: 1.75rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0.3em; }
|
||||
.markdown-preview h2 { font-size: 1.5rem; border-bottom: 1px solid hsl(var(--border)); padding-bottom: 0.3em; }
|
||||
.markdown-preview h3 { font-size: 1.25rem; }
|
||||
.markdown-preview h4 { font-size: 1.1rem; }
|
||||
|
||||
.markdown-preview p {
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-preview ul,
|
||||
.markdown-preview ol {
|
||||
margin-bottom: 1em;
|
||||
padding-left: 1.5em;
|
||||
}
|
||||
|
||||
.markdown-preview li {
|
||||
margin-bottom: 0.25em;
|
||||
}
|
||||
|
||||
.markdown-preview code {
|
||||
background: hsl(var(--muted));
|
||||
padding: 0.125rem 0.375rem;
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.875em;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.markdown-preview pre {
|
||||
background: hsl(var(--muted));
|
||||
padding: 1rem;
|
||||
border-radius: 0.5rem;
|
||||
overflow-x: auto;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-preview pre code {
|
||||
background: none;
|
||||
padding: 0;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.markdown-preview blockquote {
|
||||
border-left: 3px solid hsl(var(--primary));
|
||||
padding-left: 1rem;
|
||||
margin-left: 0;
|
||||
margin-bottom: 1em;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
.markdown-preview table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin-bottom: 1em;
|
||||
}
|
||||
|
||||
.markdown-preview th,
|
||||
.markdown-preview td {
|
||||
border: 1px solid hsl(var(--border));
|
||||
padding: 0.5rem 0.75rem;
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.markdown-preview th {
|
||||
background: hsl(var(--muted));
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.markdown-preview a {
|
||||
color: hsl(var(--primary));
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.markdown-preview hr {
|
||||
border: none;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
margin: 1.5em 0;
|
||||
}
|
||||
|
||||
/* View Details Button */
|
||||
.btn-view-details {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.375rem;
|
||||
padding: 0.375rem 0.75rem;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 0.375rem;
|
||||
font-size: 0.8rem;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.btn-view-details:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.summary-item-card {
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.summary-item-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.summary-item-name {
|
||||
font-weight: 500;
|
||||
color: hsl(var(--foreground));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.summary-item-title {
|
||||
font-weight: 600;
|
||||
font-size: 0.95rem;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.summary-item-preview {
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-top: 0.5rem;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.summary-preview-text {
|
||||
margin: 0;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.5);
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 80px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.impl-plan-card {
|
||||
background: hsl(var(--muted) / 0.3);
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 0.5rem;
|
||||
padding: 1rem;
|
||||
}
|
||||
|
||||
.impl-plan-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 0.75rem;
|
||||
}
|
||||
|
||||
.impl-plan-title {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.impl-plan-preview {
|
||||
font-size: 0.8rem;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.impl-plan-preview-text {
|
||||
margin: 0;
|
||||
padding: 0.75rem;
|
||||
background: hsl(var(--muted) / 0.5);
|
||||
border-radius: 0.25rem;
|
||||
font-size: 0.75rem;
|
||||
line-height: 1.4;
|
||||
white-space: pre-wrap;
|
||||
word-break: break-word;
|
||||
max-height: 100px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
2206
ccw/src/templates/dashboard-css/05-context.css
Normal file
2206
ccw/src/templates/dashboard-css/05-context.css
Normal file
File diff suppressed because it is too large
Load Diff
1570
ccw/src/templates/dashboard-css/06-cards.css
Normal file
1570
ccw/src/templates/dashboard-css/06-cards.css
Normal file
File diff suppressed because it is too large
Load Diff
936
ccw/src/templates/dashboard-css/07-managers.css
Normal file
936
ccw/src/templates/dashboard-css/07-managers.css
Normal file
@@ -0,0 +1,936 @@
|
||||
/* ==========================================
|
||||
REFRESH BUTTON
|
||||
========================================== */
|
||||
|
||||
.refresh-btn {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.refresh-btn:hover {
|
||||
background: hsl(var(--muted));
|
||||
}
|
||||
|
||||
.refresh-btn.refreshing svg {
|
||||
animation: spin 1s linear infinite;
|
||||
}
|
||||
|
||||
.refresh-btn:disabled {
|
||||
opacity: 0.5;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
@keyframes spin {
|
||||
from { transform: rotate(0deg); }
|
||||
to { transform: rotate(360deg); }
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
MCP MANAGER STYLES
|
||||
========================================== */
|
||||
|
||||
.mcp-manager {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mcp-section {
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mcp-server-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.mcp-server-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mcp-server-card.opacity-60 {
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.mcp-server-available {
|
||||
border-style: dashed;
|
||||
}
|
||||
|
||||
.mcp-server-available:hover {
|
||||
border-style: solid;
|
||||
}
|
||||
|
||||
/* MCP Toggle Switch */
|
||||
.mcp-toggle {
|
||||
position: relative;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.mcp-toggle input {
|
||||
position: absolute;
|
||||
opacity: 0;
|
||||
width: 0;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
.mcp-toggle > div {
|
||||
width: 36px;
|
||||
height: 20px;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 10px;
|
||||
position: relative;
|
||||
transition: background 0.2s;
|
||||
}
|
||||
|
||||
.mcp-toggle > div::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
left: 2px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: transform 0.2s;
|
||||
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.mcp-toggle input:checked + div {
|
||||
background: hsl(var(--success));
|
||||
}
|
||||
|
||||
.mcp-toggle input:checked + div::after {
|
||||
transform: translateX(16px);
|
||||
}
|
||||
|
||||
.mcp-toggle input:focus + div {
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
|
||||
}
|
||||
|
||||
/* MCP Projects List */
|
||||
.mcp-projects-list {
|
||||
max-height: 400px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.mcp-project-item {
|
||||
transition: background 0.15s;
|
||||
}
|
||||
|
||||
.mcp-project-item:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.mcp-project-item.bg-primary-light {
|
||||
background: hsl(var(--primary-light));
|
||||
}
|
||||
|
||||
/* MCP Empty State */
|
||||
.mcp-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* MCP Server Details */
|
||||
.mcp-server-details {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.mcp-server-details .font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
/* MCP Projects Table */
|
||||
.mcp-projects-table {
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
.mcp-projects-table table {
|
||||
border-collapse: collapse;
|
||||
min-width: 100%;
|
||||
}
|
||||
|
||||
.mcp-projects-table th {
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.mcp-projects-table td {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.mcp-projects-table tr:hover {
|
||||
background-color: hsl(var(--hover));
|
||||
}
|
||||
|
||||
.mcp-projects-table .bg-primary-light\/30 {
|
||||
background-color: hsl(var(--primary) / 0.08);
|
||||
}
|
||||
|
||||
/* MCP Create Modal */
|
||||
.mcp-modal {
|
||||
animation: fadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.mcp-modal-backdrop {
|
||||
animation: fadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.mcp-modal-content {
|
||||
animation: slideUp 0.2s ease-out;
|
||||
}
|
||||
|
||||
.mcp-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.mcp-modal .form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.mcp-modal input,
|
||||
.mcp-modal textarea {
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.mcp-modal input:focus,
|
||||
.mcp-modal textarea:focus {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
|
||||
}
|
||||
|
||||
/* MCP Tab Buttons */
|
||||
.mcp-tab-btn {
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.mcp-tab-btn.active {
|
||||
background: hsl(var(--background));
|
||||
color: hsl(var(--foreground));
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.mcp-tab-btn:hover:not(.active) {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
@keyframes slideUp {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateY(10px);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateY(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
HOOK MANAGER STYLES
|
||||
========================================== */
|
||||
|
||||
.hook-manager {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hook-section {
|
||||
margin-bottom: 2rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hook-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.hook-card {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.hook-details {
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.hook-details .font-mono {
|
||||
font-family: var(--font-mono);
|
||||
}
|
||||
|
||||
.hook-templates-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
.hook-template-card {
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.hook-template-card:hover {
|
||||
box-shadow: 0 4px 12px rgb(0 0 0 / 0.1);
|
||||
}
|
||||
|
||||
/* Hook Empty State */
|
||||
.hook-empty-state {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 120px;
|
||||
}
|
||||
|
||||
/* Hook Modal */
|
||||
.hook-modal {
|
||||
animation: fadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.hook-modal-backdrop {
|
||||
animation: fadeIn 0.15s ease-out;
|
||||
}
|
||||
|
||||
.hook-modal-content {
|
||||
animation: slideUp 0.2s ease-out;
|
||||
}
|
||||
|
||||
.hook-modal.hidden {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.hook-modal .form-group label {
|
||||
display: block;
|
||||
margin-bottom: 0.25rem;
|
||||
}
|
||||
|
||||
.hook-modal input,
|
||||
.hook-modal textarea,
|
||||
.hook-modal select {
|
||||
transition: border-color 0.15s, box-shadow 0.15s;
|
||||
}
|
||||
|
||||
.hook-modal input:focus,
|
||||
.hook-modal textarea:focus,
|
||||
.hook-modal select:focus {
|
||||
border-color: hsl(var(--primary));
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.2);
|
||||
}
|
||||
|
||||
.hook-template-btn {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.hook-template-btn:hover {
|
||||
background: hsl(var(--hover));
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
STATS SECTION & CAROUSEL
|
||||
========================================== */
|
||||
|
||||
.stats-section {
|
||||
min-height: 180px;
|
||||
}
|
||||
|
||||
.stats-metrics {
|
||||
width: 300px;
|
||||
}
|
||||
|
||||
.stats-carousel {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.carousel-header {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.carousel-btn {
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.carousel-btn:hover {
|
||||
background: hsl(var(--hover));
|
||||
}
|
||||
|
||||
/* Carousel dots indicator */
|
||||
.carousel-dots {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.carousel-dot {
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
padding: 0;
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.carousel-dot:focus {
|
||||
outline: none;
|
||||
box-shadow: 0 0 0 2px hsl(var(--primary) / 0.3);
|
||||
}
|
||||
|
||||
.carousel-footer {
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.carousel-content {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.carousel-slide {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
.carousel-empty {
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
}
|
||||
|
||||
/* Carousel slide animations */
|
||||
.carousel-fade-in {
|
||||
animation: carouselFadeIn 0.3s ease-out forwards;
|
||||
}
|
||||
|
||||
.carousel-slide-left {
|
||||
animation: carouselSlideLeft 0.35s ease-out forwards;
|
||||
}
|
||||
|
||||
.carousel-slide-right {
|
||||
animation: carouselSlideRight 0.35s ease-out forwards;
|
||||
}
|
||||
|
||||
@keyframes carouselFadeIn {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: scale(0.98);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: scale(1);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes carouselSlideLeft {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes carouselSlideRight {
|
||||
from {
|
||||
opacity: 0;
|
||||
transform: translateX(-100%);
|
||||
}
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
}
|
||||
|
||||
/* Task card in carousel */
|
||||
.carousel-slide .task-card {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.carousel-slide .task-timestamps {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.carousel-slide .task-session-info {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
/* Task status badge pulse for in_progress */
|
||||
.task-status-badge {
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.bg-warning-light .task-status-badge {
|
||||
animation: statusPulse 2s ease-in-out infinite;
|
||||
}
|
||||
|
||||
@keyframes statusPulse {
|
||||
0%, 100% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
opacity: 0.7;
|
||||
}
|
||||
}
|
||||
|
||||
/* Task highlight animation when navigating from carousel */
|
||||
.task-highlight {
|
||||
animation: taskHighlight 0.5s ease-out 3;
|
||||
}
|
||||
|
||||
@keyframes taskHighlight {
|
||||
0%, 100% {
|
||||
background: transparent;
|
||||
box-shadow: none;
|
||||
}
|
||||
50% {
|
||||
background: hsl(var(--primary-light));
|
||||
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* Line clamp utility */
|
||||
.line-clamp-1 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 1;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.line-clamp-2 {
|
||||
display: -webkit-box;
|
||||
-webkit-line-clamp: 2;
|
||||
-webkit-box-orient: vertical;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* Highlight pulse effect */
|
||||
.highlight-pulse {
|
||||
animation: highlightPulse 0.5s ease-out 2;
|
||||
}
|
||||
|
||||
@keyframes highlightPulse {
|
||||
0%, 100% {
|
||||
box-shadow: none;
|
||||
}
|
||||
50% {
|
||||
box-shadow: 0 0 0 3px hsl(var(--primary) / 0.3);
|
||||
}
|
||||
}
|
||||
|
||||
/* ==========================================
|
||||
NOTIFICATION BUBBLES
|
||||
========================================== */
|
||||
|
||||
.notification-bubble {
|
||||
position: fixed;
|
||||
top: 70px;
|
||||
right: 20px;
|
||||
max-width: 360px;
|
||||
padding: 12px 16px;
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 8px 24px rgb(0 0 0 / 0.15);
|
||||
z-index: 1000;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
transition: all 0.3s ease-out;
|
||||
}
|
||||
|
||||
.notification-bubble.show {
|
||||
opacity: 1;
|
||||
transform: translateX(0);
|
||||
}
|
||||
|
||||
.notification-bubble.fade-out {
|
||||
opacity: 0;
|
||||
transform: translateX(100%);
|
||||
}
|
||||
|
||||
.notification-bubble:nth-child(2) {
|
||||
top: 130px;
|
||||
}
|
||||
|
||||
.notification-bubble:nth-child(3) {
|
||||
top: 190px;
|
||||
}
|
||||
|
||||
.notification-content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-icon {
|
||||
font-size: 1.25rem;
|
||||
}
|
||||
|
||||
.notification-message {
|
||||
font-size: 0.875rem;
|
||||
color: hsl(var(--foreground));
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.notification-action {
|
||||
padding: 4px 12px;
|
||||
background: hsl(var(--primary));
|
||||
color: hsl(var(--primary-foreground));
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 0.75rem;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: opacity 0.15s;
|
||||
}
|
||||
|
||||
.notification-action:hover {
|
||||
opacity: 0.9;
|
||||
}
|
||||
|
||||
.notification-close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
background: transparent;
|
||||
border: none;
|
||||
color: hsl(var(--muted-foreground));
|
||||
font-size: 1.25rem;
|
||||
cursor: pointer;
|
||||
border-radius: 4px;
|
||||
transition: all 0.15s;
|
||||
}
|
||||
|
||||
.notification-close:hover {
|
||||
background: hsl(var(--hover));
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
/* Notification types */
|
||||
.notification-success {
|
||||
border-left: 3px solid hsl(var(--success));
|
||||
}
|
||||
|
||||
.notification-warning {
|
||||
border-left: 3px solid hsl(var(--warning));
|
||||
}
|
||||
|
||||
.notification-error {
|
||||
border-left: 3px solid hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.notification-info {
|
||||
border-left: 3px solid hsl(var(--primary));
|
||||
}
|
||||
|
||||
/* Responsive stats section */
|
||||
@media (max-width: 768px) {
|
||||
.stats-section {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.stats-metrics {
|
||||
width: 100%;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
}
|
||||
|
||||
.stats-carousel {
|
||||
min-height: 160px;
|
||||
}
|
||||
}
|
||||
/* Exploration Object Rendering - Theme Compatible */
|
||||
.exp-object {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 8px;
|
||||
padding: 12px;
|
||||
background: hsl(var(--muted));
|
||||
border-radius: 8px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.exp-obj-field {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 6px;
|
||||
align-items: flex-start;
|
||||
font-size: 13px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
.exp-obj-key {
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
min-width: 120px;
|
||||
}
|
||||
|
||||
.exp-obj-val {
|
||||
color: hsl(var(--foreground));
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.exp-obj-nested {
|
||||
margin-left: 16px;
|
||||
padding: 8px;
|
||||
border-left: 2px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.exp-obj-nested > .exp-obj-key {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
color: hsl(var(--primary));
|
||||
font-size: 12px;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
}
|
||||
|
||||
.exp-list {
|
||||
margin: 4px 0 0 0;
|
||||
padding-left: 20px;
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
.exp-list li {
|
||||
font-size: 13px;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.6;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.exp-array-objects {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.exp-object-item {
|
||||
padding: 10px;
|
||||
background: hsl(var(--card));
|
||||
border-radius: 6px;
|
||||
border: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.clarification-impact {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
margin-top: 6px;
|
||||
}
|
||||
|
||||
.priority-badge {
|
||||
display: inline-block;
|
||||
padding: 2px 8px;
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.priority-badge.priority-high {
|
||||
background: hsl(var(--destructive) / 0.15);
|
||||
color: hsl(var(--destructive));
|
||||
}
|
||||
|
||||
.priority-badge.priority-medium {
|
||||
background: hsl(var(--warning-light));
|
||||
color: hsl(var(--warning));
|
||||
}
|
||||
|
||||
.priority-badge.priority-low {
|
||||
background: hsl(var(--success-light));
|
||||
color: hsl(var(--success));
|
||||
}
|
||||
|
||||
/* Conflict Tab Styles - Theme Compatible */
|
||||
.conflict-tab-content {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.conflict-tab-header {
|
||||
margin-bottom: 24px;
|
||||
}
|
||||
|
||||
.conflict-tab-header h3 {
|
||||
font-size: 18px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 0 0 8px 0;
|
||||
}
|
||||
|
||||
.conflict-meta-info {
|
||||
display: flex;
|
||||
gap: 16px;
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.conflict-meta-info strong {
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.conflict-section-title {
|
||||
font-size: 15px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
margin: 24px 0 12px 0;
|
||||
padding-bottom: 8px;
|
||||
border-bottom: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.conflict-cards-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fill, minmax(320px, 1fr));
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.conflict-card {
|
||||
background: hsl(var(--card));
|
||||
border: 1px solid hsl(var(--border));
|
||||
border-radius: 12px;
|
||||
padding: 16px;
|
||||
transition: box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.conflict-card:hover {
|
||||
box-shadow: 0 4px 12px hsl(var(--foreground) / 0.08);
|
||||
border-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.decision-card {
|
||||
border-left: 3px solid hsl(var(--primary));
|
||||
}
|
||||
|
||||
.resolved-card {
|
||||
border-left: 3px solid hsl(var(--success));
|
||||
}
|
||||
|
||||
.conflict-card-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.conflict-card-label {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--foreground));
|
||||
}
|
||||
|
||||
.conflict-card-id {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--primary));
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.conflict-category-tag {
|
||||
padding: 3px 8px;
|
||||
background: hsl(var(--muted));
|
||||
color: hsl(var(--muted-foreground));
|
||||
border-radius: 4px;
|
||||
font-size: 11px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.conflict-card-choice {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-bottom: 10px;
|
||||
padding: 10px 12px;
|
||||
background: hsl(var(--primary-light));
|
||||
border-radius: 8px;
|
||||
}
|
||||
|
||||
.choice-label {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.choice-value {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
.conflict-card-desc {
|
||||
font-size: 13px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.5;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.conflict-card-brief {
|
||||
font-size: 13px;
|
||||
color: hsl(var(--foreground));
|
||||
line-height: 1.5;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
|
||||
.conflict-card-implications {
|
||||
margin-top: 10px;
|
||||
padding-top: 10px;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.conflict-card-implications .impl-label {
|
||||
display: block;
|
||||
font-size: 11px;
|
||||
font-weight: 600;
|
||||
color: hsl(var(--muted-foreground));
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.5px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
|
||||
.conflict-card-implications ul {
|
||||
margin: 0;
|
||||
padding-left: 18px;
|
||||
}
|
||||
|
||||
.conflict-card-implications li {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
line-height: 1.5;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
|
||||
.conflict-card-strategy {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
margin-top: 12px;
|
||||
padding-top: 12px;
|
||||
border-top: 1px solid hsl(var(--border));
|
||||
}
|
||||
|
||||
.conflict-card-strategy .strategy-label {
|
||||
font-size: 12px;
|
||||
color: hsl(var(--muted-foreground));
|
||||
}
|
||||
|
||||
.strategy-tag {
|
||||
padding: 4px 10px;
|
||||
background: hsl(var(--success-light));
|
||||
color: hsl(var(--success));
|
||||
border-radius: 6px;
|
||||
font-size: 12px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
1266
ccw/src/templates/dashboard-css/08-review.css
Normal file
1266
ccw/src/templates/dashboard-css/08-review.css
Normal file
File diff suppressed because it is too large
Load Diff
1397
ccw/src/templates/dashboard-css/09-explorer.css
Normal file
1397
ccw/src/templates/dashboard-css/09-explorer.css
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,219 @@
|
||||
// ==========================================
|
||||
// GLOBAL NOTIFICATION SYSTEM
|
||||
// ==========================================
|
||||
// Floating notification panel accessible from any view
|
||||
|
||||
/**
|
||||
* Initialize global notification panel
|
||||
*/
|
||||
function initGlobalNotifications() {
|
||||
// Create FAB and panel if not exists
|
||||
if (!document.getElementById('globalNotificationFab')) {
|
||||
const fabHtml = `
|
||||
<div class="global-notif-fab" id="globalNotificationFab" onclick="toggleGlobalNotifications()" title="Notifications">
|
||||
<span class="fab-icon">🔔</span>
|
||||
<span class="fab-badge" id="globalNotifBadge">0</span>
|
||||
</div>
|
||||
|
||||
<div class="global-notif-panel" id="globalNotificationPanel">
|
||||
<div class="global-notif-header">
|
||||
<span class="global-notif-title">🔔 Notifications</span>
|
||||
<button class="global-notif-close" onclick="toggleGlobalNotifications()">×</button>
|
||||
</div>
|
||||
<div class="global-notif-actions">
|
||||
<button class="notif-action-btn" onclick="clearGlobalNotifications()">
|
||||
<span>🗑️</span> Clear All
|
||||
</button>
|
||||
</div>
|
||||
<div class="global-notif-list" id="globalNotificationList">
|
||||
<div class="global-notif-empty">
|
||||
<span>No notifications</span>
|
||||
<p>System events and task updates will appear here</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
const container = document.createElement('div');
|
||||
container.id = 'globalNotificationContainer';
|
||||
container.innerHTML = fabHtml;
|
||||
document.body.appendChild(container);
|
||||
}
|
||||
|
||||
renderGlobalNotifications();
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle notification panel visibility
|
||||
*/
|
||||
function toggleGlobalNotifications() {
|
||||
isNotificationPanelVisible = !isNotificationPanelVisible;
|
||||
const panel = document.getElementById('globalNotificationPanel');
|
||||
const fab = document.getElementById('globalNotificationFab');
|
||||
|
||||
if (panel && fab) {
|
||||
if (isNotificationPanelVisible) {
|
||||
panel.classList.add('show');
|
||||
fab.classList.add('active');
|
||||
} else {
|
||||
panel.classList.remove('show');
|
||||
fab.classList.remove('active');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a global notification
|
||||
* @param {string} type - 'info', 'success', 'warning', 'error'
|
||||
* @param {string} message - Main notification message
|
||||
* @param {string} details - Optional details
|
||||
* @param {string} source - Optional source identifier (e.g., 'explorer', 'mcp')
|
||||
*/
|
||||
function addGlobalNotification(type, message, details = null, source = null) {
|
||||
const notification = {
|
||||
id: Date.now(),
|
||||
type,
|
||||
message,
|
||||
details,
|
||||
source,
|
||||
timestamp: new Date().toISOString(),
|
||||
read: false
|
||||
};
|
||||
|
||||
globalNotificationQueue.unshift(notification);
|
||||
|
||||
// Keep only last 100 notifications
|
||||
if (globalNotificationQueue.length > 100) {
|
||||
globalNotificationQueue = globalNotificationQueue.slice(0, 100);
|
||||
}
|
||||
|
||||
renderGlobalNotifications();
|
||||
updateGlobalNotifBadge();
|
||||
|
||||
// Show toast for important notifications
|
||||
if (type === 'error' || type === 'success') {
|
||||
showNotificationToast(notification);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a brief toast notification
|
||||
*/
|
||||
function showNotificationToast(notification) {
|
||||
const typeIcon = {
|
||||
'info': 'ℹ️',
|
||||
'success': '✅',
|
||||
'warning': '⚠️',
|
||||
'error': '❌'
|
||||
}[notification.type] || 'ℹ️';
|
||||
|
||||
// Remove existing toast
|
||||
const existing = document.querySelector('.notif-toast');
|
||||
if (existing) existing.remove();
|
||||
|
||||
const toast = document.createElement('div');
|
||||
toast.className = `notif-toast type-${notification.type}`;
|
||||
toast.innerHTML = `
|
||||
<span class="toast-icon">${typeIcon}</span>
|
||||
<span class="toast-message">${escapeHtml(notification.message)}</span>
|
||||
`;
|
||||
document.body.appendChild(toast);
|
||||
|
||||
// Animate in
|
||||
requestAnimationFrame(() => toast.classList.add('show'));
|
||||
|
||||
// Auto-remove
|
||||
setTimeout(() => {
|
||||
toast.classList.remove('show');
|
||||
setTimeout(() => toast.remove(), 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render notification list
|
||||
*/
|
||||
function renderGlobalNotifications() {
|
||||
const listEl = document.getElementById('globalNotificationList');
|
||||
if (!listEl) return;
|
||||
|
||||
if (globalNotificationQueue.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div class="global-notif-empty">
|
||||
<span>No notifications</span>
|
||||
<p>System events and task updates will appear here</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = globalNotificationQueue.map(notif => {
|
||||
const typeIcon = {
|
||||
'info': 'ℹ️',
|
||||
'success': '✅',
|
||||
'warning': '⚠️',
|
||||
'error': '❌'
|
||||
}[notif.type] || 'ℹ️';
|
||||
|
||||
const time = formatNotifTime(notif.timestamp);
|
||||
const sourceLabel = notif.source ? `<span class="notif-source">${notif.source}</span>` : '';
|
||||
|
||||
return `
|
||||
<div class="global-notif-item type-${notif.type} ${notif.read ? 'read' : ''}" data-id="${notif.id}">
|
||||
<div class="notif-item-header">
|
||||
<span class="notif-icon">${typeIcon}</span>
|
||||
<span class="notif-message">${escapeHtml(notif.message)}</span>
|
||||
${sourceLabel}
|
||||
</div>
|
||||
${notif.details ? `<div class="notif-details">${escapeHtml(notif.details)}</div>` : ''}
|
||||
<div class="notif-meta">
|
||||
<span class="notif-time">${time}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format notification time
|
||||
*/
|
||||
function formatNotifTime(timestamp) {
|
||||
const date = new Date(timestamp);
|
||||
const now = new Date();
|
||||
const diff = now - date;
|
||||
|
||||
if (diff < 60000) return 'Just now';
|
||||
if (diff < 3600000) return `${Math.floor(diff / 60000)}m ago`;
|
||||
if (diff < 86400000) return `${Math.floor(diff / 3600000)}h ago`;
|
||||
return date.toLocaleDateString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Update notification badge
|
||||
*/
|
||||
function updateGlobalNotifBadge() {
|
||||
const badge = document.getElementById('globalNotifBadge');
|
||||
if (badge) {
|
||||
const unreadCount = globalNotificationQueue.filter(n => !n.read).length;
|
||||
badge.textContent = unreadCount;
|
||||
badge.style.display = unreadCount > 0 ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear all notifications
|
||||
*/
|
||||
function clearGlobalNotifications() {
|
||||
globalNotificationQueue = [];
|
||||
renderGlobalNotifications();
|
||||
updateGlobalNotifBadge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark all as read
|
||||
*/
|
||||
function markAllNotificationsRead() {
|
||||
globalNotificationQueue.forEach(n => n.read = true);
|
||||
renderGlobalNotifications();
|
||||
updateGlobalNotifBadge();
|
||||
}
|
||||
|
||||
@@ -271,3 +271,13 @@ function getHookEventIcon(event) {
|
||||
};
|
||||
return icons[event] || '🪝';
|
||||
}
|
||||
|
||||
function getHookEventIconLucide(event) {
|
||||
const icons = {
|
||||
'PreToolUse': '<i data-lucide="clock" class="w-5 h-5"></i>',
|
||||
'PostToolUse': '<i data-lucide="check-circle" class="w-5 h-5"></i>',
|
||||
'Notification': '<i data-lucide="bell" class="w-5 h-5"></i>',
|
||||
'Stop': '<i data-lucide="octagon-x" class="w-5 h-5"></i>'
|
||||
};
|
||||
return icons[event] || '<i data-lucide="webhook" class="w-5 h-5"></i>';
|
||||
}
|
||||
@@ -1,10 +1,18 @@
|
||||
// MCP Manager Component
|
||||
// Manages MCP server configuration from .claude.json
|
||||
// Manages MCP server configuration from multiple sources:
|
||||
// - Enterprise: managed-mcp.json (highest priority)
|
||||
// - User: ~/.claude.json mcpServers
|
||||
// - Project: .mcp.json in project root
|
||||
// - Local: ~/.claude.json projects[path].mcpServers
|
||||
|
||||
// ========== MCP State ==========
|
||||
let mcpConfig = null;
|
||||
let mcpAllProjects = {};
|
||||
let mcpGlobalServers = {};
|
||||
let mcpUserServers = {};
|
||||
let mcpEnterpriseServers = {};
|
||||
let mcpCurrentProjectServers = {};
|
||||
let mcpConfigSources = [];
|
||||
let mcpCreateMode = 'form'; // 'form' or 'json'
|
||||
|
||||
// ========== Initialization ==========
|
||||
@@ -31,6 +39,10 @@ async function loadMcpConfig() {
|
||||
const data = await response.json();
|
||||
mcpConfig = data;
|
||||
mcpAllProjects = data.projects || {};
|
||||
mcpGlobalServers = data.globalServers || {};
|
||||
mcpUserServers = data.userServers || {};
|
||||
mcpEnterpriseServers = data.enterpriseServers || {};
|
||||
mcpConfigSources = data.configSources || [];
|
||||
|
||||
// Get current project servers
|
||||
const currentPath = projectPath.replace(/\//g, '\\');
|
||||
@@ -150,6 +162,15 @@ function updateMcpBadge() {
|
||||
function getAllAvailableMcpServers() {
|
||||
const allServers = {};
|
||||
|
||||
// Collect global servers first
|
||||
for (const [name, serverConfig] of Object.entries(mcpGlobalServers)) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: [],
|
||||
isGlobal: true
|
||||
};
|
||||
}
|
||||
|
||||
// Collect servers from all projects
|
||||
for (const [path, config] of Object.entries(mcpAllProjects)) {
|
||||
const servers = config.mcpServers || {};
|
||||
@@ -157,7 +178,8 @@ function getAllAvailableMcpServers() {
|
||||
if (!allServers[name]) {
|
||||
allServers[name] = {
|
||||
config: serverConfig,
|
||||
usedIn: []
|
||||
usedIn: [],
|
||||
isGlobal: false
|
||||
};
|
||||
}
|
||||
allServers[name].usedIn.push(path);
|
||||
|
||||
@@ -96,6 +96,8 @@ function initNavigation() {
|
||||
renderMcpManager();
|
||||
} else if (currentView === 'project-overview') {
|
||||
renderProjectOverview();
|
||||
} else if (currentView === 'explorer') {
|
||||
renderExplorer();
|
||||
}
|
||||
});
|
||||
});
|
||||
@@ -112,6 +114,8 @@ function updateContentTitle() {
|
||||
titleEl.textContent = 'Project Overview';
|
||||
} else if (currentView === 'mcp-manager') {
|
||||
titleEl.textContent = 'MCP Server Management';
|
||||
} else if (currentView === 'explorer') {
|
||||
titleEl.textContent = 'File Explorer';
|
||||
} else if (currentView === 'liteTasks') {
|
||||
const names = { 'lite-plan': 'Lite Plan Sessions', 'lite-fix': 'Lite Fix Sessions' };
|
||||
titleEl.textContent = names[currentLiteType] || 'Lite Tasks';
|
||||
@@ -157,17 +161,19 @@ async function refreshWorkspace() {
|
||||
// Reload data from server
|
||||
const data = await loadDashboardData(projectPath);
|
||||
if (data) {
|
||||
// Update stores
|
||||
sessionDataStore = {};
|
||||
liteTaskDataStore = {};
|
||||
// Clear and repopulate stores
|
||||
Object.keys(sessionDataStore).forEach(k => delete sessionDataStore[k]);
|
||||
Object.keys(liteTaskDataStore).forEach(k => delete liteTaskDataStore[k]);
|
||||
|
||||
// Populate stores
|
||||
[...(data.activeSessions || []), ...(data.archivedSessions || [])].forEach(s => {
|
||||
sessionDataStore[s.session_id] = s;
|
||||
const sessionKey = `session-${s.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
sessionDataStore[sessionKey] = s;
|
||||
});
|
||||
|
||||
[...(data.liteTasks?.litePlan || []), ...(data.liteTasks?.liteFix || [])].forEach(s => {
|
||||
liteTaskDataStore[s.session_id] = s;
|
||||
const sessionKey = `lite-${s.session_id}`.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||
liteTaskDataStore[sessionKey] = s;
|
||||
});
|
||||
|
||||
// Update global data
|
||||
|
||||
@@ -12,7 +12,7 @@ function renderContextContent(context) {
|
||||
if (!context) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">📦</div>
|
||||
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Context Data</div>
|
||||
<div class="empty-text">No context-package.json found for this session.</div>
|
||||
</div>
|
||||
@@ -39,9 +39,9 @@ function renderContextContent(context) {
|
||||
<!-- Header Card -->
|
||||
<div class="ctx-header-card">
|
||||
<div class="ctx-header-content">
|
||||
<h3 class="ctx-main-title">📦 Context Package</h3>
|
||||
<h3 class="ctx-main-title"><i data-lucide="package" class="w-5 h-5 inline mr-2"></i>Context Package</h3>
|
||||
<button class="btn-view-modal" onclick="openMarkdownModal('context-package.json', window._currentContextJson, 'json')">
|
||||
👁️ View JSON
|
||||
<i data-lucide="eye" class="w-4 h-4 inline mr-1"></i>View JSON
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -50,7 +50,7 @@ function renderContextContent(context) {
|
||||
${metadata.task_description || metadata.session_id ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">📋</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="clipboard-list" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Task Metadata</h4>
|
||||
</div>
|
||||
<div class="ctx-card-body">
|
||||
@@ -90,7 +90,7 @@ function renderContextContent(context) {
|
||||
${architecturePatterns.length > 0 ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">🏛️</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="building-2" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Architecture Patterns</h4>
|
||||
<span class="ctx-count-badge">${architecturePatterns.length}</span>
|
||||
</div>
|
||||
@@ -106,7 +106,7 @@ function renderContextContent(context) {
|
||||
${Object.keys(techStack).length > 0 ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">💻</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="code-2" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Technology Stack</h4>
|
||||
</div>
|
||||
<div class="ctx-card-body">
|
||||
@@ -119,7 +119,7 @@ function renderContextContent(context) {
|
||||
${Object.keys(codingConventions).length > 0 ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">📝</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="file-code" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Coding Conventions</h4>
|
||||
</div>
|
||||
<div class="ctx-card-body">
|
||||
@@ -145,7 +145,7 @@ function renderContextContent(context) {
|
||||
${(dependencies.internal && dependencies.internal.length > 0) || (dependencies.external && dependencies.external.length > 0) ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">🔗</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="link" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Dependencies</h4>
|
||||
</div>
|
||||
<div class="ctx-card-body">
|
||||
@@ -158,7 +158,7 @@ function renderContextContent(context) {
|
||||
${Object.keys(testContext).length > 0 ? `
|
||||
<div class="ctx-card">
|
||||
<div class="ctx-card-header">
|
||||
<span class="ctx-card-icon">🧪</span>
|
||||
<span class="ctx-card-icon"><i data-lucide="flask-conical" class="w-5 h-5"></i></span>
|
||||
<h4 class="ctx-card-title">Test Context</h4>
|
||||
</div>
|
||||
<div class="ctx-card-body">
|
||||
@@ -314,7 +314,7 @@ function renderAssetsCards(assets) {
|
||||
sections.push(`
|
||||
<div class="ctx-assets-category">
|
||||
<div class="ctx-assets-cat-header">
|
||||
<span class="ctx-assets-cat-icon">📄</span>
|
||||
<span class="ctx-assets-cat-icon"><i data-lucide="file-text" class="w-4 h-4"></i></span>
|
||||
<span class="ctx-assets-cat-title">Documentation</span>
|
||||
<span class="ctx-assets-cat-count">${assets.documentation.length}</span>
|
||||
</div>
|
||||
@@ -335,7 +335,7 @@ function renderAssetsCards(assets) {
|
||||
sections.push(`
|
||||
<div class="ctx-assets-category">
|
||||
<div class="ctx-assets-cat-header">
|
||||
<span class="ctx-assets-cat-icon">💻</span>
|
||||
<span class="ctx-assets-cat-icon"><i data-lucide="code-2" class="w-4 h-4"></i></span>
|
||||
<span class="ctx-assets-cat-title">Source Code</span>
|
||||
<span class="ctx-assets-cat-count">${assets.source_code.length}</span>
|
||||
</div>
|
||||
@@ -356,7 +356,7 @@ function renderAssetsCards(assets) {
|
||||
sections.push(`
|
||||
<div class="ctx-assets-category">
|
||||
<div class="ctx-assets-cat-header">
|
||||
<span class="ctx-assets-cat-icon">🧪</span>
|
||||
<span class="ctx-assets-cat-icon"><i data-lucide="flask-conical" class="w-4 h-4"></i></span>
|
||||
<span class="ctx-assets-cat-title">Tests</span>
|
||||
<span class="ctx-assets-cat-count">${assets.tests.length}</span>
|
||||
</div>
|
||||
@@ -657,7 +657,7 @@ function renderAssetsSection(assets) {
|
||||
if (assets.documentation && assets.documentation.length > 0) {
|
||||
sections.push(`
|
||||
<div class="asset-category">
|
||||
<h5 class="asset-category-title">📄 Documentation</h5>
|
||||
<h5 class="asset-category-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i>Documentation</h5>
|
||||
<div class="asset-grid">
|
||||
${assets.documentation.map(doc => `
|
||||
<div class="asset-card">
|
||||
@@ -684,7 +684,7 @@ function renderAssetsSection(assets) {
|
||||
if (assets.source_code && assets.source_code.length > 0) {
|
||||
sections.push(`
|
||||
<div class="asset-category">
|
||||
<h5 class="asset-category-title">💻 Source Code</h5>
|
||||
<h5 class="asset-category-title"><i data-lucide="code-2" class="w-4 h-4 inline mr-1"></i>Source Code</h5>
|
||||
<div class="asset-grid">
|
||||
${assets.source_code.map(src => `
|
||||
<div class="asset-card">
|
||||
@@ -712,7 +712,7 @@ function renderAssetsSection(assets) {
|
||||
if (assets.tests && assets.tests.length > 0) {
|
||||
sections.push(`
|
||||
<div class="asset-category">
|
||||
<h5 class="asset-category-title">🧪 Tests</h5>
|
||||
<h5 class="asset-category-title"><i data-lucide="flask-conical" class="w-4 h-4 inline mr-1"></i>Tests</h5>
|
||||
<div class="asset-grid">
|
||||
${assets.tests.map(test => `
|
||||
<div class="asset-card">
|
||||
@@ -764,7 +764,7 @@ function renderDependenciesSection(dependencies) {
|
||||
if (dependencies.external && dependencies.external.length > 0) {
|
||||
sections.push(`
|
||||
<div class="dep-category">
|
||||
<h5 class="dep-category-title">📦 External Dependencies</h5>
|
||||
<h5 class="dep-category-title"><i data-lucide="package" class="w-4 h-4 inline mr-1"></i>External Dependencies</h5>
|
||||
<div class="dep-grid">
|
||||
${dependencies.external.map(dep => `
|
||||
<div class="dep-external-card">
|
||||
@@ -836,7 +836,7 @@ function renderTestContextSection(testContext) {
|
||||
|
||||
sections.push(`
|
||||
<div class="test-category">
|
||||
<h5 class="test-category-title">📊 Test Statistics</h5>
|
||||
<h5 class="test-category-title"><i data-lucide="bar-chart-3" class="w-4 h-4 inline mr-1"></i>Test Statistics</h5>
|
||||
<div class="test-stats-grid">
|
||||
<div class="stat-card">
|
||||
<div class="stat-value">${totalTests}</div>
|
||||
@@ -930,7 +930,7 @@ function renderConflictDetectionSection(conflictDetection) {
|
||||
if (conflictDetection.affected_modules && conflictDetection.affected_modules.length > 0) {
|
||||
sections.push(`
|
||||
<div class="conflict-category">
|
||||
<h5 class="conflict-category-title">📦 Affected Modules</h5>
|
||||
<h5 class="conflict-category-title"><i data-lucide="package-open" class="w-4 h-4 inline mr-1"></i>Affected Modules</h5>
|
||||
<div class="affected-modules-grid">
|
||||
${conflictDetection.affected_modules.map(mod => `
|
||||
<span class="affected-module-tag">${escapeHtml(mod)}</span>
|
||||
@@ -996,7 +996,7 @@ function renderSessionContextContent(context, explorations, conflictResolution)
|
||||
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">📦</div>
|
||||
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Context Data</div>
|
||||
<div class="empty-text">No context-package.json or exploration files found for this session.</div>
|
||||
</div>
|
||||
@@ -1033,7 +1033,7 @@ function renderConflictResolutionContext(conflictResolution) {
|
||||
<div class="conflict-decisions-section collapsible-section">
|
||||
<div class="collapsible-header">
|
||||
<span class="collapse-icon">▶</span>
|
||||
<span class="section-label">🎯 User Decisions (${decisions.length})</span>
|
||||
<span class="section-label"><i data-lucide="target" class="w-4 h-4 inline mr-1"></i>User Decisions (${decisions.length})</span>
|
||||
</div>
|
||||
<div class="collapsible-content collapsed">
|
||||
<div class="decisions-list">
|
||||
|
||||
@@ -11,7 +11,7 @@ function renderSummaryContent(summaries) {
|
||||
if (!summaries || summaries.length === 0) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">📝</div>
|
||||
<div class="empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Summaries</div>
|
||||
<div class="empty-text">No summaries found in .summaries/</div>
|
||||
</div>
|
||||
@@ -31,9 +31,9 @@ function renderSummaryContent(summaries) {
|
||||
return `
|
||||
<div class="summary-item-card">
|
||||
<div class="summary-item-header">
|
||||
<h4 class="summary-item-title">📄 ${escapeHtml(s.name || 'Summary')}</h4>
|
||||
<h4 class="summary-item-title"><i data-lucide="file-text" class="w-4 h-4 inline mr-1"></i>${escapeHtml(s.name || 'Summary')}</h4>
|
||||
<button class="btn-view-modal" onclick="openMarkdownModal('${escapeHtml(s.name || 'Summary')}', window._currentSummaries[${idx}].content, 'markdown');">
|
||||
👁️ View
|
||||
<i data-lucide="eye" class="w-4 h-4 inline mr-1"></i>View
|
||||
</button>
|
||||
</div>
|
||||
<div class="summary-item-preview">
|
||||
@@ -54,7 +54,7 @@ function renderImplPlanContent(implPlan) {
|
||||
if (!implPlan) {
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">📐</div>
|
||||
<div class="empty-icon"><i data-lucide="ruler" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No IMPL Plan</div>
|
||||
<div class="empty-text">No IMPL_PLAN.md found for this session.</div>
|
||||
</div>
|
||||
@@ -73,9 +73,9 @@ function renderImplPlanContent(implPlan) {
|
||||
<div class="impl-plan-tab-content">
|
||||
<div class="impl-plan-card">
|
||||
<div class="impl-plan-header">
|
||||
<h3 class="impl-plan-title">📐 Implementation Plan</h3>
|
||||
<h3 class="impl-plan-title"><i data-lucide="ruler" class="w-5 h-5 inline mr-2"></i>Implementation Plan</h3>
|
||||
<button class="btn-view-modal" onclick="openMarkdownModal('IMPL_PLAN.md', window._currentImplPlan, 'markdown')">
|
||||
👁️ View
|
||||
<i data-lucide="eye" class="w-4 h-4 inline mr-1"></i>View
|
||||
</button>
|
||||
</div>
|
||||
<div class="impl-plan-preview">
|
||||
@@ -151,7 +151,7 @@ function renderLiteContextContent(context, explorations, session) {
|
||||
|
||||
return `
|
||||
<div class="tab-empty-state">
|
||||
<div class="empty-icon">📦</div>
|
||||
<div class="empty-icon"><i data-lucide="package" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Context Data</div>
|
||||
<div class="empty-text">No context-package.json or exploration files found for this session.</div>
|
||||
</div>
|
||||
@@ -187,10 +187,10 @@ function renderExplorationContext(explorations) {
|
||||
// Render each exploration angle as collapsible section
|
||||
const explorationOrder = ['architecture', 'dependencies', 'patterns', 'integration-points'];
|
||||
const explorationTitles = {
|
||||
'architecture': '🏗️ Architecture',
|
||||
'dependencies': '📦 Dependencies',
|
||||
'patterns': '🔄 Patterns',
|
||||
'integration-points': '🔌 Integration Points'
|
||||
'architecture': '<i data-lucide="blocks" class="w-4 h-4 inline mr-1"></i>Architecture',
|
||||
'dependencies': '<i data-lucide="package" class="w-4 h-4 inline mr-1"></i>Dependencies',
|
||||
'patterns': '<i data-lucide="git-branch" class="w-4 h-4 inline mr-1"></i>Patterns',
|
||||
'integration-points': '<i data-lucide="plug" class="w-4 h-4 inline mr-1"></i>Integration Points'
|
||||
};
|
||||
|
||||
for (const angle of explorationOrder) {
|
||||
|
||||
@@ -6,6 +6,7 @@ function initTheme() {
|
||||
const saved = localStorage.getItem('theme') || 'light';
|
||||
document.documentElement.setAttribute('data-theme', saved);
|
||||
updateThemeIcon(saved);
|
||||
updateHljsTheme(saved);
|
||||
|
||||
document.getElementById('themeToggle').addEventListener('click', () => {
|
||||
const current = document.documentElement.getAttribute('data-theme');
|
||||
@@ -13,9 +14,36 @@ function initTheme() {
|
||||
document.documentElement.setAttribute('data-theme', next);
|
||||
localStorage.setItem('theme', next);
|
||||
updateThemeIcon(next);
|
||||
updateHljsTheme(next);
|
||||
});
|
||||
}
|
||||
|
||||
function updateThemeIcon(theme) {
|
||||
document.getElementById('themeToggle').textContent = theme === 'light' ? '🌙' : '☀️';
|
||||
const darkIcon = document.querySelector('.theme-icon-dark');
|
||||
const lightIcon = document.querySelector('.theme-icon-light');
|
||||
if (darkIcon && lightIcon) {
|
||||
if (theme === 'light') {
|
||||
darkIcon.classList.remove('hidden');
|
||||
lightIcon.classList.add('hidden');
|
||||
} else {
|
||||
darkIcon.classList.add('hidden');
|
||||
lightIcon.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function updateHljsTheme(theme) {
|
||||
// Toggle highlight.js theme stylesheets
|
||||
const darkTheme = document.getElementById('hljs-theme-dark');
|
||||
const lightTheme = document.getElementById('hljs-theme-light');
|
||||
|
||||
if (darkTheme && lightTheme) {
|
||||
if (theme === 'dark') {
|
||||
darkTheme.disabled = false;
|
||||
lightTheme.disabled = true;
|
||||
} else {
|
||||
darkTheme.disabled = true;
|
||||
lightTheme.disabled = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,9 @@
|
||||
// Initializes all components and sets up global event handlers
|
||||
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Initialize Lucide icons (must be first to render SVG icons)
|
||||
try { lucide.createIcons(); } catch (e) { console.error('Lucide icons init failed:', e); }
|
||||
|
||||
// Initialize components with error handling to prevent cascading failures
|
||||
try { initTheme(); } catch (e) { console.error('Theme init failed:', e); }
|
||||
try { initSidebar(); } catch (e) { console.error('Sidebar init failed:', e); }
|
||||
@@ -12,6 +15,7 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
try { initCarousel(); } catch (e) { console.error('Carousel init failed:', e); }
|
||||
try { initMcpManager(); } catch (e) { console.error('MCP Manager init failed:', e); }
|
||||
try { initHookManager(); } catch (e) { console.error('Hook Manager init failed:', e); }
|
||||
try { initGlobalNotifications(); } catch (e) { console.error('Global notifications init failed:', e); }
|
||||
|
||||
// Initialize real-time features (WebSocket + auto-refresh)
|
||||
try { initWebSocket(); } catch (e) { console.log('WebSocket not available:', e.message); }
|
||||
@@ -20,7 +24,17 @@ document.addEventListener('DOMContentLoaded', async () => {
|
||||
// Server mode: load data from API
|
||||
try {
|
||||
if (window.SERVER_MODE) {
|
||||
await switchToPath(window.INITIAL_PATH || projectPath);
|
||||
// Check URL for path parameter (from ccw view command)
|
||||
const urlParams = new URLSearchParams(window.location.search);
|
||||
const urlPath = urlParams.get('path');
|
||||
const initialPath = urlPath || window.INITIAL_PATH || projectPath;
|
||||
|
||||
await switchToPath(initialPath);
|
||||
|
||||
// Clean up URL after loading (remove query param)
|
||||
if (urlPath && window.history.replaceState) {
|
||||
window.history.replaceState({}, '', window.location.pathname);
|
||||
}
|
||||
} else {
|
||||
renderDashboard();
|
||||
}
|
||||
|
||||
@@ -35,3 +35,8 @@ const liteTaskDataStore = {};
|
||||
// Store task JSON data in a global map instead of inline script tags
|
||||
// Key: unique task ID, Value: raw task JSON data
|
||||
const taskJsonStore = {};
|
||||
|
||||
// ========== Global Notification Queue ==========
|
||||
// Notification queue visible from any view
|
||||
let globalNotificationQueue = [];
|
||||
let isNotificationPanelVisible = false;
|
||||
852
ccw/src/templates/dashboard-js/views/explorer.js
Normal file
852
ccw/src/templates/dashboard-js/views/explorer.js
Normal file
@@ -0,0 +1,852 @@
|
||||
// ============================================
|
||||
// EXPLORER VIEW
|
||||
// ============================================
|
||||
// File tree browser with .gitignore filtering and CLAUDE.md update support
|
||||
// Split-panel layout: file tree (left) + preview (right)
|
||||
|
||||
// Explorer state
|
||||
let explorerCurrentPath = null;
|
||||
let explorerSelectedFile = null;
|
||||
let explorerExpandedDirs = new Set();
|
||||
|
||||
// Task queue for CLAUDE.md updates
|
||||
let updateTaskQueue = [];
|
||||
let isTaskQueueVisible = false;
|
||||
let isTaskRunning = false;
|
||||
let defaultCliTool = 'gemini'; // Default CLI tool for updates
|
||||
|
||||
|
||||
/**
|
||||
* Render the Explorer view
|
||||
*/
|
||||
async function renderExplorer() {
|
||||
const container = document.getElementById('mainContent');
|
||||
if (!container) return;
|
||||
|
||||
// Hide stats grid and search
|
||||
const statsGrid = document.getElementById('statsGrid');
|
||||
const searchInput = document.getElementById('searchInput');
|
||||
if (statsGrid) statsGrid.style.display = 'none';
|
||||
if (searchInput) searchInput.parentElement.style.display = 'none';
|
||||
|
||||
// Initialize explorer path to project path
|
||||
explorerCurrentPath = projectPath;
|
||||
|
||||
container.innerHTML = `
|
||||
<div class="explorer-container">
|
||||
<!-- Left Panel: File Tree -->
|
||||
<div class="explorer-tree-panel">
|
||||
<div class="explorer-tree-header">
|
||||
<div class="explorer-tree-title">
|
||||
<i data-lucide="folder-tree" class="explorer-icon"></i>
|
||||
<span class="explorer-title-text">Explorer</span>
|
||||
</div>
|
||||
<button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="Refresh">
|
||||
<i data-lucide="refresh-cw" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="explorer-tree-content" id="explorerTreeContent">
|
||||
<div class="explorer-loading">Loading file tree...</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Right Panel: Preview -->
|
||||
<div class="explorer-preview-panel">
|
||||
<div class="explorer-preview-header" id="explorerPreviewHeader">
|
||||
<span class="preview-filename">Select a file to preview</span>
|
||||
</div>
|
||||
<div class="explorer-preview-content" id="explorerPreviewContent">
|
||||
<div class="explorer-preview-empty">
|
||||
<div class="preview-empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
|
||||
<div class="preview-empty-text">Select a file from the tree to preview its contents</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Floating Action Button -->
|
||||
<div class="explorer-fab" onclick="toggleTaskQueue()" title="Task Queue">
|
||||
<span class="fab-icon"><i data-lucide="list-todo" class="w-5 h-5"></i></span>
|
||||
<span class="fab-badge" id="fabBadge">0</span>
|
||||
</div>
|
||||
|
||||
<!-- Task Queue Panel -->
|
||||
<div class="task-queue-panel" id="taskQueuePanel">
|
||||
<div class="task-queue-header">
|
||||
<span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> Update Tasks</span>
|
||||
<button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
|
||||
</div>
|
||||
<div class="task-queue-toolbar">
|
||||
<div class="queue-cli-selector">
|
||||
<label>CLI:</label>
|
||||
<select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
|
||||
<option value="gemini">Gemini</option>
|
||||
<option value="qwen">Qwen</option>
|
||||
<option value="codex">Codex</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="task-queue-actions">
|
||||
<button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
|
||||
<i data-lucide="plus" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="Start all tasks">
|
||||
<i data-lucide="play" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="task-queue-list" id="taskQueueList">
|
||||
<div class="task-queue-empty">
|
||||
<span>No tasks in queue</span>
|
||||
<p>Hover folder and click <i data-lucide="file" class="w-3 h-3 inline"></i> or <i data-lucide="folder" class="w-3 h-3 inline"></i> to add tasks</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Load initial file tree
|
||||
await loadExplorerTree(explorerCurrentPath);
|
||||
|
||||
// Initialize Lucide icons for dynamically rendered content
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and render file tree for a directory
|
||||
*/
|
||||
async function loadExplorerTree(dirPath) {
|
||||
const treeContent = document.getElementById('explorerTreeContent');
|
||||
if (!treeContent) return;
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/files?path=${encodeURIComponent(dirPath)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
treeContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
// Render root level
|
||||
treeContent.innerHTML = renderTreeLevel(data.files, dirPath, 0);
|
||||
attachTreeEventListeners();
|
||||
|
||||
// Initialize Lucide icons for tree items
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
|
||||
} catch (error) {
|
||||
treeContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a level of the file tree
|
||||
*/
|
||||
function renderTreeLevel(files, parentPath, depth) {
|
||||
if (!files || files.length === 0) {
|
||||
return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">Empty directory</div>`;
|
||||
}
|
||||
|
||||
return files.map(file => {
|
||||
const isExpanded = explorerExpandedDirs.has(file.path);
|
||||
const isSelected = explorerSelectedFile === file.path;
|
||||
|
||||
if (file.type === 'directory') {
|
||||
const folderIcon = getFolderIcon(file.name, isExpanded, file.hasClaudeMd);
|
||||
const chevronIcon = isExpanded ? '<i data-lucide="chevron-down" class="w-3 h-3"></i>' : '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
|
||||
return `
|
||||
<div class="tree-item tree-folder ${isExpanded ? 'expanded' : ''} ${file.hasClaudeMd ? 'has-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="directory">
|
||||
<div class="tree-item-row ${isSelected ? 'selected' : ''}" style="padding-left: ${depth * 16}px">
|
||||
<span class="tree-chevron">${chevronIcon}</span>
|
||||
<span class="tree-icon">${folderIcon}</span>
|
||||
<span class="tree-name">${escapeHtml(file.name)}</span>
|
||||
${file.hasClaudeMd ? `
|
||||
<span class="claude-md-badge" title="Contains CLAUDE.md documentation">
|
||||
<span class="badge-icon"><i data-lucide="file-check" class="w-3 h-3"></i></span>
|
||||
<span class="badge-text">DOC</span>
|
||||
</span>
|
||||
` : ''}
|
||||
<div class="tree-folder-actions">
|
||||
<button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="Update CLAUDE.md (current folder only)">
|
||||
<span class="update-icon"><i data-lucide="file" class="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
<button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="Update CLAUDE.md (with subdirectories)">
|
||||
<span class="update-icon"><i data-lucide="folder-tree" class="w-3.5 h-3.5"></i></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tree-children ${isExpanded ? 'show' : ''}" id="children-${btoa(file.path).replace(/[^a-zA-Z0-9]/g, '')}">
|
||||
${isExpanded ? '' : ''}
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
|
||||
const fileIcon = getFileIcon(ext);
|
||||
// Special highlight for CLAUDE.md files
|
||||
const isClaudeMd = file.name === 'CLAUDE.md';
|
||||
return `
|
||||
<div class="tree-item tree-file ${isSelected ? 'selected' : ''} ${isClaudeMd ? 'is-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="file">
|
||||
<div class="tree-item-row" style="padding-left: ${depth * 16}px">
|
||||
<span class="tree-chevron-spacer"></span>
|
||||
<span class="tree-icon">${isClaudeMd ? '<span class="file-icon file-icon-claude"><i data-lucide="bot" class="w-3 h-3"></i></span>' : fileIcon}</span>
|
||||
<span class="tree-name">${escapeHtml(file.name)}</span>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}).join('');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get file icon based on extension - using colored SVG icons for better distinction
|
||||
*/
|
||||
function getFileIcon(ext) {
|
||||
const iconMap = {
|
||||
// JavaScript/TypeScript - distinct colors
|
||||
'js': '<span class="file-icon file-icon-js">JS</span>',
|
||||
'mjs': '<span class="file-icon file-icon-js">JS</span>',
|
||||
'cjs': '<span class="file-icon file-icon-js">JS</span>',
|
||||
'jsx': '<span class="file-icon file-icon-jsx">JSX</span>',
|
||||
'ts': '<span class="file-icon file-icon-ts">TS</span>',
|
||||
'tsx': '<span class="file-icon file-icon-tsx">TSX</span>',
|
||||
|
||||
// Python
|
||||
'py': '<span class="file-icon file-icon-py">PY</span>',
|
||||
'pyw': '<span class="file-icon file-icon-py">PY</span>',
|
||||
|
||||
// Other languages
|
||||
'go': '<span class="file-icon file-icon-go">GO</span>',
|
||||
'rs': '<span class="file-icon file-icon-rs">RS</span>',
|
||||
'java': '<span class="file-icon file-icon-java">JV</span>',
|
||||
'rb': '<span class="file-icon file-icon-rb">RB</span>',
|
||||
'php': '<span class="file-icon file-icon-php">PHP</span>',
|
||||
'c': '<span class="file-icon file-icon-c">C</span>',
|
||||
'cpp': '<span class="file-icon file-icon-cpp">C++</span>',
|
||||
'h': '<span class="file-icon file-icon-h">H</span>',
|
||||
'cs': '<span class="file-icon file-icon-cs">C#</span>',
|
||||
'swift': '<span class="file-icon file-icon-swift">SW</span>',
|
||||
'kt': '<span class="file-icon file-icon-kt">KT</span>',
|
||||
|
||||
// Web
|
||||
'html': '<span class="file-icon file-icon-html">HTML</span>',
|
||||
'htm': '<span class="file-icon file-icon-html">HTML</span>',
|
||||
'css': '<span class="file-icon file-icon-css">CSS</span>',
|
||||
'scss': '<span class="file-icon file-icon-scss">SCSS</span>',
|
||||
'sass': '<span class="file-icon file-icon-scss">SASS</span>',
|
||||
'less': '<span class="file-icon file-icon-less">LESS</span>',
|
||||
'vue': '<span class="file-icon file-icon-vue">VUE</span>',
|
||||
'svelte': '<span class="file-icon file-icon-svelte">SV</span>',
|
||||
|
||||
// Config/Data
|
||||
'json': '<span class="file-icon file-icon-json">{}</span>',
|
||||
'yaml': '<span class="file-icon file-icon-yaml">YML</span>',
|
||||
'yml': '<span class="file-icon file-icon-yaml">YML</span>',
|
||||
'xml': '<span class="file-icon file-icon-xml">XML</span>',
|
||||
'toml': '<span class="file-icon file-icon-toml">TML</span>',
|
||||
'ini': '<span class="file-icon file-icon-ini">INI</span>',
|
||||
'env': '<span class="file-icon file-icon-env">ENV</span>',
|
||||
|
||||
// Documentation
|
||||
'md': '<span class="file-icon file-icon-md">MD</span>',
|
||||
'markdown': '<span class="file-icon file-icon-md">MD</span>',
|
||||
'txt': '<span class="file-icon file-icon-txt">TXT</span>',
|
||||
'log': '<span class="file-icon file-icon-log">LOG</span>',
|
||||
|
||||
// Shell/Scripts
|
||||
'sh': '<span class="file-icon file-icon-sh">SH</span>',
|
||||
'bash': '<span class="file-icon file-icon-sh">SH</span>',
|
||||
'zsh': '<span class="file-icon file-icon-sh">ZSH</span>',
|
||||
'ps1': '<span class="file-icon file-icon-ps1">PS1</span>',
|
||||
'bat': '<span class="file-icon file-icon-bat">BAT</span>',
|
||||
'cmd': '<span class="file-icon file-icon-bat">CMD</span>',
|
||||
|
||||
// Database
|
||||
'sql': '<span class="file-icon file-icon-sql">SQL</span>',
|
||||
'db': '<span class="file-icon file-icon-db">DB</span>',
|
||||
|
||||
// Docker/Container
|
||||
'dockerfile': '<span class="file-icon file-icon-docker"><i data-lucide="container" class="w-3 h-3"></i></span>',
|
||||
|
||||
// Images
|
||||
'png': '<span class="file-icon file-icon-img">IMG</span>',
|
||||
'jpg': '<span class="file-icon file-icon-img">IMG</span>',
|
||||
'jpeg': '<span class="file-icon file-icon-img">IMG</span>',
|
||||
'gif': '<span class="file-icon file-icon-img">GIF</span>',
|
||||
'svg': '<span class="file-icon file-icon-svg">SVG</span>',
|
||||
'ico': '<span class="file-icon file-icon-img">ICO</span>',
|
||||
|
||||
// Package
|
||||
'lock': '<span class="file-icon file-icon-lock"><i data-lucide="lock" class="w-3 h-3"></i></span>'
|
||||
};
|
||||
|
||||
return iconMap[ext] || '<span class="file-icon file-icon-default"><i data-lucide="file" class="w-3 h-3"></i></span>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Get folder icon based on folder name and state
|
||||
*/
|
||||
function getFolderIcon(name, isExpanded, hasClaudeMd) {
|
||||
// Only special icon for .workflow folder
|
||||
if (name === '.workflow') {
|
||||
return '<i data-lucide="zap" class="w-4 h-4 text-warning"></i>';
|
||||
}
|
||||
return isExpanded
|
||||
? '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>'
|
||||
: '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
|
||||
}
|
||||
|
||||
/**
|
||||
* Attach event listeners to tree items
|
||||
*/
|
||||
function attachTreeEventListeners() {
|
||||
// Folder click - toggle expand
|
||||
document.querySelectorAll('.tree-folder > .tree-item-row').forEach(row => {
|
||||
row.addEventListener('click', async (e) => {
|
||||
const folder = row.closest('.tree-folder');
|
||||
const path = folder.dataset.path;
|
||||
await toggleFolderExpand(path, folder);
|
||||
});
|
||||
});
|
||||
|
||||
// File click - preview
|
||||
document.querySelectorAll('.tree-file').forEach(item => {
|
||||
item.addEventListener('click', async () => {
|
||||
const path = item.dataset.path;
|
||||
await previewFile(path);
|
||||
|
||||
// Update selection
|
||||
document.querySelectorAll('.tree-item-row.selected, .tree-file.selected').forEach(el => {
|
||||
el.classList.remove('selected');
|
||||
});
|
||||
item.classList.add('selected');
|
||||
explorerSelectedFile = path;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Toggle folder expand/collapse
|
||||
*/
|
||||
async function toggleFolderExpand(path, folderElement) {
|
||||
const isExpanded = explorerExpandedDirs.has(path);
|
||||
const childrenContainer = folderElement.querySelector('.tree-children');
|
||||
const chevron = folderElement.querySelector('.tree-chevron');
|
||||
const folderIcon = folderElement.querySelector('.tree-icon');
|
||||
|
||||
if (isExpanded) {
|
||||
// Collapse
|
||||
explorerExpandedDirs.delete(path);
|
||||
folderElement.classList.remove('expanded');
|
||||
childrenContainer.classList.remove('show');
|
||||
// Update chevron and folder icon
|
||||
if (chevron) chevron.innerHTML = '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
|
||||
if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
|
||||
folderIcon.innerHTML = '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
|
||||
}
|
||||
} else {
|
||||
// Expand - load children if not loaded
|
||||
explorerExpandedDirs.add(path);
|
||||
folderElement.classList.add('expanded');
|
||||
childrenContainer.classList.add('show');
|
||||
// Update chevron and folder icon
|
||||
if (chevron) chevron.innerHTML = '<i data-lucide="chevron-down" class="w-3 h-3"></i>';
|
||||
if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
|
||||
folderIcon.innerHTML = '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>';
|
||||
}
|
||||
|
||||
if (!childrenContainer.innerHTML.trim()) {
|
||||
childrenContainer.innerHTML = '<div class="tree-loading">Loading...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
|
||||
const data = await response.json();
|
||||
|
||||
const depth = (path.match(/\//g) || []).length - (explorerCurrentPath.match(/\//g) || []).length + 1;
|
||||
childrenContainer.innerHTML = renderTreeLevel(data.files, path, depth);
|
||||
attachTreeEventListeners();
|
||||
} catch (error) {
|
||||
childrenContainer.innerHTML = `<div class="tree-error">Failed to load</div>`;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Reinitialize Lucide icons after DOM changes
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preview a file in the right panel
|
||||
*/
|
||||
async function previewFile(filePath) {
|
||||
const previewHeader = document.getElementById('explorerPreviewHeader');
|
||||
const previewContent = document.getElementById('explorerPreviewContent');
|
||||
|
||||
const fileName = filePath.split('/').pop();
|
||||
const ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
|
||||
const isMarkdown = ext === 'md' || ext === 'markdown';
|
||||
|
||||
// Build header with tabs for markdown files
|
||||
previewHeader.innerHTML = `
|
||||
<div class="preview-header-left">
|
||||
<span class="preview-filename">${escapeHtml(fileName)}</span>
|
||||
<span class="preview-path" title="${escapeHtml(filePath)}">${escapeHtml(filePath)}</span>
|
||||
</div>
|
||||
${isMarkdown ? `
|
||||
<div class="preview-header-tabs" id="previewHeaderTabs">
|
||||
<button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">Preview</button>
|
||||
<button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">Source</button>
|
||||
</div>
|
||||
` : ''}
|
||||
`;
|
||||
|
||||
previewContent.innerHTML = '<div class="explorer-loading">Loading file...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (data.error) {
|
||||
previewContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.isMarkdown) {
|
||||
// Render markdown with tabs content (tabs are in header)
|
||||
const rendered = marked.parse(data.content);
|
||||
previewContent.innerHTML = `
|
||||
<div class="preview-tab-content rendered show" data-tab="rendered">
|
||||
<div class="markdown-preview prose">${rendered}</div>
|
||||
</div>
|
||||
<div class="preview-tab-content source" data-tab="source">
|
||||
<pre><code class="language-markdown">${escapeHtml(data.content)}</code></pre>
|
||||
</div>
|
||||
`;
|
||||
} else {
|
||||
// Render code with syntax highlighting
|
||||
previewContent.innerHTML = `
|
||||
<div class="preview-info">
|
||||
<span class="preview-lang">${data.language}</span>
|
||||
<span class="preview-lines">${data.lines} lines</span>
|
||||
<span class="preview-size">${formatFileSize(data.size)}</span>
|
||||
</div>
|
||||
<pre class="preview-code"><code class="language-${data.language}">${escapeHtml(data.content)}</code></pre>
|
||||
`;
|
||||
}
|
||||
|
||||
// Apply syntax highlighting if hljs is available
|
||||
if (typeof hljs !== 'undefined') {
|
||||
previewContent.querySelectorAll('pre code').forEach(block => {
|
||||
hljs.highlightElement(block);
|
||||
});
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
previewContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Switch preview tab (for markdown files)
|
||||
*/
|
||||
function switchPreviewTab(btn, tabName) {
|
||||
const previewPanel = btn.closest('.explorer-preview-panel');
|
||||
const contentArea = previewPanel.querySelector('.explorer-preview-content');
|
||||
|
||||
// Update tab buttons in header
|
||||
previewPanel.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
|
||||
btn.classList.add('active');
|
||||
|
||||
// Update tab content
|
||||
contentArea.querySelectorAll('.preview-tab-content').forEach(c => c.classList.remove('show'));
|
||||
contentArea.querySelector(`[data-tab="${tabName}"]`).classList.add('show');
|
||||
}
|
||||
|
||||
/**
|
||||
* Format file size
|
||||
*/
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes < 1024) return bytes + ' B';
|
||||
if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
|
||||
return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
|
||||
}
|
||||
|
||||
/**
|
||||
* Refresh the explorer tree
|
||||
*/
|
||||
async function refreshExplorerTree() {
|
||||
const btn = document.querySelector('.explorer-refresh-btn');
|
||||
if (btn) {
|
||||
btn.classList.add('refreshing');
|
||||
}
|
||||
|
||||
explorerExpandedDirs.clear();
|
||||
await loadExplorerTree(explorerCurrentPath);
|
||||
|
||||
if (btn) {
|
||||
btn.classList.remove('refreshing');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open Update CLAUDE.md modal
|
||||
*/
|
||||
function openUpdateClaudeMdModal(folderPath) {
|
||||
const modal = document.getElementById('updateClaudeMdModal');
|
||||
if (!modal) return;
|
||||
|
||||
// Set folder path
|
||||
document.getElementById('claudeMdTargetPath').textContent = folderPath;
|
||||
document.getElementById('claudeMdTargetPath').dataset.path = folderPath;
|
||||
|
||||
// Reset form
|
||||
document.getElementById('claudeMdTool').value = 'gemini';
|
||||
document.getElementById('claudeMdStrategy').value = 'single-layer';
|
||||
|
||||
// Show modal
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Close Update CLAUDE.md modal
|
||||
*/
|
||||
function closeUpdateClaudeMdModal() {
|
||||
const modal = document.getElementById('updateClaudeMdModal');
|
||||
if (modal) {
|
||||
modal.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute Update CLAUDE.md
|
||||
*/
|
||||
async function executeUpdateClaudeMd() {
|
||||
const pathEl = document.getElementById('claudeMdTargetPath');
|
||||
const toolSelect = document.getElementById('claudeMdTool');
|
||||
const strategySelect = document.getElementById('claudeMdStrategy');
|
||||
const executeBtn = document.getElementById('claudeMdExecuteBtn');
|
||||
const statusEl = document.getElementById('claudeMdStatus');
|
||||
|
||||
const path = pathEl.dataset.path;
|
||||
const tool = toolSelect.value;
|
||||
const strategy = strategySelect.value;
|
||||
|
||||
// Update UI
|
||||
executeBtn.disabled = true;
|
||||
executeBtn.textContent = 'Updating...';
|
||||
statusEl.innerHTML = '<div class="status-running">⏳ Running update...</div>';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/update-claude-md', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ path, tool, strategy })
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
statusEl.innerHTML = `<div class="status-success"><i data-lucide="check-circle" class="w-4 h-4 inline text-success"></i> ${escapeHtml(result.message)}</div>`;
|
||||
// Refresh tree to update CLAUDE.md indicators
|
||||
await refreshExplorerTree();
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
} else {
|
||||
statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(result.error || 'Update failed')}</div>`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(error.message)}</div>`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
} finally {
|
||||
executeBtn.disabled = false;
|
||||
executeBtn.textContent = 'Execute';
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================
|
||||
// TASK QUEUE FUNCTIONS
|
||||
// ============================================
|
||||
|
||||
/**
|
||||
* Toggle task queue panel visibility
|
||||
*/
|
||||
function toggleTaskQueue() {
|
||||
isTaskQueueVisible = !isTaskQueueVisible;
|
||||
const panel = document.getElementById('taskQueuePanel');
|
||||
const fab = document.querySelector('.explorer-fab');
|
||||
|
||||
if (isTaskQueueVisible) {
|
||||
panel.classList.add('show');
|
||||
fab.classList.add('active');
|
||||
} else {
|
||||
panel.classList.remove('show');
|
||||
fab.classList.remove('active');
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Update default CLI tool
|
||||
*/
|
||||
function updateDefaultCliTool(tool) {
|
||||
defaultCliTool = tool;
|
||||
}
|
||||
|
||||
/**
|
||||
* Update the FAB badge count
|
||||
*/
|
||||
function updateFabBadge() {
|
||||
const badge = document.getElementById('fabBadge');
|
||||
if (badge) {
|
||||
const pendingCount = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running').length;
|
||||
badge.textContent = pendingCount || '';
|
||||
badge.style.display = pendingCount > 0 ? 'flex' : 'none';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Open add task modal
|
||||
*/
|
||||
function openAddTaskModal() {
|
||||
const modal = document.getElementById('updateClaudeMdModal');
|
||||
if (!modal) return;
|
||||
|
||||
// Set default path to current project
|
||||
document.getElementById('claudeMdTargetPath').textContent = explorerCurrentPath;
|
||||
document.getElementById('claudeMdTargetPath').dataset.path = explorerCurrentPath;
|
||||
|
||||
// Reset form
|
||||
document.getElementById('claudeMdTool').value = 'gemini';
|
||||
document.getElementById('claudeMdStrategy').value = 'single-layer';
|
||||
document.getElementById('claudeMdStatus').innerHTML = '';
|
||||
|
||||
// Change button to "Add to Queue"
|
||||
const executeBtn = document.getElementById('claudeMdExecuteBtn');
|
||||
executeBtn.textContent = 'Add to Queue';
|
||||
executeBtn.onclick = addTaskToQueue;
|
||||
|
||||
modal.classList.remove('hidden');
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task to queue from modal
|
||||
*/
|
||||
function addTaskToQueue() {
|
||||
const pathEl = document.getElementById('claudeMdTargetPath');
|
||||
const toolSelect = document.getElementById('claudeMdTool');
|
||||
const strategySelect = document.getElementById('claudeMdStrategy');
|
||||
|
||||
const path = pathEl.dataset.path;
|
||||
const tool = toolSelect.value;
|
||||
const strategy = strategySelect.value;
|
||||
|
||||
addUpdateTask(path, tool, strategy);
|
||||
closeUpdateClaudeMdModal();
|
||||
|
||||
// Show task queue
|
||||
if (!isTaskQueueVisible) {
|
||||
toggleTaskQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a task to the update queue
|
||||
*/
|
||||
function addUpdateTask(path, tool = 'gemini', strategy = 'single-layer') {
|
||||
const task = {
|
||||
id: Date.now(),
|
||||
path,
|
||||
tool,
|
||||
strategy,
|
||||
status: 'pending', // pending, running, completed, failed
|
||||
message: '',
|
||||
addedAt: new Date().toISOString()
|
||||
};
|
||||
|
||||
updateTaskQueue.push(task);
|
||||
renderTaskQueue();
|
||||
updateFabBadge();
|
||||
|
||||
// Enable start button
|
||||
document.getElementById('startQueueBtn').disabled = false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add task from folder context (right-click or button)
|
||||
*/
|
||||
function addFolderToQueue(folderPath, strategy = 'single-layer') {
|
||||
// Use the selected CLI tool from the queue panel
|
||||
addUpdateTask(folderPath, defaultCliTool, strategy);
|
||||
|
||||
// Show task queue if not visible
|
||||
if (!isTaskQueueVisible) {
|
||||
toggleTaskQueue();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the task queue list
|
||||
*/
|
||||
function renderTaskQueue() {
|
||||
const listEl = document.getElementById('taskQueueList');
|
||||
|
||||
if (updateTaskQueue.length === 0) {
|
||||
listEl.innerHTML = `
|
||||
<div class="task-queue-empty">
|
||||
<span>No tasks in queue</span>
|
||||
<p>Right-click a folder or click "Add Task" to queue CLAUDE.md updates</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
listEl.innerHTML = updateTaskQueue.map(task => {
|
||||
const folderName = task.path.split('/').pop() || task.path;
|
||||
const strategyLabel = task.strategy === 'multi-layer'
|
||||
? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> With subdirs'
|
||||
: '<i data-lucide="file" class="w-3 h-3 inline"></i> Current only';
|
||||
const statusIcon = {
|
||||
'pending': '<i data-lucide="clock" class="w-4 h-4"></i>',
|
||||
'running': '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>',
|
||||
'completed': '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>',
|
||||
'failed': '<i data-lucide="x-circle" class="w-4 h-4 text-destructive"></i>'
|
||||
}[task.status];
|
||||
|
||||
return `
|
||||
<div class="task-queue-item status-${task.status}" data-task-id="${task.id}">
|
||||
<div class="task-item-header">
|
||||
<span class="task-status-icon">${statusIcon}</span>
|
||||
<span class="task-folder-name" title="${escapeHtml(task.path)}">${escapeHtml(folderName)}</span>
|
||||
${task.status === 'pending' ? `
|
||||
<button class="task-remove-btn" onclick="removeTask(${task.id})" title="Remove">×</button>
|
||||
` : ''}
|
||||
</div>
|
||||
<div class="task-item-meta">
|
||||
<span class="task-strategy">${strategyLabel}</span>
|
||||
<span class="task-tool">${task.tool}</span>
|
||||
</div>
|
||||
${task.message ? `<div class="task-item-message">${escapeHtml(task.message)}</div>` : ''}
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// Reinitialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove a task from queue
|
||||
*/
|
||||
function removeTask(taskId) {
|
||||
updateTaskQueue = updateTaskQueue.filter(t => t.id !== taskId);
|
||||
renderTaskQueue();
|
||||
updateFabBadge();
|
||||
|
||||
// Disable start button if no pending tasks
|
||||
const hasPending = updateTaskQueue.some(t => t.status === 'pending');
|
||||
document.getElementById('startQueueBtn').disabled = !hasPending;
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear completed/failed tasks
|
||||
*/
|
||||
function clearCompletedTasks() {
|
||||
updateTaskQueue = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running');
|
||||
renderTaskQueue();
|
||||
updateFabBadge();
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a single task asynchronously
|
||||
*/
|
||||
async function executeTask(task) {
|
||||
const folderName = task.path.split('/').pop() || task.path;
|
||||
|
||||
// Update status to running
|
||||
task.status = 'running';
|
||||
task.message = 'Processing...';
|
||||
renderTaskQueue();
|
||||
|
||||
addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer');
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/update-claude-md', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
path: task.path,
|
||||
tool: task.tool,
|
||||
strategy: task.strategy
|
||||
})
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (result.success) {
|
||||
task.status = 'completed';
|
||||
task.message = 'Updated successfully';
|
||||
addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer');
|
||||
return { success: true };
|
||||
} else {
|
||||
task.status = 'failed';
|
||||
task.message = result.error || 'Update failed';
|
||||
addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer');
|
||||
return { success: false };
|
||||
}
|
||||
} catch (error) {
|
||||
task.status = 'failed';
|
||||
task.message = error.message;
|
||||
addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer');
|
||||
return { success: false };
|
||||
} finally {
|
||||
renderTaskQueue();
|
||||
updateFabBadge();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Start processing task queue - executes tasks asynchronously in parallel
|
||||
*/
|
||||
async function startTaskQueue() {
|
||||
if (isTaskRunning) return;
|
||||
|
||||
const pendingTasks = updateTaskQueue.filter(t => t.status === 'pending');
|
||||
if (pendingTasks.length === 0) return;
|
||||
|
||||
isTaskRunning = true;
|
||||
document.getElementById('startQueueBtn').disabled = true;
|
||||
|
||||
addGlobalNotification('info', `Starting ${pendingTasks.length} task(s) in parallel...`, null, 'Explorer');
|
||||
|
||||
// Execute all tasks in parallel
|
||||
const results = await Promise.all(pendingTasks.map(task => executeTask(task)));
|
||||
|
||||
const successCount = results.filter(r => r.success).length;
|
||||
const failCount = results.filter(r => !r.success).length;
|
||||
|
||||
isTaskRunning = false;
|
||||
|
||||
// Summary notification
|
||||
addGlobalNotification(
|
||||
failCount === 0 ? 'success' : 'warning',
|
||||
`Queue completed: ${successCount} succeeded, ${failCount} failed`,
|
||||
null,
|
||||
'Explorer'
|
||||
);
|
||||
|
||||
// Force refresh notification list to ensure all notifications are displayed
|
||||
if (typeof renderGlobalNotifications === 'function') {
|
||||
renderGlobalNotifications();
|
||||
updateGlobalNotifBadge();
|
||||
}
|
||||
|
||||
// Re-enable start button if there are pending tasks
|
||||
const hasPending = updateTaskQueue.some(t => t.status === 'pending');
|
||||
document.getElementById('startQueueBtn').disabled = !hasPending;
|
||||
|
||||
// Refresh tree to show updated CLAUDE.md files
|
||||
await refreshExplorerTree();
|
||||
}
|
||||
|
||||
@@ -73,15 +73,19 @@ function renderSessions() {
|
||||
if (sessions.length === 0) {
|
||||
container.innerHTML = `
|
||||
<div class="empty-state" style="grid-column: 1/-1;">
|
||||
<div class="empty-icon">📭</div>
|
||||
<div class="empty-icon"><i data-lucide="inbox" class="w-12 h-12"></i></div>
|
||||
<div class="empty-title">No Sessions Found</div>
|
||||
<div class="empty-text">No workflow sessions match your current filter.</div>
|
||||
</div>
|
||||
`;
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
return;
|
||||
}
|
||||
|
||||
container.innerHTML = `<div class="sessions-grid">${sessions.map(session => renderSessionCard(session)).join('')}</div>`;
|
||||
|
||||
// Initialize Lucide icons after rendering
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
function renderSessionCard(session) {
|
||||
@@ -120,8 +124,8 @@ function renderSessionCard(session) {
|
||||
</div>
|
||||
<div class="session-body">
|
||||
<div class="session-meta">
|
||||
<span class="session-meta-item">📅 ${formatDate(date)}</span>
|
||||
<span class="session-meta-item">📋 ${taskCount} tasks</span>
|
||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
||||
<span class="session-meta-item"><i data-lucide="list-checks" class="w-3.5 h-3.5 inline mr-1"></i>${taskCount} tasks</span>
|
||||
</div>
|
||||
${taskCount > 0 ? `
|
||||
<div class="progress-container">
|
||||
@@ -171,16 +175,16 @@ function renderReviewSessionCard(session, sessionKey, typeBadge, isActive, date)
|
||||
</div>
|
||||
<div class="session-body">
|
||||
<div class="session-meta">
|
||||
<span class="session-meta-item">📅 ${formatDate(date)}</span>
|
||||
<span class="session-meta-item">🔍 ${totalFindings} findings</span>
|
||||
<span class="session-meta-item"><i data-lucide="calendar" class="w-3.5 h-3.5 inline mr-1"></i>${formatDate(date)}</span>
|
||||
<span class="session-meta-item"><i data-lucide="search" class="w-3.5 h-3.5 inline mr-1"></i>${totalFindings} findings</span>
|
||||
</div>
|
||||
${totalFindings > 0 ? `
|
||||
<div class="review-findings-summary">
|
||||
<div class="findings-severity-row">
|
||||
${criticalCount > 0 ? `<span class="finding-count critical">🔴 ${criticalCount}</span>` : ''}
|
||||
${highCount > 0 ? `<span class="finding-count high">🟠 ${highCount}</span>` : ''}
|
||||
${mediumCount > 0 ? `<span class="finding-count medium">🟡 ${mediumCount}</span>` : ''}
|
||||
${lowCount > 0 ? `<span class="finding-count low">🟢 ${lowCount}</span>` : ''}
|
||||
${criticalCount > 0 ? `<span class="finding-count critical"><i data-lucide="alert-circle" class="w-3 h-3 inline"></i> ${criticalCount}</span>` : ''}
|
||||
${highCount > 0 ? `<span class="finding-count high"><i data-lucide="alert-triangle" class="w-3 h-3 inline"></i> ${highCount}</span>` : ''}
|
||||
${mediumCount > 0 ? `<span class="finding-count medium"><i data-lucide="info" class="w-3 h-3 inline"></i> ${mediumCount}</span>` : ''}
|
||||
${lowCount > 0 ? `<span class="finding-count low"><i data-lucide="check-circle" class="w-3 h-3 inline"></i> ${lowCount}</span>` : ''}
|
||||
</div>
|
||||
<div class="dimensions-info">
|
||||
${dimensions.length} dimensions
|
||||
|
||||
@@ -41,7 +41,7 @@ async function renderHookManager() {
|
||||
|
||||
${projectHookCount === 0 ? `
|
||||
<div class="hook-empty-state bg-card border border-border rounded-lg p-6 text-center">
|
||||
<div class="text-3xl mb-3">🪝</div>
|
||||
<div class="text-muted-foreground mb-3"><i data-lucide="webhook" class="w-10 h-10 mx-auto"></i></div>
|
||||
<p class="text-muted-foreground">No hooks configured for this project</p>
|
||||
<p class="text-sm text-muted-foreground mt-1">Create a hook to automate actions on tool usage</p>
|
||||
</div>
|
||||
@@ -133,6 +133,9 @@ async function renderHookManager() {
|
||||
|
||||
// Attach event listeners
|
||||
attachHookEventListeners();
|
||||
|
||||
// Initialize Lucide icons
|
||||
if (typeof lucide !== 'undefined') lucide.createIcons();
|
||||
}
|
||||
|
||||
function countHooks(hooks) {
|
||||
@@ -160,7 +163,7 @@ function renderHooksByEvent(hooks, scope) {
|
||||
<div class="hook-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xl">${getHookEventIcon(event)}</span>
|
||||
${getHookEventIconLucide(event)}
|
||||
<div>
|
||||
<h4 class="font-semibold text-foreground">${event}</h4>
|
||||
<p class="text-xs text-muted-foreground">${getHookEventDescription(event)}</p>
|
||||
@@ -173,7 +176,7 @@ function renderHooksByEvent(hooks, scope) {
|
||||
data-index="${index}"
|
||||
data-action="edit"
|
||||
title="Edit hook">
|
||||
✏️
|
||||
<i data-lucide="pencil" class="w-4 h-4"></i>
|
||||
</button>
|
||||
<button class="p-1.5 text-muted-foreground hover:text-destructive hover:bg-destructive/10 rounded transition-colors"
|
||||
data-scope="${scope}"
|
||||
@@ -181,7 +184,7 @@ function renderHooksByEvent(hooks, scope) {
|
||||
data-index="${index}"
|
||||
data-action="delete"
|
||||
title="Delete hook">
|
||||
🗑️
|
||||
<i data-lucide="trash-2" class="w-4 h-4"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -215,7 +218,7 @@ function renderQuickInstallCard(templateId, title, description, event, matcher)
|
||||
<div class="hook-template-card bg-card border border-border rounded-lg p-4 hover:shadow-md transition-all ${isInstalled ? 'border-success bg-success-light/30' : ''}">
|
||||
<div class="flex items-start justify-between mb-3">
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="text-xl">${isInstalled ? '✅' : '🪝'}</span>
|
||||
${isInstalled ? '<i data-lucide="check-circle" class="w-5 h-5 text-success"></i>' : '<i data-lucide="webhook" class="w-5 h-5"></i>'}
|
||||
<div>
|
||||
<h4 class="font-semibold text-foreground">${escapeHtml(title)}</h4>
|
||||
<p class="text-xs text-muted-foreground">${escapeHtml(description)}</p>
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user