claude-code-workflow 6.1.4 → 6.2.2

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 (437) hide show
  1. package/.claude/CLAUDE.md +10 -0
  2. package/.claude/agents/action-planning-agent.md +857 -778
  3. package/.claude/agents/cli-execution-agent.md +266 -269
  4. package/.claude/agents/cli-explore-agent.md +2 -2
  5. package/.claude/agents/cli-lite-planning-agent.md +142 -92
  6. package/.claude/agents/cli-planning-agent.md +4 -4
  7. package/.claude/agents/code-developer.md +7 -6
  8. package/.claude/agents/conceptual-planning-agent.md +2 -2
  9. package/.claude/agents/context-search-agent.md +31 -32
  10. package/.claude/agents/doc-generator.md +4 -4
  11. package/.claude/agents/memory-bridge.md +93 -93
  12. package/.claude/agents/test-context-search-agent.md +8 -7
  13. package/.claude/agents/test-fix-agent.md +7 -6
  14. package/.claude/commands/clean.md +516 -0
  15. package/.claude/commands/memory/compact.md +383 -0
  16. package/.claude/commands/memory/docs-full-cli.md +471 -471
  17. package/.claude/commands/memory/docs-related-cli.md +386 -386
  18. package/.claude/commands/memory/docs.md +615 -615
  19. package/.claude/commands/memory/load.md +5 -5
  20. package/.claude/commands/memory/tech-research-rules.md +310 -0
  21. package/.claude/commands/memory/update-full.md +332 -332
  22. package/.claude/commands/memory/workflow-skill-memory.md +4 -4
  23. package/.claude/commands/task/create.md +151 -151
  24. package/.claude/commands/version.md +254 -254
  25. package/.claude/commands/workflow/brainstorm/api-designer.md +587 -585
  26. package/.claude/commands/workflow/brainstorm/artifacts.md +1 -0
  27. package/.claude/commands/workflow/brainstorm/auto-parallel.md +443 -443
  28. package/.claude/commands/workflow/brainstorm/data-architect.md +220 -220
  29. package/.claude/commands/workflow/brainstorm/product-manager.md +200 -200
  30. package/.claude/commands/workflow/brainstorm/product-owner.md +200 -200
  31. package/.claude/commands/workflow/brainstorm/scrum-master.md +200 -200
  32. package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +200 -200
  33. package/.claude/commands/workflow/brainstorm/system-architect.md +389 -387
  34. package/.claude/commands/workflow/brainstorm/ui-designer.md +221 -221
  35. package/.claude/commands/workflow/brainstorm/ux-expert.md +221 -221
  36. package/.claude/commands/workflow/debug.md +321 -0
  37. package/.claude/commands/workflow/execute.md +13 -0
  38. package/.claude/commands/workflow/init.md +165 -164
  39. package/.claude/commands/workflow/lite-execute.md +119 -13
  40. package/.claude/commands/workflow/lite-fix.md +623 -621
  41. package/.claude/commands/workflow/lite-plan.md +610 -592
  42. package/.claude/commands/workflow/plan.md +5 -5
  43. package/.claude/commands/workflow/review-module-cycle.md +2 -0
  44. package/.claude/commands/workflow/review-session-cycle.md +2 -0
  45. package/.claude/commands/workflow/review.md +297 -291
  46. package/.claude/commands/workflow/session/complete.md +153 -500
  47. package/.claude/commands/workflow/session/list.md +95 -95
  48. package/.claude/commands/workflow/session/resume.md +60 -60
  49. package/.claude/commands/workflow/session/start.md +199 -199
  50. package/.claude/commands/workflow/tdd-plan.md +3 -3
  51. package/.claude/commands/workflow/tdd-verify.md +23 -9
  52. package/.claude/commands/workflow/test-cycle-execute.md +2 -0
  53. package/.claude/commands/workflow/test-fix-gen.md +699 -699
  54. package/.claude/commands/workflow/tools/conflict-resolution.md +104 -18
  55. package/.claude/commands/workflow/tools/context-gather.md +436 -434
  56. package/.claude/commands/workflow/tools/task-generate-agent.md +490 -291
  57. package/.claude/commands/workflow/tools/task-generate-tdd.md +18 -10
  58. package/.claude/commands/workflow/tools/test-concept-enhanced.md +2 -1
  59. package/.claude/commands/workflow/tools/test-context-gather.md +1 -0
  60. package/.claude/commands/workflow/tools/test-task-generate.md +1 -0
  61. package/.claude/commands/workflow/ui-design/import-from-code.md +9 -6
  62. package/.claude/skills/command-guide/SKILL.md +5 -5
  63. package/.claude/skills/command-guide/index/all-commands.json +1 -1
  64. package/.claude/skills/command-guide/index/by-category.json +1 -1
  65. package/.claude/skills/command-guide/index/by-use-case.json +1 -1
  66. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +857 -778
  67. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +266 -269
  68. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +2 -2
  69. package/.claude/skills/command-guide/reference/agents/cli-lite-planning-agent.md +142 -92
  70. package/.claude/skills/command-guide/reference/agents/cli-planning-agent.md +4 -4
  71. package/.claude/skills/command-guide/reference/agents/code-developer.md +7 -6
  72. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +2 -2
  73. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +31 -32
  74. package/.claude/skills/command-guide/reference/agents/doc-generator.md +4 -4
  75. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
  76. package/.claude/skills/command-guide/reference/agents/test-context-search-agent.md +8 -7
  77. package/.claude/skills/command-guide/reference/agents/test-fix-agent.md +7 -6
  78. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
  79. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
  80. package/.claude/skills/command-guide/reference/commands/memory/docs.md +17 -16
  81. package/.claude/skills/command-guide/reference/commands/memory/load.md +5 -5
  82. package/.claude/skills/command-guide/reference/commands/memory/tech-research.md +194 -357
  83. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
  84. package/.claude/skills/command-guide/reference/commands/memory/workflow-skill-memory.md +4 -4
  85. package/.claude/skills/command-guide/reference/commands/task/create.md +151 -151
  86. package/.claude/skills/command-guide/reference/commands/version.md +254 -254
  87. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/api-designer.md +585 -585
  88. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +443 -443
  89. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/data-architect.md +220 -220
  90. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-manager.md +200 -200
  91. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/product-owner.md +200 -200
  92. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/scrum-master.md +200 -200
  93. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/subject-matter-expert.md +200 -200
  94. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/system-architect.md +387 -387
  95. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ui-designer.md +221 -221
  96. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/ux-expert.md +221 -221
  97. package/.claude/skills/command-guide/reference/commands/workflow/execute.md +25 -20
  98. package/.claude/skills/command-guide/reference/commands/workflow/init.md +164 -164
  99. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +748 -686
  100. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +664 -621
  101. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +645 -592
  102. package/.claude/skills/command-guide/reference/commands/workflow/plan.md +5 -5
  103. package/.claude/skills/command-guide/reference/commands/workflow/review.md +25 -18
  104. package/.claude/skills/command-guide/reference/commands/workflow/session/complete.md +547 -500
  105. package/.claude/skills/command-guide/reference/commands/workflow/session/list.md +45 -27
  106. package/.claude/skills/command-guide/reference/commands/workflow/session/resume.md +35 -19
  107. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +90 -33
  108. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +3 -3
  109. package/.claude/skills/command-guide/reference/commands/workflow/tdd-verify.md +23 -9
  110. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +699 -699
  111. package/.claude/skills/command-guide/reference/commands/workflow/tools/conflict-resolution.md +103 -17
  112. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
  113. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +487 -291
  114. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +17 -10
  115. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-concept-enhanced.md +1 -1
  116. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +6 -6
  117. package/.claude/workflows/chinese-response.md +38 -0
  118. package/.claude/workflows/cli-templates/prompts/rules/rule-api.txt +122 -0
  119. package/.claude/workflows/cli-templates/prompts/rules/rule-components.txt +122 -0
  120. package/.claude/workflows/cli-templates/prompts/rules/rule-config.txt +89 -0
  121. package/.claude/workflows/cli-templates/prompts/rules/rule-core.txt +60 -0
  122. package/.claude/workflows/cli-templates/prompts/rules/rule-patterns.txt +70 -0
  123. package/.claude/workflows/cli-templates/prompts/rules/rule-testing.txt +81 -0
  124. package/.claude/workflows/cli-templates/prompts/rules/tech-rules-agent-prompt.txt +89 -0
  125. package/.claude/workflows/cli-templates/prompts/workflow/gemini-solution-design.txt +131 -131
  126. package/.claude/workflows/cli-templates/prompts/workflow/skill-conflict-patterns.txt +5 -9
  127. package/.claude/workflows/cli-templates/prompts/workflow/skill-lessons-learned.txt +5 -9
  128. package/.claude/workflows/cli-templates/protocols/analysis-protocol.md +112 -0
  129. package/.claude/workflows/cli-templates/protocols/write-protocol.md +201 -0
  130. package/.claude/workflows/cli-templates/schemas/conflict-resolution-schema.json +137 -0
  131. package/.claude/workflows/cli-templates/schemas/debug-log-json-schema.json +127 -0
  132. package/.claude/workflows/cli-templates/schemas/fix-plan-json-schema.json +25 -0
  133. package/.claude/workflows/cli-templates/schemas/plan-json-schema.json +25 -0
  134. package/.claude/workflows/cli-tools-usage.md +526 -0
  135. package/{CLAUDE.md → .claude/workflows/coding-philosophy.md} +24 -45
  136. package/.claude/workflows/context-tools.md +84 -0
  137. package/.claude/workflows/file-modification.md +64 -0
  138. package/.claude/workflows/tool-strategy.md +216 -79
  139. package/.claude/workflows/windows-platform.md +16 -0
  140. package/.claude/workflows/workflow-architecture.md +942 -942
  141. package/.codex/AGENTS.md +63 -330
  142. package/.codex/prompts/debug.md +318 -0
  143. package/.codex/prompts/execute.md +273 -0
  144. package/.codex/prompts/lite-execute.md +164 -0
  145. package/.codex/prompts/lite-plan.md +469 -0
  146. package/.codex/prompts.zip +0 -0
  147. package/.gemini/GEMINI.md +25 -164
  148. package/.qwen/QWEN.md +0 -139
  149. package/README.md +29 -9
  150. package/ccw/README.md +30 -6
  151. package/ccw/bin/ccw-mcp.js +7 -0
  152. package/ccw/bin/ccw.js +9 -9
  153. package/ccw/package.json +65 -47
  154. package/ccw/src/.workflow/.cli-history/history.db +0 -0
  155. package/ccw/src/.workflow/.cli-history/history.db-shm +0 -0
  156. package/ccw/src/.workflow/.cli-history/history.db-wal +0 -0
  157. package/ccw/src/cli.ts +244 -0
  158. package/ccw/src/commands/cli.ts +740 -0
  159. package/ccw/src/commands/core-memory.ts +770 -0
  160. package/ccw/src/commands/hook.ts +315 -0
  161. package/ccw/src/commands/install.ts +519 -0
  162. package/ccw/src/commands/{list.js → list.ts} +1 -1
  163. package/ccw/src/commands/memory.ts +1090 -0
  164. package/ccw/src/commands/{serve.js → serve.ts} +14 -5
  165. package/ccw/src/commands/session-path-resolver.ts +372 -0
  166. package/ccw/src/commands/session.ts +1141 -0
  167. package/ccw/src/commands/{stop.js → stop.ts} +16 -6
  168. package/ccw/src/commands/tool.ts +201 -0
  169. package/ccw/src/commands/{uninstall.js → uninstall.ts} +89 -40
  170. package/ccw/src/commands/{upgrade.js → upgrade.ts} +68 -23
  171. package/ccw/src/commands/{view.js → view.ts} +22 -8
  172. package/ccw/src/config/storage-paths.ts +670 -0
  173. package/ccw/src/core/cache-manager.ts +294 -0
  174. package/ccw/src/core/claude-freshness.ts +319 -0
  175. package/ccw/src/core/core-memory-store.ts +1528 -0
  176. package/ccw/src/core/{dashboard-generator-patch.js → dashboard-generator-patch.ts} +18 -0
  177. package/ccw/src/core/{dashboard-generator.js → dashboard-generator.ts} +69 -12
  178. package/ccw/src/core/data-aggregator.ts +584 -0
  179. package/ccw/src/core/history-importer.ts +625 -0
  180. package/ccw/src/core/{lite-scanner.js → lite-scanner-complete.ts} +162 -66
  181. package/ccw/src/core/lite-scanner.ts +469 -0
  182. package/ccw/src/core/{manifest.js → manifest.ts} +104 -34
  183. package/ccw/src/core/memory-embedder-bridge.ts +262 -0
  184. package/ccw/src/core/memory-store.ts +978 -0
  185. package/ccw/src/core/routes/ccw-routes.ts +96 -0
  186. package/ccw/src/core/routes/claude-routes.ts +1183 -0
  187. package/ccw/src/core/routes/cli-routes.ts +561 -0
  188. package/ccw/src/core/routes/codexlens-routes.ts +806 -0
  189. package/ccw/src/core/routes/core-memory-routes.ts +605 -0
  190. package/ccw/src/core/routes/files-routes.ts +428 -0
  191. package/ccw/src/core/routes/graph-routes.md +164 -0
  192. package/ccw/src/core/routes/graph-routes.ts +626 -0
  193. package/ccw/src/core/routes/help-routes.ts +308 -0
  194. package/ccw/src/core/routes/hooks-routes.ts +405 -0
  195. package/ccw/src/core/routes/mcp-routes.ts +1271 -0
  196. package/ccw/src/core/routes/mcp-routes.ts.backup +550 -0
  197. package/ccw/src/core/routes/mcp-templates-db.ts +268 -0
  198. package/ccw/src/core/routes/memory-routes.ts +1206 -0
  199. package/ccw/src/core/routes/rules-routes.ts +526 -0
  200. package/ccw/src/core/routes/session-routes.ts +467 -0
  201. package/ccw/src/core/routes/skills-routes.ts +599 -0
  202. package/ccw/src/core/routes/status-routes.ts +57 -0
  203. package/ccw/src/core/routes/system-routes.ts +427 -0
  204. package/ccw/src/core/server.ts +431 -0
  205. package/ccw/src/core/session-clustering-service.ts +1258 -0
  206. package/ccw/src/core/session-scanner.ts +283 -0
  207. package/ccw/src/core/websocket.ts +190 -0
  208. package/ccw/src/{index.js → index.ts} +1 -0
  209. package/ccw/src/mcp-server/index.ts +186 -0
  210. package/ccw/src/templates/assets/css/github-dark.min.css +10 -0
  211. package/ccw/src/templates/assets/css/github.min.css +10 -0
  212. package/ccw/src/templates/assets/js/cytoscape.min.js +32 -0
  213. package/ccw/src/templates/assets/js/d3.min.js +2 -0
  214. package/ccw/src/templates/assets/js/highlight.min.js +1244 -0
  215. package/ccw/src/templates/assets/js/lucide.min.js +12 -0
  216. package/ccw/src/templates/assets/js/marked.min.js +69 -0
  217. package/ccw/src/templates/assets/js/tailwind.js +83 -0
  218. package/ccw/src/templates/dashboard-css/01-base.css +11 -0
  219. package/ccw/src/templates/dashboard-css/02-session.css +22 -0
  220. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +10 -0
  221. package/ccw/src/templates/dashboard-css/06-cards.css +10 -4
  222. package/ccw/src/templates/dashboard-css/07-managers.css +1178 -7
  223. package/ccw/src/templates/dashboard-css/09-explorer.css +23 -12
  224. package/ccw/src/templates/dashboard-css/10-cli-status.css +337 -0
  225. package/ccw/src/templates/dashboard-css/11-cli-history.css +271 -0
  226. package/ccw/src/templates/dashboard-css/12-cli-legacy.css +796 -0
  227. package/ccw/src/templates/dashboard-css/13-cli-ccw.css +199 -0
  228. package/ccw/src/templates/dashboard-css/14-cli-modals.css +258 -0
  229. package/ccw/src/templates/dashboard-css/15-cli-endpoints.css +305 -0
  230. package/ccw/src/templates/dashboard-css/16-cli-session.css +241 -0
  231. package/ccw/src/templates/dashboard-css/17-cli-conversation.css +283 -0
  232. package/ccw/src/templates/dashboard-css/18-cli-settings.css +160 -0
  233. package/ccw/src/templates/dashboard-css/19-cli-native-session.css +496 -0
  234. package/ccw/src/templates/dashboard-css/20-cli-taskqueue.css +188 -0
  235. package/ccw/src/templates/dashboard-css/21-cli-toolmgmt.css +310 -0
  236. package/ccw/src/templates/dashboard-css/22-cli-semantic.css +240 -0
  237. package/ccw/src/templates/dashboard-css/23-memory.css +2390 -0
  238. package/ccw/src/templates/dashboard-css/24-prompt-history.css +1089 -0
  239. package/ccw/src/templates/dashboard-css/25-skills-rules.css +326 -0
  240. package/ccw/src/templates/dashboard-css/26-claude-manager.css +908 -0
  241. package/ccw/src/templates/dashboard-css/27-graph-explorer.css +1678 -0
  242. package/ccw/src/templates/dashboard-css/28-mcp-manager.css +748 -0
  243. package/ccw/src/templates/dashboard-css/29-help.css +264 -0
  244. package/ccw/src/templates/dashboard-css/30-core-memory.css +1700 -0
  245. package/ccw/src/templates/dashboard-js/api.js +162 -142
  246. package/ccw/src/templates/dashboard-js/components/carousel.js +4 -4
  247. package/ccw/src/templates/dashboard-js/components/cli-history.js +876 -0
  248. package/ccw/src/templates/dashboard-js/components/cli-status.js +978 -0
  249. package/ccw/src/templates/dashboard-js/components/global-notifications.js +508 -219
  250. package/ccw/src/templates/dashboard-js/components/hook-manager.js +1277 -282
  251. package/ccw/src/templates/dashboard-js/components/index-manager.js +302 -0
  252. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +718 -27
  253. package/ccw/src/templates/dashboard-js/components/modals.js +66 -0
  254. package/ccw/src/templates/dashboard-js/components/navigation.js +80 -12
  255. package/ccw/src/templates/dashboard-js/components/notifications.js +758 -194
  256. package/ccw/src/templates/dashboard-js/components/storage-manager.js +478 -0
  257. package/ccw/src/templates/dashboard-js/components/tabs-other.js +157 -6
  258. package/ccw/src/templates/dashboard-js/components/task-queue-sidebar.js +716 -0
  259. package/ccw/src/templates/dashboard-js/help-i18n.js +272 -0
  260. package/ccw/src/templates/dashboard-js/i18n.js +2807 -0
  261. package/ccw/src/templates/dashboard-js/main.js +15 -0
  262. package/ccw/src/templates/dashboard-js/state.js +243 -42
  263. package/ccw/src/templates/dashboard-js/utils.js +47 -1
  264. package/ccw/src/templates/dashboard-js/views/claude-manager.js +912 -0
  265. package/ccw/src/templates/dashboard-js/views/cli-manager.js +2272 -0
  266. package/ccw/src/templates/dashboard-js/views/codexlens-manager.js +964 -0
  267. package/ccw/src/templates/dashboard-js/views/core-memory-clusters.js +503 -0
  268. package/ccw/src/templates/dashboard-js/views/core-memory.js +782 -0
  269. package/ccw/src/templates/dashboard-js/views/explorer.js +888 -852
  270. package/ccw/src/templates/dashboard-js/views/graph-explorer.js +1157 -0
  271. package/ccw/src/templates/dashboard-js/views/help.js +856 -0
  272. package/ccw/src/templates/dashboard-js/views/history.js +337 -0
  273. package/ccw/src/templates/dashboard-js/views/home.js +61 -15
  274. package/ccw/src/templates/dashboard-js/views/hook-manager.js +311 -43
  275. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +204 -28
  276. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +2187 -411
  277. package/ccw/src/templates/dashboard-js/views/mcp-manager.js.backup +1729 -0
  278. package/ccw/src/templates/dashboard-js/views/mcp-manager.js.new +928 -0
  279. package/ccw/src/templates/dashboard-js/views/memory.js +1221 -0
  280. package/ccw/src/templates/dashboard-js/views/prompt-history.js +713 -0
  281. package/ccw/src/templates/dashboard-js/views/rules-manager.js +828 -0
  282. package/ccw/src/templates/dashboard-js/views/session-detail.js +54 -53
  283. package/ccw/src/templates/dashboard-js/views/skills-manager.js +819 -0
  284. package/ccw/src/templates/dashboard.html +185 -85
  285. package/ccw/src/templates/hooks-config-example.json +60 -0
  286. package/ccw/src/tools/classify-folders.ts +245 -0
  287. package/ccw/src/tools/cli-config-manager.ts +268 -0
  288. package/ccw/src/tools/cli-executor.ts +2014 -0
  289. package/ccw/src/tools/cli-history-store.ts +1195 -0
  290. package/ccw/src/tools/codex-lens.ts +1141 -0
  291. package/ccw/src/tools/{convert-tokens-to-css.js → convert-tokens-to-css.ts} +73 -23
  292. package/ccw/src/tools/core-memory.ts +444 -0
  293. package/ccw/src/tools/detect-changed-modules.ts +325 -0
  294. package/ccw/src/tools/{discover-design-files.js → discover-design-files.ts} +74 -24
  295. package/ccw/src/tools/edit-file.ts +568 -0
  296. package/ccw/src/tools/{generate-module-docs.js → generate-module-docs.ts} +207 -185
  297. package/ccw/src/tools/{get-modules-by-depth.js → get-modules-by-depth.ts} +120 -79
  298. package/ccw/src/tools/index.ts +370 -0
  299. package/ccw/src/tools/native-session-discovery.ts +795 -0
  300. package/ccw/src/tools/notifier.ts +129 -0
  301. package/ccw/src/tools/read-file.ts +410 -0
  302. package/ccw/src/tools/resume-strategy.ts +345 -0
  303. package/ccw/src/tools/session-content-parser.ts +619 -0
  304. package/ccw/src/tools/session-manager.ts +1026 -0
  305. package/ccw/src/tools/smart-context.ts +228 -0
  306. package/ccw/src/tools/smart-search.ts +2065 -0
  307. package/ccw/src/tools/smart-search.ts.backup +1233 -0
  308. package/ccw/src/tools/storage-manager.ts +455 -0
  309. package/ccw/src/tools/write-file.ts +222 -0
  310. package/ccw/src/types/config.ts +11 -0
  311. package/ccw/src/types/index.ts +3 -0
  312. package/ccw/src/types/session.ts +25 -0
  313. package/ccw/src/types/tool.ts +41 -0
  314. package/ccw/src/utils/{browser-launcher.js → browser-launcher.ts} +10 -8
  315. package/ccw/src/utils/file-utils.ts +48 -0
  316. package/ccw/src/utils/{path-resolver.js → path-resolver.ts} +114 -78
  317. package/ccw/src/utils/path-validator.ts +153 -0
  318. package/ccw/src/utils/{ui.js → ui.ts} +32 -25
  319. package/codex-lens/pyproject.toml +48 -0
  320. package/codex-lens/src/codexlens/.workflow/.cli-history/history.db +0 -0
  321. package/codex-lens/src/codexlens/__init__.py +28 -0
  322. package/codex-lens/src/codexlens/__main__.py +14 -0
  323. package/codex-lens/src/codexlens/__pycache__/__init__.cpython-313.pyc +0 -0
  324. package/codex-lens/src/codexlens/__pycache__/__main__.cpython-313.pyc +0 -0
  325. package/codex-lens/src/codexlens/__pycache__/config.cpython-313.pyc +0 -0
  326. package/codex-lens/src/codexlens/__pycache__/entities.cpython-313.pyc +0 -0
  327. package/codex-lens/src/codexlens/__pycache__/errors.cpython-313.pyc +0 -0
  328. package/codex-lens/src/codexlens/cli/__init__.py +27 -0
  329. package/codex-lens/src/codexlens/cli/__pycache__/__init__.cpython-313.pyc +0 -0
  330. package/codex-lens/src/codexlens/cli/__pycache__/commands.cpython-313.pyc +0 -0
  331. package/codex-lens/src/codexlens/cli/__pycache__/embedding_manager.cpython-313.pyc +0 -0
  332. package/codex-lens/src/codexlens/cli/__pycache__/model_manager.cpython-313.pyc +0 -0
  333. package/codex-lens/src/codexlens/cli/__pycache__/output.cpython-313.pyc +0 -0
  334. package/codex-lens/src/codexlens/cli/commands.py +1931 -0
  335. package/codex-lens/src/codexlens/cli/embedding_manager.py +620 -0
  336. package/codex-lens/src/codexlens/cli/model_manager.py +289 -0
  337. package/codex-lens/src/codexlens/cli/output.py +124 -0
  338. package/codex-lens/src/codexlens/config.py +201 -0
  339. package/codex-lens/src/codexlens/entities.py +121 -0
  340. package/codex-lens/src/codexlens/errors.py +55 -0
  341. package/codex-lens/src/codexlens/indexing/README.md +77 -0
  342. package/codex-lens/src/codexlens/indexing/__init__.py +4 -0
  343. package/codex-lens/src/codexlens/indexing/__pycache__/__init__.cpython-313.pyc +0 -0
  344. package/codex-lens/src/codexlens/indexing/__pycache__/symbol_extractor.cpython-313.pyc +0 -0
  345. package/codex-lens/src/codexlens/indexing/symbol_extractor.py +243 -0
  346. package/codex-lens/src/codexlens/parsers/__init__.py +8 -0
  347. package/codex-lens/src/codexlens/parsers/__pycache__/__init__.cpython-313.pyc +0 -0
  348. package/codex-lens/src/codexlens/parsers/__pycache__/encoding.cpython-313.pyc +0 -0
  349. package/codex-lens/src/codexlens/parsers/__pycache__/factory.cpython-313.pyc +0 -0
  350. package/codex-lens/src/codexlens/parsers/__pycache__/tokenizer.cpython-313.pyc +0 -0
  351. package/codex-lens/src/codexlens/parsers/__pycache__/treesitter_parser.cpython-313.pyc +0 -0
  352. package/codex-lens/src/codexlens/parsers/encoding.py +202 -0
  353. package/codex-lens/src/codexlens/parsers/factory.py +256 -0
  354. package/codex-lens/src/codexlens/parsers/tokenizer.py +98 -0
  355. package/codex-lens/src/codexlens/parsers/treesitter_parser.py +335 -0
  356. package/codex-lens/src/codexlens/search/__init__.py +15 -0
  357. package/codex-lens/src/codexlens/search/__pycache__/__init__.cpython-313.pyc +0 -0
  358. package/codex-lens/src/codexlens/search/__pycache__/chain_search.cpython-313.pyc +0 -0
  359. package/codex-lens/src/codexlens/search/__pycache__/enrichment.cpython-313.pyc +0 -0
  360. package/codex-lens/src/codexlens/search/__pycache__/hybrid_search.cpython-313.pyc +0 -0
  361. package/codex-lens/src/codexlens/search/__pycache__/query_parser.cpython-313.pyc +0 -0
  362. package/codex-lens/src/codexlens/search/__pycache__/ranking.cpython-313.pyc +0 -0
  363. package/codex-lens/src/codexlens/search/chain_search.py +647 -0
  364. package/codex-lens/src/codexlens/search/enrichment.py +150 -0
  365. package/codex-lens/src/codexlens/search/hybrid_search.py +313 -0
  366. package/codex-lens/src/codexlens/search/query_parser.py +242 -0
  367. package/codex-lens/src/codexlens/search/ranking.py +274 -0
  368. package/codex-lens/src/codexlens/semantic/__init__.py +39 -0
  369. package/codex-lens/src/codexlens/semantic/__pycache__/__init__.cpython-313.pyc +0 -0
  370. package/codex-lens/src/codexlens/semantic/__pycache__/ann_index.cpython-313.pyc +0 -0
  371. package/codex-lens/src/codexlens/semantic/__pycache__/chunker.cpython-313.pyc +0 -0
  372. package/codex-lens/src/codexlens/semantic/__pycache__/code_extractor.cpython-313.pyc +0 -0
  373. package/codex-lens/src/codexlens/semantic/__pycache__/embedder.cpython-313.pyc +0 -0
  374. package/codex-lens/src/codexlens/semantic/__pycache__/graph_analyzer.cpython-313.pyc +0 -0
  375. package/codex-lens/src/codexlens/semantic/__pycache__/llm_enhancer.cpython-313.pyc +0 -0
  376. package/codex-lens/src/codexlens/semantic/__pycache__/vector_store.cpython-313.pyc +0 -0
  377. package/codex-lens/src/codexlens/semantic/ann_index.py +414 -0
  378. package/codex-lens/src/codexlens/semantic/chunker.py +448 -0
  379. package/codex-lens/src/codexlens/semantic/code_extractor.py +274 -0
  380. package/codex-lens/src/codexlens/semantic/embedder.py +185 -0
  381. package/codex-lens/src/codexlens/semantic/vector_store.py +955 -0
  382. package/codex-lens/src/codexlens/storage/__init__.py +29 -0
  383. package/codex-lens/src/codexlens/storage/__pycache__/__init__.cpython-313.pyc +0 -0
  384. package/codex-lens/src/codexlens/storage/__pycache__/dir_index.cpython-313.pyc +0 -0
  385. package/codex-lens/src/codexlens/storage/__pycache__/file_cache.cpython-313.pyc +0 -0
  386. package/codex-lens/src/codexlens/storage/__pycache__/index_tree.cpython-313.pyc +0 -0
  387. package/codex-lens/src/codexlens/storage/__pycache__/migration_manager.cpython-313.pyc +0 -0
  388. package/codex-lens/src/codexlens/storage/__pycache__/path_mapper.cpython-313.pyc +0 -0
  389. package/codex-lens/src/codexlens/storage/__pycache__/registry.cpython-313.pyc +0 -0
  390. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_store.cpython-313.pyc +0 -0
  391. package/codex-lens/src/codexlens/storage/__pycache__/sqlite_utils.cpython-313.pyc +0 -0
  392. package/codex-lens/src/codexlens/storage/dir_index.py +1850 -0
  393. package/codex-lens/src/codexlens/storage/file_cache.py +32 -0
  394. package/codex-lens/src/codexlens/storage/index_tree.py +776 -0
  395. package/codex-lens/src/codexlens/storage/migration_manager.py +154 -0
  396. package/codex-lens/src/codexlens/storage/migrations/__init__.py +1 -0
  397. package/codex-lens/src/codexlens/storage/migrations/__pycache__/__init__.cpython-313.pyc +0 -0
  398. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_001_normalize_keywords.cpython-313.pyc +0 -0
  399. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_002_add_token_metadata.cpython-313.pyc +0 -0
  400. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_003_code_relationships.cpython-313.pyc +0 -0
  401. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_004_dual_fts.cpython-313.pyc +0 -0
  402. package/codex-lens/src/codexlens/storage/migrations/__pycache__/migration_005_cleanup_unused_fields.cpython-313.pyc +0 -0
  403. package/codex-lens/src/codexlens/storage/migrations/migration_001_normalize_keywords.py +123 -0
  404. package/codex-lens/src/codexlens/storage/migrations/migration_002_add_token_metadata.py +48 -0
  405. package/codex-lens/src/codexlens/storage/migrations/migration_004_dual_fts.py +232 -0
  406. package/codex-lens/src/codexlens/storage/migrations/migration_005_cleanup_unused_fields.py +196 -0
  407. package/codex-lens/src/codexlens/storage/path_mapper.py +274 -0
  408. package/codex-lens/src/codexlens/storage/registry.py +670 -0
  409. package/codex-lens/src/codexlens/storage/sqlite_store.py +576 -0
  410. package/codex-lens/src/codexlens/storage/sqlite_utils.py +64 -0
  411. package/package.json +4 -1
  412. package/.claude/commands/memory/tech-research.md +0 -477
  413. package/.claude/scripts/classify-folders.sh +0 -39
  414. package/.claude/scripts/convert_tokens_to_css.sh +0 -229
  415. package/.claude/scripts/detect_changed_modules.sh +0 -161
  416. package/.claude/scripts/discover-design-files.sh +0 -87
  417. package/.claude/scripts/extract-animations.js +0 -243
  418. package/.claude/scripts/extract-computed-styles.js +0 -118
  419. package/.claude/scripts/extract-layout-structure.js +0 -411
  420. package/.claude/scripts/generate_module_docs.sh +0 -717
  421. package/.claude/scripts/get_modules_by_depth.sh +0 -170
  422. package/.claude/scripts/ui-generate-preview.sh +0 -395
  423. package/.claude/scripts/ui-instantiate-prototypes.sh +0 -815
  424. package/.claude/scripts/update_module_claude.sh +0 -337
  425. package/.claude/workflows/context-search-strategy.md +0 -77
  426. package/.claude/workflows/intelligent-tools-strategy.md +0 -662
  427. package/ccw/src/cli.js +0 -119
  428. package/ccw/src/commands/install.js +0 -324
  429. package/ccw/src/commands/tool.js +0 -138
  430. package/ccw/src/core/data-aggregator.js +0 -409
  431. package/ccw/src/core/server.js +0 -2063
  432. package/ccw/src/core/session-scanner.js +0 -235
  433. package/ccw/src/tools/classify-folders.js +0 -204
  434. package/ccw/src/tools/detect-changed-modules.js +0 -288
  435. package/ccw/src/tools/edit-file.js +0 -266
  436. package/ccw/src/tools/index.js +0 -176
  437. package/ccw/src/utils/file-utils.js +0 -48
