claude-code-workflow 6.0.5 → 6.1.1

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 (118) hide show
  1. package/.claude/agents/action-planning-agent.md +1 -1
  2. package/.claude/agents/cli-execution-agent.md +269 -269
  3. package/.claude/agents/cli-explore-agent.md +182 -182
  4. package/.claude/agents/context-search-agent.md +582 -582
  5. package/.claude/agents/memory-bridge.md +93 -93
  6. package/.claude/commands/cli/cli-init.md +1 -1
  7. package/.claude/commands/memory/docs-full-cli.md +471 -471
  8. package/.claude/commands/memory/docs-related-cli.md +386 -386
  9. package/.claude/commands/memory/docs.md +615 -615
  10. package/.claude/commands/memory/load.md +1 -1
  11. package/.claude/commands/memory/update-full.md +332 -332
  12. package/.claude/commands/memory/update-related.md +5 -5
  13. package/.claude/commands/workflow/init.md +1 -1
  14. package/.claude/commands/workflow/lite-fix.md +621 -621
  15. package/.claude/commands/workflow/lite-plan.md +592 -592
  16. package/.claude/commands/workflow/tools/context-gather.md +434 -434
  17. package/.claude/commands/workflow/ui-design/generate.md +504 -504
  18. package/.claude/commands/workflow/ui-design/import-from-code.md +537 -537
  19. package/.claude/scripts/classify-folders.sh +4 -0
  20. package/.claude/scripts/convert_tokens_to_css.sh +4 -0
  21. package/.claude/scripts/detect_changed_modules.sh +5 -1
  22. package/.claude/scripts/discover-design-files.sh +87 -83
  23. package/.claude/scripts/generate_module_docs.sh +717 -713
  24. package/.claude/scripts/get_modules_by_depth.sh +5 -1
  25. package/.claude/scripts/ui-generate-preview.sh +4 -0
  26. package/.claude/scripts/ui-instantiate-prototypes.sh +4 -0
  27. package/.claude/scripts/update_module_claude.sh +4 -0
  28. package/.claude/skills/command-guide/index/all-commands.json +1 -12
  29. package/.claude/skills/command-guide/index/by-category.json +1 -12
  30. package/.claude/skills/command-guide/index/by-use-case.json +1 -12
  31. package/.claude/skills/command-guide/index/essential-commands.json +1 -12
  32. package/.claude/skills/command-guide/reference/agents/action-planning-agent.md +127 -71
  33. package/.claude/skills/command-guide/reference/agents/cli-execution-agent.md +269 -269
  34. package/.claude/skills/command-guide/reference/agents/cli-explore-agent.md +182 -182
  35. package/.claude/skills/command-guide/reference/agents/conceptual-planning-agent.md +18 -38
  36. package/.claude/skills/command-guide/reference/agents/context-search-agent.md +582 -577
  37. package/.claude/skills/command-guide/reference/agents/memory-bridge.md +93 -93
  38. package/.claude/skills/command-guide/reference/commands/cli/cli-init.md +1 -1
  39. package/.claude/skills/command-guide/reference/commands/memory/docs-full-cli.md +471 -471
  40. package/.claude/skills/command-guide/reference/commands/memory/docs-related-cli.md +386 -386
  41. package/.claude/skills/command-guide/reference/commands/memory/docs.md +615 -610
  42. package/.claude/skills/command-guide/reference/commands/memory/load.md +1 -1
  43. package/.claude/skills/command-guide/reference/commands/memory/update-full.md +332 -332
  44. package/.claude/skills/command-guide/reference/commands/memory/update-related.md +5 -5
  45. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/artifacts.md +299 -451
  46. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/auto-parallel.md +14 -37
  47. package/.claude/skills/command-guide/reference/commands/workflow/brainstorm/synthesis.md +252 -350
  48. package/.claude/skills/command-guide/reference/commands/workflow/init.md +2 -2
  49. package/.claude/skills/command-guide/reference/commands/workflow/lite-execute.md +52 -0
  50. package/.claude/skills/command-guide/reference/commands/workflow/lite-fix.md +621 -602
  51. package/.claude/skills/command-guide/reference/commands/workflow/lite-plan.md +46 -36
  52. package/.claude/skills/command-guide/reference/commands/workflow/review-fix.md +18 -58
  53. package/.claude/skills/command-guide/reference/commands/workflow/review-module-cycle.md +22 -52
  54. package/.claude/skills/command-guide/reference/commands/workflow/review-session-cycle.md +19 -48
  55. package/.claude/skills/command-guide/reference/commands/workflow/session/start.md +25 -5
  56. package/.claude/skills/command-guide/reference/commands/workflow/tdd-plan.md +1 -1
  57. package/.claude/skills/command-guide/reference/commands/workflow/test-fix-gen.md +7 -7
  58. package/.claude/skills/command-guide/reference/commands/workflow/tools/context-gather.md +434 -434
  59. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-agent.md +151 -11
  60. package/.claude/skills/command-guide/reference/commands/workflow/tools/task-generate-tdd.md +4 -4
  61. package/.claude/skills/command-guide/reference/commands/workflow/tools/test-task-generate.md +1 -1
  62. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/generate.md +504 -504
  63. package/.claude/skills/command-guide/reference/commands/workflow/ui-design/import-from-code.md +537 -537
  64. package/.claude/workflows/context-search-strategy.md +77 -77
  65. package/.claude/workflows/tool-strategy.md +90 -71
  66. package/.claude/workflows/workflow-architecture.md +1 -1
  67. package/README.md +285 -285
  68. package/ccw/src/cli.js +7 -0
  69. package/ccw/src/commands/tool.js +217 -0
  70. package/ccw/src/core/dashboard-generator.js +18 -3
  71. package/ccw/src/core/lite-scanner.js +35 -11
  72. package/ccw/src/core/server.js +531 -46
  73. package/ccw/src/templates/dashboard-css/01-base.css +161 -0
  74. package/ccw/src/templates/dashboard-css/02-session.css +726 -0
  75. package/ccw/src/templates/dashboard-css/03-tasks.css +512 -0
  76. package/ccw/src/templates/dashboard-css/04-lite-tasks.css +843 -0
  77. package/ccw/src/templates/dashboard-css/05-context.css +2206 -0
  78. package/ccw/src/templates/dashboard-css/06-cards.css +1570 -0
  79. package/ccw/src/templates/dashboard-css/07-managers.css +936 -0
  80. package/ccw/src/templates/dashboard-css/08-review.css +1266 -0
  81. package/ccw/src/templates/dashboard-css/09-explorer.css +1397 -0
  82. package/ccw/src/templates/dashboard-js/components/global-notifications.js +219 -0
  83. package/ccw/src/templates/dashboard-js/components/hook-manager.js +10 -0
  84. package/ccw/src/templates/dashboard-js/components/mcp-manager.js +11 -1
  85. package/ccw/src/templates/dashboard-js/components/navigation.js +11 -5
  86. package/ccw/src/templates/dashboard-js/components/tabs-context.js +20 -20
  87. package/ccw/src/templates/dashboard-js/components/tabs-other.js +11 -11
  88. package/ccw/src/templates/dashboard-js/components/theme.js +29 -1
  89. package/ccw/src/templates/dashboard-js/main.js +4 -0
  90. package/ccw/src/templates/dashboard-js/state.js +5 -0
  91. package/ccw/src/templates/dashboard-js/views/explorer.js +852 -0
  92. package/ccw/src/templates/dashboard-js/views/home.js +13 -9
  93. package/ccw/src/templates/dashboard-js/views/hook-manager.js +8 -5
  94. package/ccw/src/templates/dashboard-js/views/lite-tasks.js +21 -16
  95. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +90 -19
  96. package/ccw/src/templates/dashboard-js/views/project-overview.js +15 -11
  97. package/ccw/src/templates/dashboard-js/views/review-session.js +3 -3
  98. package/ccw/src/templates/dashboard-js/views/session-detail.js +38 -28
  99. package/ccw/src/templates/dashboard.html +129 -28
  100. package/ccw/src/tools/classify-folders.js +204 -0
  101. package/ccw/src/tools/convert-tokens-to-css.js +250 -0
  102. package/ccw/src/tools/detect-changed-modules.js +288 -0
  103. package/ccw/src/tools/discover-design-files.js +134 -0
  104. package/ccw/src/tools/edit-file.js +266 -0
  105. package/ccw/src/tools/generate-module-docs.js +416 -0
  106. package/ccw/src/tools/get-modules-by-depth.js +308 -0
  107. package/ccw/src/tools/index.js +176 -0
  108. package/ccw/src/tools/ui-generate-preview.js +327 -0
  109. package/ccw/src/tools/ui-instantiate-prototypes.js +301 -0
  110. package/ccw/src/tools/update-module-claude.js +380 -0
  111. package/package.json +1 -1
  112. package/.claude/skills/command-guide/reference/commands/workflow/status.md +0 -352
  113. package/ccw/src/core/server.js.bak +0 -385
  114. package/ccw/src/core/server_original.bak +0 -385
  115. package/ccw/src/templates/dashboard.css +0 -8187
  116. package/ccw/src/templates/dashboard_tailwind.html +0 -42
  117. package/ccw/src/templates/dashboard_test.html +0 -37
  118. package/ccw/src/templates/tailwind-base.css +0 -212
@@ -0,0 +1,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
+ 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
+