claude-code-workflow 6.3.48 → 6.3.49

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 (211) hide show
  1. package/.claude/CLAUDE.md +6 -8
  2. package/.claude/agents/action-planning-agent.md +28 -45
  3. package/.claude/agents/cli-lite-planning-agent.md +93 -1
  4. package/.claude/agents/code-developer.md +144 -27
  5. package/.claude/commands/ccw-coordinator.md +175 -21
  6. package/.claude/commands/ccw-debug.md +832 -0
  7. package/.claude/commands/ccw.md +90 -9
  8. package/.claude/commands/cli/cli-init.md +1 -0
  9. package/.claude/commands/issue/convert-to-plan.md +718 -0
  10. package/.claude/commands/issue/from-brainstorm.md +382 -0
  11. package/.claude/commands/memory/tips.md +332 -0
  12. package/.claude/commands/workflow/analyze-with-file.md +804 -0
  13. package/.claude/commands/workflow/brainstorm/auto-parallel.md +18 -43
  14. package/.claude/commands/workflow/brainstorm/role-analysis.md +705 -0
  15. package/.claude/commands/workflow/brainstorm-with-file.md +1153 -0
  16. package/.claude/commands/workflow/debug-with-file.md +7 -5
  17. package/.claude/commands/workflow/execute.md +6 -4
  18. package/.claude/commands/workflow/lite-plan.md +2 -2
  19. package/.claude/commands/workflow/plan-verify.md +162 -327
  20. package/.claude/commands/workflow/plan.md +162 -26
  21. package/.claude/commands/workflow/replan.md +78 -2
  22. package/.claude/commands/workflow/{review-fix.md → review-cycle-fix.md} +6 -6
  23. package/.claude/commands/workflow/review-module-cycle.md +2 -2
  24. package/.claude/commands/workflow/review-session-cycle.md +2 -2
  25. package/.claude/commands/workflow/tools/conflict-resolution.md +16 -26
  26. package/.claude/commands/workflow/tools/context-gather.md +81 -118
  27. package/.claude/commands/workflow/tools/task-generate-agent.md +94 -10
  28. package/.claude/skills/ccw-help/command.json +4 -4
  29. package/.claude/skills/lite-skill-generator/SKILL.md +650 -0
  30. package/.claude/skills/lite-skill-generator/templates/simple-skill.md +68 -0
  31. package/.claude/skills/lite-skill-generator/templates/style-guide.md +64 -0
  32. package/.claude/skills/skill-generator/SKILL.md +277 -85
  33. package/.claude/skills/skill-generator/phases/01-requirements-discovery.md +4 -15
  34. package/.claude/skills/skill-generator/phases/02-structure-generation.md +72 -17
  35. package/.claude/skills/skill-generator/phases/03-phase-generation.md +218 -51
  36. package/.claude/skills/skill-generator/phases/04-specs-templates.md +111 -41
  37. package/.claude/skills/skill-generator/phases/05-validation.md +139 -56
  38. package/.claude/skills/skill-generator/templates/autonomous-action.md +78 -268
  39. package/.claude/skills/skill-generator/templates/autonomous-orchestrator.md +14 -0
  40. package/.claude/skills/skill-generator/templates/code-analysis-action.md +12 -0
  41. package/.claude/skills/skill-generator/templates/llm-action.md +12 -0
  42. package/.claude/skills/skill-generator/templates/script-template.md +368 -0
  43. package/.claude/skills/skill-generator/templates/sequential-phase.md +14 -0
  44. package/.claude/skills/skill-generator/templates/skill-md.md +14 -0
  45. package/.claude/skills/skill-tuning/SKILL.md +130 -266
  46. package/.claude/skills/skill-tuning/phases/orchestrator.md +95 -283
  47. package/.claude/skills/skill-tuning/specs/problem-taxonomy.md +90 -198
  48. package/.claude/skills/skill-tuning/specs/tuning-strategies.md +193 -1345
  49. package/.claude/workflows/cli-templates/schemas/plan-verify-agent-schema.json +47 -0
  50. package/.claude/workflows/cli-templates/schemas/verify-json-schema.json +158 -0
  51. package/.claude/workflows/cli-tools-usage.md +1 -1
  52. package/.codex/AGENTS.md +1 -3
  53. package/.codex/prompts/analyze-with-file.md +607 -0
  54. package/.codex/prompts/brainstorm-to-cycle.md +455 -0
  55. package/.codex/prompts/brainstorm-with-file.md +933 -0
  56. package/.codex/prompts/debug-with-file.md +15 -20
  57. package/.codex/skills/ccw-cli-tools/SKILL.md +559 -0
  58. package/ccw/dist/commands/cli.d.ts.map +1 -1
  59. package/ccw/dist/commands/cli.js +29 -5
  60. package/ccw/dist/commands/cli.js.map +1 -1
  61. package/ccw/dist/commands/issue.d.ts +2 -0
  62. package/ccw/dist/commands/issue.d.ts.map +1 -1
  63. package/ccw/dist/commands/issue.js +62 -20
  64. package/ccw/dist/commands/issue.js.map +1 -1
  65. package/ccw/dist/config/litellm-api-config-manager.d.ts.map +1 -1
  66. package/ccw/dist/config/litellm-api-config-manager.js +5 -3
  67. package/ccw/dist/config/litellm-api-config-manager.js.map +1 -1
  68. package/ccw/dist/config/litellm-provider-models.d.ts +73 -0
  69. package/ccw/dist/config/litellm-provider-models.d.ts.map +1 -0
  70. package/ccw/dist/config/litellm-provider-models.js +172 -0
  71. package/ccw/dist/config/litellm-provider-models.js.map +1 -0
  72. package/ccw/dist/config/provider-models.d.ts +25 -51
  73. package/ccw/dist/config/provider-models.d.ts.map +1 -1
  74. package/ccw/dist/config/provider-models.js +84 -149
  75. package/ccw/dist/config/provider-models.js.map +1 -1
  76. package/ccw/dist/config/storage-paths.d.ts.map +1 -1
  77. package/ccw/dist/config/storage-paths.js +23 -5
  78. package/ccw/dist/config/storage-paths.js.map +1 -1
  79. package/ccw/dist/core/auth/csrf-middleware.js +3 -3
  80. package/ccw/dist/core/auth/csrf-middleware.js.map +1 -1
  81. package/ccw/dist/core/dashboard-generator.d.ts.map +1 -1
  82. package/ccw/dist/core/dashboard-generator.js +3 -1
  83. package/ccw/dist/core/dashboard-generator.js.map +1 -1
  84. package/ccw/dist/core/routes/claude-routes.d.ts.map +1 -1
  85. package/ccw/dist/core/routes/claude-routes.js +206 -14
  86. package/ccw/dist/core/routes/claude-routes.js.map +1 -1
  87. package/ccw/dist/core/routes/cli-routes.d.ts.map +1 -1
  88. package/ccw/dist/core/routes/cli-routes.js.map +1 -1
  89. package/ccw/dist/core/routes/commands-routes.d.ts +7 -0
  90. package/ccw/dist/core/routes/commands-routes.d.ts.map +1 -0
  91. package/ccw/dist/core/routes/commands-routes.js +480 -0
  92. package/ccw/dist/core/routes/commands-routes.js.map +1 -0
  93. package/ccw/dist/core/routes/model-routes.d.ts +11 -0
  94. package/ccw/dist/core/routes/model-routes.d.ts.map +1 -0
  95. package/ccw/dist/core/routes/model-routes.js +112 -0
  96. package/ccw/dist/core/routes/model-routes.js.map +1 -0
  97. package/ccw/dist/core/routes/nav-status-routes.d.ts.map +1 -1
  98. package/ccw/dist/core/routes/nav-status-routes.js +84 -1
  99. package/ccw/dist/core/routes/nav-status-routes.js.map +1 -1
  100. package/ccw/dist/core/routes/provider-routes.d.ts +11 -0
  101. package/ccw/dist/core/routes/provider-routes.d.ts.map +1 -0
  102. package/ccw/dist/core/routes/provider-routes.js +67 -0
  103. package/ccw/dist/core/routes/provider-routes.js.map +1 -0
  104. package/ccw/dist/core/routes/skills-routes.d.ts.map +1 -1
  105. package/ccw/dist/core/routes/skills-routes.js +219 -7
  106. package/ccw/dist/core/routes/skills-routes.js.map +1 -1
  107. package/ccw/dist/core/routes/system-routes.d.ts.map +1 -1
  108. package/ccw/dist/core/routes/system-routes.js +58 -6
  109. package/ccw/dist/core/routes/system-routes.js.map +1 -1
  110. package/ccw/dist/core/server.d.ts.map +1 -1
  111. package/ccw/dist/core/server.js +13 -0
  112. package/ccw/dist/core/server.js.map +1 -1
  113. package/ccw/dist/mcp-server/index.js +2 -2
  114. package/ccw/dist/mcp-server/index.js.map +1 -1
  115. package/ccw/dist/tools/claude-cli-tools.d.ts +48 -11
  116. package/ccw/dist/tools/claude-cli-tools.d.ts.map +1 -1
  117. package/ccw/dist/tools/claude-cli-tools.js +146 -50
  118. package/ccw/dist/tools/claude-cli-tools.js.map +1 -1
  119. package/ccw/dist/tools/cli-config-manager.d.ts +1 -13
  120. package/ccw/dist/tools/cli-config-manager.d.ts.map +1 -1
  121. package/ccw/dist/tools/cli-config-manager.js +3 -27
  122. package/ccw/dist/tools/cli-config-manager.js.map +1 -1
  123. package/ccw/dist/tools/cli-executor-core.d.ts.map +1 -1
  124. package/ccw/dist/tools/cli-executor-core.js +7 -2
  125. package/ccw/dist/tools/cli-executor-core.js.map +1 -1
  126. package/ccw/dist/tools/cli-executor-state.d.ts.map +1 -1
  127. package/ccw/dist/tools/cli-history-store.d.ts +11 -0
  128. package/ccw/dist/tools/cli-history-store.d.ts.map +1 -1
  129. package/ccw/dist/tools/cli-history-store.js +82 -2
  130. package/ccw/dist/tools/cli-history-store.js.map +1 -1
  131. package/ccw/dist/tools/command-registry.d.ts +7 -0
  132. package/ccw/dist/tools/command-registry.d.ts.map +1 -1
  133. package/ccw/dist/tools/command-registry.js +14 -1
  134. package/ccw/dist/tools/command-registry.js.map +1 -1
  135. package/ccw/dist/tools/generate-module-docs.d.ts.map +1 -1
  136. package/ccw/dist/tools/generate-module-docs.js +11 -7
  137. package/ccw/dist/tools/generate-module-docs.js.map +1 -1
  138. package/ccw/dist/tools/litellm-executor.d.ts +1 -0
  139. package/ccw/dist/tools/litellm-executor.d.ts.map +1 -1
  140. package/ccw/dist/tools/litellm-executor.js +11 -9
  141. package/ccw/dist/tools/litellm-executor.js.map +1 -1
  142. package/ccw/dist/types/skill-types.d.ts +97 -0
  143. package/ccw/dist/types/skill-types.d.ts.map +1 -0
  144. package/ccw/dist/types/skill-types.js +6 -0
  145. package/ccw/dist/types/skill-types.js.map +1 -0
  146. package/ccw/src/commands/cli.ts +36 -5
  147. package/ccw/src/commands/issue.ts +81 -26
  148. package/ccw/src/config/litellm-api-config-manager.ts +5 -3
  149. package/ccw/src/config/litellm-provider-models.ts +222 -0
  150. package/ccw/src/config/provider-models.ts +91 -190
  151. package/ccw/src/config/storage-paths.ts +20 -5
  152. package/ccw/src/core/auth/csrf-middleware.ts +3 -3
  153. package/ccw/src/core/dashboard-generator.ts +3 -1
  154. package/ccw/src/core/routes/claude-routes.ts +233 -15
  155. package/ccw/src/core/routes/cli-routes.ts +2 -3
  156. package/ccw/src/core/routes/commands-routes.ts +620 -0
  157. package/ccw/src/core/routes/nav-status-routes.ts +95 -1
  158. package/ccw/src/core/routes/provider-routes.ts +78 -0
  159. package/ccw/src/core/routes/skills-routes.ts +266 -45
  160. package/ccw/src/core/routes/system-routes.ts +102 -50
  161. package/ccw/src/core/server.ts +13 -0
  162. package/ccw/src/mcp-server/index.ts +2 -2
  163. package/ccw/src/templates/dashboard-css/18-cli-settings.css +35 -0
  164. package/ccw/src/templates/dashboard-css/37-commands.css +193 -0
  165. package/ccw/src/templates/dashboard-js/components/navigation.js +4 -0
  166. package/ccw/src/templates/dashboard-js/i18n.js +116 -0
  167. package/ccw/src/templates/dashboard-js/views/cli-manager.js +249 -4
  168. package/ccw/src/templates/dashboard-js/views/commands-manager.js +503 -0
  169. package/ccw/src/templates/dashboard-js/views/issue-manager.js +7 -7
  170. package/ccw/src/templates/dashboard-js/views/mcp-manager.js +2 -7
  171. package/ccw/src/templates/dashboard-js/views/skills-manager.js +164 -23
  172. package/ccw/src/templates/dashboard.html +7 -0
  173. package/ccw/src/tools/claude-cli-tools.ts +170 -56
  174. package/ccw/src/tools/cli-config-manager.ts +2 -33
  175. package/ccw/src/tools/cli-executor-core.ts +8 -2
  176. package/ccw/src/tools/cli-history-store.ts +92 -2
  177. package/ccw/src/tools/command-registry.ts +16 -1
  178. package/ccw/src/tools/generate-module-docs.ts +11 -7
  179. package/ccw/src/tools/litellm-executor.ts +13 -9
  180. package/ccw/src/types/skill-types.ts +99 -0
  181. package/package.json +1 -1
  182. package/.claude/commands/enhance-prompt.md +0 -93
  183. package/.claude/commands/memory/code-map-memory.md +0 -687
  184. package/.claude/commands/memory/docs.md +0 -615
  185. package/.claude/commands/memory/load-skill-memory.md +0 -182
  186. package/.claude/commands/memory/skill-memory.md +0 -525
  187. package/.claude/commands/memory/swagger-docs.md +0 -773
  188. package/.claude/commands/memory/tech-research-rules.md +0 -310
  189. package/.claude/commands/memory/workflow-skill-memory.md +0 -517
  190. package/.claude/commands/task/breakdown.md +0 -208
  191. package/.claude/commands/task/create.md +0 -152
  192. package/.claude/commands/task/execute.md +0 -270
  193. package/.claude/commands/task/replan.md +0 -441
  194. package/.claude/commands/version.md +0 -254
  195. package/.claude/commands/workflow/action-plan-verify.md +0 -485
  196. package/.claude/commands/workflow/brainstorm/api-designer.md +0 -587
  197. package/.claude/commands/workflow/brainstorm/data-architect.md +0 -220
  198. package/.claude/commands/workflow/brainstorm/product-manager.md +0 -200
  199. package/.claude/commands/workflow/brainstorm/product-owner.md +0 -200
  200. package/.claude/commands/workflow/brainstorm/scrum-master.md +0 -200
  201. package/.claude/commands/workflow/brainstorm/subject-matter-expert.md +0 -200
  202. package/.claude/commands/workflow/brainstorm/system-architect.md +0 -389
  203. package/.claude/commands/workflow/brainstorm/ui-designer.md +0 -221
  204. package/.claude/commands/workflow/brainstorm/ux-expert.md +0 -221
  205. package/.claude/commands/workflow/debug.md +0 -331
  206. package/.claude/commands/workflow/develop-with-file.md +0 -1044
  207. package/.claude/skills/ccw-loop/README.md +0 -303
  208. package/.claude/skills/skill-generator/templates/script-bash.md +0 -277
  209. package/.claude/skills/skill-generator/templates/script-python.md +0 -198
  210. package/.codex/prompts/debug.md +0 -318
  211. package/ccw/src/core/routes/mcp-routes.ts.backup +0 -549
