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,852 +1,888 @@
1
- // ============================================
2
- // EXPLORER VIEW
3
- // ============================================
4
- // File tree browser with .gitignore filtering and CLAUDE.md update support
5
- // Split-panel layout: file tree (left) + preview (right)
6
-
7
- // Explorer state
8
- let explorerCurrentPath = null;
9
- let explorerSelectedFile = null;
10
- let explorerExpandedDirs = new Set();
11
-
12
- // Task queue for CLAUDE.md updates
13
- let updateTaskQueue = [];
14
- let isTaskQueueVisible = false;
15
- let isTaskRunning = false;
16
- let defaultCliTool = 'gemini'; // Default CLI tool for updates
17
-
18
-
19
- /**
20
- * Render the Explorer view
21
- */
22
- async function renderExplorer() {
23
- const container = document.getElementById('mainContent');
24
- if (!container) return;
25
-
26
- // Hide stats grid and search
27
- const statsGrid = document.getElementById('statsGrid');
28
- const searchInput = document.getElementById('searchInput');
29
- if (statsGrid) statsGrid.style.display = 'none';
30
- if (searchInput) searchInput.parentElement.style.display = 'none';
31
-
32
- // Initialize explorer path to project path
33
- explorerCurrentPath = projectPath;
34
-
35
- container.innerHTML = `
36
- <div class="explorer-container">
37
- <!-- Left Panel: File Tree -->
38
- <div class="explorer-tree-panel">
39
- <div class="explorer-tree-header">
40
- <div class="explorer-tree-title">
41
- <i data-lucide="folder-tree" class="explorer-icon"></i>
42
- <span class="explorer-title-text">Explorer</span>
43
- </div>
44
- <button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="Refresh">
45
- <i data-lucide="refresh-cw" class="w-4 h-4"></i>
46
- </button>
47
- </div>
48
- <div class="explorer-tree-content" id="explorerTreeContent">
49
- <div class="explorer-loading">Loading file tree...</div>
50
- </div>
51
- </div>
52
-
53
- <!-- Right Panel: Preview -->
54
- <div class="explorer-preview-panel">
55
- <div class="explorer-preview-header" id="explorerPreviewHeader">
56
- <span class="preview-filename">Select a file to preview</span>
57
- </div>
58
- <div class="explorer-preview-content" id="explorerPreviewContent">
59
- <div class="explorer-preview-empty">
60
- <div class="preview-empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
61
- <div class="preview-empty-text">Select a file from the tree to preview its contents</div>
62
- </div>
63
- </div>
64
- </div>
65
- </div>
66
-
67
- <!-- Floating Action Button -->
68
- <div class="explorer-fab" onclick="toggleTaskQueue()" title="Task Queue">
69
- <span class="fab-icon"><i data-lucide="list-todo" class="w-5 h-5"></i></span>
70
- <span class="fab-badge" id="fabBadge">0</span>
71
- </div>
72
-
73
- <!-- Task Queue Panel -->
74
- <div class="task-queue-panel" id="taskQueuePanel">
75
- <div class="task-queue-header">
76
- <span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> Update Tasks</span>
77
- <button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
78
- </div>
79
- <div class="task-queue-toolbar">
80
- <div class="queue-cli-selector">
81
- <label>CLI:</label>
82
- <select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
83
- <option value="gemini">Gemini</option>
84
- <option value="qwen">Qwen</option>
85
- <option value="codex">Codex</option>
86
- </select>
87
- </div>
88
- <div class="task-queue-actions">
89
- <button class="queue-action-btn" onclick="openAddTaskModal()" title="Add update task">
90
- <i data-lucide="plus" class="w-4 h-4"></i>
91
- </button>
92
- <button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="Start all tasks">
93
- <i data-lucide="play" class="w-4 h-4"></i>
94
- </button>
95
- <button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="Clear completed">
96
- <i data-lucide="trash-2" class="w-4 h-4"></i>
97
- </button>
98
- </div>
99
- </div>
100
- <div class="task-queue-list" id="taskQueueList">
101
- <div class="task-queue-empty">
102
- <span>No tasks in queue</span>
103
- <p>Hover folder and click <i data-lucide="file" class="w-3 h-3 inline"></i> or <i data-lucide="folder" class="w-3 h-3 inline"></i> to add tasks</p>
104
- </div>
105
- </div>
106
- </div>
107
- `;
108
-
109
- // Load initial file tree
110
- await loadExplorerTree(explorerCurrentPath);
111
-
112
- // Initialize Lucide icons for dynamically rendered content
113
- if (typeof lucide !== 'undefined') lucide.createIcons();
114
- }
115
-
116
- /**
117
- * Load and render file tree for a directory
118
- */
119
- async function loadExplorerTree(dirPath) {
120
- const treeContent = document.getElementById('explorerTreeContent');
121
- if (!treeContent) return;
122
-
123
- try {
124
- const response = await fetch(`/api/files?path=${encodeURIComponent(dirPath)}`);
125
- const data = await response.json();
126
-
127
- if (data.error) {
128
- treeContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
129
- return;
130
- }
131
-
132
- // Render root level
133
- treeContent.innerHTML = renderTreeLevel(data.files, dirPath, 0);
134
- attachTreeEventListeners();
135
-
136
- // Initialize Lucide icons for tree items
137
- if (typeof lucide !== 'undefined') lucide.createIcons();
138
-
139
- } catch (error) {
140
- treeContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
141
- }
142
- }
143
-
144
- /**
145
- * Render a level of the file tree
146
- */
147
- function renderTreeLevel(files, parentPath, depth) {
148
- if (!files || files.length === 0) {
149
- return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">Empty directory</div>`;
150
- }
151
-
152
- return files.map(file => {
153
- const isExpanded = explorerExpandedDirs.has(file.path);
154
- const isSelected = explorerSelectedFile === file.path;
155
-
156
- if (file.type === 'directory') {
157
- const folderIcon = getFolderIcon(file.name, isExpanded, file.hasClaudeMd);
158
- const chevronIcon = isExpanded ? '<i data-lucide="chevron-down" class="w-3 h-3"></i>' : '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
159
- return `
160
- <div class="tree-item tree-folder ${isExpanded ? 'expanded' : ''} ${file.hasClaudeMd ? 'has-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="directory">
161
- <div class="tree-item-row ${isSelected ? 'selected' : ''}" style="padding-left: ${depth * 16}px">
162
- <span class="tree-chevron">${chevronIcon}</span>
163
- <span class="tree-icon">${folderIcon}</span>
164
- <span class="tree-name">${escapeHtml(file.name)}</span>
165
- ${file.hasClaudeMd ? `
166
- <span class="claude-md-badge" title="Contains CLAUDE.md documentation">
167
- <span class="badge-icon"><i data-lucide="file-check" class="w-3 h-3"></i></span>
168
- <span class="badge-text">DOC</span>
169
- </span>
170
- ` : ''}
171
- <div class="tree-folder-actions">
172
- <button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="Update CLAUDE.md (current folder only)">
173
- <span class="update-icon"><i data-lucide="file" class="w-3.5 h-3.5"></i></span>
174
- </button>
175
- <button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="Update CLAUDE.md (with subdirectories)">
176
- <span class="update-icon"><i data-lucide="folder-tree" class="w-3.5 h-3.5"></i></span>
177
- </button>
178
- </div>
179
- </div>
180
- <div class="tree-children ${isExpanded ? 'show' : ''}" id="children-${btoa(file.path).replace(/[^a-zA-Z0-9]/g, '')}">
181
- ${isExpanded ? '' : ''}
182
- </div>
183
- </div>
184
- `;
185
- } else {
186
- const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
187
- const fileIcon = getFileIcon(ext);
188
- // Special highlight for CLAUDE.md files
189
- const isClaudeMd = file.name === 'CLAUDE.md';
190
- return `
191
- <div class="tree-item tree-file ${isSelected ? 'selected' : ''} ${isClaudeMd ? 'is-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="file">
192
- <div class="tree-item-row" style="padding-left: ${depth * 16}px">
193
- <span class="tree-chevron-spacer"></span>
194
- <span class="tree-icon">${isClaudeMd ? '<span class="file-icon file-icon-claude"><i data-lucide="bot" class="w-3 h-3"></i></span>' : fileIcon}</span>
195
- <span class="tree-name">${escapeHtml(file.name)}</span>
196
- </div>
197
- </div>
198
- `;
199
- }
200
- }).join('');
201
- }
202
-
203
- /**
204
- * Get file icon based on extension - using colored SVG icons for better distinction
205
- */
206
- function getFileIcon(ext) {
207
- const iconMap = {
208
- // JavaScript/TypeScript - distinct colors
209
- 'js': '<span class="file-icon file-icon-js">JS</span>',
210
- 'mjs': '<span class="file-icon file-icon-js">JS</span>',
211
- 'cjs': '<span class="file-icon file-icon-js">JS</span>',
212
- 'jsx': '<span class="file-icon file-icon-jsx">JSX</span>',
213
- 'ts': '<span class="file-icon file-icon-ts">TS</span>',
214
- 'tsx': '<span class="file-icon file-icon-tsx">TSX</span>',
215
-
216
- // Python
217
- 'py': '<span class="file-icon file-icon-py">PY</span>',
218
- 'pyw': '<span class="file-icon file-icon-py">PY</span>',
219
-
220
- // Other languages
221
- 'go': '<span class="file-icon file-icon-go">GO</span>',
222
- 'rs': '<span class="file-icon file-icon-rs">RS</span>',
223
- 'java': '<span class="file-icon file-icon-java">JV</span>',
224
- 'rb': '<span class="file-icon file-icon-rb">RB</span>',
225
- 'php': '<span class="file-icon file-icon-php">PHP</span>',
226
- 'c': '<span class="file-icon file-icon-c">C</span>',
227
- 'cpp': '<span class="file-icon file-icon-cpp">C++</span>',
228
- 'h': '<span class="file-icon file-icon-h">H</span>',
229
- 'cs': '<span class="file-icon file-icon-cs">C#</span>',
230
- 'swift': '<span class="file-icon file-icon-swift">SW</span>',
231
- 'kt': '<span class="file-icon file-icon-kt">KT</span>',
232
-
233
- // Web
234
- 'html': '<span class="file-icon file-icon-html">HTML</span>',
235
- 'htm': '<span class="file-icon file-icon-html">HTML</span>',
236
- 'css': '<span class="file-icon file-icon-css">CSS</span>',
237
- 'scss': '<span class="file-icon file-icon-scss">SCSS</span>',
238
- 'sass': '<span class="file-icon file-icon-scss">SASS</span>',
239
- 'less': '<span class="file-icon file-icon-less">LESS</span>',
240
- 'vue': '<span class="file-icon file-icon-vue">VUE</span>',
241
- 'svelte': '<span class="file-icon file-icon-svelte">SV</span>',
242
-
243
- // Config/Data
244
- 'json': '<span class="file-icon file-icon-json">{}</span>',
245
- 'yaml': '<span class="file-icon file-icon-yaml">YML</span>',
246
- 'yml': '<span class="file-icon file-icon-yaml">YML</span>',
247
- 'xml': '<span class="file-icon file-icon-xml">XML</span>',
248
- 'toml': '<span class="file-icon file-icon-toml">TML</span>',
249
- 'ini': '<span class="file-icon file-icon-ini">INI</span>',
250
- 'env': '<span class="file-icon file-icon-env">ENV</span>',
251
-
252
- // Documentation
253
- 'md': '<span class="file-icon file-icon-md">MD</span>',
254
- 'markdown': '<span class="file-icon file-icon-md">MD</span>',
255
- 'txt': '<span class="file-icon file-icon-txt">TXT</span>',
256
- 'log': '<span class="file-icon file-icon-log">LOG</span>',
257
-
258
- // Shell/Scripts
259
- 'sh': '<span class="file-icon file-icon-sh">SH</span>',
260
- 'bash': '<span class="file-icon file-icon-sh">SH</span>',
261
- 'zsh': '<span class="file-icon file-icon-sh">ZSH</span>',
262
- 'ps1': '<span class="file-icon file-icon-ps1">PS1</span>',
263
- 'bat': '<span class="file-icon file-icon-bat">BAT</span>',
264
- 'cmd': '<span class="file-icon file-icon-bat">CMD</span>',
265
-
266
- // Database
267
- 'sql': '<span class="file-icon file-icon-sql">SQL</span>',
268
- 'db': '<span class="file-icon file-icon-db">DB</span>',
269
-
270
- // Docker/Container
271
- 'dockerfile': '<span class="file-icon file-icon-docker"><i data-lucide="container" class="w-3 h-3"></i></span>',
272
-
273
- // Images
274
- 'png': '<span class="file-icon file-icon-img">IMG</span>',
275
- 'jpg': '<span class="file-icon file-icon-img">IMG</span>',
276
- 'jpeg': '<span class="file-icon file-icon-img">IMG</span>',
277
- 'gif': '<span class="file-icon file-icon-img">GIF</span>',
278
- 'svg': '<span class="file-icon file-icon-svg">SVG</span>',
279
- 'ico': '<span class="file-icon file-icon-img">ICO</span>',
280
-
281
- // Package
282
- 'lock': '<span class="file-icon file-icon-lock"><i data-lucide="lock" class="w-3 h-3"></i></span>'
283
- };
284
-
285
- return iconMap[ext] || '<span class="file-icon file-icon-default"><i data-lucide="file" class="w-3 h-3"></i></span>';
286
- }
287
-
288
- /**
289
- * Get folder icon based on folder name and state
290
- */
291
- function getFolderIcon(name, isExpanded, hasClaudeMd) {
292
- // Only special icon for .workflow folder
293
- if (name === '.workflow') {
294
- return '<i data-lucide="zap" class="w-4 h-4 text-warning"></i>';
295
- }
296
- return isExpanded
297
- ? '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>'
298
- : '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
299
- }
300
-
301
- /**
302
- * Attach event listeners to tree items
303
- */
304
- function attachTreeEventListeners() {
305
- // Folder click - toggle expand
306
- document.querySelectorAll('.tree-folder > .tree-item-row').forEach(row => {
307
- row.addEventListener('click', async (e) => {
308
- const folder = row.closest('.tree-folder');
309
- const path = folder.dataset.path;
310
- await toggleFolderExpand(path, folder);
311
- });
312
- });
313
-
314
- // File click - preview
315
- document.querySelectorAll('.tree-file').forEach(item => {
316
- item.addEventListener('click', async () => {
317
- const path = item.dataset.path;
318
- await previewFile(path);
319
-
320
- // Update selection
321
- document.querySelectorAll('.tree-item-row.selected, .tree-file.selected').forEach(el => {
322
- el.classList.remove('selected');
323
- });
324
- item.classList.add('selected');
325
- explorerSelectedFile = path;
326
- });
327
- });
328
- }
329
-
330
- /**
331
- * Toggle folder expand/collapse
332
- */
333
- async function toggleFolderExpand(path, folderElement) {
334
- const isExpanded = explorerExpandedDirs.has(path);
335
- const childrenContainer = folderElement.querySelector('.tree-children');
336
- const chevron = folderElement.querySelector('.tree-chevron');
337
- const folderIcon = folderElement.querySelector('.tree-icon');
338
-
339
- if (isExpanded) {
340
- // Collapse
341
- explorerExpandedDirs.delete(path);
342
- folderElement.classList.remove('expanded');
343
- childrenContainer.classList.remove('show');
344
- // Update chevron and folder icon
345
- if (chevron) chevron.innerHTML = '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
346
- if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
347
- folderIcon.innerHTML = '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
348
- }
349
- } else {
350
- // Expand - load children if not loaded
351
- explorerExpandedDirs.add(path);
352
- folderElement.classList.add('expanded');
353
- childrenContainer.classList.add('show');
354
- // Update chevron and folder icon
355
- if (chevron) chevron.innerHTML = '<i data-lucide="chevron-down" class="w-3 h-3"></i>';
356
- if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
357
- folderIcon.innerHTML = '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>';
358
- }
359
-
360
- if (!childrenContainer.innerHTML.trim()) {
361
- childrenContainer.innerHTML = '<div class="tree-loading">Loading...</div>';
362
-
363
- try {
364
- const response = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
365
- const data = await response.json();
366
-
367
- const depth = (path.match(/\//g) || []).length - (explorerCurrentPath.match(/\//g) || []).length + 1;
368
- childrenContainer.innerHTML = renderTreeLevel(data.files, path, depth);
369
- attachTreeEventListeners();
370
- } catch (error) {
371
- childrenContainer.innerHTML = `<div class="tree-error">Failed to load</div>`;
372
- }
373
- }
374
- }
375
-
376
- // Reinitialize Lucide icons after DOM changes
377
- if (typeof lucide !== 'undefined') lucide.createIcons();
378
- }
379
-
380
- /**
381
- * Preview a file in the right panel
382
- */
383
- async function previewFile(filePath) {
384
- const previewHeader = document.getElementById('explorerPreviewHeader');
385
- const previewContent = document.getElementById('explorerPreviewContent');
386
-
387
- const fileName = filePath.split('/').pop();
388
- const ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
389
- const isMarkdown = ext === 'md' || ext === 'markdown';
390
-
391
- // Build header with tabs for markdown files
392
- previewHeader.innerHTML = `
393
- <div class="preview-header-left">
394
- <span class="preview-filename">${escapeHtml(fileName)}</span>
395
- <span class="preview-path" title="${escapeHtml(filePath)}">${escapeHtml(filePath)}</span>
396
- </div>
397
- ${isMarkdown ? `
398
- <div class="preview-header-tabs" id="previewHeaderTabs">
399
- <button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">Preview</button>
400
- <button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">Source</button>
401
- </div>
402
- ` : ''}
403
- `;
404
-
405
- previewContent.innerHTML = '<div class="explorer-loading">Loading file...</div>';
406
-
407
- try {
408
- const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`);
409
- const data = await response.json();
410
-
411
- if (data.error) {
412
- previewContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
413
- return;
414
- }
415
-
416
- if (data.isMarkdown) {
417
- // Render markdown with tabs content (tabs are in header)
418
- const rendered = marked.parse(data.content);
419
- previewContent.innerHTML = `
420
- <div class="preview-tab-content rendered show" data-tab="rendered">
421
- <div class="markdown-preview prose">${rendered}</div>
422
- </div>
423
- <div class="preview-tab-content source" data-tab="source">
424
- <pre><code class="language-markdown">${escapeHtml(data.content)}</code></pre>
425
- </div>
426
- `;
427
- } else {
428
- // Render code with syntax highlighting
429
- previewContent.innerHTML = `
430
- <div class="preview-info">
431
- <span class="preview-lang">${data.language}</span>
432
- <span class="preview-lines">${data.lines} lines</span>
433
- <span class="preview-size">${formatFileSize(data.size)}</span>
434
- </div>
435
- <pre class="preview-code"><code class="language-${data.language}">${escapeHtml(data.content)}</code></pre>
436
- `;
437
- }
438
-
439
- // Apply syntax highlighting if hljs is available
440
- if (typeof hljs !== 'undefined') {
441
- previewContent.querySelectorAll('pre code').forEach(block => {
442
- hljs.highlightElement(block);
443
- });
444
- }
445
-
446
- } catch (error) {
447
- previewContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
448
- }
449
- }
450
-
451
- /**
452
- * Switch preview tab (for markdown files)
453
- */
454
- function switchPreviewTab(btn, tabName) {
455
- const previewPanel = btn.closest('.explorer-preview-panel');
456
- const contentArea = previewPanel.querySelector('.explorer-preview-content');
457
-
458
- // Update tab buttons in header
459
- previewPanel.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
460
- btn.classList.add('active');
461
-
462
- // Update tab content
463
- contentArea.querySelectorAll('.preview-tab-content').forEach(c => c.classList.remove('show'));
464
- contentArea.querySelector(`[data-tab="${tabName}"]`).classList.add('show');
465
- }
466
-
467
- /**
468
- * Format file size
469
- */
470
- function formatFileSize(bytes) {
471
- if (bytes < 1024) return bytes + ' B';
472
- if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
473
- return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
474
- }
475
-
476
- /**
477
- * Refresh the explorer tree
478
- */
479
- async function refreshExplorerTree() {
480
- const btn = document.querySelector('.explorer-refresh-btn');
481
- if (btn) {
482
- btn.classList.add('refreshing');
483
- }
484
-
485
- explorerExpandedDirs.clear();
486
- await loadExplorerTree(explorerCurrentPath);
487
-
488
- if (btn) {
489
- btn.classList.remove('refreshing');
490
- }
491
- }
492
-
493
- /**
494
- * Open Update CLAUDE.md modal
495
- */
496
- function openUpdateClaudeMdModal(folderPath) {
497
- const modal = document.getElementById('updateClaudeMdModal');
498
- if (!modal) return;
499
-
500
- // Set folder path
501
- document.getElementById('claudeMdTargetPath').textContent = folderPath;
502
- document.getElementById('claudeMdTargetPath').dataset.path = folderPath;
503
-
504
- // Reset form
505
- document.getElementById('claudeMdTool').value = 'gemini';
506
- document.getElementById('claudeMdStrategy').value = 'single-layer';
507
-
508
- // Show modal
509
- modal.classList.remove('hidden');
510
- }
511
-
512
- /**
513
- * Close Update CLAUDE.md modal
514
- */
515
- function closeUpdateClaudeMdModal() {
516
- const modal = document.getElementById('updateClaudeMdModal');
517
- if (modal) {
518
- modal.classList.add('hidden');
519
- }
520
- }
521
-
522
- /**
523
- * Execute Update CLAUDE.md
524
- */
525
- async function executeUpdateClaudeMd() {
526
- const pathEl = document.getElementById('claudeMdTargetPath');
527
- const toolSelect = document.getElementById('claudeMdTool');
528
- const strategySelect = document.getElementById('claudeMdStrategy');
529
- const executeBtn = document.getElementById('claudeMdExecuteBtn');
530
- const statusEl = document.getElementById('claudeMdStatus');
531
-
532
- const path = pathEl.dataset.path;
533
- const tool = toolSelect.value;
534
- const strategy = strategySelect.value;
535
-
536
- // Update UI
537
- executeBtn.disabled = true;
538
- executeBtn.textContent = 'Updating...';
539
- statusEl.innerHTML = '<div class="status-running">⏳ Running update...</div>';
540
-
541
- try {
542
- const response = await fetch('/api/update-claude-md', {
543
- method: 'POST',
544
- headers: { 'Content-Type': 'application/json' },
545
- body: JSON.stringify({ path, tool, strategy })
546
- });
547
-
548
- const result = await response.json();
549
-
550
- if (result.success) {
551
- statusEl.innerHTML = `<div class="status-success"><i data-lucide="check-circle" class="w-4 h-4 inline text-success"></i> ${escapeHtml(result.message)}</div>`;
552
- // Refresh tree to update CLAUDE.md indicators
553
- await refreshExplorerTree();
554
- if (typeof lucide !== 'undefined') lucide.createIcons();
555
- } else {
556
- statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(result.error || 'Update failed')}</div>`;
557
- if (typeof lucide !== 'undefined') lucide.createIcons();
558
- }
559
-
560
- } catch (error) {
561
- statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(error.message)}</div>`;
562
- if (typeof lucide !== 'undefined') lucide.createIcons();
563
- } finally {
564
- executeBtn.disabled = false;
565
- executeBtn.textContent = 'Execute';
566
- }
567
- }
568
-
569
- // ============================================
570
- // TASK QUEUE FUNCTIONS
571
- // ============================================
572
-
573
- /**
574
- * Toggle task queue panel visibility
575
- */
576
- function toggleTaskQueue() {
577
- isTaskQueueVisible = !isTaskQueueVisible;
578
- const panel = document.getElementById('taskQueuePanel');
579
- const fab = document.querySelector('.explorer-fab');
580
-
581
- if (isTaskQueueVisible) {
582
- panel.classList.add('show');
583
- fab.classList.add('active');
584
- } else {
585
- panel.classList.remove('show');
586
- fab.classList.remove('active');
587
- }
588
- }
589
-
590
- /**
591
- * Update default CLI tool
592
- */
593
- function updateDefaultCliTool(tool) {
594
- defaultCliTool = tool;
595
- }
596
-
597
- /**
598
- * Update the FAB badge count
599
- */
600
- function updateFabBadge() {
601
- const badge = document.getElementById('fabBadge');
602
- if (badge) {
603
- const pendingCount = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running').length;
604
- badge.textContent = pendingCount || '';
605
- badge.style.display = pendingCount > 0 ? 'flex' : 'none';
606
- }
607
- }
608
-
609
- /**
610
- * Open add task modal
611
- */
612
- function openAddTaskModal() {
613
- const modal = document.getElementById('updateClaudeMdModal');
614
- if (!modal) return;
615
-
616
- // Set default path to current project
617
- document.getElementById('claudeMdTargetPath').textContent = explorerCurrentPath;
618
- document.getElementById('claudeMdTargetPath').dataset.path = explorerCurrentPath;
619
-
620
- // Reset form
621
- document.getElementById('claudeMdTool').value = 'gemini';
622
- document.getElementById('claudeMdStrategy').value = 'single-layer';
623
- document.getElementById('claudeMdStatus').innerHTML = '';
624
-
625
- // Change button to "Add to Queue"
626
- const executeBtn = document.getElementById('claudeMdExecuteBtn');
627
- executeBtn.textContent = 'Add to Queue';
628
- executeBtn.onclick = addTaskToQueue;
629
-
630
- modal.classList.remove('hidden');
631
- }
632
-
633
- /**
634
- * Add task to queue from modal
635
- */
636
- function addTaskToQueue() {
637
- const pathEl = document.getElementById('claudeMdTargetPath');
638
- const toolSelect = document.getElementById('claudeMdTool');
639
- const strategySelect = document.getElementById('claudeMdStrategy');
640
-
641
- const path = pathEl.dataset.path;
642
- const tool = toolSelect.value;
643
- const strategy = strategySelect.value;
644
-
645
- addUpdateTask(path, tool, strategy);
646
- closeUpdateClaudeMdModal();
647
-
648
- // Show task queue
649
- if (!isTaskQueueVisible) {
650
- toggleTaskQueue();
651
- }
652
- }
653
-
654
- /**
655
- * Add a task to the update queue
656
- */
657
- function addUpdateTask(path, tool = 'gemini', strategy = 'single-layer') {
658
- const task = {
659
- id: Date.now(),
660
- path,
661
- tool,
662
- strategy,
663
- status: 'pending', // pending, running, completed, failed
664
- message: '',
665
- addedAt: new Date().toISOString()
666
- };
667
-
668
- updateTaskQueue.push(task);
669
- renderTaskQueue();
670
- updateFabBadge();
671
-
672
- // Enable start button
673
- document.getElementById('startQueueBtn').disabled = false;
674
- }
675
-
676
- /**
677
- * Add task from folder context (right-click or button)
678
- */
679
- function addFolderToQueue(folderPath, strategy = 'single-layer') {
680
- // Use the selected CLI tool from the queue panel
681
- addUpdateTask(folderPath, defaultCliTool, strategy);
682
-
683
- // Show task queue if not visible
684
- if (!isTaskQueueVisible) {
685
- toggleTaskQueue();
686
- }
687
- }
688
-
689
- /**
690
- * Render the task queue list
691
- */
692
- function renderTaskQueue() {
693
- const listEl = document.getElementById('taskQueueList');
694
-
695
- if (updateTaskQueue.length === 0) {
696
- listEl.innerHTML = `
697
- <div class="task-queue-empty">
698
- <span>No tasks in queue</span>
699
- <p>Right-click a folder or click "Add Task" to queue CLAUDE.md updates</p>
700
- </div>
701
- `;
702
- return;
703
- }
704
-
705
- listEl.innerHTML = updateTaskQueue.map(task => {
706
- const folderName = task.path.split('/').pop() || task.path;
707
- const strategyLabel = task.strategy === 'multi-layer'
708
- ? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> With subdirs'
709
- : '<i data-lucide="file" class="w-3 h-3 inline"></i> Current only';
710
- const statusIcon = {
711
- 'pending': '<i data-lucide="clock" class="w-4 h-4"></i>',
712
- 'running': '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>',
713
- 'completed': '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>',
714
- 'failed': '<i data-lucide="x-circle" class="w-4 h-4 text-destructive"></i>'
715
- }[task.status];
716
-
717
- return `
718
- <div class="task-queue-item status-${task.status}" data-task-id="${task.id}">
719
- <div class="task-item-header">
720
- <span class="task-status-icon">${statusIcon}</span>
721
- <span class="task-folder-name" title="${escapeHtml(task.path)}">${escapeHtml(folderName)}</span>
722
- ${task.status === 'pending' ? `
723
- <button class="task-remove-btn" onclick="removeTask(${task.id})" title="Remove">×</button>
724
- ` : ''}
725
- </div>
726
- <div class="task-item-meta">
727
- <span class="task-strategy">${strategyLabel}</span>
728
- <span class="task-tool">${task.tool}</span>
729
- </div>
730
- ${task.message ? `<div class="task-item-message">${escapeHtml(task.message)}</div>` : ''}
731
- </div>
732
- `;
733
- }).join('');
734
-
735
- // Reinitialize Lucide icons
736
- if (typeof lucide !== 'undefined') lucide.createIcons();
737
- }
738
-
739
- /**
740
- * Remove a task from queue
741
- */
742
- function removeTask(taskId) {
743
- updateTaskQueue = updateTaskQueue.filter(t => t.id !== taskId);
744
- renderTaskQueue();
745
- updateFabBadge();
746
-
747
- // Disable start button if no pending tasks
748
- const hasPending = updateTaskQueue.some(t => t.status === 'pending');
749
- document.getElementById('startQueueBtn').disabled = !hasPending;
750
- }
751
-
752
- /**
753
- * Clear completed/failed tasks
754
- */
755
- function clearCompletedTasks() {
756
- updateTaskQueue = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running');
757
- renderTaskQueue();
758
- updateFabBadge();
759
- }
760
-
761
- /**
762
- * Execute a single task asynchronously
763
- */
764
- async function executeTask(task) {
765
- const folderName = task.path.split('/').pop() || task.path;
766
-
767
- // Update status to running
768
- task.status = 'running';
769
- task.message = 'Processing...';
770
- renderTaskQueue();
771
-
772
- addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer');
773
-
774
- try {
775
- const response = await fetch('/api/update-claude-md', {
776
- method: 'POST',
777
- headers: { 'Content-Type': 'application/json' },
778
- body: JSON.stringify({
779
- path: task.path,
780
- tool: task.tool,
781
- strategy: task.strategy
782
- })
783
- });
784
-
785
- const result = await response.json();
786
-
787
- if (result.success) {
788
- task.status = 'completed';
789
- task.message = 'Updated successfully';
790
- addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer');
791
- return { success: true };
792
- } else {
793
- task.status = 'failed';
794
- task.message = result.error || 'Update failed';
795
- addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer');
796
- return { success: false };
797
- }
798
- } catch (error) {
799
- task.status = 'failed';
800
- task.message = error.message;
801
- addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer');
802
- return { success: false };
803
- } finally {
804
- renderTaskQueue();
805
- updateFabBadge();
806
- }
807
- }
808
-
809
- /**
810
- * Start processing task queue - executes tasks asynchronously in parallel
811
- */
812
- async function startTaskQueue() {
813
- if (isTaskRunning) return;
814
-
815
- const pendingTasks = updateTaskQueue.filter(t => t.status === 'pending');
816
- if (pendingTasks.length === 0) return;
817
-
818
- isTaskRunning = true;
819
- document.getElementById('startQueueBtn').disabled = true;
820
-
821
- addGlobalNotification('info', `Starting ${pendingTasks.length} task(s) in parallel...`, null, 'Explorer');
822
-
823
- // Execute all tasks in parallel
824
- const results = await Promise.all(pendingTasks.map(task => executeTask(task)));
825
-
826
- const successCount = results.filter(r => r.success).length;
827
- const failCount = results.filter(r => !r.success).length;
828
-
829
- isTaskRunning = false;
830
-
831
- // Summary notification
832
- addGlobalNotification(
833
- failCount === 0 ? 'success' : 'warning',
834
- `Queue completed: ${successCount} succeeded, ${failCount} failed`,
835
- null,
836
- 'Explorer'
837
- );
838
-
839
- // Force refresh notification list to ensure all notifications are displayed
840
- if (typeof renderGlobalNotifications === 'function') {
841
- renderGlobalNotifications();
842
- updateGlobalNotifBadge();
843
- }
844
-
845
- // Re-enable start button if there are pending tasks
846
- const hasPending = updateTaskQueue.some(t => t.status === 'pending');
847
- document.getElementById('startQueueBtn').disabled = !hasPending;
848
-
849
- // Refresh tree to show updated CLAUDE.md files
850
- await refreshExplorerTree();
851
- }
852
-
1
+ // ============================================
2
+ // EXPLORER VIEW
3
+ // ============================================
4
+ // File tree browser with .gitignore filtering and CLAUDE.md update support
5
+ // Split-panel layout: file tree (left) + preview (right)
6
+
7
+ // Explorer state
8
+ let explorerCurrentPath = null;
9
+ let explorerSelectedFile = null;
10
+ let explorerExpandedDirs = new Set();
11
+
12
+ // Task queue for CLAUDE.md updates
13
+ let updateTaskQueue = [];
14
+ let isTaskQueueVisible = false;
15
+ let isTaskRunning = false;
16
+ // Note: defaultCliTool is defined in components/cli-status.js
17
+
18
+
19
+ /**
20
+ * Safe base64 encode that handles Unicode characters
21
+ * Returns alphanumeric-only string suitable for HTML IDs
22
+ */
23
+ function safeBase64Encode(str) {
24
+ try {
25
+ // Encode Unicode string to UTF-8 bytes, then to base64
26
+ const encoded = btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g, (_, p1) => String.fromCharCode(parseInt(p1, 16))));
27
+ return encoded.replace(/[^a-zA-Z0-9]/g, '');
28
+ } catch (e) {
29
+ // Fallback: use simple hash if encoding fails
30
+ let hash = 0;
31
+ for (let i = 0; i < str.length; i++) {
32
+ const char = str.charCodeAt(i);
33
+ hash = ((hash << 5) - hash) + char;
34
+ hash = hash & hash;
35
+ }
36
+ return 'path' + Math.abs(hash).toString(36);
37
+ }
38
+ }
39
+
40
+ /**
41
+ * Render the Explorer view
42
+ */
43
+ async function renderExplorer() {
44
+ const container = document.getElementById('mainContent');
45
+ if (!container) return;
46
+
47
+ // Hide stats grid and search
48
+ const statsGrid = document.getElementById('statsGrid');
49
+ const searchInput = document.getElementById('searchInput');
50
+ if (statsGrid) statsGrid.style.display = 'none';
51
+ if (searchInput) searchInput.parentElement.style.display = 'none';
52
+
53
+ // Initialize explorer path to project path
54
+ explorerCurrentPath = projectPath;
55
+
56
+ container.innerHTML = `
57
+ <div class="explorer-container">
58
+ <!-- Left Panel: File Tree -->
59
+ <div class="explorer-tree-panel">
60
+ <div class="explorer-tree-header">
61
+ <div class="explorer-tree-title">
62
+ <i data-lucide="folder-tree" class="explorer-icon"></i>
63
+ <span class="explorer-title-text">${t('explorer.title')}</span>
64
+ </div>
65
+ <button class="explorer-refresh-btn" onclick="refreshExplorerTree()" title="${t('explorer.refresh')}">
66
+ <i data-lucide="refresh-cw" class="w-4 h-4"></i>
67
+ </button>
68
+ </div>
69
+ <div class="explorer-tree-content" id="explorerTreeContent">
70
+ <div class="explorer-loading">${t('explorer.loading')}</div>
71
+ </div>
72
+ </div>
73
+
74
+ <!-- Right Panel: Preview -->
75
+ <div class="explorer-preview-panel">
76
+ <div class="explorer-preview-header" id="explorerPreviewHeader">
77
+ <span class="preview-filename">${t('explorer.selectFile')}</span>
78
+ </div>
79
+ <div class="explorer-preview-content" id="explorerPreviewContent">
80
+ <div class="explorer-preview-empty">
81
+ <div class="preview-empty-icon"><i data-lucide="file-text" class="w-12 h-12"></i></div>
82
+ <div class="preview-empty-text">${t('explorer.selectFileHint')}</div>
83
+ </div>
84
+ </div>
85
+ </div>
86
+ </div>
87
+
88
+ <!-- Floating Action Button -->
89
+ <div class="explorer-fab" onclick="toggleTaskQueue()" title="${t('taskQueue.title')}">
90
+ <span class="fab-icon"><i data-lucide="list-todo" class="w-5 h-5"></i></span>
91
+ <span class="fab-badge" id="fabBadge">0</span>
92
+ </div>
93
+
94
+ <!-- Task Queue Panel -->
95
+ <div class="task-queue-panel" id="taskQueuePanel">
96
+ <div class="task-queue-header">
97
+ <span class="task-queue-title"><i data-lucide="clipboard-list" class="w-4 h-4 inline-block mr-1"></i> ${t('taskQueue.title')}</span>
98
+ <button class="task-queue-close" onclick="toggleTaskQueue()">×</button>
99
+ </div>
100
+ <div class="task-queue-toolbar">
101
+ <div class="queue-cli-selector">
102
+ <label>${t('taskQueue.cli')}</label>
103
+ <select id="queueCliTool" onchange="updateDefaultCliTool(this.value)">
104
+ <option value="gemini">Gemini</option>
105
+ <option value="qwen">Qwen</option>
106
+ <option value="codex">Codex</option>
107
+ <option value="claude">Claude</option>
108
+ </select>
109
+ </div>
110
+ <div class="task-queue-actions">
111
+ <button class="queue-action-btn" onclick="openAddTaskModal()" title="${t('taskQueue.addTask')}">
112
+ <i data-lucide="plus" class="w-4 h-4"></i>
113
+ </button>
114
+ <button class="queue-action-btn queue-start-btn" onclick="startTaskQueue()" id="startQueueBtn" disabled title="${t('taskQueue.startAll')}">
115
+ <i data-lucide="play" class="w-4 h-4"></i>
116
+ </button>
117
+ <button class="queue-action-btn queue-clear-btn" onclick="clearCompletedTasks()" title="${t('taskQueue.clearCompleted')}">
118
+ <i data-lucide="trash-2" class="w-4 h-4"></i>
119
+ </button>
120
+ </div>
121
+ </div>
122
+ <div class="task-queue-list" id="taskQueueList">
123
+ <div class="task-queue-empty">
124
+ <span>${t('taskQueue.noTasks')}</span>
125
+ <p>${t('taskQueue.noTasksHint')}</p>
126
+ </div>
127
+ </div>
128
+ </div>
129
+ `;
130
+
131
+ // Load initial file tree
132
+ await loadExplorerTree(explorerCurrentPath);
133
+
134
+ // Initialize Lucide icons for dynamically rendered content
135
+ if (typeof lucide !== 'undefined') lucide.createIcons();
136
+ }
137
+
138
+ /**
139
+ * Load and render file tree for a directory
140
+ */
141
+ async function loadExplorerTree(dirPath) {
142
+ const treeContent = document.getElementById('explorerTreeContent');
143
+ if (!treeContent) return;
144
+
145
+ try {
146
+ const response = await fetch(`/api/files?path=${encodeURIComponent(dirPath)}`);
147
+ const data = await response.json();
148
+
149
+ if (data.error) {
150
+ treeContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
151
+ return;
152
+ }
153
+
154
+ // Render root level
155
+ treeContent.innerHTML = renderTreeLevel(data.files, dirPath, 0);
156
+ attachTreeEventListeners();
157
+
158
+ // Initialize Lucide icons for tree items
159
+ if (typeof lucide !== 'undefined') lucide.createIcons();
160
+
161
+ } catch (error) {
162
+ treeContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
163
+ }
164
+ }
165
+
166
+ /**
167
+ * Render a level of the file tree
168
+ */
169
+ function renderTreeLevel(files, parentPath, depth) {
170
+ if (!files || files.length === 0) {
171
+ return `<div class="tree-empty" style="padding-left: ${depth * 16 + 8}px">${t('explorer.emptyDir')}</div>`;
172
+ }
173
+
174
+ return files.map(file => {
175
+ const isExpanded = explorerExpandedDirs.has(file.path);
176
+ const isSelected = explorerSelectedFile === file.path;
177
+
178
+ if (file.type === 'directory') {
179
+ const folderIcon = getFolderIcon(file.name, isExpanded, file.hasClaudeMd);
180
+ const chevronIcon = isExpanded ? '<i data-lucide="chevron-down" class="w-3 h-3"></i>' : '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
181
+ return `
182
+ <div class="tree-item tree-folder ${isExpanded ? 'expanded' : ''} ${file.hasClaudeMd ? 'has-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="directory">
183
+ <div class="tree-item-row ${isSelected ? 'selected' : ''}" style="padding-left: ${depth * 16}px">
184
+ <span class="tree-chevron">${chevronIcon}</span>
185
+ <span class="tree-icon">${folderIcon}</span>
186
+ <span class="tree-name">${escapeHtml(file.name)}</span>
187
+ ${file.hasClaudeMd ? `
188
+ <span class="claude-md-badge" title="Contains CLAUDE.md documentation">
189
+ <span class="badge-icon"><i data-lucide="file-check" class="w-3 h-3"></i></span>
190
+ <span class="badge-text">DOC</span>
191
+ </span>
192
+ ` : ''}
193
+ <div class="tree-folder-actions">
194
+ <button class="tree-update-btn" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'single-layer')" title="${t('explorer.currentFolderOnly')}">
195
+ <span class="update-icon"><i data-lucide="file" class="w-3.5 h-3.5"></i></span>
196
+ </button>
197
+ <button class="tree-update-btn tree-update-multi" onclick="event.stopPropagation(); addFolderToQueue('${escapeHtml(file.path)}', 'multi-layer')" title="${t('explorer.withSubdirs')}">
198
+ <span class="update-icon"><i data-lucide="folder-tree" class="w-3.5 h-3.5"></i></span>
199
+ </button>
200
+ </div>
201
+ </div>
202
+ <div class="tree-children ${isExpanded ? 'show' : ''}" id="children-${safeBase64Encode(file.path)}">
203
+ ${isExpanded ? '' : ''}
204
+ </div>
205
+ </div>
206
+ `;
207
+ } else {
208
+ const ext = file.name.includes('.') ? file.name.split('.').pop().toLowerCase() : '';
209
+ const fileIcon = getFileIcon(ext);
210
+ // Special highlight for CLAUDE.md files
211
+ const isClaudeMd = file.name === 'CLAUDE.md';
212
+ return `
213
+ <div class="tree-item tree-file ${isSelected ? 'selected' : ''} ${isClaudeMd ? 'is-claude-md' : ''}" data-path="${escapeHtml(file.path)}" data-type="file">
214
+ <div class="tree-item-row" style="padding-left: ${depth * 16}px">
215
+ <span class="tree-chevron-spacer"></span>
216
+ <span class="tree-icon">${isClaudeMd ? '<span class="file-icon file-icon-claude"><i data-lucide="bot" class="w-3 h-3"></i></span>' : fileIcon}</span>
217
+ <span class="tree-name">${escapeHtml(file.name)}</span>
218
+ </div>
219
+ </div>
220
+ `;
221
+ }
222
+ }).join('');
223
+ }
224
+
225
+ /**
226
+ * Get file icon based on extension - using colored SVG icons for better distinction
227
+ */
228
+ function getFileIcon(ext) {
229
+ const iconMap = {
230
+ // JavaScript/TypeScript - distinct colors
231
+ 'js': '<span class="file-icon file-icon-js">JS</span>',
232
+ 'mjs': '<span class="file-icon file-icon-js">JS</span>',
233
+ 'cjs': '<span class="file-icon file-icon-js">JS</span>',
234
+ 'jsx': '<span class="file-icon file-icon-jsx">JSX</span>',
235
+ 'ts': '<span class="file-icon file-icon-ts">TS</span>',
236
+ 'tsx': '<span class="file-icon file-icon-tsx">TSX</span>',
237
+
238
+ // Python
239
+ 'py': '<span class="file-icon file-icon-py">PY</span>',
240
+ 'pyw': '<span class="file-icon file-icon-py">PY</span>',
241
+
242
+ // Other languages
243
+ 'go': '<span class="file-icon file-icon-go">GO</span>',
244
+ 'rs': '<span class="file-icon file-icon-rs">RS</span>',
245
+ 'java': '<span class="file-icon file-icon-java">JV</span>',
246
+ 'rb': '<span class="file-icon file-icon-rb">RB</span>',
247
+ 'php': '<span class="file-icon file-icon-php">PHP</span>',
248
+ 'c': '<span class="file-icon file-icon-c">C</span>',
249
+ 'cpp': '<span class="file-icon file-icon-cpp">C++</span>',
250
+ 'h': '<span class="file-icon file-icon-h">H</span>',
251
+ 'cs': '<span class="file-icon file-icon-cs">C#</span>',
252
+ 'swift': '<span class="file-icon file-icon-swift">SW</span>',
253
+ 'kt': '<span class="file-icon file-icon-kt">KT</span>',
254
+
255
+ // Web
256
+ 'html': '<span class="file-icon file-icon-html">HTML</span>',
257
+ 'htm': '<span class="file-icon file-icon-html">HTML</span>',
258
+ 'css': '<span class="file-icon file-icon-css">CSS</span>',
259
+ 'scss': '<span class="file-icon file-icon-scss">SCSS</span>',
260
+ 'sass': '<span class="file-icon file-icon-scss">SASS</span>',
261
+ 'less': '<span class="file-icon file-icon-less">LESS</span>',
262
+ 'vue': '<span class="file-icon file-icon-vue">VUE</span>',
263
+ 'svelte': '<span class="file-icon file-icon-svelte">SV</span>',
264
+
265
+ // Config/Data
266
+ 'json': '<span class="file-icon file-icon-json">{}</span>',
267
+ 'yaml': '<span class="file-icon file-icon-yaml">YML</span>',
268
+ 'yml': '<span class="file-icon file-icon-yaml">YML</span>',
269
+ 'xml': '<span class="file-icon file-icon-xml">XML</span>',
270
+ 'toml': '<span class="file-icon file-icon-toml">TML</span>',
271
+ 'ini': '<span class="file-icon file-icon-ini">INI</span>',
272
+ 'env': '<span class="file-icon file-icon-env">ENV</span>',
273
+
274
+ // Documentation
275
+ 'md': '<span class="file-icon file-icon-md">MD</span>',
276
+ 'markdown': '<span class="file-icon file-icon-md">MD</span>',
277
+ 'txt': '<span class="file-icon file-icon-txt">TXT</span>',
278
+ 'log': '<span class="file-icon file-icon-log">LOG</span>',
279
+
280
+ // Shell/Scripts
281
+ 'sh': '<span class="file-icon file-icon-sh">SH</span>',
282
+ 'bash': '<span class="file-icon file-icon-sh">SH</span>',
283
+ 'zsh': '<span class="file-icon file-icon-sh">ZSH</span>',
284
+ 'ps1': '<span class="file-icon file-icon-ps1">PS1</span>',
285
+ 'bat': '<span class="file-icon file-icon-bat">BAT</span>',
286
+ 'cmd': '<span class="file-icon file-icon-bat">CMD</span>',
287
+
288
+ // Database
289
+ 'sql': '<span class="file-icon file-icon-sql">SQL</span>',
290
+ 'db': '<span class="file-icon file-icon-db">DB</span>',
291
+
292
+ // Docker/Container
293
+ 'dockerfile': '<span class="file-icon file-icon-docker"><i data-lucide="container" class="w-3 h-3"></i></span>',
294
+
295
+ // Images
296
+ 'png': '<span class="file-icon file-icon-img">IMG</span>',
297
+ 'jpg': '<span class="file-icon file-icon-img">IMG</span>',
298
+ 'jpeg': '<span class="file-icon file-icon-img">IMG</span>',
299
+ 'gif': '<span class="file-icon file-icon-img">GIF</span>',
300
+ 'svg': '<span class="file-icon file-icon-svg">SVG</span>',
301
+ 'ico': '<span class="file-icon file-icon-img">ICO</span>',
302
+
303
+ // Package
304
+ 'lock': '<span class="file-icon file-icon-lock"><i data-lucide="lock" class="w-3 h-3"></i></span>'
305
+ };
306
+
307
+ return iconMap[ext] || '<span class="file-icon file-icon-default"><i data-lucide="file" class="w-3 h-3"></i></span>';
308
+ }
309
+
310
+ /**
311
+ * Get folder icon based on folder name and state
312
+ */
313
+ function getFolderIcon(name, isExpanded, hasClaudeMd) {
314
+ // Only special icon for .workflow folder
315
+ if (name === '.workflow') {
316
+ return '<i data-lucide="zap" class="w-4 h-4 text-warning"></i>';
317
+ }
318
+ return isExpanded
319
+ ? '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>'
320
+ : '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
321
+ }
322
+
323
+ // Flag to track if event delegation is already set up
324
+ let explorerEventsDelegated = false;
325
+
326
+ /**
327
+ * Attach event listeners using event delegation (only once on container)
328
+ */
329
+ function attachTreeEventListeners() {
330
+ const treeContent = document.getElementById('explorerTreeContent');
331
+ if (!treeContent || explorerEventsDelegated) return;
332
+
333
+ explorerEventsDelegated = true;
334
+
335
+ // Use event delegation - single listener on container handles all clicks
336
+ treeContent.addEventListener('click', async (e) => {
337
+ // Check if clicked on folder row (but not on action buttons)
338
+ const folderRow = e.target.closest('.tree-folder > .tree-item-row');
339
+ if (folderRow && !e.target.closest('.tree-folder-actions')) {
340
+ const folder = folderRow.closest('.tree-folder');
341
+ const path = folder.dataset.path;
342
+ await toggleFolderExpand(path, folder);
343
+ return;
344
+ }
345
+
346
+ // Check if clicked on file
347
+ const fileItem = e.target.closest('.tree-file');
348
+ if (fileItem) {
349
+ const path = fileItem.dataset.path;
350
+ await previewFile(path);
351
+
352
+ // Update selection
353
+ document.querySelectorAll('.tree-item-row.selected, .tree-file.selected').forEach(el => {
354
+ el.classList.remove('selected');
355
+ });
356
+ fileItem.classList.add('selected');
357
+ explorerSelectedFile = path;
358
+ }
359
+ });
360
+ }
361
+ /**
362
+ * Toggle folder expand/collapse
363
+ */
364
+ async function toggleFolderExpand(path, folderElement) {
365
+ const isExpanded = explorerExpandedDirs.has(path);
366
+ const childrenContainer = folderElement.querySelector('.tree-children');
367
+ const chevron = folderElement.querySelector('.tree-chevron');
368
+ const folderIcon = folderElement.querySelector('.tree-icon');
369
+
370
+ if (isExpanded) {
371
+ // Collapse
372
+ explorerExpandedDirs.delete(path);
373
+ folderElement.classList.remove('expanded');
374
+ childrenContainer.classList.remove('show');
375
+ // Update chevron and folder icon
376
+ if (chevron) chevron.innerHTML = '<i data-lucide="chevron-right" class="w-3 h-3"></i>';
377
+ if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
378
+ folderIcon.innerHTML = '<i data-lucide="folder" class="w-4 h-4 text-warning"></i>';
379
+ }
380
+ } else {
381
+ // Expand - load children if not loaded
382
+ explorerExpandedDirs.add(path);
383
+ folderElement.classList.add('expanded');
384
+ childrenContainer.classList.add('show');
385
+ // Update chevron and folder icon
386
+ if (chevron) chevron.innerHTML = '<i data-lucide="chevron-down" class="w-3 h-3"></i>';
387
+ if (folderIcon && !folderElement.querySelector('[data-lucide="zap"]')) {
388
+ folderIcon.innerHTML = '<i data-lucide="folder-open" class="w-4 h-4 text-warning"></i>';
389
+ }
390
+
391
+ if (!childrenContainer.innerHTML.trim()) {
392
+ childrenContainer.innerHTML = '<div class="tree-loading">Loading...</div>';
393
+
394
+ try {
395
+ const response = await fetch(`/api/files?path=${encodeURIComponent(path)}`);
396
+ const data = await response.json();
397
+
398
+ const depth = (path.match(/\//g) || []).length - (explorerCurrentPath.match(/\//g) || []).length + 1;
399
+ childrenContainer.innerHTML = renderTreeLevel(data.files, path, depth);
400
+ } catch (error) {
401
+ childrenContainer.innerHTML = `<div class="tree-error">Failed to load</div>`;
402
+ }
403
+ }
404
+ }
405
+
406
+ // Reinitialize Lucide icons after DOM changes
407
+ if (typeof lucide !== 'undefined') lucide.createIcons();
408
+ }
409
+
410
+ /**
411
+ * Preview a file in the right panel
412
+ */
413
+ async function previewFile(filePath) {
414
+ const previewHeader = document.getElementById('explorerPreviewHeader');
415
+ const previewContent = document.getElementById('explorerPreviewContent');
416
+
417
+ const fileName = filePath.split('/').pop();
418
+ const ext = fileName.includes('.') ? fileName.split('.').pop().toLowerCase() : '';
419
+ const isMarkdown = ext === 'md' || ext === 'markdown';
420
+
421
+ // Build header with tabs for markdown files
422
+ previewHeader.innerHTML = `
423
+ <div class="preview-header-left">
424
+ <span class="preview-filename">${escapeHtml(fileName)}</span>
425
+ <span class="preview-path" title="${escapeHtml(filePath)}">${escapeHtml(filePath)}</span>
426
+ </div>
427
+ ${isMarkdown ? `
428
+ <div class="preview-header-tabs" id="previewHeaderTabs">
429
+ <button class="preview-tab active" data-tab="rendered" onclick="switchPreviewTab(this, 'rendered')">${t('explorer.preview')}</button>
430
+ <button class="preview-tab" data-tab="source" onclick="switchPreviewTab(this, 'source')">${t('explorer.source')}</button>
431
+ </div>
432
+ ` : ''}
433
+ `;
434
+
435
+ previewContent.innerHTML = '<div class="explorer-loading">Loading file...</div>';
436
+
437
+ try {
438
+ const response = await fetch(`/api/file-content?path=${encodeURIComponent(filePath)}`);
439
+ const data = await response.json();
440
+
441
+ if (data.error) {
442
+ previewContent.innerHTML = `<div class="explorer-error">${escapeHtml(data.error)}</div>`;
443
+ return;
444
+ }
445
+
446
+ if (data.isMarkdown) {
447
+ // Render markdown with tabs content (tabs are in header)
448
+ const rendered = marked.parse(data.content);
449
+ previewContent.innerHTML = `
450
+ <div class="preview-tab-content rendered show" data-tab="rendered">
451
+ <div class="markdown-preview prose">${rendered}</div>
452
+ </div>
453
+ <div class="preview-tab-content source" data-tab="source">
454
+ <pre><code class="language-markdown">${escapeHtml(data.content)}</code></pre>
455
+ </div>
456
+ `;
457
+ } else {
458
+ // Render code with syntax highlighting
459
+ previewContent.innerHTML = `
460
+ <div class="preview-info">
461
+ <span class="preview-lang">${data.language}</span>
462
+ <span class="preview-lines">${data.lines} ${t('explorer.lines')}</span>
463
+ <span class="preview-size">${formatFileSize(data.size)}</span>
464
+ </div>
465
+ <pre class="preview-code"><code class="language-${data.language}">${escapeHtml(data.content)}</code></pre>
466
+ `;
467
+ }
468
+
469
+ // Apply syntax highlighting if hljs is available
470
+ if (typeof hljs !== 'undefined') {
471
+ previewContent.querySelectorAll('pre code').forEach(block => {
472
+ hljs.highlightElement(block);
473
+ });
474
+ }
475
+
476
+ } catch (error) {
477
+ previewContent.innerHTML = `<div class="explorer-error">Failed to load: ${escapeHtml(error.message)}</div>`;
478
+ }
479
+ }
480
+
481
+ /**
482
+ * Switch preview tab (for markdown files)
483
+ */
484
+ function switchPreviewTab(btn, tabName) {
485
+ const previewPanel = btn.closest('.explorer-preview-panel');
486
+ const contentArea = previewPanel.querySelector('.explorer-preview-content');
487
+
488
+ // Update tab buttons in header
489
+ previewPanel.querySelectorAll('.preview-tab').forEach(t => t.classList.remove('active'));
490
+ btn.classList.add('active');
491
+
492
+ // Update tab content
493
+ contentArea.querySelectorAll('.preview-tab-content').forEach(c => c.classList.remove('show'));
494
+ contentArea.querySelector(`[data-tab="${tabName}"]`).classList.add('show');
495
+ }
496
+
497
+ /**
498
+ * Format file size
499
+ */
500
+ function formatFileSize(bytes) {
501
+ if (bytes < 1024) return bytes + ' B';
502
+ if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(1) + ' KB';
503
+ return (bytes / (1024 * 1024)).toFixed(1) + ' MB';
504
+ }
505
+
506
+ /**
507
+ * Refresh the explorer tree
508
+ */
509
+ async function refreshExplorerTree() {
510
+ const btn = document.querySelector('.explorer-refresh-btn');
511
+ if (btn) {
512
+ btn.classList.add('refreshing');
513
+ }
514
+
515
+ explorerExpandedDirs.clear();
516
+ explorerEventsDelegated = false;
517
+ await loadExplorerTree(explorerCurrentPath);
518
+
519
+ if (btn) {
520
+ btn.classList.remove('refreshing');
521
+ }
522
+ }
523
+
524
+ /**
525
+ * Open Update CLAUDE.md modal
526
+ */
527
+ function openUpdateClaudeMdModal(folderPath) {
528
+ const modal = document.getElementById('updateClaudeMdModal');
529
+ if (!modal) return;
530
+
531
+ // Set folder path
532
+ document.getElementById('claudeMdTargetPath').textContent = folderPath;
533
+ document.getElementById('claudeMdTargetPath').dataset.path = folderPath;
534
+
535
+ // Reset form
536
+ document.getElementById('claudeMdTool').value = 'gemini';
537
+ document.getElementById('claudeMdStrategy').value = 'single-layer';
538
+
539
+ // Show modal
540
+ modal.classList.remove('hidden');
541
+ }
542
+
543
+ /**
544
+ * Close Update CLAUDE.md modal
545
+ */
546
+ function closeUpdateClaudeMdModal() {
547
+ const modal = document.getElementById('updateClaudeMdModal');
548
+ if (modal) {
549
+ modal.classList.add('hidden');
550
+ }
551
+ }
552
+
553
+ /**
554
+ * Execute Update CLAUDE.md
555
+ */
556
+ async function executeUpdateClaudeMd() {
557
+ const pathEl = document.getElementById('claudeMdTargetPath');
558
+ const toolSelect = document.getElementById('claudeMdTool');
559
+ const strategySelect = document.getElementById('claudeMdStrategy');
560
+ const executeBtn = document.getElementById('claudeMdExecuteBtn');
561
+ const statusEl = document.getElementById('claudeMdStatus');
562
+
563
+ const path = pathEl.dataset.path;
564
+ const tool = toolSelect.value;
565
+ const strategy = strategySelect.value;
566
+
567
+ // Update UI
568
+ executeBtn.disabled = true;
569
+ executeBtn.textContent = 'Updating...';
570
+ statusEl.innerHTML = '<div class="status-running">⏳ Running update...</div>';
571
+
572
+ try {
573
+ const response = await fetch('/api/update-claude-md', {
574
+ method: 'POST',
575
+ headers: { 'Content-Type': 'application/json' },
576
+ body: JSON.stringify({ path, tool, strategy })
577
+ });
578
+
579
+ const result = await response.json();
580
+
581
+ if (result.success) {
582
+ statusEl.innerHTML = `<div class="status-success"><i data-lucide="check-circle" class="w-4 h-4 inline text-success"></i> ${escapeHtml(result.message)}</div>`;
583
+ // Refresh tree to update CLAUDE.md indicators
584
+ await refreshExplorerTree();
585
+ if (typeof lucide !== 'undefined') lucide.createIcons();
586
+ } else {
587
+ statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(result.error || 'Update failed')}</div>`;
588
+ if (typeof lucide !== 'undefined') lucide.createIcons();
589
+ }
590
+
591
+ } catch (error) {
592
+ statusEl.innerHTML = `<div class="status-error"><i data-lucide="x-circle" class="w-4 h-4 inline text-destructive"></i> ${escapeHtml(error.message)}</div>`;
593
+ if (typeof lucide !== 'undefined') lucide.createIcons();
594
+ } finally {
595
+ executeBtn.disabled = false;
596
+ executeBtn.textContent = 'Execute';
597
+ }
598
+ }
599
+
600
+ // ============================================
601
+ // TASK QUEUE FUNCTIONS
602
+ // ============================================
603
+
604
+ /**
605
+ * Toggle task queue panel visibility
606
+ */
607
+ function toggleTaskQueue() {
608
+ isTaskQueueVisible = !isTaskQueueVisible;
609
+ const panel = document.getElementById('taskQueuePanel');
610
+ const fab = document.querySelector('.explorer-fab');
611
+
612
+ if (isTaskQueueVisible) {
613
+ panel.classList.add('show');
614
+ fab.classList.add('active');
615
+ } else {
616
+ panel.classList.remove('show');
617
+ fab.classList.remove('active');
618
+ }
619
+ }
620
+
621
+ /**
622
+ * Update default CLI tool
623
+ */
624
+ function updateDefaultCliTool(tool) {
625
+ defaultCliTool = tool;
626
+ }
627
+
628
+ /**
629
+ * Update the FAB badge count
630
+ */
631
+ function updateFabBadge() {
632
+ const badge = document.getElementById('fabBadge');
633
+ if (badge) {
634
+ const pendingCount = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running').length;
635
+ badge.textContent = pendingCount || '';
636
+ badge.style.display = pendingCount > 0 ? 'flex' : 'none';
637
+ }
638
+ }
639
+
640
+ /**
641
+ * Open add task modal
642
+ */
643
+ function openAddTaskModal() {
644
+ const modal = document.getElementById('updateClaudeMdModal');
645
+ if (!modal) return;
646
+
647
+ // Set default path to current project
648
+ document.getElementById('claudeMdTargetPath').textContent = explorerCurrentPath;
649
+ document.getElementById('claudeMdTargetPath').dataset.path = explorerCurrentPath;
650
+
651
+ // Reset form
652
+ document.getElementById('claudeMdTool').value = 'gemini';
653
+ document.getElementById('claudeMdStrategy').value = 'single-layer';
654
+ document.getElementById('claudeMdStatus').innerHTML = '';
655
+
656
+ // Change button to "Add to Queue"
657
+ const executeBtn = document.getElementById('claudeMdExecuteBtn');
658
+ executeBtn.textContent = 'Add to Queue';
659
+ executeBtn.onclick = addTaskToQueue;
660
+
661
+ modal.classList.remove('hidden');
662
+ }
663
+
664
+ /**
665
+ * Add task to queue from modal
666
+ */
667
+ function addTaskToQueue() {
668
+ const pathEl = document.getElementById('claudeMdTargetPath');
669
+ const toolSelect = document.getElementById('claudeMdTool');
670
+ const strategySelect = document.getElementById('claudeMdStrategy');
671
+
672
+ const path = pathEl.dataset.path;
673
+ const tool = toolSelect.value;
674
+ const strategy = strategySelect.value;
675
+
676
+ addUpdateTask(path, tool, strategy);
677
+ closeUpdateClaudeMdModal();
678
+
679
+ // Show task queue
680
+ if (!isTaskQueueVisible) {
681
+ toggleTaskQueue();
682
+ }
683
+ }
684
+
685
+ /**
686
+ * Add a task to the update queue
687
+ */
688
+ function addUpdateTask(path, tool = 'gemini', strategy = 'single-layer') {
689
+ const task = {
690
+ id: Date.now(),
691
+ path,
692
+ tool,
693
+ strategy,
694
+ status: 'pending', // pending, running, completed, failed
695
+ message: '',
696
+ addedAt: new Date().toISOString()
697
+ };
698
+
699
+ updateTaskQueue.push(task);
700
+ renderTaskQueue();
701
+ updateFabBadge();
702
+
703
+ // Enable start button
704
+ document.getElementById('startQueueBtn').disabled = false;
705
+ }
706
+
707
+ /**
708
+ * Add task from folder context (right-click or button)
709
+ */
710
+ function addFolderToQueue(folderPath, strategy = 'single-layer') {
711
+ // Use the sidebar queue instead of floating panel
712
+ if (typeof addUpdateTaskToSidebar === 'function') {
713
+ addUpdateTaskToSidebar(folderPath, defaultCliTool, strategy);
714
+ } else {
715
+ // Fallback to local queue
716
+ addUpdateTask(folderPath, defaultCliTool, strategy);
717
+
718
+ // Show task queue if not visible
719
+ if (!isTaskQueueVisible) {
720
+ toggleTaskQueue();
721
+ }
722
+ }
723
+ }
724
+
725
+ /**
726
+ * Render the task queue list
727
+ */
728
+ function renderTaskQueue() {
729
+ const listEl = document.getElementById('taskQueueList');
730
+
731
+ if (updateTaskQueue.length === 0) {
732
+ listEl.innerHTML = `
733
+ <div class="task-queue-empty">
734
+ <span>${t('taskQueue.noTasks')}</span>
735
+ <p>${t('taskQueue.noTasksHint')}</p>
736
+ </div>
737
+ `;
738
+ return;
739
+ }
740
+
741
+ listEl.innerHTML = updateTaskQueue.map(task => {
742
+ const folderName = task.path.split('/').pop() || task.path;
743
+ const strategyLabel = task.strategy === 'multi-layer'
744
+ ? '<i data-lucide="folder-tree" class="w-3 h-3 inline"></i> ' + t('taskQueue.withSubdirs')
745
+ : '<i data-lucide="file" class="w-3 h-3 inline"></i> ' + t('taskQueue.currentOnly');
746
+ const statusIcon = {
747
+ 'pending': '<i data-lucide="clock" class="w-4 h-4"></i>',
748
+ 'running': '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i>',
749
+ 'completed': '<i data-lucide="check-circle" class="w-4 h-4 text-success"></i>',
750
+ 'failed': '<i data-lucide="x-circle" class="w-4 h-4 text-destructive"></i>'
751
+ }[task.status];
752
+
753
+ return `
754
+ <div class="task-queue-item status-${task.status}" data-task-id="${task.id}">
755
+ <div class="task-item-header">
756
+ <span class="task-status-icon">${statusIcon}</span>
757
+ <span class="task-folder-name" title="${escapeHtml(task.path)}">${escapeHtml(folderName)}</span>
758
+ ${task.status === 'pending' ? `
759
+ <button class="task-remove-btn" onclick="removeTask(${task.id})" title="Remove">×</button>
760
+ ` : ''}
761
+ </div>
762
+ <div class="task-item-meta">
763
+ <span class="task-strategy">${strategyLabel}</span>
764
+ <span class="task-tool">${task.tool}</span>
765
+ </div>
766
+ ${task.message ? `<div class="task-item-message">${escapeHtml(task.message)}</div>` : ''}
767
+ </div>
768
+ `;
769
+ }).join('');
770
+
771
+ // Reinitialize Lucide icons
772
+ if (typeof lucide !== 'undefined') lucide.createIcons();
773
+ }
774
+
775
+ /**
776
+ * Remove a task from queue
777
+ */
778
+ function removeTask(taskId) {
779
+ updateTaskQueue = updateTaskQueue.filter(t => t.id !== taskId);
780
+ renderTaskQueue();
781
+ updateFabBadge();
782
+
783
+ // Disable start button if no pending tasks
784
+ const hasPending = updateTaskQueue.some(t => t.status === 'pending');
785
+ document.getElementById('startQueueBtn').disabled = !hasPending;
786
+ }
787
+
788
+ /**
789
+ * Clear completed/failed tasks
790
+ */
791
+ function clearCompletedTasks() {
792
+ updateTaskQueue = updateTaskQueue.filter(t => t.status === 'pending' || t.status === 'running');
793
+ renderTaskQueue();
794
+ updateFabBadge();
795
+ }
796
+
797
+ /**
798
+ * Execute a single task asynchronously
799
+ */
800
+ async function executeTask(task) {
801
+ const folderName = task.path.split('/').pop() || task.path;
802
+
803
+ // Update status to running
804
+ task.status = 'running';
805
+ task.message = t('taskQueue.processing');
806
+ renderTaskQueue();
807
+
808
+ addGlobalNotification('info', `Processing: ${folderName}`, `Strategy: ${task.strategy}, Tool: ${task.tool}`, 'Explorer');
809
+
810
+ try {
811
+ const response = await fetch('/api/update-claude-md', {
812
+ method: 'POST',
813
+ headers: { 'Content-Type': 'application/json' },
814
+ body: JSON.stringify({
815
+ path: task.path,
816
+ tool: task.tool,
817
+ strategy: task.strategy
818
+ })
819
+ });
820
+
821
+ const result = await response.json();
822
+
823
+ if (result.success) {
824
+ task.status = 'completed';
825
+ task.message = t('taskQueue.updated');
826
+ addGlobalNotification('success', `Completed: ${folderName}`, result.message, 'Explorer');
827
+ return { success: true };
828
+ } else {
829
+ task.status = 'failed';
830
+ task.message = result.error || t('taskQueue.failed');
831
+ addGlobalNotification('error', `Failed: ${folderName}`, result.error || 'Unknown error', 'Explorer');
832
+ return { success: false };
833
+ }
834
+ } catch (error) {
835
+ task.status = 'failed';
836
+ task.message = error.message;
837
+ addGlobalNotification('error', `Error: ${folderName}`, error.message, 'Explorer');
838
+ return { success: false };
839
+ } finally {
840
+ renderTaskQueue();
841
+ updateFabBadge();
842
+ }
843
+ }
844
+
845
+ /**
846
+ * Start processing task queue - executes tasks asynchronously in parallel
847
+ */
848
+ async function startTaskQueue() {
849
+ if (isTaskRunning) return;
850
+
851
+ const pendingTasks = updateTaskQueue.filter(t => t.status === 'pending');
852
+ if (pendingTasks.length === 0) return;
853
+
854
+ isTaskRunning = true;
855
+ document.getElementById('startQueueBtn').disabled = true;
856
+
857
+ addGlobalNotification('info', t('taskQueue.startingTasks', { count: pendingTasks.length }), null, 'Explorer');
858
+
859
+ // Execute all tasks in parallel
860
+ const results = await Promise.all(pendingTasks.map(task => executeTask(task)));
861
+
862
+ const successCount = results.filter(r => r.success).length;
863
+ const failCount = results.filter(r => !r.success).length;
864
+
865
+ isTaskRunning = false;
866
+
867
+ // Summary notification
868
+ addGlobalNotification(
869
+ failCount === 0 ? 'success' : 'warning',
870
+ t('taskQueue.queueCompleted', { success: successCount, failed: failCount }),
871
+ null,
872
+ 'Explorer'
873
+ );
874
+
875
+ // Force refresh notification list to ensure all notifications are displayed
876
+ if (typeof renderGlobalNotifications === 'function') {
877
+ renderGlobalNotifications();
878
+ updateGlobalNotifBadge();
879
+ }
880
+
881
+ // Re-enable start button if there are pending tasks
882
+ const hasPending = updateTaskQueue.some(t => t.status === 'pending');
883
+ document.getElementById('startQueueBtn').disabled = !hasPending;
884
+
885
+ // Refresh tree to show updated CLAUDE.md files
886
+ await refreshExplorerTree();
887
+ }
888
+