@@ -1,283 +1,1278 @@
1
- // Hook Manager Component
2
- // Manages Claude Code hooks configuration from settings.json
3
-
4
- // ========== Hook State ==========
5
- let hookConfig = {
6
- global: { hooks: {} },
7
- project: { hooks: {} }
8
- };
9
-
10
- // ========== Hook Templates ==========
11
- const HOOK_TEMPLATES = {
12
- 'ccw-notify': {
13
- event: 'PostToolUse',
14
- matcher: 'Write',
15
- command: 'curl',
16
- args: ['-s', '-X', 'POST', '-H', 'Content-Type: application/json', '-d', '{"type":"summary_written","filePath":"$CLAUDE_FILE_PATHS"}', 'http://localhost:3456/api/hook']
17
- },
18
- 'log-tool': {
19
- event: 'PostToolUse',
20
- matcher: '',
21
- command: 'bash',
22
- args: ['-c', 'echo "[$(date)] Tool: $CLAUDE_TOOL_NAME, Files: $CLAUDE_FILE_PATHS" >> ~/.claude/tool-usage.log']
23
- },
24
- 'lint-check': {
25
- event: 'PostToolUse',
26
- matcher: 'Write',
27
- command: 'bash',
28
- args: ['-c', 'for f in $CLAUDE_FILE_PATHS; do if [[ "$f" =~ \\.(js|ts|jsx|tsx)$ ]]; then npx eslint "$f" --fix 2>/dev/null || true; fi; done']
29
- },
30
- 'git-add': {
31
- event: 'PostToolUse',
32
- matcher: 'Write',
33
- command: 'bash',
34
- args: ['-c', 'for f in $CLAUDE_FILE_PATHS; do git add "$f" 2>/dev/null || true; done']
35
- }
36
- };
37
-
38
- // ========== Initialization ==========
39
- function initHookManager() {
40
- // Initialize Hook navigation
41
- document.querySelectorAll('.nav-item[data-view="hook-manager"]').forEach(item => {
42
- item.addEventListener('click', () => {
43
- setActiveNavItem(item);
44
- currentView = 'hook-manager';
45
- currentFilter = null;
46
- currentLiteType = null;
47
- currentSessionDetailKey = null;
48
- updateContentTitle();
49
- renderHookManager();
50
- });
51
- });
52
- }
53
-
54
- // ========== Data Loading ==========
55
- async function loadHookConfig() {
56
- try {
57
- const response = await fetch(`/api/hooks?path=${encodeURIComponent(projectPath)}`);
58
- if (!response.ok) throw new Error('Failed to load hook config');
59
- const data = await response.json();
60
- hookConfig = data;
61
- updateHookBadge();
62
- return data;
63
- } catch (err) {
64
- console.error('Failed to load hook config:', err);
65
- return null;
66
- }
67
- }
68
-
69
- async function saveHook(scope, event, hookData) {
70
- try {
71
- const response = await fetch('/api/hooks', {
72
- method: 'POST',
73
- headers: { 'Content-Type': 'application/json' },
74
- body: JSON.stringify({
75
- projectPath: projectPath,
76
- scope: scope,
77
- event: event,
78
- hookData: hookData
79
- })
80
- });
81
-
82
- if (!response.ok) throw new Error('Failed to save hook');
83
-
84
- const result = await response.json();
85
- if (result.success) {
86
- await loadHookConfig();
87
- renderHookManager();
88
- showRefreshToast(`Hook saved successfully`, 'success');
89
- }
90
- return result;
91
- } catch (err) {
92
- console.error('Failed to save hook:', err);
93
- showRefreshToast(`Failed to save hook: ${err.message}`, 'error');
94
- return null;
95
- }
96
- }
97
-
98
- async function removeHook(scope, event, hookIndex) {
99
- try {
100
- const response = await fetch('/api/hooks', {
101
- method: 'DELETE',
102
- headers: { 'Content-Type': 'application/json' },
103
- body: JSON.stringify({
104
- projectPath: projectPath,
105
- scope: scope,
106
- event: event,
107
- hookIndex: hookIndex
108
- })
109
- });
110
-
111
- if (!response.ok) throw new Error('Failed to remove hook');
112
-
113
- const result = await response.json();
114
- if (result.success) {
115
- await loadHookConfig();
116
- renderHookManager();
117
- showRefreshToast(`Hook removed successfully`, 'success');
118
- }
119
- return result;
120
- } catch (err) {
121
- console.error('Failed to remove hook:', err);
122
- showRefreshToast(`Failed to remove hook: ${err.message}`, 'error');
123
- return null;
124
- }
125
- }
126
-
127
- // ========== Badge Update ==========
128
- function updateHookBadge() {
129
- const badge = document.getElementById('badgeHooks');
130
- if (badge) {
131
- let totalHooks = 0;
132
-
133
- // Count global hooks
134
- if (hookConfig.global?.hooks) {
135
- for (const event of Object.keys(hookConfig.global.hooks)) {
136
- const hooks = hookConfig.global.hooks[event];
137
- totalHooks += Array.isArray(hooks) ? hooks.length : 1;
138
- }
139
- }
140
-
141
- // Count project hooks
142
- if (hookConfig.project?.hooks) {
143
- for (const event of Object.keys(hookConfig.project.hooks)) {
144
- const hooks = hookConfig.project.hooks[event];
145
- totalHooks += Array.isArray(hooks) ? hooks.length : 1;
146
- }
147
- }
148
-
149
- badge.textContent = totalHooks;
150
- }
151
- }
152
-
153
- // ========== Hook Modal Functions ==========
154
- let editingHookData = null;
155
-
156
- function openHookCreateModal(editData = null) {
157
- const modal = document.getElementById('hookCreateModal');
158
- const title = document.getElementById('hookModalTitle');
159
-
160
- if (modal) {
161
- modal.classList.remove('hidden');
162
- editingHookData = editData;
163
-
164
- // Set title based on mode
165
- title.textContent = editData ? 'Edit Hook' : 'Create Hook';
166
-
167
- // Clear or populate form
168
- if (editData) {
169
- document.getElementById('hookEvent').value = editData.event || '';
170
- document.getElementById('hookMatcher').value = editData.matcher || '';
171
- document.getElementById('hookCommand').value = editData.command || '';
172
- document.getElementById('hookArgs').value = (editData.args || []).join('\n');
173
-
174
- // Set scope radio
175
- const scopeRadio = document.querySelector(`input[name="hookScope"][value="${editData.scope || 'project'}"]`);
176
- if (scopeRadio) scopeRadio.checked = true;
177
- } else {
178
- document.getElementById('hookEvent').value = '';
179
- document.getElementById('hookMatcher').value = '';
180
- document.getElementById('hookCommand').value = '';
181
- document.getElementById('hookArgs').value = '';
182
- document.querySelector('input[name="hookScope"][value="project"]').checked = true;
183
- }
184
-
185
- // Focus on event select
186
- document.getElementById('hookEvent').focus();
187
- }
188
- }
189
-
190
- function closeHookCreateModal() {
191
- const modal = document.getElementById('hookCreateModal');
192
- if (modal) {
193
- modal.classList.add('hidden');
194
- editingHookData = null;
195
- }
196
- }
197
-
198
- function applyHookTemplate(templateName) {
199
- const template = HOOK_TEMPLATES[templateName];
200
- if (!template) return;
201
-
202
- document.getElementById('hookEvent').value = template.event;
203
- document.getElementById('hookMatcher').value = template.matcher;
204
- document.getElementById('hookCommand').value = template.command;
205
- document.getElementById('hookArgs').value = template.args.join('\n');
206
- }
207
-
208
- async function submitHookCreate() {
209
- const event = document.getElementById('hookEvent').value;
210
- const matcher = document.getElementById('hookMatcher').value.trim();
211
- const command = document.getElementById('hookCommand').value.trim();
212
- const argsText = document.getElementById('hookArgs').value.trim();
213
- const scope = document.querySelector('input[name="hookScope"]:checked').value;
214
-
215
- // Validate required fields
216
- if (!event) {
217
- showRefreshToast('Hook event is required', 'error');
218
- document.getElementById('hookEvent').focus();
219
- return;
220
- }
221
-
222
- if (!command) {
223
- showRefreshToast('Command is required', 'error');
224
- document.getElementById('hookCommand').focus();
225
- return;
226
- }
227
-
228
- // Parse args (one per line)
229
- const args = argsText ? argsText.split('\n').map(a => a.trim()).filter(a => a) : [];
230
-
231
- // Build hook data
232
- const hookData = {
233
- command: command
234
- };
235
-
236
- if (args.length > 0) {
237
- hookData.args = args;
238
- }
239
-
240
- if (matcher) {
241
- hookData.matcher = matcher;
242
- }
243
-
244
- // If editing, include original index for replacement
245
- if (editingHookData && editingHookData.index !== undefined) {
246
- hookData.replaceIndex = editingHookData.index;
247
- }
248
-
249
- // Submit to API
250
- await saveHook(scope, event, hookData);
251
- closeHookCreateModal();
252
- }
253
-
254
- // ========== Helpers ==========
255
- function getHookEventDescription(event) {
256
- const descriptions = {
257
- 'PreToolUse': 'Runs before a tool is executed',
258
- 'PostToolUse': 'Runs after a tool completes',
259
- 'Notification': 'Runs when a notification is triggered',
260
- 'Stop': 'Runs when the agent stops'
261
- };
262
- return descriptions[event] || event;
263
- }
264
-
265
- function getHookEventIcon(event) {
266
- const icons = {
267
- 'PreToolUse': '⏳',
268
- 'PostToolUse': '✅',
269
- 'Notification': '🔔',
270
- 'Stop': '🛑'
271
- };
272
- return icons[event] || '🪝';
273
- }
274
-
275
- function getHookEventIconLucide(event) {
276
- const icons = {
277
- 'PreToolUse': '<i data-lucide="clock" class="w-5 h-5"></i>',
278
- 'PostToolUse': '<i data-lucide="check-circle" class="w-5 h-5"></i>',
279
- 'Notification': '<i data-lucide="bell" class="w-5 h-5"></i>',
280
- 'Stop': '<i data-lucide="octagon-x" class="w-5 h-5"></i>'
281
- };
282
- return icons[event] || '<i data-lucide="webhook" class="w-5 h-5"></i>';
1
+ // Hook Manager Component
2
+ // Manages Claude Code hooks configuration from settings.json
3
+
4
+ // ========== Hook State ==========
5
+ let hookConfig = {
6
+ global: { hooks: {} },
7
+ project: { hooks: {} }
8
+ };
9
+
10
+ // ========== Hook Templates ==========
11
+ const HOOK_TEMPLATES = {
12
+ 'ccw-notify': {
13
+ event: 'PostToolUse',
14
+ matcher: 'Write',
15
+ command: 'bash',
16
+ args: ['-c', 'INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty"); [ -n "$FILE_PATH" ] && curl -s -X POST -H "Content-Type: application/json" -d "{\\"type\\":\\"file_written\\",\\"filePath\\":\\"$FILE_PATH\\"}" http://localhost:3456/api/hook || true'],
17
+ description: 'Notify CCW dashboard when files are written',
18
+ category: 'notification'
19
+ },
20
+ 'log-tool': {
21
+ event: 'PostToolUse',
22
+ matcher: '',
23
+ command: 'bash',
24
+ args: ['-c', 'INPUT=$(cat); TOOL=$(echo "$INPUT" | jq -r ".tool_name // empty"); FILE=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty"); echo "[$(date)] Tool: $TOOL, File: $FILE" >> ~/.claude/tool-usage.log'],
25
+ description: 'Log all tool executions to a file',
26
+ category: 'logging'
27
+ },
28
+ 'lint-check': {
29
+ event: 'PostToolUse',
30
+ matcher: 'Write',
31
+ command: 'bash',
32
+ args: ['-c', 'INPUT=$(cat); FILE=$(echo "$INPUT" | jq -r ".tool_input.file_path // empty"); if [[ "$FILE" =~ \\.(js|ts|jsx|tsx)$ ]]; then npx eslint "$FILE" --fix 2>/dev/null || true; fi'],
33
+ description: 'Run ESLint on JavaScript/TypeScript files after write',
34
+ category: 'quality'
35
+ },
36
+ 'git-add': {
37
+ event: 'PostToolUse',
38
+ matcher: 'Write',
39
+ command: 'bash',
40
+ args: ['-c', 'INPUT=$(cat); FILE=$(echo "$INPUT" | jq -r ".tool_input.file_path // empty"); [ -n "$FILE" ] && git add "$FILE" 2>/dev/null || true'],
41
+ description: 'Automatically stage written files to git',
42
+ category: 'git'
43
+ },
44
+ 'codexlens-update': {
45
+ event: 'PostToolUse',
46
+ matcher: 'Write|Edit',
47
+ command: 'bash',
48
+ args: ['-c', 'INPUT=$(cat); FILE=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty"); [ -d ".codexlens" ] && [ -n "$FILE" ] && (python -m codexlens update "$FILE" --json 2>/dev/null || ~/.codexlens/venv/bin/python -m codexlens update "$FILE" --json 2>/dev/null || true)'],
49
+ description: 'Auto-update code index when files are written or edited',
50
+ category: 'indexing'
51
+ },
52
+ 'memory-update-related': {
53
+ event: 'Stop',
54
+ matcher: '',
55
+ command: 'bash',
56
+ args: ['-c', 'ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\''],
57
+ description: 'Update CLAUDE.md for changed modules when session ends',
58
+ category: 'memory',
59
+ configurable: true,
60
+ config: {
61
+ tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
62
+ strategy: { type: 'select', options: ['related', 'single-layer'], default: 'related', label: 'Strategy' }
63
+ }
64
+ },
65
+ 'memory-update-periodic': {
66
+ event: 'PostToolUse',
67
+ matcher: 'Write|Edit',
68
+ command: 'bash',
69
+ args: ['-c', 'INTERVAL=300; LAST_FILE=~/.claude/.last_memory_update; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE"); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
70
+ description: 'Periodically update CLAUDE.md (default: 5 min interval)',
71
+ category: 'memory',
72
+ configurable: true,
73
+ config: {
74
+ tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
75
+ interval: { type: 'number', default: 300, min: 60, max: 3600, label: 'Interval (seconds)', step: 60 }
76
+ }
77
+ },
78
+ 'memory-update-count-based': {
79
+ event: 'PostToolUse',
80
+ matcher: 'Write|Edit',
81
+ command: 'bash',
82
+ args: ['-c', 'THRESHOLD=10; COUNT_FILE=~/.claude/.memory_update_count; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty"); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude \'{"strategy":"related","tool":"gemini"}\' & fi'],
83
+ description: 'Update CLAUDE.md when file changes reach threshold (default: 10 files)',
84
+ category: 'memory',
85
+ configurable: true,
86
+ config: {
87
+ tool: { type: 'select', options: ['gemini', 'qwen', 'codex'], default: 'gemini', label: 'CLI Tool' },
88
+ threshold: { type: 'number', default: 10, min: 3, max: 50, label: 'File count threshold', step: 1 }
89
+ }
90
+ },
91
+ // SKILL Context Loader templates
92
+ 'skill-context-keyword': {
93
+ event: 'UserPromptSubmit',
94
+ matcher: '',
95
+ command: 'bash',
96
+ args: ['-c', 'ccw tool exec skill_context_loader --stdin'],
97
+ description: 'Load SKILL context based on keyword matching in user prompt',
98
+ category: 'skill',
99
+ configurable: true,
100
+ config: {
101
+ keywords: { type: 'text', default: '', label: 'Keywords (comma-separated)', placeholder: 'react,workflow,api' },
102
+ skills: { type: 'text', default: '', label: 'SKILL Names (comma-separated)', placeholder: 'prompt-enhancer,command-guide' }
103
+ }
104
+ },
105
+ 'skill-context-auto': {
106
+ event: 'UserPromptSubmit',
107
+ matcher: '',
108
+ command: 'bash',
109
+ args: ['-c', 'ccw tool exec skill_context_loader --stdin --mode auto'],
110
+ description: 'Auto-detect and load SKILL based on skill name in prompt',
111
+ category: 'skill',
112
+ configurable: false
113
+ },
114
+ 'memory-file-read': {
115
+ event: 'PostToolUse',
116
+ matcher: 'Read|mcp__ccw-tools__read_file',
117
+ command: 'ccw',
118
+ args: ['memory', 'track', '--type', 'file', '--action', 'read', '--stdin'],
119
+ description: 'Track file reads to build context heatmap',
120
+ category: 'memory',
121
+ timeout: 5000
122
+ },
123
+ 'memory-file-write': {
124
+ event: 'PostToolUse',
125
+ matcher: 'Write|Edit|mcp__ccw-tools__write_file|mcp__ccw-tools__edit_file',
126
+ command: 'ccw',
127
+ args: ['memory', 'track', '--type', 'file', '--action', 'write', '--stdin'],
128
+ description: 'Track file modifications to identify core modules',
129
+ category: 'memory',
130
+ timeout: 5000
131
+ },
132
+ 'memory-prompt-track': {
133
+ event: 'UserPromptSubmit',
134
+ matcher: '',
135
+ command: 'ccw',
136
+ args: ['memory', 'track', '--type', 'topic', '--action', 'mention', '--stdin'],
137
+ description: 'Record user prompts for pattern analysis',
138
+ category: 'memory',
139
+ timeout: 5000
140
+ },
141
+ // Session Context - Progressive disclosure based on session state
142
+ // First prompt: returns cluster overview, subsequent: intent-matched sessions
143
+ 'session-context': {
144
+ event: 'UserPromptSubmit',
145
+ matcher: '',
146
+ command: 'ccw',
147
+ args: ['hook', 'session-context', '--stdin'],
148
+ description: 'Progressive session context (cluster overview → intent matching)',
149
+ category: 'context',
150
+ timeout: 5000
151
+ }
152
+ };
153
+
154
+ // ========== Wizard Templates (Special Category) ==========
155
+ const WIZARD_TEMPLATES = {
156
+ 'memory-update': {
157
+ name: 'Memory Update Hook',
158
+ description: 'Automatically update CLAUDE.md documentation based on code changes',
159
+ icon: 'brain',
160
+ options: [
161
+ {
162
+ id: 'on-stop',
163
+ name: 'On Session End',
164
+ description: 'Update documentation when Claude session ends',
165
+ templateId: 'memory-update-related'
166
+ },
167
+ {
168
+ id: 'periodic',
169
+ name: 'Periodic Update',
170
+ description: 'Update documentation at regular intervals during session',
171
+ templateId: 'memory-update-periodic'
172
+ },
173
+ {
174
+ id: 'count-based',
175
+ name: 'Count-Based Update',
176
+ description: 'Update documentation when file changes reach threshold',
177
+ templateId: 'memory-update-count-based'
178
+ }
179
+ ],
180
+ configFields: [
181
+ { key: 'tool', type: 'select', label: 'CLI Tool', options: ['gemini', 'qwen', 'codex'], default: 'gemini', description: 'Tool for documentation generation' },
182
+ { key: 'interval', type: 'number', label: 'Interval (seconds)', default: 300, min: 60, max: 3600, step: 60, showFor: ['periodic'], description: 'Time between updates' },
183
+ { key: 'threshold', type: 'number', label: 'File Count Threshold', default: 10, min: 3, max: 50, step: 1, showFor: ['count-based'], description: 'Number of file changes to trigger update' },
184
+ { key: 'strategy', type: 'select', label: 'Update Strategy', options: ['related', 'single-layer'], default: 'related', description: 'Related: changed modules, Single-layer: current directory' }
185
+ ]
186
+ },
187
+ 'skill-context': {
188
+ name: 'SKILL Context Loader',
189
+ description: 'Automatically load SKILL packages based on keywords in user prompts',
190
+ icon: 'sparkles',
191
+ options: [
192
+ {
193
+ id: 'keyword',
194
+ name: 'Keyword Matching',
195
+ description: 'Load specific SKILLs when keywords are detected in prompt',
196
+ templateId: 'skill-context-keyword'
197
+ },
198
+ {
199
+ id: 'auto',
200
+ name: 'Auto Detection',
201
+ description: 'Automatically detect and load SKILLs by name in prompt',
202
+ templateId: 'skill-context-auto'
203
+ }
204
+ ],
205
+ configFields: [],
206
+ requiresSkillDiscovery: true,
207
+ customRenderer: 'renderSkillContextConfig'
208
+ },
209
+ 'memory-setup': {
210
+ name: 'Memory Module Setup',
211
+ description: 'Configure automatic context tracking',
212
+ icon: 'brain',
213
+ options: [
214
+ {
215
+ id: 'file-read',
216
+ name: 'File Read Tracker',
217
+ description: 'Track file reads to build context heatmap',
218
+ templateId: 'memory-file-read'
219
+ },
220
+ {
221
+ id: 'file-write',
222
+ name: 'File Write Tracker',
223
+ description: 'Track file modifications to identify core modules',
224
+ templateId: 'memory-file-write'
225
+ },
226
+ {
227
+ id: 'prompts',
228
+ name: 'Prompt Tracker',
229
+ description: 'Record user prompts for pattern analysis',
230
+ templateId: 'memory-prompt-track'
231
+ }
232
+ ],
233
+ configFields: [],
234
+ multiSelect: true
235
+ }
236
+ };
237
+
238
+ // ========== Initialization ==========
239
+ function initHookManager() {
240
+ // Initialize Hook navigation
241
+ document.querySelectorAll('.nav-item[data-view="hook-manager"]').forEach(item => {
242
+ item.addEventListener('click', () => {
243
+ setActiveNavItem(item);
244
+ currentView = 'hook-manager';
245
+ currentFilter = null;
246
+ currentLiteType = null;
247
+ currentSessionDetailKey = null;
248
+ updateContentTitle();
249
+ renderHookManager();
250
+ });
251
+ });
252
+ }
253
+
254
+ // ========== Data Loading ==========
255
+ async function loadHookConfig() {
256
+ try {
257
+ const response = await fetch(`/api/hooks?path=${encodeURIComponent(projectPath)}`);
258
+ if (!response.ok) throw new Error('Failed to load hook config');
259
+ const data = await response.json();
260
+ hookConfig = data;
261
+ updateHookBadge();
262
+ return data;
263
+ } catch (err) {
264
+ console.error('Failed to load hook config:', err);
265
+ return null;
266
+ }
267
+ }
268
+
269
+ async function loadAvailableSkills() {
270
+ try {
271
+ const response = await fetch('/api/skills?path=' + encodeURIComponent(projectPath));
272
+ if (!response.ok) throw new Error('Failed to load skills');
273
+ const data = await response.json();
274
+
275
+ // Combine project and user skills
276
+ const projectSkills = (data.projectSkills || []).map(s => ({
277
+ name: s.name,
278
+ path: s.path,
279
+ scope: 'project'
280
+ }));
281
+ const userSkills = (data.userSkills || []).map(s => ({
282
+ name: s.name,
283
+ path: s.path,
284
+ scope: 'user'
285
+ }));
286
+
287
+ // Store in window for access by wizard
288
+ window.availableSkills = [...projectSkills, ...userSkills];
289
+
290
+ return window.availableSkills;
291
+ } catch (err) {
292
+ console.error('Failed to load available skills:', err);
293
+ window.availableSkills = [];
294
+ return [];
295
+ }
296
+ }
297
+
298
+ /**
299
+ * Convert internal hook format to Claude Code format
300
+ * Internal: { command, args, matcher, timeout }
301
+ * Claude Code: { matcher, hooks: [{ type: "command", command: "...", timeout }] }
302
+ */
303
+ function convertToClaudeCodeFormat(hookData) {
304
+ // If already in correct format, return as-is
305
+ if (hookData.hooks && Array.isArray(hookData.hooks)) {
306
+ return hookData;
307
+ }
308
+
309
+ // Build command string from command + args
310
+ let commandStr = hookData.command || '';
311
+ if (hookData.args && Array.isArray(hookData.args)) {
312
+ // Join args, properly quoting if needed
313
+ const quotedArgs = hookData.args.map(arg => {
314
+ if (arg.includes(' ') && !arg.startsWith('"') && !arg.startsWith("'")) {
315
+ return `"${arg.replace(/"/g, '\\"')}"`;
316
+ }
317
+ return arg;
318
+ });
319
+ commandStr = `${commandStr} ${quotedArgs.join(' ')}`.trim();
320
+ }
321
+
322
+ const converted = {
323
+ hooks: [{
324
+ type: 'command',
325
+ command: commandStr
326
+ }]
327
+ };
328
+
329
+ // Add matcher if present (not needed for UserPromptSubmit, Stop, etc.)
330
+ if (hookData.matcher) {
331
+ converted.matcher = hookData.matcher;
332
+ }
333
+
334
+ // Add timeout if present (in seconds for Claude Code)
335
+ if (hookData.timeout) {
336
+ converted.hooks[0].timeout = Math.ceil(hookData.timeout / 1000);
337
+ }
338
+
339
+ // Preserve replaceIndex for updates
340
+ if (hookData.replaceIndex !== undefined) {
341
+ converted.replaceIndex = hookData.replaceIndex;
342
+ }
343
+
344
+ return converted;
345
+ }
346
+
347
+ async function saveHook(scope, event, hookData) {
348
+ try {
349
+ // Convert to Claude Code format before saving
350
+ const convertedHookData = convertToClaudeCodeFormat(hookData);
351
+
352
+ const response = await fetch('/api/hooks', {
353
+ method: 'POST',
354
+ headers: { 'Content-Type': 'application/json' },
355
+ body: JSON.stringify({
356
+ projectPath: projectPath,
357
+ scope: scope,
358
+ event: event,
359
+ hookData: convertedHookData
360
+ })
361
+ });
362
+
363
+ if (!response.ok) throw new Error('Failed to save hook');
364
+
365
+ const result = await response.json();
366
+ if (result.success) {
367
+ await loadHookConfig();
368
+ renderHookManager();
369
+ showRefreshToast(`Hook saved successfully`, 'success');
370
+ }
371
+ return result;
372
+ } catch (err) {
373
+ console.error('Failed to save hook:', err);
374
+ showRefreshToast(`Failed to save hook: ${err.message}`, 'error');
375
+ return null;
376
+ }
377
+ }
378
+
379
+ async function removeHook(scope, event, hookIndex) {
380
+ try {
381
+ const response = await fetch('/api/hooks', {
382
+ method: 'DELETE',
383
+ headers: { 'Content-Type': 'application/json' },
384
+ body: JSON.stringify({
385
+ projectPath: projectPath,
386
+ scope: scope,
387
+ event: event,
388
+ hookIndex: hookIndex
389
+ })
390
+ });
391
+
392
+ if (!response.ok) throw new Error('Failed to remove hook');
393
+
394
+ const result = await response.json();
395
+ if (result.success) {
396
+ await loadHookConfig();
397
+ renderHookManager();
398
+ showRefreshToast(`Hook removed successfully`, 'success');
399
+ }
400
+ return result;
401
+ } catch (err) {
402
+ console.error('Failed to remove hook:', err);
403
+ showRefreshToast(`Failed to remove hook: ${err.message}`, 'error');
404
+ return null;
405
+ }
406
+ }
407
+
408
+ // ========== Badge Update ==========
409
+ function updateHookBadge() {
410
+ const badge = document.getElementById('badgeHooks');
411
+ if (badge) {
412
+ let totalHooks = 0;
413
+
414
+ // Count global hooks
415
+ if (hookConfig.global?.hooks) {
416
+ for (const event of Object.keys(hookConfig.global.hooks)) {
417
+ const hooks = hookConfig.global.hooks[event];
418
+ totalHooks += Array.isArray(hooks) ? hooks.length : 1;
419
+ }
420
+ }
421
+
422
+ // Count project hooks
423
+ if (hookConfig.project?.hooks) {
424
+ for (const event of Object.keys(hookConfig.project.hooks)) {
425
+ const hooks = hookConfig.project.hooks[event];
426
+ totalHooks += Array.isArray(hooks) ? hooks.length : 1;
427
+ }
428
+ }
429
+
430
+ badge.textContent = totalHooks;
431
+ }
432
+ }
433
+
434
+ // ========== Hook Modal Functions ==========
435
+ let editingHookData = null;
436
+
437
+ function openHookCreateModal(editData = null) {
438
+ const modal = document.getElementById('hookCreateModal');
439
+ const title = document.getElementById('hookModalTitle');
440
+
441
+ if (modal) {
442
+ modal.classList.remove('hidden');
443
+ editingHookData = editData;
444
+
445
+ // Set title based on mode
446
+ title.textContent = editData ? 'Edit Hook' : 'Create Hook';
447
+
448
+ // Clear or populate form
449
+ if (editData) {
450
+ document.getElementById('hookEvent').value = editData.event || '';
451
+ document.getElementById('hookMatcher').value = editData.matcher || '';
452
+ document.getElementById('hookCommand').value = editData.command || '';
453
+ document.getElementById('hookArgs').value = (editData.args || []).join('\n');
454
+
455
+ // Set scope radio
456
+ const scopeRadio = document.querySelector(`input[name="hookScope"][value="${editData.scope || 'project'}"]`);
457
+ if (scopeRadio) scopeRadio.checked = true;
458
+ } else {
459
+ document.getElementById('hookEvent').value = '';
460
+ document.getElementById('hookMatcher').value = '';
461
+ document.getElementById('hookCommand').value = '';
462
+ document.getElementById('hookArgs').value = '';
463
+ document.querySelector('input[name="hookScope"][value="project"]').checked = true;
464
+ }
465
+
466
+ // Focus on event select
467
+ document.getElementById('hookEvent').focus();
468
+ }
469
+ }
470
+
471
+ function closeHookCreateModal() {
472
+ const modal = document.getElementById('hookCreateModal');
473
+ if (modal) {
474
+ modal.classList.add('hidden');
475
+ editingHookData = null;
476
+ }
477
+ }
478
+
479
+ function applyHookTemplate(templateName) {
480
+ const template = HOOK_TEMPLATES[templateName];
481
+ if (!template) return;
482
+
483
+ document.getElementById('hookEvent').value = template.event;
484
+ document.getElementById('hookMatcher').value = template.matcher;
485
+ document.getElementById('hookCommand').value = template.command;
486
+ document.getElementById('hookArgs').value = template.args.join('\n');
487
+ }
488
+
489
+ async function submitHookCreate() {
490
+ const event = document.getElementById('hookEvent').value;
491
+ const matcher = document.getElementById('hookMatcher').value.trim();
492
+ const command = document.getElementById('hookCommand').value.trim();
493
+ const argsText = document.getElementById('hookArgs').value.trim();
494
+ const scope = document.querySelector('input[name="hookScope"]:checked').value;
495
+
496
+ // Validate required fields
497
+ if (!event) {
498
+ showRefreshToast('Hook event is required', 'error');
499
+ document.getElementById('hookEvent').focus();
500
+ return;
501
+ }
502
+
503
+ if (!command) {
504
+ showRefreshToast('Command is required', 'error');
505
+ document.getElementById('hookCommand').focus();
506
+ return;
507
+ }
508
+
509
+ // Parse args (one per line)
510
+ const args = argsText ? argsText.split('\n').map(a => a.trim()).filter(a => a) : [];
511
+
512
+ // Build hook data
513
+ const hookData = {
514
+ command: command
515
+ };
516
+
517
+ if (args.length > 0) {
518
+ hookData.args = args;
519
+ }
520
+
521
+ if (matcher) {
522
+ hookData.matcher = matcher;
523
+ }
524
+
525
+ // If editing, include original index for replacement
526
+ if (editingHookData && editingHookData.index !== undefined) {
527
+ hookData.replaceIndex = editingHookData.index;
528
+ }
529
+
530
+ // Submit to API
531
+ await saveHook(scope, event, hookData);
532
+ closeHookCreateModal();
533
+ }
534
+
535
+ // ========== Helpers ==========
536
+ function getHookEventDescription(event) {
537
+ const descriptions = {
538
+ 'PreToolUse': 'Runs before a tool is executed',
539
+ 'PostToolUse': 'Runs after a tool completes',
540
+ 'Notification': 'Runs when a notification is triggered',
541
+ 'Stop': 'Runs when the agent stops',
542
+ 'UserPromptSubmit': 'Runs when user submits a prompt'
543
+ };
544
+ return descriptions[event] || event;
545
+ }
546
+
547
+ function getHookEventIcon(event) {
548
+ const icons = {
549
+ 'PreToolUse': '⏳',
550
+ 'PostToolUse': '✅',
551
+ 'Notification': '🔔',
552
+ 'Stop': '🛑',
553
+ 'UserPromptSubmit': '💬'
554
+ };
555
+ return icons[event] || '🪝';
556
+ }
557
+
558
+ function getHookEventIconLucide(event) {
559
+ const icons = {
560
+ 'PreToolUse': '<i data-lucide="clock" class="w-5 h-5"></i>',
561
+ 'PostToolUse': '<i data-lucide="check-circle" class="w-5 h-5"></i>',
562
+ 'Notification': '<i data-lucide="bell" class="w-5 h-5"></i>',
563
+ 'Stop': '<i data-lucide="octagon-x" class="w-5 h-5"></i>',
564
+ 'UserPromptSubmit': '<i data-lucide="message-square" class="w-5 h-5"></i>'
565
+ };
566
+ return icons[event] || '<i data-lucide="webhook" class="w-5 h-5"></i>';
567
+ }
568
+
569
+ // ========== Wizard Modal Functions ==========
570
+ let currentWizardTemplate = null;
571
+ let wizardConfig = {};
572
+
573
+ async function openHookWizardModal(wizardId) {
574
+ const wizard = WIZARD_TEMPLATES[wizardId];
575
+ if (!wizard) {
576
+ showRefreshToast('Wizard template not found', 'error');
577
+ return;
578
+ }
579
+
580
+ currentWizardTemplate = { id: wizardId, ...wizard };
581
+ wizardConfig = {};
582
+
583
+ // Set defaults
584
+ wizard.configFields.forEach(field => {
585
+ wizardConfig[field.key] = field.default;
586
+ });
587
+
588
+ // Initialize selectedOptions for multi-select wizards
589
+ if (wizard.multiSelect) {
590
+ wizardConfig.selectedOptions = [];
591
+ }
592
+
593
+ // Always refresh available skills when opening SKILL context wizard
594
+ if (wizardId === 'skill-context') {
595
+ await loadAvailableSkills();
596
+ }
597
+
598
+ const modal = document.getElementById('hookWizardModal');
599
+ if (modal) {
600
+ renderWizardModalContent();
601
+ modal.classList.remove('hidden');
602
+ }
603
+ }
604
+
605
+ function closeHookWizardModal() {
606
+ const modal = document.getElementById('hookWizardModal');
607
+ if (modal) {
608
+ modal.classList.add('hidden');
609
+ currentWizardTemplate = null;
610
+ wizardConfig = {};
611
+ }
612
+ }
613
+
614
+ function renderWizardModalContent() {
615
+ const container = document.getElementById('wizardModalContent');
616
+ if (!container || !currentWizardTemplate) return;
617
+
618
+ const wizard = currentWizardTemplate;
619
+ const wizardId = wizard.id;
620
+ const selectedOption = wizardConfig.triggerType || wizard.options[0].id;
621
+
622
+ // Get translated wizard name and description
623
+ const wizardName = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdate') :
624
+ wizardId === 'memory-setup' ? t('hook.wizard.memorySetup') :
625
+ wizardId === 'skill-context' ? t('hook.wizard.skillContext') : wizard.name;
626
+ const wizardDesc = wizardId === 'memory-update' ? t('hook.wizard.memoryUpdateDesc') :
627
+ wizardId === 'memory-setup' ? t('hook.wizard.memorySetupDesc') :
628
+ wizardId === 'skill-context' ? t('hook.wizard.skillContextDesc') : wizard.description;
629
+
630
+ // Helper to get translated option names
631
+ const getOptionName = (optId) => {
632
+ if (wizardId === 'memory-update') {
633
+ if (optId === 'on-stop') return t('hook.wizard.onSessionEnd');
634
+ if (optId === 'periodic') return t('hook.wizard.periodicUpdate');
635
+ if (optId === 'count-based') return t('hook.wizard.countBasedUpdate');
636
+ }
637
+ if (wizardId === 'memory-setup') {
638
+ if (optId === 'file-read') return t('hook.wizard.fileReadTracker');
639
+ if (optId === 'file-write') return t('hook.wizard.fileWriteTracker');
640
+ if (optId === 'prompts') return t('hook.wizard.promptTracker');
641
+ }
642
+ if (wizardId === 'skill-context') {
643
+ if (optId === 'keyword') return t('hook.wizard.keywordMatching');
644
+ if (optId === 'auto') return t('hook.wizard.autoDetection');
645
+ }
646
+ return wizard.options.find(o => o.id === optId)?.name || '';
647
+ };
648
+
649
+ const getOptionDesc = (optId) => {
650
+ if (wizardId === 'memory-update') {
651
+ if (optId === 'on-stop') return t('hook.wizard.onSessionEndDesc');
652
+ if (optId === 'periodic') return t('hook.wizard.periodicUpdateDesc');
653
+ if (optId === 'count-based') return t('hook.wizard.countBasedUpdateDesc');
654
+ }
655
+ if (wizardId === 'memory-setup') {
656
+ if (optId === 'file-read') return t('hook.wizard.fileReadTrackerDesc');
657
+ if (optId === 'file-write') return t('hook.wizard.fileWriteTrackerDesc');
658
+ if (optId === 'prompts') return t('hook.wizard.promptTrackerDesc');
659
+ }
660
+ if (wizardId === 'skill-context') {
661
+ if (optId === 'keyword') return t('hook.wizard.keywordMatchingDesc');
662
+ if (optId === 'auto') return t('hook.wizard.autoDetectionDesc');
663
+ }
664
+ return wizard.options.find(o => o.id === optId)?.description || '';
665
+ };
666
+
667
+ // Helper to get translated field labels
668
+ const getFieldLabel = (fieldKey) => {
669
+ const labels = {
670
+ 'tool': t('hook.wizard.cliTool'),
671
+ 'interval': t('hook.wizard.intervalSeconds'),
672
+ 'threshold': t('hook.wizard.fileCountThreshold'),
673
+ 'strategy': t('hook.wizard.updateStrategy')
674
+ };
675
+ return labels[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.label || fieldKey;
676
+ };
677
+
678
+ const getFieldDesc = (fieldKey) => {
679
+ const descs = {
680
+ 'tool': t('hook.wizard.toolForDocGen'),
681
+ 'interval': t('hook.wizard.timeBetweenUpdates'),
682
+ 'threshold': t('hook.wizard.fileCountThresholdDesc'),
683
+ 'strategy': t('hook.wizard.relatedStrategy')
684
+ };
685
+ return descs[fieldKey] || wizard.configFields.find(f => f.key === fieldKey)?.description || '';
686
+ };
687
+
688
+ container.innerHTML = `
689
+ <div class="space-y-6">
690
+ <!-- Wizard Header -->
691
+ <div class="flex items-center gap-3 pb-4 border-b border-border">
692
+ <div class="p-2 bg-primary/10 rounded-lg">
693
+ <i data-lucide="${wizard.icon}" class="w-6 h-6 text-primary"></i>
694
+ </div>
695
+ <div>
696
+ <h3 class="text-lg font-semibold text-foreground">${escapeHtml(wizardName)}</h3>
697
+ <p class="text-sm text-muted-foreground">${escapeHtml(wizardDesc)}</p>
698
+ </div>
699
+ </div>
700
+
701
+ <!-- Trigger Type Selection -->
702
+ <div class="space-y-3">
703
+ <label class="block text-sm font-medium text-foreground">${wizard.multiSelect ? t('hook.wizard.selectTrackers') : t('hook.wizard.whenToTrigger')}</label>
704
+ <div class="grid grid-cols-1 gap-3">
705
+ ${wizard.multiSelect ? wizard.options.map(opt => {
706
+ const isSelected = wizardConfig.selectedOptions?.includes(opt.id) || false;
707
+ return `
708
+ <label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-all ${isSelected ? 'border-primary bg-primary/5' : 'border-border hover:border-muted-foreground'}">
709
+ <input type="checkbox" name="wizardTrigger" value="${opt.id}"
710
+ ${isSelected ? 'checked' : ''}
711
+ onchange="toggleWizardOption('${opt.id}')"
712
+ class="mt-1">
713
+ <div class="flex-1">
714
+ <span class="font-medium text-foreground">${escapeHtml(getOptionName(opt.id))}</span>
715
+ <p class="text-sm text-muted-foreground">${escapeHtml(getOptionDesc(opt.id))}</p>
716
+ </div>
717
+ </label>
718
+ `;
719
+ }).join('') : wizard.options.map(opt => `
720
+ <label class="flex items-start gap-3 p-3 border rounded-lg cursor-pointer transition-all ${selectedOption === opt.id ? 'border-primary bg-primary/5' : 'border-border hover:border-muted-foreground'}">
721
+ <input type="radio" name="wizardTrigger" value="${opt.id}"
722
+ ${selectedOption === opt.id ? 'checked' : ''}
723
+ onchange="updateWizardTrigger('${opt.id}')"
724
+ class="mt-1">
725
+ <div class="flex-1">
726
+ <span class="font-medium text-foreground">${escapeHtml(getOptionName(opt.id))}</span>
727
+ <p class="text-sm text-muted-foreground">${escapeHtml(getOptionDesc(opt.id))}</p>
728
+ </div>
729
+ </label>
730
+ `).join('')}
731
+ </div>
732
+ </div>
733
+
734
+ <!-- Configuration Fields -->
735
+ <div class="space-y-4">
736
+ <label class="block text-sm font-medium text-foreground">${t('hook.wizard.configuration')}</label>
737
+ ${wizard.customRenderer ? window[wizard.customRenderer]() : wizard.configFields.map(field => {
738
+ // Check if field should be shown for current trigger type
739
+ const shouldShow = !field.showFor || field.showFor.includes(selectedOption);
740
+ if (!shouldShow) return '';
741
+
742
+ const value = wizardConfig[field.key] ?? field.default;
743
+ const fieldLabel = getFieldLabel(field.key);
744
+ const fieldDesc = getFieldDesc(field.key);
745
+
746
+ if (field.type === 'select') {
747
+ return `
748
+ <div class="space-y-1">
749
+ <label class="block text-sm text-muted-foreground">${escapeHtml(fieldLabel)}</label>
750
+ <select id="wizard_${field.key}"
751
+ onchange="updateWizardConfig('${field.key}', this.value)"
752
+ class="w-full px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary">
753
+ ${field.options.map(opt => `
754
+ <option value="${opt}" ${value === opt ? 'selected' : ''}>${opt}</option>
755
+ `).join('')}
756
+ </select>
757
+ ${fieldDesc ? `<p class="text-xs text-muted-foreground">${escapeHtml(fieldDesc)}</p>` : ''}
758
+ </div>
759
+ `;
760
+ } else if (field.type === 'number') {
761
+ return `
762
+ <div class="space-y-1">
763
+ <label class="block text-sm text-muted-foreground">${escapeHtml(fieldLabel)}</label>
764
+ <div class="flex items-center gap-2">
765
+ <input type="number" id="wizard_${field.key}"
766
+ value="${value}"
767
+ min="${field.min || 0}"
768
+ max="${field.max || 9999}"
769
+ step="${field.step || 1}"
770
+ onchange="updateWizardConfig('${field.key}', parseInt(this.value))"
771
+ class="flex-1 px-3 py-2 bg-background border border-border rounded-lg text-foreground focus:outline-none focus:ring-2 focus:ring-primary">
772
+ <span class="text-sm text-muted-foreground">${formatIntervalDisplay(value)}</span>
773
+ </div>
774
+ ${fieldDesc ? `<p class="text-xs text-muted-foreground">${escapeHtml(fieldDesc)}</p>` : ''}
775
+ </div>
776
+ `;
777
+ }
778
+ return '';
779
+ }).join('')}
780
+ </div>
781
+
782
+ <!-- Preview -->
783
+ <div class="space-y-2">
784
+ <label class="block text-sm font-medium text-foreground">${t('hook.wizard.commandPreview')}</label>
785
+ <div class="bg-muted/50 rounded-lg p-3 font-mono text-xs overflow-x-auto">
786
+ <pre id="wizardCommandPreview" class="whitespace-pre-wrap text-muted-foreground">${escapeHtml(generateWizardCommand())}</pre>
787
+ </div>
788
+ </div>
789
+
790
+ <!-- Scope Selection -->
791
+ <div class="space-y-3">
792
+ <label class="block text-sm font-medium text-foreground">${t('hook.wizard.installTo')}</label>
793
+ <div class="flex gap-4">
794
+ <label class="flex items-center gap-2 cursor-pointer">
795
+ <input type="radio" name="wizardScope" value="project" checked>
796
+ <span class="text-sm text-foreground">${t('hook.scopeProject').split('(')[0]}</span>
797
+ <span class="text-xs text-muted-foreground">(.claude/settings.json)</span>
798
+ </label>
799
+ <label class="flex items-center gap-2 cursor-pointer">
800
+ <input type="radio" name="wizardScope" value="global">
801
+ <span class="text-sm text-foreground">${t('hook.scopeGlobal').split('(')[0]}</span>
802
+ <span class="text-xs text-muted-foreground">(~/.claude/settings.json)</span>
803
+ </label>
804
+ </div>
805
+ </div>
806
+ </div>
807
+ `;
808
+
809
+ // Initialize Lucide icons
810
+ if (typeof lucide !== 'undefined') lucide.createIcons();
811
+ }
812
+
813
+ function updateWizardTrigger(triggerId) {
814
+ wizardConfig.triggerType = triggerId;
815
+ renderWizardModalContent();
816
+ }
817
+
818
+ function toggleWizardOption(optionId) {
819
+ if (!wizardConfig.selectedOptions) {
820
+ wizardConfig.selectedOptions = [];
821
+ }
822
+
823
+ const index = wizardConfig.selectedOptions.indexOf(optionId);
824
+ if (index === -1) {
825
+ wizardConfig.selectedOptions.push(optionId);
826
+ } else {
827
+ wizardConfig.selectedOptions.splice(index, 1);
828
+ }
829
+
830
+ renderWizardModalContent();
831
+ }
832
+
833
+ function updateWizardConfig(key, value) {
834
+ wizardConfig[key] = value;
835
+ // Update command preview
836
+ const preview = document.getElementById('wizardCommandPreview');
837
+ if (preview) {
838
+ preview.textContent = generateWizardCommand();
839
+ }
840
+ // Re-render if interval changed (to update display)
841
+ if (key === 'interval') {
842
+ const displaySpan = document.querySelector(`#wizard_${key}`)?.parentElement?.querySelector('.text-muted-foreground:last-child');
843
+ if (displaySpan) {
844
+ displaySpan.textContent = formatIntervalDisplay(value);
845
+ }
846
+ }
847
+ }
848
+
849
+ function formatIntervalDisplay(seconds) {
850
+ if (seconds < 60) return `${seconds}s`;
851
+ const mins = Math.floor(seconds / 60);
852
+ const secs = seconds % 60;
853
+ if (secs === 0) return `${mins}min`;
854
+ return `${mins}min ${secs}s`;
855
+ }
856
+
857
+ // ========== SKILL Context Wizard Custom Functions ==========
858
+ function renderSkillContextConfig() {
859
+ const selectedOption = wizardConfig.triggerType || 'keyword';
860
+ const skillConfigs = wizardConfig.skillConfigs || [];
861
+ const availableSkills = window.availableSkills || [];
862
+
863
+ if (selectedOption === 'auto') {
864
+ let skillBadges = '';
865
+ let isLoading = typeof window.availableSkills === 'undefined' || window.skillsLoading;
866
+ if (isLoading) {
867
+ // Still loading
868
+ skillBadges = '<span class="px-1.5 py-0.5 bg-muted text-muted-foreground rounded text-xs">' + t('common.loading') + '...</span>';
869
+ } else if (availableSkills.length === 0) {
870
+ // No skills found
871
+ skillBadges = '<span class="px-1.5 py-0.5 bg-warning/10 text-warning rounded text-xs">' + t('hook.wizard.noSkillsFound') + '</span>';
872
+ } else {
873
+ // Skills found
874
+ skillBadges = availableSkills.map(function(s) {
875
+ return '<span class="px-1.5 py-0.5 bg-emerald-500/10 text-emerald-500 rounded text-xs">' + escapeHtml(s.name) + '</span>';
876
+ }).join(' ');
877
+ }
878
+ return '<div class="bg-muted/30 rounded-lg p-4 text-sm text-muted-foreground">' +
879
+ '<div class="flex items-center justify-between mb-2">' +
880
+ '<div class="flex items-center gap-2">' +
881
+ '<i data-lucide="info" class="w-4 h-4"></i>' +
882
+ '<span class="font-medium">' + t('hook.wizard.autoDetectionMode') + '</span>' +
883
+ '</div>' +
884
+ '<button type="button" onclick="refreshAvailableSkills()" ' +
885
+ 'class="p-1.5 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded transition-colors" ' +
886
+ 'title="' + t('common.refresh') + '">' +
887
+ '<i data-lucide="refresh-cw" class="w-4 h-4' + (isLoading ? ' animate-spin' : '') + '"></i>' +
888
+ '</button>' +
889
+ '</div>' +
890
+ '<p>' + t('hook.wizard.autoDetectionInfo') + '</p>' +
891
+ '<div class="mt-2 flex items-center gap-2 flex-wrap">' +
892
+ '<span>' + t('hook.wizard.availableSkills') + '</span>' +
893
+ skillBadges +
894
+ '</div>' +
895
+ '</div>';
896
+ }
897
+
898
+ var configListHtml = '';
899
+ if (skillConfigs.length === 0) {
900
+ configListHtml = '<div class="text-center py-6 text-muted-foreground text-sm border border-dashed border-border rounded-lg">' +
901
+ '<i data-lucide="package" class="w-8 h-8 mx-auto mb-2 opacity-50"></i>' +
902
+ '<p>' + t('hook.wizard.noSkillsConfigured') + '</p>' +
903
+ '<p class="text-xs mt-1">' + t('hook.wizard.clickAddSkill') + '</p>' +
904
+ '</div>';
905
+ } else {
906
+ configListHtml = skillConfigs.map(function(config, idx) {
907
+ var skillOptions = '';
908
+ if (availableSkills.length === 0) {
909
+ skillOptions = '<option value="" disabled>' + t('hook.wizard.noSkillsFound') + '</option>';
910
+ } else {
911
+ skillOptions = availableSkills.map(function(s) {
912
+ var selected = config.skill === s.name ? 'selected' : '';
913
+ return '<option value="' + escapeHtml(s.name) + '" ' + selected + '>' + escapeHtml(s.name) + '</option>';
914
+ }).join('');
915
+ }
916
+ return '<div class="border border-border rounded-lg p-3 bg-card">' +
917
+ '<div class="flex items-center justify-between mb-2">' +
918
+ '<select onchange="updateSkillConfig(' + idx + ', \'skill\', this.value)" ' +
919
+ 'class="px-2 py-1 text-sm bg-background border border-border rounded text-foreground">' +
920
+ '<option value="">' + t('hook.wizard.selectSkill') + '</option>' +
921
+ skillOptions +
922
+ '</select>' +
923
+ '<button onclick="removeSkillConfig(' + idx + ')" ' +
924
+ 'class="p-1 text-muted-foreground hover:text-destructive rounded">' +
925
+ '<i data-lucide="trash-2" class="w-4 h-4"></i>' +
926
+ '</button>' +
927
+ '</div>' +
928
+ '<div class="space-y-1">' +
929
+ '<label class="text-xs text-muted-foreground">' + t('hook.wizard.triggerKeywords') + '</label>' +
930
+ '<input type="text" ' +
931
+ 'value="' + (config.keywords || '') + '" ' +
932
+ 'onchange="updateSkillConfig(' + idx + ', \'keywords\', this.value)" ' +
933
+ 'placeholder="e.g., react, hooks, component" ' +
934
+ 'class="w-full px-2 py-1.5 text-sm bg-background border border-border rounded text-foreground">' +
935
+ '</div>' +
936
+ '</div>';
937
+ }).join('');
938
+ }
939
+
940
+ var isLoading = typeof window.availableSkills === 'undefined' || window.skillsLoading;
941
+ var skillsStatusHtml = '';
942
+ if (isLoading) {
943
+ skillsStatusHtml = '<span class="text-xs text-muted-foreground flex items-center gap-1">' +
944
+ '<i data-lucide="loader-2" class="w-3 h-3 animate-spin"></i>' +
945
+ t('common.loading') +
946
+ '</span>';
947
+ } else if (availableSkills.length === 0) {
948
+ skillsStatusHtml = '<span class="text-xs text-amber-500 flex items-center gap-1">' +
949
+ '<i data-lucide="alert-triangle" class="w-3 h-3"></i>' +
950
+ t('hook.wizard.noSkillsFound') +
951
+ '</span>';
952
+ } else {
953
+ skillsStatusHtml = '<span class="text-xs text-muted-foreground">' +
954
+ availableSkills.length + ' ' + t('skills.skillsCount') +
955
+ '</span>';
956
+ }
957
+
958
+ return '<div class="space-y-4">' +
959
+ '<div class="flex items-center justify-between">' +
960
+ '<div class="flex items-center gap-2">' +
961
+ '<span class="text-sm font-medium text-foreground">' + t('hook.wizard.configureSkills') + '</span>' +
962
+ skillsStatusHtml +
963
+ '<button type="button" onclick="refreshAvailableSkills()" ' +
964
+ 'class="p-1 text-muted-foreground hover:text-primary hover:bg-primary/10 rounded transition-colors" ' +
965
+ 'title="' + t('common.refresh') + '">' +
966
+ '<i data-lucide="refresh-cw" class="w-3 h-3' + (isLoading ? ' animate-spin' : '') + '"></i>' +
967
+ '</button>' +
968
+ '</div>' +
969
+ '<button type="button" onclick="addSkillConfig()" ' +
970
+ 'class="px-3 py-1.5 text-xs bg-primary text-primary-foreground rounded-lg hover:opacity-90 flex items-center gap-1">' +
971
+ '<i data-lucide="plus" class="w-3 h-3"></i> ' + t('hook.wizard.addSkill') +
972
+ '</button>' +
973
+ '</div>' +
974
+ '<div id="skillConfigsList" class="space-y-3">' + configListHtml + '</div>' +
975
+ '</div>';
976
+ }
977
+
978
+ async function refreshAvailableSkills() {
979
+ // Set loading state
980
+ window.skillsLoading = true;
981
+ renderWizardModalContent();
982
+
983
+ try {
984
+ await loadAvailableSkills();
985
+ } finally {
986
+ window.skillsLoading = false;
987
+ renderWizardModalContent();
988
+ // Refresh Lucide icons
989
+ if (typeof lucide !== 'undefined') lucide.createIcons();
990
+ }
991
+ }
992
+
993
+ function addSkillConfig() {
994
+ if (!wizardConfig.skillConfigs) {
995
+ wizardConfig.skillConfigs = [];
996
+ }
997
+ wizardConfig.skillConfigs.push({ skill: '', keywords: '' });
998
+ renderWizardModalContent();
999
+ }
1000
+
1001
+ function removeSkillConfig(index) {
1002
+ if (wizardConfig.skillConfigs) {
1003
+ wizardConfig.skillConfigs.splice(index, 1);
1004
+ renderWizardModalContent();
1005
+ }
1006
+ }
1007
+
1008
+ function updateSkillConfig(index, key, value) {
1009
+ if (wizardConfig.skillConfigs && wizardConfig.skillConfigs[index]) {
1010
+ wizardConfig.skillConfigs[index][key] = value;
1011
+ const preview = document.getElementById('wizardCommandPreview');
1012
+ if (preview) {
1013
+ preview.textContent = generateWizardCommand();
1014
+ }
1015
+ }
1016
+ }
1017
+
1018
+
1019
+
1020
+ function generateWizardCommand() {
1021
+ if (!currentWizardTemplate) return '';
1022
+
1023
+ const wizard = currentWizardTemplate;
1024
+ const wizardId = wizard.id;
1025
+ const triggerType = wizardConfig.triggerType || wizard.options[0].id;
1026
+ const selectedOption = wizard.options.find(o => o.id === triggerType);
1027
+ if (!selectedOption) return '';
1028
+
1029
+ const baseTemplate = HOOK_TEMPLATES[selectedOption.templateId];
1030
+ if (!baseTemplate) return '';
1031
+
1032
+ // Handle skill-context wizard
1033
+ if (wizardId === 'skill-context') {
1034
+ if (triggerType === 'keyword') {
1035
+ const skillConfigs = wizardConfig.skillConfigs || [];
1036
+ const validConfigs = skillConfigs.filter(c => c.skill && c.keywords);
1037
+
1038
+ if (validConfigs.length === 0) {
1039
+ return '# No SKILL configurations yet';
1040
+ }
1041
+
1042
+ const configJson = validConfigs.map(c => ({
1043
+ skill: c.skill,
1044
+ keywords: c.keywords.split(',').map(k => k.trim()).filter(k => k)
1045
+ }));
1046
+
1047
+ const params = JSON.stringify({ configs: configJson, prompt: '$CLAUDE_PROMPT' });
1048
+ return `ccw tool exec skill_context_loader '${params}'`;
1049
+ } else {
1050
+ // auto mode
1051
+ const params = JSON.stringify({ mode: 'auto', prompt: '$CLAUDE_PROMPT' });
1052
+ return `ccw tool exec skill_context_loader '${params}'`;
1053
+ }
1054
+ }
1055
+
1056
+ // Handle memory-update wizard (default)
1057
+ const tool = wizardConfig.tool || 'gemini';
1058
+ const strategy = wizardConfig.strategy || 'related';
1059
+ const interval = wizardConfig.interval || 300;
1060
+ const threshold = wizardConfig.threshold || 10;
1061
+
1062
+ // Build the ccw tool command based on configuration
1063
+ const params = JSON.stringify({ strategy, tool });
1064
+
1065
+ if (triggerType === 'periodic') {
1066
+ return `INTERVAL=${interval}; LAST_FILE=~/.claude/.last_memory_update; NOW=$(date +%s); LAST=0; [ -f "$LAST_FILE" ] && LAST=$(cat "$LAST_FILE"); if [ $((NOW - LAST)) -ge $INTERVAL ]; then echo $NOW > "$LAST_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1067
+ } else if (triggerType === 'count-based') {
1068
+ return `THRESHOLD=${threshold}; COUNT_FILE=~/.claude/.memory_update_count; INPUT=$(cat); FILE_PATH=$(echo "$INPUT" | jq -r ".tool_input.file_path // .tool_input.path // empty"); [ -z "$FILE_PATH" ] && exit 0; COUNT=0; [ -f "$COUNT_FILE" ] && COUNT=$(cat "$COUNT_FILE" 2>/dev/null || echo 0); COUNT=$((COUNT + 1)); echo $COUNT > "$COUNT_FILE"; if [ $COUNT -ge $THRESHOLD ]; then echo 0 > "$COUNT_FILE"; ccw tool exec update_module_claude '${params}' & fi`;
1069
+ } else {
1070
+ return `ccw tool exec update_module_claude '${params}'`;
1071
+ }
1072
+ }
1073
+
1074
+ async function submitHookWizard() {
1075
+ if (!currentWizardTemplate) return;
1076
+
1077
+ const wizard = currentWizardTemplate;
1078
+ const scope = document.querySelector('input[name="wizardScope"]:checked')?.value || 'project';
1079
+
1080
+ // Handle multi-select wizards
1081
+ if (wizard.multiSelect) {
1082
+ const selectedOptions = wizardConfig.selectedOptions || [];
1083
+ if (selectedOptions.length === 0) {
1084
+ showRefreshToast('Please select at least one option', 'error');
1085
+ return;
1086
+ }
1087
+
1088
+ // Install each selected hook (skip if already exists)
1089
+ let installedCount = 0;
1090
+ let skippedCount = 0;
1091
+
1092
+ for (const optionId of selectedOptions) {
1093
+ const selectedOption = wizard.options.find(o => o.id === optionId);
1094
+ if (!selectedOption) continue;
1095
+
1096
+ const baseTemplate = HOOK_TEMPLATES[selectedOption.templateId];
1097
+ if (!baseTemplate) continue;
1098
+
1099
+ // Check if hook already exists
1100
+ const existingHooks = scope === 'global'
1101
+ ? hookConfig.global?.hooks?.[baseTemplate.event] || []
1102
+ : hookConfig.project?.hooks?.[baseTemplate.event] || [];
1103
+
1104
+ const hookList = Array.isArray(existingHooks) ? existingHooks : [existingHooks];
1105
+ const alreadyExists = hookList.some(h => {
1106
+ // Check by matcher and command
1107
+ const existingMatcher = h.matcher || '';
1108
+ const templateMatcher = baseTemplate.matcher || '';
1109
+ const existingCmd = h.hooks?.[0]?.command || h.command || '';
1110
+ const templateCmd = baseTemplate.command + ' ' + (baseTemplate.args || []).join(' ');
1111
+ return existingMatcher === templateMatcher && existingCmd.includes(baseTemplate.command);
1112
+ });
1113
+
1114
+ if (alreadyExists) {
1115
+ skippedCount++;
1116
+ continue;
1117
+ }
1118
+
1119
+ const hookData = {
1120
+ command: baseTemplate.command,
1121
+ args: baseTemplate.args
1122
+ };
1123
+
1124
+ if (baseTemplate.matcher) {
1125
+ hookData.matcher = baseTemplate.matcher;
1126
+ }
1127
+
1128
+ if (baseTemplate.timeout) {
1129
+ hookData.timeout = baseTemplate.timeout;
1130
+ }
1131
+
1132
+ await saveHook(scope, baseTemplate.event, hookData);
1133
+ installedCount++;
1134
+ }
1135
+
1136
+ closeHookWizardModal();
1137
+
1138
+ if (skippedCount > 0 && installedCount === 0) {
1139
+ showRefreshToast(`All ${skippedCount} hook(s) already installed`, 'info');
1140
+ } else if (skippedCount > 0) {
1141
+ showRefreshToast(`Installed ${installedCount}, skipped ${skippedCount} (already exists)`, 'success');
1142
+ }
1143
+ return;
1144
+ }
1145
+
1146
+ // Handle single-select wizards
1147
+ const triggerType = wizardConfig.triggerType || wizard.options[0].id;
1148
+ const selectedOption = wizard.options.find(o => o.id === triggerType);
1149
+ if (!selectedOption) return;
1150
+
1151
+ const baseTemplate = HOOK_TEMPLATES[selectedOption.templateId];
1152
+ if (!baseTemplate) return;
1153
+
1154
+ const command = generateWizardCommand();
1155
+
1156
+ const hookData = {
1157
+ command: 'bash',
1158
+ args: ['-c', command]
1159
+ };
1160
+
1161
+ if (baseTemplate.matcher) {
1162
+ hookData.matcher = baseTemplate.matcher;
1163
+ }
1164
+
1165
+ await saveHook(scope, baseTemplate.event, hookData);
1166
+ closeHookWizardModal();
1167
+ }
1168
+
1169
+ // ========== Template View/Copy Functions ==========
1170
+ function viewTemplateDetails(templateId) {
1171
+ const template = HOOK_TEMPLATES[templateId];
1172
+ if (!template) return;
1173
+
1174
+ const modal = document.getElementById('templateViewModal');
1175
+ const content = document.getElementById('templateViewContent');
1176
+
1177
+ if (modal && content) {
1178
+ const args = template.args || [];
1179
+ content.innerHTML = `
1180
+ <div class="space-y-4">
1181
+ <div class="flex items-center gap-3 pb-3 border-b border-border">
1182
+ <i data-lucide="webhook" class="w-5 h-5 text-primary"></i>
1183
+ <div>
1184
+ <h4 class="font-semibold text-foreground">${escapeHtml(templateId)}</h4>
1185
+ <p class="text-sm text-muted-foreground">${escapeHtml(template.description || 'No description')}</p>
1186
+ </div>
1187
+ </div>
1188
+
1189
+ <div class="space-y-3 text-sm">
1190
+ <div class="flex items-start gap-2">
1191
+ <span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0 w-16">Event</span>
1192
+ <span class="font-medium text-foreground">${escapeHtml(template.event)}</span>
1193
+ </div>
1194
+ <div class="flex items-start gap-2">
1195
+ <span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0 w-16">Matcher</span>
1196
+ <span class="text-muted-foreground">${escapeHtml(template.matcher || 'All tools')}</span>
1197
+ </div>
1198
+ <div class="flex items-start gap-2">
1199
+ <span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0 w-16">Command</span>
1200
+ <code class="font-mono text-xs text-foreground">${escapeHtml(template.command)}</code>
1201
+ </div>
1202
+ ${args.length > 0 ? `
1203
+ <div class="flex items-start gap-2">
1204
+ <span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0 w-16">Args</span>
1205
+ <div class="flex-1">
1206
+ <pre class="font-mono text-xs text-muted-foreground bg-muted/50 rounded p-2 overflow-x-auto whitespace-pre-wrap">${escapeHtml(args.join('\n'))}</pre>
1207
+ </div>
1208
+ </div>
1209
+ ` : ''}
1210
+ ${template.category ? `
1211
+ <div class="flex items-start gap-2">
1212
+ <span class="font-mono text-xs bg-muted px-1.5 py-0.5 rounded shrink-0 w-16">Category</span>
1213
+ <span class="px-2 py-0.5 text-xs rounded-full bg-primary/10 text-primary">${escapeHtml(template.category)}</span>
1214
+ </div>
1215
+ ` : ''}
1216
+ </div>
1217
+
1218
+ <div class="flex gap-2 pt-3 border-t border-border">
1219
+ <button class="flex-1 px-3 py-2 text-sm bg-primary text-primary-foreground rounded-lg hover:opacity-90 transition-opacity"
1220
+ onclick="copyTemplateToClipboard('${templateId}')">
1221
+ <i data-lucide="copy" class="w-4 h-4 inline mr-1"></i> Copy JSON
1222
+ </button>
1223
+ <button class="flex-1 px-3 py-2 text-sm bg-muted text-foreground rounded-lg hover:bg-hover transition-colors"
1224
+ onclick="editTemplateAsNew('${templateId}')">
1225
+ <i data-lucide="pencil" class="w-4 h-4 inline mr-1"></i> Edit as New
1226
+ </button>
1227
+ </div>
1228
+ </div>
1229
+ `;
1230
+
1231
+ modal.classList.remove('hidden');
1232
+ if (typeof lucide !== 'undefined') lucide.createIcons();
1233
+ }
1234
+ }
1235
+
1236
+ function closeTemplateViewModal() {
1237
+ const modal = document.getElementById('templateViewModal');
1238
+ if (modal) {
1239
+ modal.classList.add('hidden');
1240
+ }
1241
+ }
1242
+
1243
+ function copyTemplateToClipboard(templateId) {
1244
+ const template = HOOK_TEMPLATES[templateId];
1245
+ if (!template) return;
1246
+
1247
+ const hookJson = {
1248
+ matcher: template.matcher || undefined,
1249
+ command: template.command,
1250
+ args: template.args
1251
+ };
1252
+
1253
+ // Clean up undefined values
1254
+ Object.keys(hookJson).forEach(key => {
1255
+ if (hookJson[key] === undefined || hookJson[key] === '') {
1256
+ delete hookJson[key];
1257
+ }
1258
+ });
1259
+
1260
+ navigator.clipboard.writeText(JSON.stringify(hookJson, null, 2))
1261
+ .then(() => showRefreshToast('Template copied to clipboard', 'success'))
1262
+ .catch(() => showRefreshToast('Failed to copy', 'error'));
1263
+ }
1264
+
1265
+ function editTemplateAsNew(templateId) {
1266
+ const template = HOOK_TEMPLATES[templateId];
1267
+ if (!template) return;
1268
+
1269
+ closeTemplateViewModal();
1270
+
1271
+ // Open create modal with template data
1272
+ openHookCreateModal({
1273
+ event: template.event,
1274
+ matcher: template.matcher || '',
1275
+ command: template.command,
1276
+ args: template.args || []
1277
+ });
283
1278
  }