@@ -0,0 +1,620 @@
1
+ /**
2
+ * Commands Routes Module
3
+ * Handles all Commands-related API endpoints
4
+ *
5
+ * API Endpoints:
6
+ * - GET /api/commands - List all commands with groups
7
+ * - POST /api/commands/:name/toggle - Enable/disable single command
8
+ * - POST /api/commands/group/:groupName/toggle - Batch toggle commands by group
9
+ */
10
+ import { existsSync, readdirSync, readFileSync, mkdirSync, renameSync } from 'fs';
11
+ import { join, relative, dirname, basename } from 'path';
12
+ import { homedir } from 'os';
13
+ import { validatePath as validateAllowedPath } from '../../utils/path-validator.js';
14
+ import type { RouteContext } from './types.js';
15
+
16
+ // ========== Types ==========
17
+
18
+ type CommandLocation = 'project' | 'user';
19
+
20
+ interface CommandMetadata {
21
+ name: string;
22
+ description: string;
23
+ group: string;
24
+ argumentHint?: string;
25
+ allowedTools?: string[];
26
+ }
27
+
28
+ interface CommandInfo {
29
+ name: string;
30
+ description: string;
31
+ group: string;
32
+ enabled: boolean;
33
+ location: CommandLocation;
34
+ path: string;
35
+ relativePath: string; // Path relative to commands root (e.g., 'workflow/plan.md')
36
+ argumentHint?: string;
37
+ allowedTools?: string[];
38
+ }
39
+
40
+ interface CommandsConfig {
41
+ projectCommands: CommandInfo[];
42
+ userCommands: CommandInfo[];
43
+ groups: string[];
44
+ }
45
+
46
+ interface CommandOperationResult {
47
+ success: boolean;
48
+ message: string;
49
+ commandName?: string;
50
+ location?: CommandLocation;
51
+ status?: number;
52
+ }
53
+
54
+ interface GroupDefinition {
55
+ name: string;
56
+ icon?: string;
57
+ color?: string;
58
+ }
59
+
60
+ interface CommandGroupsConfig {
61
+ groups: Record<string, GroupDefinition>; // Custom group definitions
62
+ assignments: Record<string, string>; // commandName -> groupId mapping
63
+ }
64
+
65
+ // ========== Helper Functions ==========
66
+
67
+ function isRecord(value: unknown): value is Record<string, unknown> {
68
+ return typeof value === 'object' && value !== null;
69
+ }
70
+
71
+ /**
72
+ * Get commands directory path
73
+ */
74
+ function getCommandsDir(location: CommandLocation, projectPath: string): string {
75
+ if (location === 'project') {
76
+ return join(projectPath, '.claude', 'commands');
77
+ }
78
+ return join(homedir(), '.claude', 'commands');
79
+ }
80
+
81
+
82
+ /**
83
+ * Parse YAML frontmatter from command file
84
+ */
85
+ function parseCommandFrontmatter(content: string): CommandMetadata {
86
+ const result: CommandMetadata = {
87
+ name: '',
88
+ description: '',
89
+ group: 'other' // Default group
90
+ };
91
+
92
+ // Check for YAML frontmatter
93
+ if (content.startsWith('---')) {
94
+ const endIndex = content.indexOf('---', 3);
95
+ if (endIndex > 0) {
96
+ const frontmatter = content.substring(3, endIndex).trim();
97
+
98
+ // Parse frontmatter lines
99
+ const lines = frontmatter.split(/[\r\n]+/);
100
+ for (const line of lines) {
101
+ const colonIndex = line.indexOf(':');
102
+ if (colonIndex > 0) {
103
+ const key = line.substring(0, colonIndex).trim().toLowerCase();
104
+ const value = line.substring(colonIndex + 1).trim().replace(/^["']|["']$/g, '');
105
+
106
+ if (key === 'name') {
107
+ result.name = value;
108
+ } else if (key === 'description') {
109
+ result.description = value;
110
+ } else if (key === 'group') {
111
+ result.group = value || 'other';
112
+ } else if (key === 'argument-hint') {
113
+ result.argumentHint = value;
114
+ } else if (key === 'allowed-tools') {
115
+ result.allowedTools = value
116
+ .replace(/^\[|\]$/g, '')
117
+ .split(',')
118
+ .map(t => t.trim())
119
+ .filter(Boolean);
120
+ }
121
+ }
122
+ }
123
+ }
124
+ }
125
+
126
+ return result;
127
+ }
128
+
129
+ /**
130
+ * Get command groups config file path
131
+ */
132
+ function getGroupsConfigPath(location: CommandLocation, projectPath: string): string {
133
+ const baseDir = location === 'project'
134
+ ? join(projectPath, '.claude')
135
+ : join(homedir(), '.claude');
136
+ return join(baseDir, 'command-groups.json');
137
+ }
138
+
139
+ /**
140
+ * Load command groups configuration
141
+ */
142
+ function loadGroupsConfig(location: CommandLocation, projectPath: string): CommandGroupsConfig {
143
+ const configPath = getGroupsConfigPath(location, projectPath);
144
+
145
+ const defaultConfig: CommandGroupsConfig = {
146
+ groups: {},
147
+ assignments: {}
148
+ };
149
+
150
+ if (!existsSync(configPath)) {
151
+ return defaultConfig;
152
+ }
153
+
154
+ try {
155
+ const content = readFileSync(configPath, 'utf8');
156
+ const parsed = JSON.parse(content);
157
+
158
+ return {
159
+ groups: isRecord(parsed.groups) ? parsed.groups as Record<string, GroupDefinition> : {},
160
+ assignments: isRecord(parsed.assignments) ? parsed.assignments as Record<string, string> : {}
161
+ };
162
+ } catch (err) {
163
+ console.error(`[Commands] Failed to load groups config from ${configPath}:`, err);
164
+ return defaultConfig;
165
+ }
166
+ }
167
+
168
+ /**
169
+ * Save command groups configuration
170
+ */
171
+ function saveGroupsConfig(location: CommandLocation, projectPath: string, config: CommandGroupsConfig): void {
172
+ const configPath = getGroupsConfigPath(location, projectPath);
173
+ const configDir = dirname(configPath);
174
+
175
+ if (!existsSync(configDir)) {
176
+ mkdirSync(configDir, { recursive: true });
177
+ }
178
+
179
+ try {
180
+ const content = JSON.stringify(config, null, 2);
181
+ require('fs').writeFileSync(configPath, content, 'utf8');
182
+ } catch (err) {
183
+ console.error(`[Commands] Failed to save groups config to ${configPath}:`, err);
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Get group for a command (from config or inferred from path)
189
+ */
190
+ function getCommandGroup(commandName: string, relativePath: string, location: CommandLocation, projectPath: string): string {
191
+ // First check custom assignments
192
+ const config = loadGroupsConfig(location, projectPath);
193
+ if (config.assignments[commandName]) {
194
+ return config.assignments[commandName];
195
+ }
196
+
197
+ // Fallback to path-based inference - use full directory path as group
198
+ const parts = relativePath.split(/[/\\]/);
199
+ if (parts.length > 1) {
200
+ // Use full directory path (excluding filename) as group
201
+ // e.g., 'workflow/review/code-review.md' -> 'workflow/review'
202
+ return parts.slice(0, -1).join('/');
203
+ }
204
+
205
+ return 'other';
206
+ }
207
+
208
+ /**
209
+ * Recursively scan directory for command files
210
+ */
211
+ function scanCommandsRecursive(
212
+ baseDir: string,
213
+ currentDir: string,
214
+ location: CommandLocation,
215
+ projectPath: string
216
+ ): CommandInfo[] {
217
+ const results: CommandInfo[] = [];
218
+
219
+ if (!existsSync(currentDir)) {
220
+ return results;
221
+ }
222
+
223
+ try {
224
+ const entries = readdirSync(currentDir, { withFileTypes: true });
225
+
226
+ for (const entry of entries) {
227
+ const fullPath = join(currentDir, entry.name);
228
+ let relativePath = relative(baseDir, fullPath);
229
+
230
+ if (entry.isDirectory()) {
231
+ // Recursively scan subdirectories
232
+ results.push(...scanCommandsRecursive(baseDir, fullPath, location, projectPath));
233
+ } else if (entry.isFile()) {
234
+ // Check for .md or .md.disabled files
235
+ const isEnabled = entry.name.endsWith('.md') && !entry.name.endsWith('.md.disabled');
236
+ const isDisabled = entry.name.endsWith('.md.disabled');
237
+
238
+ if (isEnabled || isDisabled) {
239
+ try {
240
+ const content = readFileSync(fullPath, 'utf8');
241
+ const metadata = parseCommandFrontmatter(content);
242
+
243
+ // For disabled files, remove .disabled from relativePath for consistency
244
+ if (isDisabled) {
245
+ relativePath = relativePath.replace(/\.disabled$/, '');
246
+ }
247
+
248
+ const commandName = metadata.name || basename(relativePath, '.md');
249
+
250
+ // Get group from external config (not from frontmatter)
251
+ const group = getCommandGroup(commandName, relativePath, location, projectPath);
252
+
253
+ results.push({
254
+ name: commandName,
255
+ description: metadata.description,
256
+ group,
257
+ enabled: isEnabled,
258
+ location,
259
+ path: fullPath,
260
+ relativePath,
261
+ argumentHint: metadata.argumentHint,
262
+ allowedTools: metadata.allowedTools
263
+ });
264
+ } catch (err) {
265
+ // Skip files that fail to read
266
+ console.error(`[Commands] Failed to read ${fullPath}:`, err);
267
+ }
268
+ }
269
+ }
270
+ }
271
+ } catch (err) {
272
+ console.error(`[Commands] Failed to scan directory ${currentDir}:`, err);
273
+ }
274
+
275
+ return results;
276
+ }
277
+
278
+ /**
279
+ * Get all commands configuration
280
+ */
281
+ function getCommandsConfig(projectPath: string): CommandsConfig {
282
+ const result: CommandsConfig = {
283
+ projectCommands: [],
284
+ userCommands: [],
285
+ groups: []
286
+ };
287
+
288
+ const groupSet = new Set<string>();
289
+
290
+ try {
291
+ // Scan project commands (includes both .md and .md.disabled)
292
+ const projectDir = getCommandsDir('project', projectPath);
293
+ result.projectCommands = scanCommandsRecursive(projectDir, projectDir, 'project', projectPath);
294
+
295
+ // Scan user commands (includes both .md and .md.disabled)
296
+ const userDir = getCommandsDir('user', projectPath);
297
+ result.userCommands = scanCommandsRecursive(userDir, userDir, 'user', projectPath);
298
+
299
+ // Collect all groups
300
+ for (const cmd of [...result.projectCommands, ...result.userCommands]) {
301
+ groupSet.add(cmd.group);
302
+ }
303
+
304
+ result.groups = Array.from(groupSet).sort();
305
+ } catch (error) {
306
+ console.error('[Commands] Error reading commands config:', error);
307
+ }
308
+
309
+ return result;
310
+ }
311
+
312
+
313
+ /**
314
+ * Find command by name in commands list
315
+ */
316
+ function findCommand(
317
+ commands: CommandInfo[],
318
+ commandName: string
319
+ ): CommandInfo | undefined {
320
+ // Try exact name match first
321
+ let cmd = commands.find(c => c.name === commandName);
322
+ if (cmd) return cmd;
323
+
324
+ // Try matching by relative path (without extension)
325
+ cmd = commands.find(c => {
326
+ const pathWithoutExt = c.relativePath.replace(/\.md$/, '');
327
+ return pathWithoutExt === commandName;
328
+ });
329
+ if (cmd) return cmd;
330
+
331
+ // Try matching by filename (without extension)
332
+ cmd = commands.find(c => {
333
+ const filename = basename(c.relativePath, '.md');
334
+ return filename === commandName;
335
+ });
336
+
337
+ return cmd;
338
+ }
339
+
340
+ /**
341
+ * Toggle a command's enabled state
342
+ */
343
+ async function toggleCommand(
344
+ commandName: string,
345
+ location: CommandLocation,
346
+ projectPath: string,
347
+ initialPath: string
348
+ ): Promise<CommandOperationResult> {
349
+ try {
350
+ // Validate command name
351
+ if (commandName.includes('..')) {
352
+ return { success: false, message: 'Invalid command name', status: 400 };
353
+ }
354
+
355
+ const config = getCommandsConfig(projectPath);
356
+ const commands = location === 'project' ? config.projectCommands : config.userCommands;
357
+ const command = findCommand(commands, commandName);
358
+
359
+ if (!command) {
360
+ return { success: false, message: 'Command not found', status: 404 };
361
+ }
362
+
363
+ const commandsDir = getCommandsDir(location, projectPath);
364
+ // relativePath already includes .md extension (e.g., 'workflow/plan.md')
365
+ const commandPath = join(commandsDir, command.relativePath);
366
+ const disabledPath = commandPath + '.disabled';
367
+
368
+ if (command.enabled) {
369
+ // Disable: rename .md to .md.disabled
370
+ if (!existsSync(commandPath)) {
371
+ return { success: false, message: 'Command file not found', status: 404 };
372
+ }
373
+ if (existsSync(disabledPath)) {
374
+ return { success: false, message: 'Command already disabled', status: 409 };
375
+ }
376
+
377
+ renameSync(commandPath, disabledPath);
378
+ return {
379
+ success: true,
380
+ message: 'Command disabled',
381
+ commandName: command.name,
382
+ location
383
+ };
384
+ } else {
385
+ // Enable: rename .md.disabled back to .md
386
+ if (!existsSync(disabledPath)) {
387
+ return { success: false, message: 'Disabled command not found', status: 404 };
388
+ }
389
+ if (existsSync(commandPath)) {
390
+ return { success: false, message: 'Command already enabled', status: 409 };
391
+ }
392
+
393
+ renameSync(disabledPath, commandPath);
394
+ return {
395
+ success: true,
396
+ message: 'Command enabled',
397
+ commandName: command.name,
398
+ location
399
+ };
400
+ }
401
+ } catch (error) {
402
+ return {
403
+ success: false,
404
+ message: (error as Error).message,
405
+ status: 500
406
+ };
407
+ }
408
+ }
409
+
410
+ /**
411
+ * Toggle all commands in a group
412
+ */
413
+ async function toggleGroup(
414
+ groupName: string,
415
+ location: CommandLocation,
416
+ enable: boolean,
417
+ projectPath: string,
418
+ initialPath: string
419
+ ): Promise<{ success: boolean; results: CommandOperationResult[]; message: string }> {
420
+ const config = getCommandsConfig(projectPath);
421
+ const commands = location === 'project' ? config.projectCommands : config.userCommands;
422
+
423
+ // Filter commands by group and current state
424
+ const targetCommands = commands.filter(cmd =>
425
+ cmd.group === groupName && cmd.enabled !== enable
426
+ );
427
+
428
+ if (targetCommands.length === 0) {
429
+ return {
430
+ success: true,
431
+ results: [],
432
+ message: `No commands to ${enable ? 'enable' : 'disable'} in group '${groupName}'`
433
+ };
434
+ }
435
+
436
+ const results: CommandOperationResult[] = [];
437
+
438
+ for (const cmd of targetCommands) {
439
+ const result = await toggleCommand(cmd.name, location, projectPath, initialPath);
440
+ results.push(result);
441
+ }
442
+
443
+ const successCount = results.filter(r => r.success).length;
444
+ const failCount = results.filter(r => !r.success).length;
445
+
446
+ return {
447
+ success: failCount === 0,
448
+ results,
449
+ message: `${enable ? 'Enabled' : 'Disabled'} ${successCount} commands${failCount > 0 ? `, ${failCount} failed` : ''}`
450
+ };
451
+ }
452
+
453
+ // ========== Route Handler ==========
454
+
455
+ /**
456
+ * Handle Commands routes
457
+ * @returns true if route was handled, false otherwise
458
+ */
459
+ export async function handleCommandsRoutes(ctx: RouteContext): Promise<boolean> {
460
+ const { pathname, url, req, res, initialPath, handlePostRequest } = ctx;
461
+
462
+ // GET /api/commands - List all commands
463
+ if (pathname === '/api/commands' && req.method === 'GET') {
464
+ const projectPathParam = url.searchParams.get('path') || initialPath;
465
+
466
+ try {
467
+ const validatedProjectPath = await validateAllowedPath(projectPathParam, {
468
+ mustExist: true,
469
+ allowedDirectories: [initialPath]
470
+ });
471
+
472
+ const config = getCommandsConfig(validatedProjectPath);
473
+
474
+ // Include groups config from both project and user
475
+ const projectGroupsConfig = loadGroupsConfig('project', validatedProjectPath);
476
+ const userGroupsConfig = loadGroupsConfig('user', validatedProjectPath);
477
+
478
+ res.writeHead(200, { 'Content-Type': 'application/json' });
479
+ res.end(JSON.stringify({
480
+ ...config,
481
+ projectGroupsConfig,
482
+ userGroupsConfig
483
+ }));
484
+ } catch (err) {
485
+ const message = err instanceof Error ? err.message : String(err);
486
+ const status = message.includes('Access denied') ? 403 : 400;
487
+ console.error(`[Commands] Project path validation failed: ${message}`);
488
+ res.writeHead(status, { 'Content-Type': 'application/json' });
489
+ res.end(JSON.stringify({
490
+ error: status === 403 ? 'Access denied' : 'Invalid path',
491
+ projectCommands: [],
492
+ userCommands: [],
493
+ groups: []
494
+ }));
495
+ }
496
+ return true;
497
+ }
498
+
499
+ // POST /api/commands/:name/toggle - Toggle single command
500
+ if (pathname.match(/^\/api\/commands\/[^/]+\/toggle$/) && req.method === 'POST') {
501
+ const pathParts = pathname.split('/');
502
+ const commandName = decodeURIComponent(pathParts[3]);
503
+
504
+ handlePostRequest(req, res, async (body) => {
505
+ if (!isRecord(body)) {
506
+ return { error: 'Invalid request body', status: 400 };
507
+ }
508
+
509
+ const locationValue = body.location;
510
+ const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
511
+
512
+ if (locationValue !== 'project' && locationValue !== 'user') {
513
+ return { error: 'Location is required (project or user)' };
514
+ }
515
+
516
+ const projectPath = projectPathParam || initialPath;
517
+ return toggleCommand(commandName, locationValue, projectPath, initialPath);
518
+ });
519
+ return true;
520
+ }
521
+
522
+ // POST /api/commands/group/:groupName/toggle - Toggle all commands in group
523
+ if (pathname.match(/^\/api\/commands\/group\/[^/]+\/toggle$/) && req.method === 'POST') {
524
+ const pathParts = pathname.split('/');
525
+ const groupName = decodeURIComponent(pathParts[4]);
526
+
527
+ handlePostRequest(req, res, async (body) => {
528
+ if (!isRecord(body)) {
529
+ return { error: 'Invalid request body', status: 400 };
530
+ }
531
+
532
+ const locationValue = body.location;
533
+ const enable = body.enable === true;
534
+ const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
535
+
536
+ if (locationValue !== 'project' && locationValue !== 'user') {
537
+ return { error: 'Location is required (project or user)' };
538
+ }
539
+
540
+ const projectPath = projectPathParam || initialPath;
541
+ return toggleGroup(groupName, locationValue, enable, projectPath, initialPath);
542
+ });
543
+ return true;
544
+ }
545
+
546
+ // GET /api/commands/groups - Get groups configuration
547
+ if (pathname === '/api/commands/groups' && req.method === 'GET') {
548
+ const projectPathParam = url.searchParams.get('path') || initialPath;
549
+ const location = url.searchParams.get('location') || 'project';
550
+
551
+ try {
552
+ const validatedProjectPath = await validateAllowedPath(projectPathParam, {
553
+ mustExist: true,
554
+ allowedDirectories: [initialPath]
555
+ });
556
+
557
+ if (location !== 'project' && location !== 'user') {
558
+ res.writeHead(400, { 'Content-Type': 'application/json' });
559
+ res.end(JSON.stringify({ error: 'Invalid location' }));
560
+ return true;
561
+ }
562
+
563
+ const groupsConfig = loadGroupsConfig(location as CommandLocation, validatedProjectPath);
564
+ res.writeHead(200, { 'Content-Type': 'application/json' });
565
+ res.end(JSON.stringify(groupsConfig));
566
+ } catch (err) {
567
+ const message = err instanceof Error ? err.message : String(err);
568
+ const status = message.includes('Access denied') ? 403 : 400;
569
+ res.writeHead(status, { 'Content-Type': 'application/json' });
570
+ res.end(JSON.stringify({ error: message }));
571
+ }
572
+ return true;
573
+ }
574
+
575
+ // PUT /api/commands/groups - Update groups configuration
576
+ if (pathname === '/api/commands/groups' && req.method === 'PUT') {
577
+ const projectPathParam = url.searchParams.get('path') || initialPath;
578
+ const location = url.searchParams.get('location') || 'project';
579
+
580
+ handlePostRequest(req, res, async (body) => {
581
+ try {
582
+ const validatedProjectPath = await validateAllowedPath(projectPathParam, {
583
+ mustExist: true,
584
+ allowedDirectories: [initialPath]
585
+ });
586
+
587
+ if (location !== 'project' && location !== 'user') {
588
+ return { error: 'Invalid location', status: 400 };
589
+ }
590
+
591
+ if (!isRecord(body)) {
592
+ return { error: 'Invalid request body', status: 400 };
593
+ }
594
+
595
+ // Validate and save groups config
596
+ const config: CommandGroupsConfig = {
597
+ groups: isRecord(body.groups) ? body.groups as Record<string, GroupDefinition> : {},
598
+ assignments: isRecord(body.assignments) ? body.assignments as Record<string, string> : {}
599
+ };
600
+
601
+ saveGroupsConfig(location as CommandLocation, validatedProjectPath, config);
602
+
603
+ return {
604
+ success: true,
605
+ message: 'Groups configuration updated',
606
+ data: config,
607
+ status: 200
608
+ };
609
+ } catch (err) {
610
+ const message = err instanceof Error ? err.message : String(err);
611
+ const status = message.includes('Access denied') ? 403 : 400;
612
+ console.error(`[Commands] Failed to update groups config: ${message}`);
613
+ return { error: message, status };
614
+ }
615
+ });
616
+ return true;
617
+ }
618
+
619
+ return false;
620
+ }