claude-code-workflow 6.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude/agents/action-planning-agent.md +778 -0
- package/.claude/agents/cli-execution-agent.md +270 -0
- package/.claude/agents/cli-explore-agent.md +182 -0
- package/.claude/agents/cli-lite-planning-agent.md +396 -0
- package/.claude/agents/cli-planning-agent.md +558 -0
- package/.claude/agents/code-developer.md +310 -0
- package/.claude/agents/conceptual-planning-agent.md +308 -0
- package/.claude/agents/context-search-agent.md +582 -0
- package/.claude/agents/doc-generator.md +330 -0
- package/.claude/agents/memory-bridge.md +94 -0
- package/.claude/agents/test-context-search-agent.md +399 -0
- package/.claude/agents/test-fix-agent.md +343 -0
- package/.claude/agents/ui-design-agent.md +593 -0
- package/.claude/agents/universal-executor.md +131 -0
- package/.claude/commands/cli/cli-init.md +440 -0
- package/.claude/commands/enhance-prompt.md +93 -0
- package/.claude/commands/memory/code-map-memory.md +687 -0
- package/.claude/commands/memory/docs-full-cli.md +471 -0
- package/.claude/commands/memory/docs-related-cli.md +386 -0
- package/.claude/commands/memory/docs.md +615 -0
- package/.claude/commands/memory/load-skill-memory.md +182 -0
- package/.claude/commands/memory/load.md +240 -0
- package/.claude/commands/memory/skill-memory.md +525 -0
- package/.claude/commands/memory/style-skill-memory.md +396 -0
- package/.claude/commands/memory/tech-research.md +477 -0
- package/.claude/commands/memory/update-full.md +332 -0
- package/.claude/commands/memory/update-related.md +332 -0
- package/.claude/commands/memory/workflow-skill-memory.md +517 -0
- package/.claude/commands/task/breakdown.md +204 -0
- package/.claude/commands/task/create.md +152 -0
- package/.claude/commands/task/execute.md +270 -0
- package/.claude/commands/task/replan.md +437 -0
- package/.claude/commands/version.md +254 -0
- package/.claude/commands/workflow/action-plan-verify.md +447 -0
- package/.claude/commands/workflow/brainstorm/api-designer.md +585 -0
- package/.claude/commands/workflow/brainstorm/artifacts.md +452 -0
- package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -0
- package/.claude/commands/workflow/brainstorm/data-architect.md +220 -0
- package/.claude/commands/workflow/brainstorm/product-manager.md +200 -0
- package/.claude/commands/workflow/brainstorm/product-owner.md +200 -0
- package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -0
- package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
- package/.claude/commands/workflow/brainstorm/synthesis.md +398 -0
- package/.claude/commands/workflow/brainstorm/system-architect.md +387 -0
- package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -0
- package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -0
- package/.claude/commands/workflow/execute.md +460 -0
- package/.claude/commands/workflow/init.md +164 -0
- package/.claude/commands/workflow/lite-execute.md +686 -0
- package/.claude/commands/workflow/lite-fix.md +621 -0
- package/.claude/commands/workflow/lite-plan.md +592 -0
- package/.claude/commands/workflow/plan.md +551 -0
- package/.claude/commands/workflow/replan.md +515 -0
- package/.claude/commands/workflow/review-fix.md +646 -0
- package/.claude/commands/workflow/review-module-cycle.md +795 -0
- package/.claude/commands/workflow/review-session-cycle.md +805 -0
- package/.claude/commands/workflow/review.md +291 -0
- package/.claude/commands/workflow/session/complete.md +500 -0
- package/.claude/commands/workflow/session/list.md +96 -0
- package/.claude/commands/workflow/session/resume.md +61 -0
- package/.claude/commands/workflow/session/start.md +200 -0
- package/.claude/commands/workflow/status.md +352 -0
- package/.claude/commands/workflow/tdd-plan.md +460 -0
- package/.claude/commands/workflow/tdd-verify.md +386 -0
- package/.claude/commands/workflow/test-cycle-execute.md +498 -0
- package/.claude/commands/workflow/test-fix-gen.md +699 -0
- package/.claude/commands/workflow/test-gen.md +529 -0
- package/.claude/commands/workflow/tools/conflict-resolution.md +680 -0
- package/.claude/commands/workflow/tools/context-gather.md +434 -0
- package/.claude/commands/workflow/tools/task-generate-agent.md +291 -0
- package/.claude/commands/workflow/tools/task-generate-tdd.md +518 -0
- package/.claude/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
- package/.claude/commands/workflow/tools/test-concept-enhanced.md +163 -0
- package/.claude/commands/workflow/tools/test-context-gather.md +235 -0
- package/.claude/commands/workflow/tools/test-task-generate.md +256 -0
- package/.claude/commands/workflow/ui-design/animation-extract.md +1150 -0
- package/.claude/commands/workflow/ui-design/codify-style.md +652 -0
- package/.claude/commands/workflow/ui-design/design-sync.md +454 -0
- package/.claude/commands/workflow/ui-design/explore-auto.md +678 -0
- package/.claude/commands/workflow/ui-design/generate.md +504 -0
- package/.claude/commands/workflow/ui-design/imitate-auto.md +745 -0
- package/.claude/commands/workflow/ui-design/import-from-code.md +537 -0
- package/.claude/commands/workflow/ui-design/layout-extract.md +788 -0
- package/.claude/commands/workflow/ui-design/reference-page-generator.md +356 -0
- package/.claude/commands/workflow/ui-design/style-extract.md +773 -0
- package/.claude/scripts/classify-folders.sh +35 -0
- package/.claude/scripts/convert_tokens_to_css.sh +225 -0
- package/.claude/scripts/detect_changed_modules.sh +157 -0
- package/.claude/scripts/discover-design-files.sh +83 -0
- package/.claude/scripts/extract-animations.js +243 -0
- package/.claude/scripts/extract-computed-styles.js +118 -0
- package/.claude/scripts/extract-layout-structure.js +411 -0
- package/.claude/scripts/generate_module_docs.sh +713 -0
- package/.claude/scripts/get_modules_by_depth.sh +166 -0
- package/.claude/scripts/ui-generate-preview.sh +391 -0
- package/.claude/scripts/ui-instantiate-prototypes.sh +811 -0
- package/.claude/scripts/update_module_claude.sh +333 -0
- package/.claude/skills/command-guide/SKILL.md +388 -0
- package/.claude/skills/command-guide/UPDATE-GUIDELINE.md +592 -0
- package/.claude/skills/command-guide/guides/cli-tools-guide.md +410 -0
- package/.claude/skills/command-guide/guides/examples.md +537 -0
- package/.claude/skills/command-guide/guides/getting-started.md +242 -0
- package/.claude/skills/command-guide/guides/implementation-details.md +1010 -0
- package/.claude/skills/command-guide/guides/index-structure.md +326 -0
- package/.claude/skills/command-guide/guides/troubleshooting.md +92 -0
- package/.claude/skills/command-guide/guides/ui-design-workflow-guide.md +316 -0
- package/.claude/skills/command-guide/guides/workflow-patterns.md +662 -0
- package/.claude/skills/command-guide/index/all-commands.json +783 -0
- package/.claude/skills/command-guide/index/by-category.json +811 -0
- package/.claude/skills/command-guide/index/by-use-case.json +797 -0
- package/.claude/skills/command-guide/index/command-relationships.json +307 -0
- package/.claude/skills/command-guide/index/essential-commands.json +123 -0
- package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +722 -0
- package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +270 -0
- package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -0
- package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +396 -0
- package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +558 -0
- package/.claude/skills/command-guide/reference/agents/code-developer.md +310 -0
- package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +328 -0
- package/.claude/skills/command-guide/reference/agents/context-search-agent.md +577 -0
- package/.claude/skills/command-guide/reference/agents/doc-generator.md +330 -0
- package/.claude/skills/command-guide/reference/agents/memory-bridge.md +94 -0
- package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +399 -0
- package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +343 -0
- package/.claude/skills/command-guide/reference/agents/ui-design-agent.md +593 -0
- package/.claude/skills/command-guide/reference/agents/universal-executor.md +131 -0
- package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +440 -0
- package/.claude/skills/command-guide/reference/commands/enhance-prompt.md +93 -0
- package/.claude/skills/command-guide/reference/commands/memory/code-map-memory.md +687 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -0
- package/.claude/skills/command-guide/reference/commands/memory/docs.md +610 -0
- package/.claude/skills/command-guide/reference/commands/memory/load-skill-memory.md +182 -0
- package/.claude/skills/command-guide/reference/commands/memory/load.md +240 -0
- package/.claude/skills/command-guide/reference/commands/memory/skill-memory.md +525 -0
- package/.claude/skills/command-guide/reference/commands/memory/style-skill-memory.md +396 -0
- package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +477 -0
- package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -0
- package/.claude/skills/command-guide/reference/commands/memory/update-related.md +332 -0
- package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +517 -0
- package/.claude/skills/command-guide/reference/commands/task/breakdown.md +204 -0
- package/.claude/skills/command-guide/reference/commands/task/create.md +152 -0
- package/.claude/skills/command-guide/reference/commands/task/execute.md +270 -0
- package/.claude/skills/command-guide/reference/commands/task/replan.md +437 -0
- package/.claude/skills/command-guide/reference/commands/version.md +254 -0
- package/.claude/skills/command-guide/reference/commands/workflow/action-plan-verify.md +447 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +604 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +466 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +496 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -0
- package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -0
- package/.claude/skills/command-guide/reference/commands/workflow/execute.md +460 -0
- package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +634 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +602 -0
- package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +582 -0
- package/.claude/skills/command-guide/reference/commands/workflow/plan.md +551 -0
- package/.claude/skills/command-guide/reference/commands/workflow/replan.md +515 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +646 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +795 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +805 -0
- package/.claude/skills/command-guide/reference/commands/workflow/review.md +291 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +500 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +96 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +61 -0
- package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +180 -0
- package/.claude/skills/command-guide/reference/commands/workflow/status.md +352 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +460 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +386 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-cycle-execute.md +498 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -0
- package/.claude/skills/command-guide/reference/commands/workflow/test-gen.md +529 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +680 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +518 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +163 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +235 -0
- package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +256 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/animation-extract.md +1150 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/codify-style.md +652 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/design-sync.md +454 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/explore-auto.md +678 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/imitate-auto.md +745 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/layout-extract.md +788 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/reference-page-generator.md +356 -0
- package/.claude/skills/command-guide/reference/commands/workflow/ui-design/style-extract.md +773 -0
- package/.claude/skills/command-guide/scripts/analyze_commands.py +502 -0
- package/.claude/skills/command-guide/scripts/update-index.sh +130 -0
- package/.claude/skills/command-guide/templates/issue-bug.md +104 -0
- package/.claude/skills/command-guide/templates/issue-diagnosis.md +275 -0
- package/.claude/skills/command-guide/templates/issue-feature.md +97 -0
- package/.claude/skills/command-guide/templates/issue-question.md +141 -0
- package/.claude/skills/prompt-enhancer/SKILL.md +124 -0
- package/.claude/workflows/_template-compare-matrix.html +692 -0
- package/.claude/workflows/cli-templates/fix-plan-template.json +75 -0
- package/.claude/workflows/cli-templates/fix-progress-template.json +48 -0
- package/.claude/workflows/cli-templates/memory/style-skill-memory/skill-md-template.md +299 -0
- package/.claude/workflows/cli-templates/planning-roles/data-architect.md +120 -0
- package/.claude/workflows/cli-templates/planning-roles/product-manager.md +119 -0
- package/.claude/workflows/cli-templates/planning-roles/product-owner.md +261 -0
- package/.claude/workflows/cli-templates/planning-roles/scrum-master.md +186 -0
- package/.claude/workflows/cli-templates/planning-roles/subject-matter-expert.md +281 -0
- package/.claude/workflows/cli-templates/planning-roles/synthesis-role.md +414 -0
- package/.claude/workflows/cli-templates/planning-roles/system-architect.md +106 -0
- package/.claude/workflows/cli-templates/planning-roles/test-strategist.md +124 -0
- package/.claude/workflows/cli-templates/planning-roles/ui-designer.md +379 -0
- package/.claude/workflows/cli-templates/planning-roles/ux-expert.md +240 -0
- package/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt +127 -0
- package/.claude/workflows/cli-templates/prompts/analysis/01-trace-code-execution.txt +115 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt +37 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-technical-document.txt +33 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-review-architecture.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/02-review-code-quality.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-analyze-performance.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-assess-security-risks.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/analysis/03-review-quality-standards.txt +29 -0
- package/.claude/workflows/cli-templates/prompts/development/02-generate-tests.txt +70 -0
- package/.claude/workflows/cli-templates/prompts/development/02-implement-component-ui.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt +58 -0
- package/.claude/workflows/cli-templates/prompts/development/02-refactor-codebase.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/development/03-debug-runtime-issues.txt +55 -0
- package/.claude/workflows/cli-templates/prompts/documentation/api.txt +15 -0
- package/.claude/workflows/cli-templates/prompts/documentation/folder-navigation.txt +27 -0
- package/.claude/workflows/cli-templates/prompts/documentation/module-readme.txt +49 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-architecture.txt +41 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-examples.txt +35 -0
- package/.claude/workflows/cli-templates/prompts/documentation/project-readme.txt +35 -0
- package/.claude/workflows/cli-templates/prompts/memory/02-document-module-structure.txt +165 -0
- package/.claude/workflows/cli-templates/prompts/planning/01-plan-architecture-design.txt +109 -0
- package/.claude/workflows/cli-templates/prompts/planning/02-breakdown-task-steps.txt +30 -0
- package/.claude/workflows/cli-templates/prompts/planning/02-design-component-spec.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/planning/03-evaluate-concept-feasibility.txt +127 -0
- package/.claude/workflows/cli-templates/prompts/planning/03-plan-migration-strategy.txt +30 -0
- package/.claude/workflows/cli-templates/prompts/tech/tech-module-format.txt +359 -0
- package/.claude/workflows/cli-templates/prompts/tech/tech-skill-index.txt +185 -0
- package/.claude/workflows/cli-templates/prompts/test/test-concept-analysis.txt +179 -0
- package/.claude/workflows/cli-templates/prompts/universal/00-universal-creative-style.txt +95 -0
- package/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt +92 -0
- package/.claude/workflows/cli-templates/prompts/verification/codex-technical.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/verification/cross-validation.txt +28 -0
- package/.claude/workflows/cli-templates/prompts/verification/gemini-strategic.txt +27 -0
- package/.claude/workflows/cli-templates/prompts/workflow/analysis-results-structure.txt +224 -0
- package/.claude/workflows/cli-templates/prompts/workflow/codex-feasibility-validation.txt +176 -0
- package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -0
- package/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt +286 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-aggregation.txt +172 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +98 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-index.txt +224 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +98 -0
- package/.claude/workflows/cli-templates/prompts/workflow/skill-sessions-timeline.txt +53 -0
- package/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt +123 -0
- package/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt +182 -0
- package/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json +234 -0
- package/.claude/workflows/cli-templates/schemas/explore-json-schema.json +124 -0
- package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +273 -0
- package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +219 -0
- package/.claude/workflows/cli-templates/schemas/project-json-schema.json +221 -0
- package/.claude/workflows/cli-templates/schemas/review-deep-dive-results-schema.json +82 -0
- package/.claude/workflows/cli-templates/schemas/review-dimension-results-schema.json +51 -0
- package/.claude/workflows/cli-templates/tech-stacks/go-dev.md +91 -0
- package/.claude/workflows/cli-templates/tech-stacks/java-dev.md +107 -0
- package/.claude/workflows/cli-templates/tech-stacks/javascript-dev.md +58 -0
- package/.claude/workflows/cli-templates/tech-stacks/python-dev.md +79 -0
- package/.claude/workflows/cli-templates/tech-stacks/react-dev.md +103 -0
- package/.claude/workflows/cli-templates/tech-stacks/typescript-dev.md +83 -0
- package/.claude/workflows/cli-templates/ui-design/systems/animation-tokens.json +247 -0
- package/.claude/workflows/cli-templates/ui-design/systems/design-tokens.json +342 -0
- package/.claude/workflows/cli-templates/ui-design/systems/layout-templates.json +145 -0
- package/.claude/workflows/context-search-strategy.md +77 -0
- package/.claude/workflows/intelligent-tools-strategy.md +662 -0
- package/.claude/workflows/review-directory-specification.md +336 -0
- package/.claude/workflows/task-core.md +214 -0
- package/.claude/workflows/tool-strategy.md +71 -0
- package/.claude/workflows/workflow-architecture.md +942 -0
- package/.codex/AGENTS.md +330 -0
- package/.gemini/GEMINI.md +164 -0
- package/.qwen/QWEN.md +164 -0
- package/CLAUDE.md +91 -0
- package/LICENSE +21 -0
- package/README.md +219 -0
- package/ccw/README.md +121 -0
- package/ccw/bin/ccw.js +10 -0
- package/ccw/src/cli.js +100 -0
- package/ccw/src/commands/install.js +324 -0
- package/ccw/src/commands/list.js +37 -0
- package/ccw/src/commands/serve.js +67 -0
- package/ccw/src/commands/uninstall.js +238 -0
- package/ccw/src/commands/upgrade.js +307 -0
- package/ccw/src/commands/view.js +14 -0
- package/ccw/src/core/dashboard-generator-patch.js +29 -0
- package/ccw/src/core/dashboard-generator.js +667 -0
- package/ccw/src/core/data-aggregator.js +409 -0
- package/ccw/src/core/lite-scanner.js +290 -0
- package/ccw/src/core/manifest.js +201 -0
- package/ccw/src/core/server.js +1327 -0
- package/ccw/src/core/server.js.bak +385 -0
- package/ccw/src/core/server_original.bak +385 -0
- package/ccw/src/core/session-scanner.js +235 -0
- package/ccw/src/index.js +9 -0
- package/ccw/src/templates/dashboard-js/api.js +200 -0
- package/ccw/src/templates/dashboard-js/components/_conflict_tab.js +112 -0
- package/ccw/src/templates/dashboard-js/components/_exp_helpers.js +54 -0
- package/ccw/src/templates/dashboard-js/components/_review_tab.js +640 -0
- package/ccw/src/templates/dashboard-js/components/carousel.js +398 -0
- package/ccw/src/templates/dashboard-js/components/flowchart.js +493 -0
- package/ccw/src/templates/dashboard-js/components/hook-manager.js +273 -0
- package/ccw/src/templates/dashboard-js/components/mcp-manager.js +506 -0
- package/ccw/src/templates/dashboard-js/components/modals.js +260 -0
- package/ccw/src/templates/dashboard-js/components/navigation.js +239 -0
- package/ccw/src/templates/dashboard-js/components/notifications.js +194 -0
- package/ccw/src/templates/dashboard-js/components/sidebar.js +31 -0
- package/ccw/src/templates/dashboard-js/components/tabs-context.js +1093 -0
- package/ccw/src/templates/dashboard-js/components/tabs-other.js +273 -0
- package/ccw/src/templates/dashboard-js/components/task-drawer-core.js +477 -0
- package/ccw/src/templates/dashboard-js/components/task-drawer-renderers.js +447 -0
- package/ccw/src/templates/dashboard-js/components/theme.js +21 -0
- package/ccw/src/templates/dashboard-js/main.js +57 -0
- package/ccw/src/templates/dashboard-js/state.js +37 -0
- package/ccw/src/templates/dashboard-js/utils.js +153 -0
- package/ccw/src/templates/dashboard-js/views/fix-session.js +180 -0
- package/ccw/src/templates/dashboard-js/views/home.js +193 -0
- package/ccw/src/templates/dashboard-js/views/hook-manager.js +387 -0
- package/ccw/src/templates/dashboard-js/views/lite-tasks.js +390 -0
- package/ccw/src/templates/dashboard-js/views/mcp-manager.js +271 -0
- package/ccw/src/templates/dashboard-js/views/project-overview.js +246 -0
- package/ccw/src/templates/dashboard-js/views/review-session.js +711 -0
- package/ccw/src/templates/dashboard-js/views/session-detail.js +770 -0
- package/ccw/src/templates/dashboard.css +7660 -0
- package/ccw/src/templates/dashboard.html +630 -0
- package/ccw/src/templates/dashboard_tailwind.html +42 -0
- package/ccw/src/templates/dashboard_test.html +37 -0
- package/ccw/src/templates/review-cycle-dashboard.html +1930 -0
- package/ccw/src/templates/tailwind-base.css +212 -0
- package/ccw/src/templates/workflow-dashboard.html +401 -0
- package/ccw/src/utils/browser-launcher.js +49 -0
- package/ccw/src/utils/file-utils.js +48 -0
- package/ccw/src/utils/path-resolver.js +279 -0
- package/ccw/src/utils/ui.js +148 -0
- package/package.json +66 -0
|
@@ -0,0 +1,1327 @@
|
|
|
1
|
+
import http from 'http';
|
|
2
|
+
import { URL } from 'url';
|
|
3
|
+
import { readFileSync, writeFileSync, existsSync, readdirSync, mkdirSync } from 'fs';
|
|
4
|
+
import { join, dirname } from 'path';
|
|
5
|
+
import { homedir } from 'os';
|
|
6
|
+
import { createHash } from 'crypto';
|
|
7
|
+
import { scanSessions } from './session-scanner.js';
|
|
8
|
+
import { aggregateData } from './data-aggregator.js';
|
|
9
|
+
import { resolvePath, getRecentPaths, trackRecentPath, removeRecentPath, normalizePathForDisplay, getWorkflowDir } from '../utils/path-resolver.js';
|
|
10
|
+
|
|
11
|
+
// Claude config file path
|
|
12
|
+
const CLAUDE_CONFIG_PATH = join(homedir(), '.claude.json');
|
|
13
|
+
|
|
14
|
+
// WebSocket clients for real-time notifications
|
|
15
|
+
const wsClients = new Set();
|
|
16
|
+
|
|
17
|
+
const TEMPLATE_PATH = join(import.meta.dirname, '../templates/dashboard.html');
|
|
18
|
+
const CSS_FILE = join(import.meta.dirname, '../templates/dashboard.css');
|
|
19
|
+
const JS_FILE = join(import.meta.dirname, '../templates/dashboard.js');
|
|
20
|
+
const MODULE_JS_DIR = join(import.meta.dirname, '../templates/dashboard-js');
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Handle POST request with JSON body
|
|
24
|
+
*/
|
|
25
|
+
function handlePostRequest(req, res, handler) {
|
|
26
|
+
let body = '';
|
|
27
|
+
req.on('data', chunk => { body += chunk; });
|
|
28
|
+
req.on('end', async () => {
|
|
29
|
+
try {
|
|
30
|
+
const parsed = JSON.parse(body);
|
|
31
|
+
const result = await handler(parsed);
|
|
32
|
+
|
|
33
|
+
if (result.error) {
|
|
34
|
+
const status = result.status || 500;
|
|
35
|
+
res.writeHead(status, { 'Content-Type': 'application/json' });
|
|
36
|
+
res.end(JSON.stringify({ error: result.error }));
|
|
37
|
+
} else {
|
|
38
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
39
|
+
res.end(JSON.stringify(result));
|
|
40
|
+
}
|
|
41
|
+
} catch (error) {
|
|
42
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
43
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// Modular JS files in dependency order
|
|
49
|
+
const MODULE_FILES = [
|
|
50
|
+
'utils.js',
|
|
51
|
+
'state.js',
|
|
52
|
+
'api.js',
|
|
53
|
+
'components/theme.js',
|
|
54
|
+
'components/modals.js',
|
|
55
|
+
'components/navigation.js',
|
|
56
|
+
'components/sidebar.js',
|
|
57
|
+
'components/carousel.js',
|
|
58
|
+
'components/notifications.js',
|
|
59
|
+
'components/mcp-manager.js',
|
|
60
|
+
'components/hook-manager.js',
|
|
61
|
+
'components/_exp_helpers.js',
|
|
62
|
+
'components/tabs-other.js',
|
|
63
|
+
'components/tabs-context.js',
|
|
64
|
+
'components/_conflict_tab.js',
|
|
65
|
+
'components/_review_tab.js',
|
|
66
|
+
'components/task-drawer-core.js',
|
|
67
|
+
'components/task-drawer-renderers.js',
|
|
68
|
+
'components/flowchart.js',
|
|
69
|
+
'views/home.js',
|
|
70
|
+
'views/project-overview.js',
|
|
71
|
+
'views/session-detail.js',
|
|
72
|
+
'views/review-session.js',
|
|
73
|
+
'views/lite-tasks.js',
|
|
74
|
+
'views/fix-session.js',
|
|
75
|
+
'views/mcp-manager.js',
|
|
76
|
+
'views/hook-manager.js',
|
|
77
|
+
'main.js'
|
|
78
|
+
];
|
|
79
|
+
/**
|
|
80
|
+
* Create and start the dashboard server
|
|
81
|
+
* @param {Object} options - Server options
|
|
82
|
+
* @param {number} options.port - Port to listen on (default: 3456)
|
|
83
|
+
* @param {string} options.initialPath - Initial project path
|
|
84
|
+
* @returns {Promise<http.Server>}
|
|
85
|
+
*/
|
|
86
|
+
export async function startServer(options = {}) {
|
|
87
|
+
const port = options.port || 3456;
|
|
88
|
+
const initialPath = options.initialPath || process.cwd();
|
|
89
|
+
|
|
90
|
+
const server = http.createServer(async (req, res) => {
|
|
91
|
+
const url = new URL(req.url, `http://localhost:${port}`);
|
|
92
|
+
const pathname = url.pathname;
|
|
93
|
+
|
|
94
|
+
// CORS headers for API requests
|
|
95
|
+
res.setHeader('Access-Control-Allow-Origin', '*');
|
|
96
|
+
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, OPTIONS');
|
|
97
|
+
res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
|
|
98
|
+
|
|
99
|
+
if (req.method === 'OPTIONS') {
|
|
100
|
+
res.writeHead(200);
|
|
101
|
+
res.end();
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
try {
|
|
106
|
+
// Debug log for API requests
|
|
107
|
+
if (pathname.startsWith('/api/')) {
|
|
108
|
+
console.log(`[API] ${req.method} ${pathname}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// API: Get workflow data for a path
|
|
112
|
+
if (pathname === '/api/data') {
|
|
113
|
+
const projectPath = url.searchParams.get('path') || initialPath;
|
|
114
|
+
const data = await getWorkflowData(projectPath);
|
|
115
|
+
|
|
116
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
117
|
+
res.end(JSON.stringify(data));
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// API: Get recent paths
|
|
122
|
+
if (pathname === '/api/recent-paths') {
|
|
123
|
+
const paths = getRecentPaths();
|
|
124
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
125
|
+
res.end(JSON.stringify({ paths }));
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// API: Remove a recent path
|
|
130
|
+
if (pathname === '/api/remove-recent-path' && req.method === 'POST') {
|
|
131
|
+
handlePostRequest(req, res, async (body) => {
|
|
132
|
+
const { path } = body;
|
|
133
|
+
if (!path) {
|
|
134
|
+
return { error: 'path is required', status: 400 };
|
|
135
|
+
}
|
|
136
|
+
const removed = removeRecentPath(path);
|
|
137
|
+
return { success: removed, paths: getRecentPaths() };
|
|
138
|
+
});
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// API: Get session detail data (context, summaries, impl-plan, review)
|
|
143
|
+
if (pathname === '/api/session-detail') {
|
|
144
|
+
const sessionPath = url.searchParams.get('path');
|
|
145
|
+
const dataType = url.searchParams.get('type') || 'all';
|
|
146
|
+
|
|
147
|
+
if (!sessionPath) {
|
|
148
|
+
res.writeHead(400, { 'Content-Type': 'application/json' });
|
|
149
|
+
res.end(JSON.stringify({ error: 'Session path is required' }));
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
const detail = await getSessionDetailData(sessionPath, dataType);
|
|
154
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
155
|
+
res.end(JSON.stringify(detail));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// API: Update task status
|
|
160
|
+
if (pathname === '/api/update-task-status' && req.method === 'POST') {
|
|
161
|
+
handlePostRequest(req, res, async (body) => {
|
|
162
|
+
const { sessionPath, taskId, newStatus } = body;
|
|
163
|
+
|
|
164
|
+
if (!sessionPath || !taskId || !newStatus) {
|
|
165
|
+
return { error: 'sessionPath, taskId, and newStatus are required', status: 400 };
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return await updateTaskStatus(sessionPath, taskId, newStatus);
|
|
169
|
+
});
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// API: Bulk update task status
|
|
174
|
+
if (pathname === '/api/bulk-update-task-status' && req.method === 'POST') {
|
|
175
|
+
handlePostRequest(req, res, async (body) => {
|
|
176
|
+
const { sessionPath, taskIds, newStatus } = body;
|
|
177
|
+
|
|
178
|
+
if (!sessionPath || !taskIds || !newStatus) {
|
|
179
|
+
return { error: 'sessionPath, taskIds, and newStatus are required', status: 400 };
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
const results = [];
|
|
183
|
+
for (const taskId of taskIds) {
|
|
184
|
+
try {
|
|
185
|
+
const result = await updateTaskStatus(sessionPath, taskId, newStatus);
|
|
186
|
+
results.push(result);
|
|
187
|
+
} catch (err) {
|
|
188
|
+
results.push({ taskId, error: err.message });
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { success: true, results };
|
|
192
|
+
});
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// API: Get MCP configuration
|
|
197
|
+
if (pathname === '/api/mcp-config') {
|
|
198
|
+
const mcpData = getMcpConfig();
|
|
199
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
200
|
+
res.end(JSON.stringify(mcpData));
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// API: Toggle MCP server enabled/disabled
|
|
205
|
+
if (pathname === '/api/mcp-toggle' && req.method === 'POST') {
|
|
206
|
+
handlePostRequest(req, res, async (body) => {
|
|
207
|
+
const { projectPath, serverName, enable } = body;
|
|
208
|
+
if (!projectPath || !serverName) {
|
|
209
|
+
return { error: 'projectPath and serverName are required', status: 400 };
|
|
210
|
+
}
|
|
211
|
+
return toggleMcpServerEnabled(projectPath, serverName, enable);
|
|
212
|
+
});
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// API: Copy MCP server to project
|
|
217
|
+
if (pathname === '/api/mcp-copy-server' && req.method === 'POST') {
|
|
218
|
+
handlePostRequest(req, res, async (body) => {
|
|
219
|
+
const { projectPath, serverName, serverConfig } = body;
|
|
220
|
+
if (!projectPath || !serverName || !serverConfig) {
|
|
221
|
+
return { error: 'projectPath, serverName, and serverConfig are required', status: 400 };
|
|
222
|
+
}
|
|
223
|
+
return addMcpServerToProject(projectPath, serverName, serverConfig);
|
|
224
|
+
});
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// API: Remove MCP server from project
|
|
229
|
+
if (pathname === '/api/mcp-remove-server' && req.method === 'POST') {
|
|
230
|
+
handlePostRequest(req, res, async (body) => {
|
|
231
|
+
const { projectPath, serverName } = body;
|
|
232
|
+
if (!projectPath || !serverName) {
|
|
233
|
+
return { error: 'projectPath and serverName are required', status: 400 };
|
|
234
|
+
}
|
|
235
|
+
return removeMcpServerFromProject(projectPath, serverName);
|
|
236
|
+
});
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// API: Hook endpoint for Claude Code notifications
|
|
241
|
+
if (pathname === '/api/hook' && req.method === 'POST') {
|
|
242
|
+
handlePostRequest(req, res, async (body) => {
|
|
243
|
+
const { type, filePath, sessionId } = body;
|
|
244
|
+
|
|
245
|
+
// Determine session ID from file path if not provided
|
|
246
|
+
let resolvedSessionId = sessionId;
|
|
247
|
+
if (!resolvedSessionId && filePath) {
|
|
248
|
+
resolvedSessionId = extractSessionIdFromPath(filePath);
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// Broadcast to all connected WebSocket clients
|
|
252
|
+
const notification = {
|
|
253
|
+
type: type || 'session_updated',
|
|
254
|
+
payload: {
|
|
255
|
+
sessionId: resolvedSessionId,
|
|
256
|
+
filePath: filePath,
|
|
257
|
+
timestamp: new Date().toISOString()
|
|
258
|
+
}
|
|
259
|
+
};
|
|
260
|
+
|
|
261
|
+
broadcastToClients(notification);
|
|
262
|
+
|
|
263
|
+
return { success: true, notification };
|
|
264
|
+
});
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// API: Get hooks configuration
|
|
269
|
+
if (pathname === '/api/hooks' && req.method === 'GET') {
|
|
270
|
+
const projectPathParam = url.searchParams.get('path');
|
|
271
|
+
const hooksData = getHooksConfig(projectPathParam);
|
|
272
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
273
|
+
res.end(JSON.stringify(hooksData));
|
|
274
|
+
return;
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// API: Save hook
|
|
278
|
+
if (pathname === '/api/hooks' && req.method === 'POST') {
|
|
279
|
+
handlePostRequest(req, res, async (body) => {
|
|
280
|
+
const { projectPath, scope, event, hookData } = body;
|
|
281
|
+
if (!scope || !event || !hookData) {
|
|
282
|
+
return { error: 'scope, event, and hookData are required', status: 400 };
|
|
283
|
+
}
|
|
284
|
+
return saveHookToSettings(projectPath, scope, event, hookData);
|
|
285
|
+
});
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// API: Delete hook
|
|
290
|
+
if (pathname === '/api/hooks' && req.method === 'DELETE') {
|
|
291
|
+
handlePostRequest(req, res, async (body) => {
|
|
292
|
+
const { projectPath, scope, event, hookIndex } = body;
|
|
293
|
+
if (!scope || !event || hookIndex === undefined) {
|
|
294
|
+
return { error: 'scope, event, and hookIndex are required', status: 400 };
|
|
295
|
+
}
|
|
296
|
+
return deleteHookFromSettings(projectPath, scope, event, hookIndex);
|
|
297
|
+
});
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
// Serve dashboard HTML
|
|
302
|
+
if (pathname === '/' || pathname === '/index.html') {
|
|
303
|
+
const html = generateServerDashboard(initialPath);
|
|
304
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
305
|
+
res.end(html);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// 404
|
|
310
|
+
res.writeHead(404, { 'Content-Type': 'text/plain' });
|
|
311
|
+
res.end('Not Found');
|
|
312
|
+
|
|
313
|
+
} catch (error) {
|
|
314
|
+
console.error('Server error:', error);
|
|
315
|
+
res.writeHead(500, { 'Content-Type': 'application/json' });
|
|
316
|
+
res.end(JSON.stringify({ error: error.message }));
|
|
317
|
+
}
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
// Handle WebSocket upgrade requests
|
|
321
|
+
server.on('upgrade', (req, socket, head) => {
|
|
322
|
+
if (req.url === '/ws') {
|
|
323
|
+
handleWebSocketUpgrade(req, socket, head);
|
|
324
|
+
} else {
|
|
325
|
+
socket.destroy();
|
|
326
|
+
}
|
|
327
|
+
});
|
|
328
|
+
|
|
329
|
+
return new Promise((resolve, reject) => {
|
|
330
|
+
server.listen(port, () => {
|
|
331
|
+
console.log(`Dashboard server running at http://localhost:${port}`);
|
|
332
|
+
console.log(`WebSocket endpoint available at ws://localhost:${port}/ws`);
|
|
333
|
+
console.log(`Hook endpoint available at POST http://localhost:${port}/api/hook`);
|
|
334
|
+
resolve(server);
|
|
335
|
+
});
|
|
336
|
+
server.on('error', reject);
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// ========================================
|
|
341
|
+
// WebSocket Functions
|
|
342
|
+
// ========================================
|
|
343
|
+
|
|
344
|
+
/**
|
|
345
|
+
* Handle WebSocket upgrade
|
|
346
|
+
*/
|
|
347
|
+
function handleWebSocketUpgrade(req, socket, head) {
|
|
348
|
+
const key = req.headers['sec-websocket-key'];
|
|
349
|
+
const acceptKey = createHash('sha1')
|
|
350
|
+
.update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11')
|
|
351
|
+
.digest('base64');
|
|
352
|
+
|
|
353
|
+
const responseHeaders = [
|
|
354
|
+
'HTTP/1.1 101 Switching Protocols',
|
|
355
|
+
'Upgrade: websocket',
|
|
356
|
+
'Connection: Upgrade',
|
|
357
|
+
`Sec-WebSocket-Accept: ${acceptKey}`,
|
|
358
|
+
'',
|
|
359
|
+
''
|
|
360
|
+
].join('\r\n');
|
|
361
|
+
|
|
362
|
+
socket.write(responseHeaders);
|
|
363
|
+
|
|
364
|
+
// Add to clients set
|
|
365
|
+
wsClients.add(socket);
|
|
366
|
+
console.log(`[WS] Client connected (${wsClients.size} total)`);
|
|
367
|
+
|
|
368
|
+
// Handle incoming messages
|
|
369
|
+
socket.on('data', (buffer) => {
|
|
370
|
+
try {
|
|
371
|
+
const message = parseWebSocketFrame(buffer);
|
|
372
|
+
if (message) {
|
|
373
|
+
console.log('[WS] Received:', message);
|
|
374
|
+
}
|
|
375
|
+
} catch (e) {
|
|
376
|
+
// Ignore parse errors
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
// Handle disconnect
|
|
381
|
+
socket.on('close', () => {
|
|
382
|
+
wsClients.delete(socket);
|
|
383
|
+
console.log(`[WS] Client disconnected (${wsClients.size} remaining)`);
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
socket.on('error', () => {
|
|
387
|
+
wsClients.delete(socket);
|
|
388
|
+
});
|
|
389
|
+
}
|
|
390
|
+
|
|
391
|
+
/**
|
|
392
|
+
* Parse WebSocket frame (simplified)
|
|
393
|
+
*/
|
|
394
|
+
function parseWebSocketFrame(buffer) {
|
|
395
|
+
if (buffer.length < 2) return null;
|
|
396
|
+
|
|
397
|
+
const secondByte = buffer[1];
|
|
398
|
+
const isMasked = (secondByte & 0x80) !== 0;
|
|
399
|
+
let payloadLength = secondByte & 0x7f;
|
|
400
|
+
|
|
401
|
+
let offset = 2;
|
|
402
|
+
if (payloadLength === 126) {
|
|
403
|
+
payloadLength = buffer.readUInt16BE(2);
|
|
404
|
+
offset = 4;
|
|
405
|
+
} else if (payloadLength === 127) {
|
|
406
|
+
payloadLength = Number(buffer.readBigUInt64BE(2));
|
|
407
|
+
offset = 10;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let mask = null;
|
|
411
|
+
if (isMasked) {
|
|
412
|
+
mask = buffer.slice(offset, offset + 4);
|
|
413
|
+
offset += 4;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const payload = buffer.slice(offset, offset + payloadLength);
|
|
417
|
+
|
|
418
|
+
if (isMasked && mask) {
|
|
419
|
+
for (let i = 0; i < payload.length; i++) {
|
|
420
|
+
payload[i] ^= mask[i % 4];
|
|
421
|
+
}
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
return payload.toString('utf8');
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
/**
|
|
428
|
+
* Create WebSocket frame
|
|
429
|
+
*/
|
|
430
|
+
function createWebSocketFrame(data) {
|
|
431
|
+
const payload = Buffer.from(JSON.stringify(data), 'utf8');
|
|
432
|
+
const length = payload.length;
|
|
433
|
+
|
|
434
|
+
let frame;
|
|
435
|
+
if (length <= 125) {
|
|
436
|
+
frame = Buffer.alloc(2 + length);
|
|
437
|
+
frame[0] = 0x81; // Text frame, FIN
|
|
438
|
+
frame[1] = length;
|
|
439
|
+
payload.copy(frame, 2);
|
|
440
|
+
} else if (length <= 65535) {
|
|
441
|
+
frame = Buffer.alloc(4 + length);
|
|
442
|
+
frame[0] = 0x81;
|
|
443
|
+
frame[1] = 126;
|
|
444
|
+
frame.writeUInt16BE(length, 2);
|
|
445
|
+
payload.copy(frame, 4);
|
|
446
|
+
} else {
|
|
447
|
+
frame = Buffer.alloc(10 + length);
|
|
448
|
+
frame[0] = 0x81;
|
|
449
|
+
frame[1] = 127;
|
|
450
|
+
frame.writeBigUInt64BE(BigInt(length), 2);
|
|
451
|
+
payload.copy(frame, 10);
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
return frame;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
/**
|
|
458
|
+
* Broadcast message to all connected WebSocket clients
|
|
459
|
+
*/
|
|
460
|
+
function broadcastToClients(data) {
|
|
461
|
+
const frame = createWebSocketFrame(data);
|
|
462
|
+
|
|
463
|
+
for (const client of wsClients) {
|
|
464
|
+
try {
|
|
465
|
+
client.write(frame);
|
|
466
|
+
} catch (e) {
|
|
467
|
+
wsClients.delete(client);
|
|
468
|
+
}
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
console.log(`[WS] Broadcast to ${wsClients.size} clients:`, data.type);
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
/**
|
|
475
|
+
* Extract session ID from file path
|
|
476
|
+
*/
|
|
477
|
+
function extractSessionIdFromPath(filePath) {
|
|
478
|
+
// Normalize path
|
|
479
|
+
const normalized = filePath.replace(/\\/g, '/');
|
|
480
|
+
|
|
481
|
+
// Look for session pattern: WFS-xxx, WRS-xxx, etc.
|
|
482
|
+
const sessionMatch = normalized.match(/\/(W[A-Z]S-[^/]+)\//);
|
|
483
|
+
if (sessionMatch) {
|
|
484
|
+
return sessionMatch[1];
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Look for .workflow/.sessions/xxx pattern
|
|
488
|
+
const sessionsMatch = normalized.match(/\.workflow\/\.sessions\/([^/]+)/);
|
|
489
|
+
if (sessionsMatch) {
|
|
490
|
+
return sessionsMatch[1];
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
// Look for lite-plan/lite-fix pattern
|
|
494
|
+
const liteMatch = normalized.match(/\.(lite-plan|lite-fix)\/([^/]+)/);
|
|
495
|
+
if (liteMatch) {
|
|
496
|
+
return liteMatch[2];
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
return null;
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/**
|
|
503
|
+
* Get workflow data for a project path
|
|
504
|
+
* @param {string} projectPath
|
|
505
|
+
* @returns {Promise<Object>}
|
|
506
|
+
*/
|
|
507
|
+
async function getWorkflowData(projectPath) {
|
|
508
|
+
const resolvedPath = resolvePath(projectPath);
|
|
509
|
+
const workflowDir = join(resolvedPath, '.workflow');
|
|
510
|
+
|
|
511
|
+
// Track this path
|
|
512
|
+
trackRecentPath(resolvedPath);
|
|
513
|
+
|
|
514
|
+
// Check if .workflow exists
|
|
515
|
+
if (!existsSync(workflowDir)) {
|
|
516
|
+
return {
|
|
517
|
+
generatedAt: new Date().toISOString(),
|
|
518
|
+
activeSessions: [],
|
|
519
|
+
archivedSessions: [],
|
|
520
|
+
liteTasks: { litePlan: [], liteFix: [] },
|
|
521
|
+
reviewData: { dimensions: {} },
|
|
522
|
+
projectOverview: null,
|
|
523
|
+
statistics: {
|
|
524
|
+
totalSessions: 0,
|
|
525
|
+
activeSessions: 0,
|
|
526
|
+
totalTasks: 0,
|
|
527
|
+
completedTasks: 0,
|
|
528
|
+
reviewFindings: 0,
|
|
529
|
+
litePlanCount: 0,
|
|
530
|
+
liteFixCount: 0
|
|
531
|
+
},
|
|
532
|
+
projectPath: normalizePathForDisplay(resolvedPath),
|
|
533
|
+
recentPaths: getRecentPaths()
|
|
534
|
+
};
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Scan and aggregate data
|
|
538
|
+
const sessions = await scanSessions(workflowDir);
|
|
539
|
+
const data = await aggregateData(sessions, workflowDir);
|
|
540
|
+
|
|
541
|
+
data.projectPath = normalizePathForDisplay(resolvedPath);
|
|
542
|
+
data.recentPaths = getRecentPaths();
|
|
543
|
+
|
|
544
|
+
return data;
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
/**
|
|
548
|
+
* Get session detail data (context, summaries, impl-plan, review)
|
|
549
|
+
* @param {string} sessionPath - Path to session directory
|
|
550
|
+
* @param {string} dataType - Type of data to load: context, summary, impl-plan, review, or all
|
|
551
|
+
* @returns {Promise<Object>}
|
|
552
|
+
*/
|
|
553
|
+
async function getSessionDetailData(sessionPath, dataType) {
|
|
554
|
+
const result = {};
|
|
555
|
+
|
|
556
|
+
// Normalize path
|
|
557
|
+
const normalizedPath = sessionPath.replace(/\\/g, '/');
|
|
558
|
+
|
|
559
|
+
try {
|
|
560
|
+
// Load context-package.json (in .process/ subfolder)
|
|
561
|
+
if (dataType === 'context' || dataType === 'all') {
|
|
562
|
+
// Try .process/context-package.json first (common location)
|
|
563
|
+
let contextFile = join(normalizedPath, '.process', 'context-package.json');
|
|
564
|
+
if (!existsSync(contextFile)) {
|
|
565
|
+
// Fallback to session root
|
|
566
|
+
contextFile = join(normalizedPath, 'context-package.json');
|
|
567
|
+
}
|
|
568
|
+
if (existsSync(contextFile)) {
|
|
569
|
+
try {
|
|
570
|
+
result.context = JSON.parse(readFileSync(contextFile, 'utf8'));
|
|
571
|
+
} catch (e) {
|
|
572
|
+
result.context = null;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
}
|
|
576
|
+
|
|
577
|
+
// Load task JSONs from .task/ folder
|
|
578
|
+
if (dataType === 'tasks' || dataType === 'all') {
|
|
579
|
+
const taskDir = join(normalizedPath, '.task');
|
|
580
|
+
result.tasks = [];
|
|
581
|
+
if (existsSync(taskDir)) {
|
|
582
|
+
const files = readdirSync(taskDir).filter(f => f.endsWith('.json') && f.startsWith('IMPL-'));
|
|
583
|
+
for (const file of files) {
|
|
584
|
+
try {
|
|
585
|
+
const content = JSON.parse(readFileSync(join(taskDir, file), 'utf8'));
|
|
586
|
+
result.tasks.push({
|
|
587
|
+
filename: file,
|
|
588
|
+
task_id: file.replace('.json', ''),
|
|
589
|
+
...content
|
|
590
|
+
});
|
|
591
|
+
} catch (e) {
|
|
592
|
+
// Skip unreadable files
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
// Sort by task ID
|
|
596
|
+
result.tasks.sort((a, b) => a.task_id.localeCompare(b.task_id));
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
// Load summaries from .summaries/
|
|
601
|
+
if (dataType === 'summary' || dataType === 'all') {
|
|
602
|
+
const summariesDir = join(normalizedPath, '.summaries');
|
|
603
|
+
result.summaries = [];
|
|
604
|
+
if (existsSync(summariesDir)) {
|
|
605
|
+
const files = readdirSync(summariesDir).filter(f => f.endsWith('.md'));
|
|
606
|
+
for (const file of files) {
|
|
607
|
+
try {
|
|
608
|
+
const content = readFileSync(join(summariesDir, file), 'utf8');
|
|
609
|
+
result.summaries.push({ name: file.replace('.md', ''), content });
|
|
610
|
+
} catch (e) {
|
|
611
|
+
// Skip unreadable files
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
}
|
|
616
|
+
|
|
617
|
+
// Load plan.json (for lite tasks)
|
|
618
|
+
if (dataType === 'plan' || dataType === 'all') {
|
|
619
|
+
const planFile = join(normalizedPath, 'plan.json');
|
|
620
|
+
if (existsSync(planFile)) {
|
|
621
|
+
try {
|
|
622
|
+
result.plan = JSON.parse(readFileSync(planFile, 'utf8'));
|
|
623
|
+
} catch (e) {
|
|
624
|
+
result.plan = null;
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
}
|
|
628
|
+
|
|
629
|
+
// Load explorations (exploration-*.json files) - check .process/ first, then session root
|
|
630
|
+
if (dataType === 'context' || dataType === 'explorations' || dataType === 'all') {
|
|
631
|
+
result.explorations = { manifest: null, data: {} };
|
|
632
|
+
|
|
633
|
+
// Try .process/ first (standard workflow sessions), then session root (lite tasks)
|
|
634
|
+
const searchDirs = [
|
|
635
|
+
join(normalizedPath, '.process'),
|
|
636
|
+
normalizedPath
|
|
637
|
+
];
|
|
638
|
+
|
|
639
|
+
for (const searchDir of searchDirs) {
|
|
640
|
+
if (!existsSync(searchDir)) continue;
|
|
641
|
+
|
|
642
|
+
// Look for explorations-manifest.json
|
|
643
|
+
const manifestFile = join(searchDir, 'explorations-manifest.json');
|
|
644
|
+
if (existsSync(manifestFile)) {
|
|
645
|
+
try {
|
|
646
|
+
result.explorations.manifest = JSON.parse(readFileSync(manifestFile, 'utf8'));
|
|
647
|
+
|
|
648
|
+
// Load each exploration file based on manifest
|
|
649
|
+
const explorations = result.explorations.manifest.explorations || [];
|
|
650
|
+
for (const exp of explorations) {
|
|
651
|
+
const expFile = join(searchDir, exp.file);
|
|
652
|
+
if (existsSync(expFile)) {
|
|
653
|
+
try {
|
|
654
|
+
result.explorations.data[exp.angle] = JSON.parse(readFileSync(expFile, 'utf8'));
|
|
655
|
+
} catch (e) {
|
|
656
|
+
// Skip unreadable exploration files
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
break; // Found manifest, stop searching
|
|
661
|
+
} catch (e) {
|
|
662
|
+
result.explorations.manifest = null;
|
|
663
|
+
}
|
|
664
|
+
} else {
|
|
665
|
+
// Fallback: scan for exploration-*.json files directly
|
|
666
|
+
try {
|
|
667
|
+
const files = readdirSync(searchDir).filter(f => f.startsWith('exploration-') && f.endsWith('.json'));
|
|
668
|
+
if (files.length > 0) {
|
|
669
|
+
// Create synthetic manifest
|
|
670
|
+
result.explorations.manifest = {
|
|
671
|
+
exploration_count: files.length,
|
|
672
|
+
explorations: files.map((f, i) => ({
|
|
673
|
+
angle: f.replace('exploration-', '').replace('.json', ''),
|
|
674
|
+
file: f,
|
|
675
|
+
index: i + 1
|
|
676
|
+
}))
|
|
677
|
+
};
|
|
678
|
+
|
|
679
|
+
// Load each file
|
|
680
|
+
for (const file of files) {
|
|
681
|
+
const angle = file.replace('exploration-', '').replace('.json', '');
|
|
682
|
+
try {
|
|
683
|
+
result.explorations.data[angle] = JSON.parse(readFileSync(join(searchDir, file), 'utf8'));
|
|
684
|
+
} catch (e) {
|
|
685
|
+
// Skip unreadable files
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
break; // Found explorations, stop searching
|
|
689
|
+
}
|
|
690
|
+
} catch (e) {
|
|
691
|
+
// Directory read failed
|
|
692
|
+
}
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
// Load conflict resolution decisions (conflict-resolution-decisions.json)
|
|
698
|
+
if (dataType === 'context' || dataType === 'conflict' || dataType === 'all') {
|
|
699
|
+
result.conflictResolution = null;
|
|
700
|
+
|
|
701
|
+
// Try .process/ first (standard workflow sessions)
|
|
702
|
+
const conflictFiles = [
|
|
703
|
+
join(normalizedPath, '.process', 'conflict-resolution-decisions.json'),
|
|
704
|
+
join(normalizedPath, 'conflict-resolution-decisions.json')
|
|
705
|
+
];
|
|
706
|
+
|
|
707
|
+
for (const conflictFile of conflictFiles) {
|
|
708
|
+
if (existsSync(conflictFile)) {
|
|
709
|
+
try {
|
|
710
|
+
result.conflictResolution = JSON.parse(readFileSync(conflictFile, 'utf8'));
|
|
711
|
+
break; // Found file, stop searching
|
|
712
|
+
} catch (e) {
|
|
713
|
+
// Skip unreadable file
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
// Load IMPL_PLAN.md
|
|
720
|
+
if (dataType === 'impl-plan' || dataType === 'all') {
|
|
721
|
+
const implPlanFile = join(normalizedPath, 'IMPL_PLAN.md');
|
|
722
|
+
if (existsSync(implPlanFile)) {
|
|
723
|
+
try {
|
|
724
|
+
result.implPlan = readFileSync(implPlanFile, 'utf8');
|
|
725
|
+
} catch (e) {
|
|
726
|
+
result.implPlan = null;
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
// Load review data from .review/
|
|
732
|
+
if (dataType === 'review' || dataType === 'all') {
|
|
733
|
+
const reviewDir = join(normalizedPath, '.review');
|
|
734
|
+
result.review = {
|
|
735
|
+
state: null,
|
|
736
|
+
dimensions: [],
|
|
737
|
+
severityDistribution: null,
|
|
738
|
+
totalFindings: 0
|
|
739
|
+
};
|
|
740
|
+
|
|
741
|
+
if (existsSync(reviewDir)) {
|
|
742
|
+
// Load review-state.json
|
|
743
|
+
const stateFile = join(reviewDir, 'review-state.json');
|
|
744
|
+
if (existsSync(stateFile)) {
|
|
745
|
+
try {
|
|
746
|
+
const state = JSON.parse(readFileSync(stateFile, 'utf8'));
|
|
747
|
+
result.review.state = state;
|
|
748
|
+
result.review.severityDistribution = state.severity_distribution || {};
|
|
749
|
+
result.review.totalFindings = state.total_findings || 0;
|
|
750
|
+
result.review.phase = state.phase || 'unknown';
|
|
751
|
+
result.review.dimensionSummaries = state.dimension_summaries || {};
|
|
752
|
+
result.review.crossCuttingConcerns = state.cross_cutting_concerns || [];
|
|
753
|
+
result.review.criticalFiles = state.critical_files || [];
|
|
754
|
+
} catch (e) {
|
|
755
|
+
// Skip unreadable state
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// Load dimension findings
|
|
760
|
+
const dimensionsDir = join(reviewDir, 'dimensions');
|
|
761
|
+
if (existsSync(dimensionsDir)) {
|
|
762
|
+
const files = readdirSync(dimensionsDir).filter(f => f.endsWith('.json'));
|
|
763
|
+
for (const file of files) {
|
|
764
|
+
try {
|
|
765
|
+
const dimName = file.replace('.json', '');
|
|
766
|
+
const data = JSON.parse(readFileSync(join(dimensionsDir, file), 'utf8'));
|
|
767
|
+
|
|
768
|
+
// Handle array structure: [ { findings: [...] } ]
|
|
769
|
+
let findings = [];
|
|
770
|
+
let summary = null;
|
|
771
|
+
|
|
772
|
+
if (Array.isArray(data) && data.length > 0) {
|
|
773
|
+
const dimData = data[0];
|
|
774
|
+
findings = dimData.findings || [];
|
|
775
|
+
summary = dimData.summary || null;
|
|
776
|
+
} else if (data.findings) {
|
|
777
|
+
findings = data.findings;
|
|
778
|
+
summary = data.summary || null;
|
|
779
|
+
}
|
|
780
|
+
|
|
781
|
+
result.review.dimensions.push({
|
|
782
|
+
name: dimName,
|
|
783
|
+
findings: findings,
|
|
784
|
+
summary: summary,
|
|
785
|
+
count: findings.length
|
|
786
|
+
});
|
|
787
|
+
} catch (e) {
|
|
788
|
+
// Skip unreadable files
|
|
789
|
+
}
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
} catch (error) {
|
|
796
|
+
console.error('Error loading session detail:', error);
|
|
797
|
+
result.error = error.message;
|
|
798
|
+
}
|
|
799
|
+
|
|
800
|
+
return result;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
/**
|
|
804
|
+
* Update task status in a task JSON file
|
|
805
|
+
* @param {string} sessionPath - Path to session directory
|
|
806
|
+
* @param {string} taskId - Task ID (e.g., IMPL-001)
|
|
807
|
+
* @param {string} newStatus - New status (pending, in_progress, completed)
|
|
808
|
+
* @returns {Promise<Object>}
|
|
809
|
+
*/
|
|
810
|
+
async function updateTaskStatus(sessionPath, taskId, newStatus) {
|
|
811
|
+
// Normalize path (handle both forward and back slashes)
|
|
812
|
+
let normalizedPath = sessionPath.replace(/\\/g, '/');
|
|
813
|
+
|
|
814
|
+
// Handle Windows drive letter format
|
|
815
|
+
if (normalizedPath.match(/^[a-zA-Z]:\//)) {
|
|
816
|
+
// Already in correct format
|
|
817
|
+
} else if (normalizedPath.match(/^\/[a-zA-Z]\//)) {
|
|
818
|
+
// Convert /D/path to D:/path
|
|
819
|
+
normalizedPath = normalizedPath.charAt(1).toUpperCase() + ':' + normalizedPath.slice(2);
|
|
820
|
+
}
|
|
821
|
+
|
|
822
|
+
const taskDir = join(normalizedPath, '.task');
|
|
823
|
+
|
|
824
|
+
// Check if task directory exists
|
|
825
|
+
if (!existsSync(taskDir)) {
|
|
826
|
+
throw new Error(`Task directory not found: ${taskDir}`);
|
|
827
|
+
}
|
|
828
|
+
|
|
829
|
+
// Try to find the task file
|
|
830
|
+
let taskFile = join(taskDir, `${taskId}.json`);
|
|
831
|
+
|
|
832
|
+
if (!existsSync(taskFile)) {
|
|
833
|
+
// Try without .json if taskId already has it
|
|
834
|
+
if (taskId.endsWith('.json')) {
|
|
835
|
+
taskFile = join(taskDir, taskId);
|
|
836
|
+
}
|
|
837
|
+
if (!existsSync(taskFile)) {
|
|
838
|
+
throw new Error(`Task file not found: ${taskId}.json in ${taskDir}`);
|
|
839
|
+
}
|
|
840
|
+
}
|
|
841
|
+
|
|
842
|
+
try {
|
|
843
|
+
const content = JSON.parse(readFileSync(taskFile, 'utf8'));
|
|
844
|
+
const oldStatus = content.status || 'pending';
|
|
845
|
+
content.status = newStatus;
|
|
846
|
+
|
|
847
|
+
// Add status change timestamp
|
|
848
|
+
if (!content.status_history) {
|
|
849
|
+
content.status_history = [];
|
|
850
|
+
}
|
|
851
|
+
content.status_history.push({
|
|
852
|
+
from: oldStatus,
|
|
853
|
+
to: newStatus,
|
|
854
|
+
changed_at: new Date().toISOString()
|
|
855
|
+
});
|
|
856
|
+
|
|
857
|
+
writeFileSync(taskFile, JSON.stringify(content, null, 2), 'utf8');
|
|
858
|
+
|
|
859
|
+
return {
|
|
860
|
+
success: true,
|
|
861
|
+
taskId,
|
|
862
|
+
oldStatus,
|
|
863
|
+
newStatus,
|
|
864
|
+
file: taskFile
|
|
865
|
+
};
|
|
866
|
+
} catch (error) {
|
|
867
|
+
throw new Error(`Failed to update task ${taskId}: ${error.message}`);
|
|
868
|
+
}
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
/**
|
|
872
|
+
* Generate dashboard HTML for server mode
|
|
873
|
+
* @param {string} initialPath
|
|
874
|
+
* @returns {string}
|
|
875
|
+
*/
|
|
876
|
+
function generateServerDashboard(initialPath) {
|
|
877
|
+
let html = readFileSync(TEMPLATE_PATH, 'utf8');
|
|
878
|
+
|
|
879
|
+
// Read CSS file
|
|
880
|
+
const cssContent = existsSync(CSS_FILE) ? readFileSync(CSS_FILE, 'utf8') : '';
|
|
881
|
+
|
|
882
|
+
// Read and concatenate modular JS files in dependency order
|
|
883
|
+
let jsContent = MODULE_FILES.map(file => {
|
|
884
|
+
const filePath = join(MODULE_JS_DIR, file);
|
|
885
|
+
return existsSync(filePath) ? readFileSync(filePath, 'utf8') : '';
|
|
886
|
+
}).join('\n\n');
|
|
887
|
+
|
|
888
|
+
// Inject CSS content
|
|
889
|
+
html = html.replace('{{CSS_CONTENT}}', cssContent);
|
|
890
|
+
|
|
891
|
+
// Prepare JS content with empty initial data (will be loaded dynamically)
|
|
892
|
+
const emptyData = {
|
|
893
|
+
generatedAt: new Date().toISOString(),
|
|
894
|
+
activeSessions: [],
|
|
895
|
+
archivedSessions: [],
|
|
896
|
+
liteTasks: { litePlan: [], liteFix: [] },
|
|
897
|
+
reviewData: { dimensions: {} },
|
|
898
|
+
projectOverview: null,
|
|
899
|
+
statistics: { totalSessions: 0, activeSessions: 0, totalTasks: 0, completedTasks: 0, reviewFindings: 0, litePlanCount: 0, liteFixCount: 0 }
|
|
900
|
+
};
|
|
901
|
+
|
|
902
|
+
// Replace JS placeholders
|
|
903
|
+
jsContent = jsContent.replace('{{WORKFLOW_DATA}}', JSON.stringify(emptyData, null, 2));
|
|
904
|
+
jsContent = jsContent.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
|
905
|
+
jsContent = jsContent.replace('{{RECENT_PATHS}}', JSON.stringify(getRecentPaths()));
|
|
906
|
+
|
|
907
|
+
// Add server mode flag and dynamic loading functions at the start of JS
|
|
908
|
+
const serverModeScript = `
|
|
909
|
+
// Server mode - load data dynamically
|
|
910
|
+
window.SERVER_MODE = true;
|
|
911
|
+
window.INITIAL_PATH = '${normalizePathForDisplay(initialPath).replace(/\\/g, '/')}';
|
|
912
|
+
|
|
913
|
+
async function loadDashboardData(path) {
|
|
914
|
+
try {
|
|
915
|
+
const res = await fetch('/api/data?path=' + encodeURIComponent(path));
|
|
916
|
+
if (!res.ok) throw new Error('Failed to load data');
|
|
917
|
+
return await res.json();
|
|
918
|
+
} catch (err) {
|
|
919
|
+
console.error('Error loading data:', err);
|
|
920
|
+
return null;
|
|
921
|
+
}
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
async function loadRecentPaths() {
|
|
925
|
+
try {
|
|
926
|
+
const res = await fetch('/api/recent-paths');
|
|
927
|
+
if (!res.ok) return [];
|
|
928
|
+
const data = await res.json();
|
|
929
|
+
return data.paths || [];
|
|
930
|
+
} catch (err) {
|
|
931
|
+
return [];
|
|
932
|
+
}
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
`;
|
|
936
|
+
|
|
937
|
+
// Prepend server mode script to JS content
|
|
938
|
+
jsContent = serverModeScript + jsContent;
|
|
939
|
+
|
|
940
|
+
// Inject JS content
|
|
941
|
+
html = html.replace('{{JS_CONTENT}}', jsContent);
|
|
942
|
+
|
|
943
|
+
// Replace any remaining placeholders in HTML
|
|
944
|
+
html = html.replace(/\{\{PROJECT_PATH\}\}/g, normalizePathForDisplay(initialPath).replace(/\\/g, '/'));
|
|
945
|
+
|
|
946
|
+
return html;
|
|
947
|
+
}
|
|
948
|
+
|
|
949
|
+
// ========================================
|
|
950
|
+
// MCP Configuration Functions
|
|
951
|
+
// ========================================
|
|
952
|
+
|
|
953
|
+
/**
|
|
954
|
+
* Get MCP configuration from .claude.json
|
|
955
|
+
* @returns {Object}
|
|
956
|
+
*/
|
|
957
|
+
function getMcpConfig() {
|
|
958
|
+
try {
|
|
959
|
+
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
|
960
|
+
return { projects: {} };
|
|
961
|
+
}
|
|
962
|
+
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
|
963
|
+
const config = JSON.parse(content);
|
|
964
|
+
return {
|
|
965
|
+
projects: config.projects || {}
|
|
966
|
+
};
|
|
967
|
+
} catch (error) {
|
|
968
|
+
console.error('Error reading MCP config:', error);
|
|
969
|
+
return { projects: {}, error: error.message };
|
|
970
|
+
}
|
|
971
|
+
}
|
|
972
|
+
|
|
973
|
+
/**
|
|
974
|
+
* Normalize project path for .claude.json (Windows backslash format)
|
|
975
|
+
* @param {string} path
|
|
976
|
+
* @returns {string}
|
|
977
|
+
*/
|
|
978
|
+
function normalizeProjectPathForConfig(path) {
|
|
979
|
+
// Convert forward slashes to backslashes for Windows .claude.json format
|
|
980
|
+
let normalized = path.replace(/\//g, '\\');
|
|
981
|
+
|
|
982
|
+
// Handle /d/path format -> D:\path
|
|
983
|
+
if (normalized.match(/^\\[a-zA-Z]\\/)) {
|
|
984
|
+
normalized = normalized.charAt(1).toUpperCase() + ':' + normalized.slice(2);
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
return normalized;
|
|
988
|
+
}
|
|
989
|
+
|
|
990
|
+
/**
|
|
991
|
+
* Toggle MCP server enabled/disabled
|
|
992
|
+
* @param {string} projectPath
|
|
993
|
+
* @param {string} serverName
|
|
994
|
+
* @param {boolean} enable
|
|
995
|
+
* @returns {Object}
|
|
996
|
+
*/
|
|
997
|
+
function toggleMcpServerEnabled(projectPath, serverName, enable) {
|
|
998
|
+
try {
|
|
999
|
+
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
|
1000
|
+
return { error: '.claude.json not found' };
|
|
1001
|
+
}
|
|
1002
|
+
|
|
1003
|
+
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
|
1004
|
+
const config = JSON.parse(content);
|
|
1005
|
+
|
|
1006
|
+
const normalizedPath = normalizeProjectPathForConfig(projectPath);
|
|
1007
|
+
|
|
1008
|
+
if (!config.projects || !config.projects[normalizedPath]) {
|
|
1009
|
+
return { error: `Project not found: ${normalizedPath}` };
|
|
1010
|
+
}
|
|
1011
|
+
|
|
1012
|
+
const projectConfig = config.projects[normalizedPath];
|
|
1013
|
+
|
|
1014
|
+
// Ensure disabledMcpServers array exists
|
|
1015
|
+
if (!projectConfig.disabledMcpServers) {
|
|
1016
|
+
projectConfig.disabledMcpServers = [];
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
if (enable) {
|
|
1020
|
+
// Remove from disabled list
|
|
1021
|
+
projectConfig.disabledMcpServers = projectConfig.disabledMcpServers.filter(s => s !== serverName);
|
|
1022
|
+
} else {
|
|
1023
|
+
// Add to disabled list if not already there
|
|
1024
|
+
if (!projectConfig.disabledMcpServers.includes(serverName)) {
|
|
1025
|
+
projectConfig.disabledMcpServers.push(serverName);
|
|
1026
|
+
}
|
|
1027
|
+
}
|
|
1028
|
+
|
|
1029
|
+
// Write back to file
|
|
1030
|
+
writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
1031
|
+
|
|
1032
|
+
return {
|
|
1033
|
+
success: true,
|
|
1034
|
+
serverName,
|
|
1035
|
+
enabled: enable,
|
|
1036
|
+
disabledMcpServers: projectConfig.disabledMcpServers
|
|
1037
|
+
};
|
|
1038
|
+
} catch (error) {
|
|
1039
|
+
console.error('Error toggling MCP server:', error);
|
|
1040
|
+
return { error: error.message };
|
|
1041
|
+
}
|
|
1042
|
+
}
|
|
1043
|
+
|
|
1044
|
+
/**
|
|
1045
|
+
* Add MCP server to project
|
|
1046
|
+
* @param {string} projectPath
|
|
1047
|
+
* @param {string} serverName
|
|
1048
|
+
* @param {Object} serverConfig
|
|
1049
|
+
* @returns {Object}
|
|
1050
|
+
*/
|
|
1051
|
+
function addMcpServerToProject(projectPath, serverName, serverConfig) {
|
|
1052
|
+
try {
|
|
1053
|
+
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
|
1054
|
+
return { error: '.claude.json not found' };
|
|
1055
|
+
}
|
|
1056
|
+
|
|
1057
|
+
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
|
1058
|
+
const config = JSON.parse(content);
|
|
1059
|
+
|
|
1060
|
+
const normalizedPath = normalizeProjectPathForConfig(projectPath);
|
|
1061
|
+
|
|
1062
|
+
// Create project entry if it doesn't exist
|
|
1063
|
+
if (!config.projects) {
|
|
1064
|
+
config.projects = {};
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
if (!config.projects[normalizedPath]) {
|
|
1068
|
+
config.projects[normalizedPath] = {
|
|
1069
|
+
allowedTools: [],
|
|
1070
|
+
mcpContextUris: [],
|
|
1071
|
+
mcpServers: {},
|
|
1072
|
+
enabledMcpjsonServers: [],
|
|
1073
|
+
disabledMcpjsonServers: [],
|
|
1074
|
+
hasTrustDialogAccepted: false,
|
|
1075
|
+
projectOnboardingSeenCount: 0,
|
|
1076
|
+
hasClaudeMdExternalIncludesApproved: false,
|
|
1077
|
+
hasClaudeMdExternalIncludesWarningShown: false
|
|
1078
|
+
};
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1081
|
+
const projectConfig = config.projects[normalizedPath];
|
|
1082
|
+
|
|
1083
|
+
// Ensure mcpServers exists
|
|
1084
|
+
if (!projectConfig.mcpServers) {
|
|
1085
|
+
projectConfig.mcpServers = {};
|
|
1086
|
+
}
|
|
1087
|
+
|
|
1088
|
+
// Add the server
|
|
1089
|
+
projectConfig.mcpServers[serverName] = serverConfig;
|
|
1090
|
+
|
|
1091
|
+
// Write back to file
|
|
1092
|
+
writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
1093
|
+
|
|
1094
|
+
return {
|
|
1095
|
+
success: true,
|
|
1096
|
+
serverName,
|
|
1097
|
+
serverConfig
|
|
1098
|
+
};
|
|
1099
|
+
} catch (error) {
|
|
1100
|
+
console.error('Error adding MCP server:', error);
|
|
1101
|
+
return { error: error.message };
|
|
1102
|
+
}
|
|
1103
|
+
}
|
|
1104
|
+
|
|
1105
|
+
/**
|
|
1106
|
+
* Remove MCP server from project
|
|
1107
|
+
* @param {string} projectPath
|
|
1108
|
+
* @param {string} serverName
|
|
1109
|
+
* @returns {Object}
|
|
1110
|
+
*/
|
|
1111
|
+
function removeMcpServerFromProject(projectPath, serverName) {
|
|
1112
|
+
try {
|
|
1113
|
+
if (!existsSync(CLAUDE_CONFIG_PATH)) {
|
|
1114
|
+
return { error: '.claude.json not found' };
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
const content = readFileSync(CLAUDE_CONFIG_PATH, 'utf8');
|
|
1118
|
+
const config = JSON.parse(content);
|
|
1119
|
+
|
|
1120
|
+
const normalizedPath = normalizeProjectPathForConfig(projectPath);
|
|
1121
|
+
|
|
1122
|
+
if (!config.projects || !config.projects[normalizedPath]) {
|
|
1123
|
+
return { error: `Project not found: ${normalizedPath}` };
|
|
1124
|
+
}
|
|
1125
|
+
|
|
1126
|
+
const projectConfig = config.projects[normalizedPath];
|
|
1127
|
+
|
|
1128
|
+
if (!projectConfig.mcpServers || !projectConfig.mcpServers[serverName]) {
|
|
1129
|
+
return { error: `Server not found: ${serverName}` };
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// Remove the server
|
|
1133
|
+
delete projectConfig.mcpServers[serverName];
|
|
1134
|
+
|
|
1135
|
+
// Also remove from disabled list if present
|
|
1136
|
+
if (projectConfig.disabledMcpServers) {
|
|
1137
|
+
projectConfig.disabledMcpServers = projectConfig.disabledMcpServers.filter(s => s !== serverName);
|
|
1138
|
+
}
|
|
1139
|
+
|
|
1140
|
+
// Write back to file
|
|
1141
|
+
writeFileSync(CLAUDE_CONFIG_PATH, JSON.stringify(config, null, 2), 'utf8');
|
|
1142
|
+
|
|
1143
|
+
return {
|
|
1144
|
+
success: true,
|
|
1145
|
+
serverName,
|
|
1146
|
+
removed: true
|
|
1147
|
+
};
|
|
1148
|
+
} catch (error) {
|
|
1149
|
+
console.error('Error removing MCP server:', error);
|
|
1150
|
+
return { error: error.message };
|
|
1151
|
+
}
|
|
1152
|
+
}
|
|
1153
|
+
|
|
1154
|
+
// ========================================
|
|
1155
|
+
// Hook Configuration Functions
|
|
1156
|
+
// ========================================
|
|
1157
|
+
|
|
1158
|
+
const GLOBAL_SETTINGS_PATH = join(homedir(), '.claude', 'settings.json');
|
|
1159
|
+
|
|
1160
|
+
/**
|
|
1161
|
+
* Get project settings path
|
|
1162
|
+
* @param {string} projectPath
|
|
1163
|
+
* @returns {string}
|
|
1164
|
+
*/
|
|
1165
|
+
function getProjectSettingsPath(projectPath) {
|
|
1166
|
+
const normalizedPath = projectPath.replace(/\//g, '\\').replace(/^\\([a-zA-Z])\\/, '$1:\\');
|
|
1167
|
+
return join(normalizedPath, '.claude', 'settings.json');
|
|
1168
|
+
}
|
|
1169
|
+
|
|
1170
|
+
/**
|
|
1171
|
+
* Read settings file safely
|
|
1172
|
+
* @param {string} filePath
|
|
1173
|
+
* @returns {Object}
|
|
1174
|
+
*/
|
|
1175
|
+
function readSettingsFile(filePath) {
|
|
1176
|
+
try {
|
|
1177
|
+
if (!existsSync(filePath)) {
|
|
1178
|
+
return { hooks: {} };
|
|
1179
|
+
}
|
|
1180
|
+
const content = readFileSync(filePath, 'utf8');
|
|
1181
|
+
return JSON.parse(content);
|
|
1182
|
+
} catch (error) {
|
|
1183
|
+
console.error(`Error reading settings file ${filePath}:`, error);
|
|
1184
|
+
return { hooks: {} };
|
|
1185
|
+
}
|
|
1186
|
+
}
|
|
1187
|
+
|
|
1188
|
+
/**
|
|
1189
|
+
* Write settings file safely
|
|
1190
|
+
* @param {string} filePath
|
|
1191
|
+
* @param {Object} settings
|
|
1192
|
+
*/
|
|
1193
|
+
function writeSettingsFile(filePath, settings) {
|
|
1194
|
+
const dirPath = dirname(filePath);
|
|
1195
|
+
// Ensure directory exists
|
|
1196
|
+
if (!existsSync(dirPath)) {
|
|
1197
|
+
mkdirSync(dirPath, { recursive: true });
|
|
1198
|
+
}
|
|
1199
|
+
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1200
|
+
}
|
|
1201
|
+
|
|
1202
|
+
/**
|
|
1203
|
+
* Get hooks configuration from both global and project settings
|
|
1204
|
+
* @param {string} projectPath
|
|
1205
|
+
* @returns {Object}
|
|
1206
|
+
*/
|
|
1207
|
+
function getHooksConfig(projectPath) {
|
|
1208
|
+
const globalSettings = readSettingsFile(GLOBAL_SETTINGS_PATH);
|
|
1209
|
+
const projectSettingsPath = projectPath ? getProjectSettingsPath(projectPath) : null;
|
|
1210
|
+
const projectSettings = projectSettingsPath ? readSettingsFile(projectSettingsPath) : { hooks: {} };
|
|
1211
|
+
|
|
1212
|
+
return {
|
|
1213
|
+
global: {
|
|
1214
|
+
path: GLOBAL_SETTINGS_PATH,
|
|
1215
|
+
hooks: globalSettings.hooks || {}
|
|
1216
|
+
},
|
|
1217
|
+
project: {
|
|
1218
|
+
path: projectSettingsPath,
|
|
1219
|
+
hooks: projectSettings.hooks || {}
|
|
1220
|
+
}
|
|
1221
|
+
};
|
|
1222
|
+
}
|
|
1223
|
+
|
|
1224
|
+
/**
|
|
1225
|
+
* Save a hook to settings file
|
|
1226
|
+
* @param {string} projectPath
|
|
1227
|
+
* @param {string} scope - 'global' or 'project'
|
|
1228
|
+
* @param {string} event - Hook event type
|
|
1229
|
+
* @param {Object} hookData - Hook configuration
|
|
1230
|
+
* @returns {Object}
|
|
1231
|
+
*/
|
|
1232
|
+
function saveHookToSettings(projectPath, scope, event, hookData) {
|
|
1233
|
+
try {
|
|
1234
|
+
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
1235
|
+
const settings = readSettingsFile(filePath);
|
|
1236
|
+
|
|
1237
|
+
// Ensure hooks object exists
|
|
1238
|
+
if (!settings.hooks) {
|
|
1239
|
+
settings.hooks = {};
|
|
1240
|
+
}
|
|
1241
|
+
|
|
1242
|
+
// Ensure the event array exists
|
|
1243
|
+
if (!settings.hooks[event]) {
|
|
1244
|
+
settings.hooks[event] = [];
|
|
1245
|
+
}
|
|
1246
|
+
|
|
1247
|
+
// Ensure it's an array
|
|
1248
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
1249
|
+
settings.hooks[event] = [settings.hooks[event]];
|
|
1250
|
+
}
|
|
1251
|
+
|
|
1252
|
+
// Check if we're replacing an existing hook
|
|
1253
|
+
if (hookData.replaceIndex !== undefined) {
|
|
1254
|
+
const index = hookData.replaceIndex;
|
|
1255
|
+
delete hookData.replaceIndex;
|
|
1256
|
+
if (index >= 0 && index < settings.hooks[event].length) {
|
|
1257
|
+
settings.hooks[event][index] = hookData;
|
|
1258
|
+
}
|
|
1259
|
+
} else {
|
|
1260
|
+
// Add new hook
|
|
1261
|
+
settings.hooks[event].push(hookData);
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// Ensure directory exists and write file
|
|
1265
|
+
const dirPath = dirname(filePath);
|
|
1266
|
+
if (!existsSync(dirPath)) {
|
|
1267
|
+
mkdirSync(dirPath, { recursive: true });
|
|
1268
|
+
}
|
|
1269
|
+
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1270
|
+
|
|
1271
|
+
return {
|
|
1272
|
+
success: true,
|
|
1273
|
+
event,
|
|
1274
|
+
hookData
|
|
1275
|
+
};
|
|
1276
|
+
} catch (error) {
|
|
1277
|
+
console.error('Error saving hook:', error);
|
|
1278
|
+
return { error: error.message };
|
|
1279
|
+
}
|
|
1280
|
+
}
|
|
1281
|
+
|
|
1282
|
+
/**
|
|
1283
|
+
* Delete a hook from settings file
|
|
1284
|
+
* @param {string} projectPath
|
|
1285
|
+
* @param {string} scope - 'global' or 'project'
|
|
1286
|
+
* @param {string} event - Hook event type
|
|
1287
|
+
* @param {number} hookIndex - Index of hook to delete
|
|
1288
|
+
* @returns {Object}
|
|
1289
|
+
*/
|
|
1290
|
+
function deleteHookFromSettings(projectPath, scope, event, hookIndex) {
|
|
1291
|
+
try {
|
|
1292
|
+
const filePath = scope === 'global' ? GLOBAL_SETTINGS_PATH : getProjectSettingsPath(projectPath);
|
|
1293
|
+
const settings = readSettingsFile(filePath);
|
|
1294
|
+
|
|
1295
|
+
if (!settings.hooks || !settings.hooks[event]) {
|
|
1296
|
+
return { error: 'Hook not found' };
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
// Ensure it's an array
|
|
1300
|
+
if (!Array.isArray(settings.hooks[event])) {
|
|
1301
|
+
settings.hooks[event] = [settings.hooks[event]];
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
if (hookIndex < 0 || hookIndex >= settings.hooks[event].length) {
|
|
1305
|
+
return { error: 'Invalid hook index' };
|
|
1306
|
+
}
|
|
1307
|
+
|
|
1308
|
+
// Remove the hook
|
|
1309
|
+
settings.hooks[event].splice(hookIndex, 1);
|
|
1310
|
+
|
|
1311
|
+
// Remove empty event arrays
|
|
1312
|
+
if (settings.hooks[event].length === 0) {
|
|
1313
|
+
delete settings.hooks[event];
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
writeFileSync(filePath, JSON.stringify(settings, null, 2), 'utf8');
|
|
1317
|
+
|
|
1318
|
+
return {
|
|
1319
|
+
success: true,
|
|
1320
|
+
event,
|
|
1321
|
+
hookIndex
|
|
1322
|
+
};
|
|
1323
|
+
} catch (error) {
|
|
1324
|
+
console.error('Error deleting hook:', error);
|
|
1325
|
+
return { error: error.message };
|
|
1326
|
+
}
|
|
1327
|
+
}
|