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.
Files changed (350) hide show
  1. package/.claude/agents/action-planning-agent.md +778 -0
  2. package/.claude/agents/cli-execution-agent.md +270 -0
  3. package/.claude/agents/cli-explore-agent.md +182 -0
  4. package/.claude/agents/cli-lite-planning-agent.md +396 -0
  5. package/.claude/agents/cli-planning-agent.md +558 -0
  6. package/.claude/agents/code-developer.md +310 -0
  7. package/.claude/agents/conceptual-planning-agent.md +308 -0
  8. package/.claude/agents/context-search-agent.md +582 -0
  9. package/.claude/agents/doc-generator.md +330 -0
  10. package/.claude/agents/memory-bridge.md +94 -0
  11. package/.claude/agents/test-context-search-agent.md +399 -0
  12. package/.claude/agents/test-fix-agent.md +343 -0
  13. package/.claude/agents/ui-design-agent.md +593 -0
  14. package/.claude/agents/universal-executor.md +131 -0
  15. package/.claude/commands/cli/cli-init.md +440 -0
  16. package/.claude/commands/enhance-prompt.md +93 -0
  17. package/.claude/commands/memory/code-map-memory.md +687 -0
  18. package/.claude/commands/memory/docs-full-cli.md +471 -0
  19. package/.claude/commands/memory/docs-related-cli.md +386 -0
  20. package/.claude/commands/memory/docs.md +615 -0
  21. package/.claude/commands/memory/load-skill-memory.md +182 -0
  22. package/.claude/commands/memory/load.md +240 -0
  23. package/.claude/commands/memory/skill-memory.md +525 -0
  24. package/.claude/commands/memory/style-skill-memory.md +396 -0
  25. package/.claude/commands/memory/tech-research.md +477 -0
  26. package/.claude/commands/memory/update-full.md +332 -0
  27. package/.claude/commands/memory/update-related.md +332 -0
  28. package/.claude/commands/memory/workflow-skill-memory.md +517 -0
  29. package/.claude/commands/task/breakdown.md +204 -0
  30. package/.claude/commands/task/create.md +152 -0
  31. package/.claude/commands/task/execute.md +270 -0
  32. package/.claude/commands/task/replan.md +437 -0
  33. package/.claude/commands/version.md +254 -0
  34. package/.claude/commands/workflow/action-plan-verify.md +447 -0
  35. package/.claude/commands/workflow/brainstorm/api-designer.md +585 -0
  36. package/.claude/commands/workflow/brainstorm/artifacts.md +452 -0
  37. package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -0
  38. package/.claude/commands/workflow/brainstorm/data-architect.md +220 -0
  39. package/.claude/commands/workflow/brainstorm/product-manager.md +200 -0
  40. package/.claude/commands/workflow/brainstorm/product-owner.md +200 -0
  41. package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -0
  42. package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
  43. package/.claude/commands/workflow/brainstorm/synthesis.md +398 -0
  44. package/.claude/commands/workflow/brainstorm/system-architect.md +387 -0
  45. package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -0
  46. package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -0
  47. package/.claude/commands/workflow/execute.md +460 -0
  48. package/.claude/commands/workflow/init.md +164 -0
  49. package/.claude/commands/workflow/lite-execute.md +686 -0
  50. package/.claude/commands/workflow/lite-fix.md +621 -0
  51. package/.claude/commands/workflow/lite-plan.md +592 -0
  52. package/.claude/commands/workflow/plan.md +551 -0
  53. package/.claude/commands/workflow/replan.md +515 -0
  54. package/.claude/commands/workflow/review-fix.md +646 -0
  55. package/.claude/commands/workflow/review-module-cycle.md +795 -0
  56. package/.claude/commands/workflow/review-session-cycle.md +805 -0
  57. package/.claude/commands/workflow/review.md +291 -0
  58. package/.claude/commands/workflow/session/complete.md +500 -0
  59. package/.claude/commands/workflow/session/list.md +96 -0
  60. package/.claude/commands/workflow/session/resume.md +61 -0
  61. package/.claude/commands/workflow/session/start.md +200 -0
  62. package/.claude/commands/workflow/status.md +352 -0
  63. package/.claude/commands/workflow/tdd-plan.md +460 -0
  64. package/.claude/commands/workflow/tdd-verify.md +386 -0
  65. package/.claude/commands/workflow/test-cycle-execute.md +498 -0
  66. package/.claude/commands/workflow/test-fix-gen.md +699 -0
  67. package/.claude/commands/workflow/test-gen.md +529 -0
  68. package/.claude/commands/workflow/tools/conflict-resolution.md +680 -0
  69. package/.claude/commands/workflow/tools/context-gather.md +434 -0
  70. package/.claude/commands/workflow/tools/task-generate-agent.md +291 -0
  71. package/.claude/commands/workflow/tools/task-generate-tdd.md +518 -0
  72. package/.claude/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
  73. package/.claude/commands/workflow/tools/test-concept-enhanced.md +163 -0
  74. package/.claude/commands/workflow/tools/test-context-gather.md +235 -0
  75. package/.claude/commands/workflow/tools/test-task-generate.md +256 -0
  76. package/.claude/commands/workflow/ui-design/animation-extract.md +1150 -0
  77. package/.claude/commands/workflow/ui-design/codify-style.md +652 -0
  78. package/.claude/commands/workflow/ui-design/design-sync.md +454 -0
  79. package/.claude/commands/workflow/ui-design/explore-auto.md +678 -0
  80. package/.claude/commands/workflow/ui-design/generate.md +504 -0
  81. package/.claude/commands/workflow/ui-design/imitate-auto.md +745 -0
  82. package/.claude/commands/workflow/ui-design/import-from-code.md +537 -0
  83. package/.claude/commands/workflow/ui-design/layout-extract.md +788 -0
  84. package/.claude/commands/workflow/ui-design/reference-page-generator.md +356 -0
  85. package/.claude/commands/workflow/ui-design/style-extract.md +773 -0
  86. package/.claude/scripts/classify-folders.sh +35 -0
  87. package/.claude/scripts/convert_tokens_to_css.sh +225 -0
  88. package/.claude/scripts/detect_changed_modules.sh +157 -0
  89. package/.claude/scripts/discover-design-files.sh +83 -0
  90. package/.claude/scripts/extract-animations.js +243 -0
  91. package/.claude/scripts/extract-computed-styles.js +118 -0
  92. package/.claude/scripts/extract-layout-structure.js +411 -0
  93. package/.claude/scripts/generate_module_docs.sh +713 -0
  94. package/.claude/scripts/get_modules_by_depth.sh +166 -0
  95. package/.claude/scripts/ui-generate-preview.sh +391 -0
  96. package/.claude/scripts/ui-instantiate-prototypes.sh +811 -0
  97. package/.claude/scripts/update_module_claude.sh +333 -0
  98. package/.claude/skills/command-guide/SKILL.md +388 -0
  99. package/.claude/skills/command-guide/UPDATE-GUIDELINE.md +592 -0
  100. package/.claude/skills/command-guide/guides/cli-tools-guide.md +410 -0
  101. package/.claude/skills/command-guide/guides/examples.md +537 -0
  102. package/.claude/skills/command-guide/guides/getting-started.md +242 -0
  103. package/.claude/skills/command-guide/guides/implementation-details.md +1010 -0
  104. package/.claude/skills/command-guide/guides/index-structure.md +326 -0
  105. package/.claude/skills/command-guide/guides/troubleshooting.md +92 -0
  106. package/.claude/skills/command-guide/guides/ui-design-workflow-guide.md +316 -0
  107. package/.claude/skills/command-guide/guides/workflow-patterns.md +662 -0
  108. package/.claude/skills/command-guide/index/all-commands.json +783 -0
  109. package/.claude/skills/command-guide/index/by-category.json +811 -0
  110. package/.claude/skills/command-guide/index/by-use-case.json +797 -0
  111. package/.claude/skills/command-guide/index/command-relationships.json +307 -0
  112. package/.claude/skills/command-guide/index/essential-commands.json +123 -0
  113. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +722 -0
  114. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +270 -0
  115. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -0
  116. package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +396 -0
  117. package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +558 -0
  118. package/.claude/skills/command-guide/reference/agents/code-developer.md +310 -0
  119. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +328 -0
  120. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +577 -0
  121. package/.claude/skills/command-guide/reference/agents/doc-generator.md +330 -0
  122. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +94 -0
  123. package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +399 -0
  124. package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +343 -0
  125. package/.claude/skills/command-guide/reference/agents/ui-design-agent.md +593 -0
  126. package/.claude/skills/command-guide/reference/agents/universal-executor.md +131 -0
  127. package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +440 -0
  128. package/.claude/skills/command-guide/reference/commands/enhance-prompt.md +93 -0
  129. package/.claude/skills/command-guide/reference/commands/memory/code-map-memory.md +687 -0
  130. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -0
  131. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -0
  132. package/.claude/skills/command-guide/reference/commands/memory/docs.md +610 -0
  133. package/.claude/skills/command-guide/reference/commands/memory/load-skill-memory.md +182 -0
  134. package/.claude/skills/command-guide/reference/commands/memory/load.md +240 -0
  135. package/.claude/skills/command-guide/reference/commands/memory/skill-memory.md +525 -0
  136. package/.claude/skills/command-guide/reference/commands/memory/style-skill-memory.md +396 -0
  137. package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +477 -0
  138. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -0
  139. package/.claude/skills/command-guide/reference/commands/memory/update-related.md +332 -0
  140. package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +517 -0
  141. package/.claude/skills/command-guide/reference/commands/task/breakdown.md +204 -0
  142. package/.claude/skills/command-guide/reference/commands/task/create.md +152 -0
  143. package/.claude/skills/command-guide/reference/commands/task/execute.md +270 -0
  144. package/.claude/skills/command-guide/reference/commands/task/replan.md +437 -0
  145. package/.claude/skills/command-guide/reference/commands/version.md +254 -0
  146. package/.claude/skills/command-guide/reference/commands/workflow/action-plan-verify.md +447 -0
  147. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -0
  148. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +604 -0
  149. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +466 -0
  150. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -0
  151. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -0
  152. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -0
  153. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -0
  154. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -0
  155. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +496 -0
  156. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -0
  157. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -0
  158. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -0
  159. package/.claude/skills/command-guide/reference/commands/workflow/execute.md +460 -0
  160. package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -0
  161. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +634 -0
  162. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +602 -0
  163. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +582 -0
  164. package/.claude/skills/command-guide/reference/commands/workflow/plan.md +551 -0
  165. package/.claude/skills/command-guide/reference/commands/workflow/replan.md +515 -0
  166. package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +646 -0
  167. package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +795 -0
  168. package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +805 -0
  169. package/.claude/skills/command-guide/reference/commands/workflow/review.md +291 -0
  170. package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +500 -0
  171. package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +96 -0
  172. package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +61 -0
  173. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +180 -0
  174. package/.claude/skills/command-guide/reference/commands/workflow/status.md +352 -0
  175. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +460 -0
  176. package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +386 -0
  177. package/.claude/skills/command-guide/reference/commands/workflow/test-cycle-execute.md +498 -0
  178. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -0
  179. package/.claude/skills/command-guide/reference/commands/workflow/test-gen.md +529 -0
  180. package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +680 -0
  181. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -0
  182. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -0
  183. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +518 -0
  184. package/.claude/skills/command-guide/reference/commands/workflow/tools/tdd-coverage-analysis.md +309 -0
  185. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +163 -0
  186. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-context-gather.md +235 -0
  187. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +256 -0
  188. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/animation-extract.md +1150 -0
  189. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/codify-style.md +652 -0
  190. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/design-sync.md +454 -0
  191. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/explore-auto.md +678 -0
  192. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -0
  193. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/imitate-auto.md +745 -0
  194. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -0
  195. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/layout-extract.md +788 -0
  196. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/reference-page-generator.md +356 -0
  197. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/style-extract.md +773 -0
  198. package/.claude/skills/command-guide/scripts/analyze_commands.py +502 -0
  199. package/.claude/skills/command-guide/scripts/update-index.sh +130 -0
  200. package/.claude/skills/command-guide/templates/issue-bug.md +104 -0
  201. package/.claude/skills/command-guide/templates/issue-diagnosis.md +275 -0
  202. package/.claude/skills/command-guide/templates/issue-feature.md +97 -0
  203. package/.claude/skills/command-guide/templates/issue-question.md +141 -0
  204. package/.claude/skills/prompt-enhancer/SKILL.md +124 -0
  205. package/.claude/workflows/_template-compare-matrix.html +692 -0
  206. package/.claude/workflows/cli-templates/fix-plan-template.json +75 -0
  207. package/.claude/workflows/cli-templates/fix-progress-template.json +48 -0
  208. package/.claude/workflows/cli-templates/memory/style-skill-memory/skill-md-template.md +299 -0
  209. package/.claude/workflows/cli-templates/planning-roles/data-architect.md +120 -0
  210. package/.claude/workflows/cli-templates/planning-roles/product-manager.md +119 -0
  211. package/.claude/workflows/cli-templates/planning-roles/product-owner.md +261 -0
  212. package/.claude/workflows/cli-templates/planning-roles/scrum-master.md +186 -0
  213. package/.claude/workflows/cli-templates/planning-roles/subject-matter-expert.md +281 -0
  214. package/.claude/workflows/cli-templates/planning-roles/synthesis-role.md +414 -0
  215. package/.claude/workflows/cli-templates/planning-roles/system-architect.md +106 -0
  216. package/.claude/workflows/cli-templates/planning-roles/test-strategist.md +124 -0
  217. package/.claude/workflows/cli-templates/planning-roles/ui-designer.md +379 -0
  218. package/.claude/workflows/cli-templates/planning-roles/ux-expert.md +240 -0
  219. package/.claude/workflows/cli-templates/prompts/analysis/01-diagnose-bug-root-cause.txt +127 -0
  220. package/.claude/workflows/cli-templates/prompts/analysis/01-trace-code-execution.txt +115 -0
  221. package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-code-patterns.txt +37 -0
  222. package/.claude/workflows/cli-templates/prompts/analysis/02-analyze-technical-document.txt +33 -0
  223. package/.claude/workflows/cli-templates/prompts/analysis/02-review-architecture.txt +29 -0
  224. package/.claude/workflows/cli-templates/prompts/analysis/02-review-code-quality.txt +28 -0
  225. package/.claude/workflows/cli-templates/prompts/analysis/03-analyze-performance.txt +29 -0
  226. package/.claude/workflows/cli-templates/prompts/analysis/03-assess-security-risks.txt +29 -0
  227. package/.claude/workflows/cli-templates/prompts/analysis/03-review-quality-standards.txt +29 -0
  228. package/.claude/workflows/cli-templates/prompts/development/02-generate-tests.txt +70 -0
  229. package/.claude/workflows/cli-templates/prompts/development/02-implement-component-ui.txt +55 -0
  230. package/.claude/workflows/cli-templates/prompts/development/02-implement-feature.txt +58 -0
  231. package/.claude/workflows/cli-templates/prompts/development/02-refactor-codebase.txt +55 -0
  232. package/.claude/workflows/cli-templates/prompts/development/03-debug-runtime-issues.txt +55 -0
  233. package/.claude/workflows/cli-templates/prompts/documentation/api.txt +15 -0
  234. package/.claude/workflows/cli-templates/prompts/documentation/folder-navigation.txt +27 -0
  235. package/.claude/workflows/cli-templates/prompts/documentation/module-readme.txt +49 -0
  236. package/.claude/workflows/cli-templates/prompts/documentation/project-architecture.txt +41 -0
  237. package/.claude/workflows/cli-templates/prompts/documentation/project-examples.txt +35 -0
  238. package/.claude/workflows/cli-templates/prompts/documentation/project-readme.txt +35 -0
  239. package/.claude/workflows/cli-templates/prompts/memory/02-document-module-structure.txt +165 -0
  240. package/.claude/workflows/cli-templates/prompts/planning/01-plan-architecture-design.txt +109 -0
  241. package/.claude/workflows/cli-templates/prompts/planning/02-breakdown-task-steps.txt +30 -0
  242. package/.claude/workflows/cli-templates/prompts/planning/02-design-component-spec.txt +28 -0
  243. package/.claude/workflows/cli-templates/prompts/planning/03-evaluate-concept-feasibility.txt +127 -0
  244. package/.claude/workflows/cli-templates/prompts/planning/03-plan-migration-strategy.txt +30 -0
  245. package/.claude/workflows/cli-templates/prompts/tech/tech-module-format.txt +359 -0
  246. package/.claude/workflows/cli-templates/prompts/tech/tech-skill-index.txt +185 -0
  247. package/.claude/workflows/cli-templates/prompts/test/test-concept-analysis.txt +179 -0
  248. package/.claude/workflows/cli-templates/prompts/universal/00-universal-creative-style.txt +95 -0
  249. package/.claude/workflows/cli-templates/prompts/universal/00-universal-rigorous-style.txt +92 -0
  250. package/.claude/workflows/cli-templates/prompts/verification/codex-technical.txt +28 -0
  251. package/.claude/workflows/cli-templates/prompts/verification/cross-validation.txt +28 -0
  252. package/.claude/workflows/cli-templates/prompts/verification/gemini-strategic.txt +27 -0
  253. package/.claude/workflows/cli-templates/prompts/workflow/analysis-results-structure.txt +224 -0
  254. package/.claude/workflows/cli-templates/prompts/workflow/codex-feasibility-validation.txt +176 -0
  255. package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -0
  256. package/.claude/workflows/cli-templates/prompts/workflow/impl-plan-template.txt +286 -0
  257. package/.claude/workflows/cli-templates/prompts/workflow/skill-aggregation.txt +172 -0
  258. package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +98 -0
  259. package/.claude/workflows/cli-templates/prompts/workflow/skill-index.txt +224 -0
  260. package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +98 -0
  261. package/.claude/workflows/cli-templates/prompts/workflow/skill-sessions-timeline.txt +53 -0
  262. package/.claude/workflows/cli-templates/prompts/workflow/task-json-agent-mode.txt +123 -0
  263. package/.claude/workflows/cli-templates/prompts/workflow/task-json-cli-mode.txt +182 -0
  264. package/.claude/workflows/cli-templates/schemas/diagnosis-json-schema.json +234 -0
  265. package/.claude/workflows/cli-templates/schemas/explore-json-schema.json +124 -0
  266. package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +273 -0
  267. package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +219 -0
  268. package/.claude/workflows/cli-templates/schemas/project-json-schema.json +221 -0
  269. package/.claude/workflows/cli-templates/schemas/review-deep-dive-results-schema.json +82 -0
  270. package/.claude/workflows/cli-templates/schemas/review-dimension-results-schema.json +51 -0
  271. package/.claude/workflows/cli-templates/tech-stacks/go-dev.md +91 -0
  272. package/.claude/workflows/cli-templates/tech-stacks/java-dev.md +107 -0
  273. package/.claude/workflows/cli-templates/tech-stacks/javascript-dev.md +58 -0
  274. package/.claude/workflows/cli-templates/tech-stacks/python-dev.md +79 -0
  275. package/.claude/workflows/cli-templates/tech-stacks/react-dev.md +103 -0
  276. package/.claude/workflows/cli-templates/tech-stacks/typescript-dev.md +83 -0
  277. package/.claude/workflows/cli-templates/ui-design/systems/animation-tokens.json +247 -0
  278. package/.claude/workflows/cli-templates/ui-design/systems/design-tokens.json +342 -0
  279. package/.claude/workflows/cli-templates/ui-design/systems/layout-templates.json +145 -0
  280. package/.claude/workflows/context-search-strategy.md +77 -0
  281. package/.claude/workflows/intelligent-tools-strategy.md +662 -0
  282. package/.claude/workflows/review-directory-specification.md +336 -0
  283. package/.claude/workflows/task-core.md +214 -0
  284. package/.claude/workflows/tool-strategy.md +71 -0
  285. package/.claude/workflows/workflow-architecture.md +942 -0
  286. package/.codex/AGENTS.md +330 -0
  287. package/.gemini/GEMINI.md +164 -0
  288. package/.qwen/QWEN.md +164 -0
  289. package/CLAUDE.md +91 -0
  290. package/LICENSE +21 -0
  291. package/README.md +219 -0
  292. package/ccw/README.md +121 -0
  293. package/ccw/bin/ccw.js +10 -0
  294. package/ccw/src/cli.js +100 -0
  295. package/ccw/src/commands/install.js +324 -0
  296. package/ccw/src/commands/list.js +37 -0
  297. package/ccw/src/commands/serve.js +67 -0
  298. package/ccw/src/commands/uninstall.js +238 -0
  299. package/ccw/src/commands/upgrade.js +307 -0
  300. package/ccw/src/commands/view.js +14 -0
  301. package/ccw/src/core/dashboard-generator-patch.js +29 -0
  302. package/ccw/src/core/dashboard-generator.js +667 -0
  303. package/ccw/src/core/data-aggregator.js +409 -0
  304. package/ccw/src/core/lite-scanner.js +290 -0
  305. package/ccw/src/core/manifest.js +201 -0
  306. package/ccw/src/core/server.js +1327 -0
  307. package/ccw/src/core/server.js.bak +385 -0
  308. package/ccw/src/core/server_original.bak +385 -0
  309. package/ccw/src/core/session-scanner.js +235 -0
  310. package/ccw/src/index.js +9 -0
  311. package/ccw/src/templates/dashboard-js/api.js +200 -0
  312. package/ccw/src/templates/dashboard-js/components/_conflict_tab.js +112 -0
  313. package/ccw/src/templates/dashboard-js/components/_exp_helpers.js +54 -0
  314. package/ccw/src/templates/dashboard-js/components/_review_tab.js +640 -0
  315. package/ccw/src/templates/dashboard-js/components/carousel.js +398 -0
  316. package/ccw/src/templates/dashboard-js/components/flowchart.js +493 -0
  317. package/ccw/src/templates/dashboard-js/components/hook-manager.js +273 -0
  318. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +506 -0
  319. package/ccw/src/templates/dashboard-js/components/modals.js +260 -0
  320. package/ccw/src/templates/dashboard-js/components/navigation.js +239 -0
  321. package/ccw/src/templates/dashboard-js/components/notifications.js +194 -0
  322. package/ccw/src/templates/dashboard-js/components/sidebar.js +31 -0
  323. package/ccw/src/templates/dashboard-js/components/tabs-context.js +1093 -0
  324. package/ccw/src/templates/dashboard-js/components/tabs-other.js +273 -0
  325. package/ccw/src/templates/dashboard-js/components/task-drawer-core.js +477 -0
  326. package/ccw/src/templates/dashboard-js/components/task-drawer-renderers.js +447 -0
  327. package/ccw/src/templates/dashboard-js/components/theme.js +21 -0
  328. package/ccw/src/templates/dashboard-js/main.js +57 -0
  329. package/ccw/src/templates/dashboard-js/state.js +37 -0
  330. package/ccw/src/templates/dashboard-js/utils.js +153 -0
  331. package/ccw/src/templates/dashboard-js/views/fix-session.js +180 -0
  332. package/ccw/src/templates/dashboard-js/views/home.js +193 -0
  333. package/ccw/src/templates/dashboard-js/views/hook-manager.js +387 -0
  334. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +390 -0
  335. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +271 -0
  336. package/ccw/src/templates/dashboard-js/views/project-overview.js +246 -0
  337. package/ccw/src/templates/dashboard-js/views/review-session.js +711 -0
  338. package/ccw/src/templates/dashboard-js/views/session-detail.js +770 -0
  339. package/ccw/src/templates/dashboard.css +7660 -0
  340. package/ccw/src/templates/dashboard.html +630 -0
  341. package/ccw/src/templates/dashboard_tailwind.html +42 -0
  342. package/ccw/src/templates/dashboard_test.html +37 -0
  343. package/ccw/src/templates/review-cycle-dashboard.html +1930 -0
  344. package/ccw/src/templates/tailwind-base.css +212 -0
  345. package/ccw/src/templates/workflow-dashboard.html +401 -0
  346. package/ccw/src/utils/browser-launcher.js +49 -0
  347. package/ccw/src/utils/file-utils.js +48 -0
  348. package/ccw/src/utils/path-resolver.js +279 -0
  349. package/ccw/src/utils/ui.js +148 -0
  350. 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
+ }