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
@@ -51,6 +51,92 @@ function countDiscoveries(projectPath: string): number {
51
51
  }
52
52
  }
53
53
 
54
+ /**
55
+ * Recursively count command files in a directory
56
+ */
57
+ function countCommandsInDir(dirPath: string): { enabled: number; disabled: number } {
58
+ let enabled = 0;
59
+ let disabled = 0;
60
+
61
+ if (!existsSync(dirPath)) {
62
+ return { enabled, disabled };
63
+ }
64
+
65
+ try {
66
+ const entries = readdirSync(dirPath, { withFileTypes: true });
67
+ for (const entry of entries) {
68
+ const fullPath = join(dirPath, entry.name);
69
+ if (entry.isDirectory()) {
70
+ if (entry.name === '_disabled') {
71
+ // Count disabled commands recursively
72
+ disabled += countAllMdFiles(fullPath);
73
+ } else {
74
+ // Recursively count enabled commands
75
+ const subCounts = countCommandsInDir(fullPath);
76
+ enabled += subCounts.enabled;
77
+ disabled += subCounts.disabled;
78
+ }
79
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
80
+ enabled++;
81
+ }
82
+ }
83
+ } catch { /* ignore */ }
84
+
85
+ return { enabled, disabled };
86
+ }
87
+
88
+ /**
89
+ * Count all .md files recursively (for disabled directory)
90
+ */
91
+ function countAllMdFiles(dirPath: string): number {
92
+ let count = 0;
93
+ if (!existsSync(dirPath)) return count;
94
+
95
+ try {
96
+ const entries = readdirSync(dirPath, { withFileTypes: true });
97
+ for (const entry of entries) {
98
+ const fullPath = join(dirPath, entry.name);
99
+ if (entry.isDirectory()) {
100
+ count += countAllMdFiles(fullPath);
101
+ } else if (entry.isFile() && entry.name.endsWith('.md')) {
102
+ count++;
103
+ }
104
+ }
105
+ } catch { /* ignore */ }
106
+
107
+ return count;
108
+ }
109
+
110
+ /**
111
+ * Count commands from project and user directories
112
+ */
113
+ function countCommands(projectPath: string): {
114
+ project: { enabled: number; disabled: number };
115
+ user: { enabled: number; disabled: number };
116
+ total: number;
117
+ enabled: number;
118
+ disabled: number;
119
+ } {
120
+ // Project commands
121
+ const projectDir = join(projectPath, '.claude', 'commands');
122
+ const projectCounts = countCommandsInDir(projectDir);
123
+
124
+ // User commands
125
+ const userDir = join(homedir(), '.claude', 'commands');
126
+ const userCounts = countCommandsInDir(userDir);
127
+
128
+ const totalEnabled = projectCounts.enabled + userCounts.enabled;
129
+ const totalDisabled = projectCounts.disabled + userCounts.disabled;
130
+
131
+ return {
132
+ project: projectCounts,
133
+ user: userCounts,
134
+ total: totalEnabled + totalDisabled,
135
+ enabled: totalEnabled,
136
+ disabled: totalDisabled
137
+ };
138
+ }
139
+
54
140
  /**
55
141
  * Count skills from project and user directories
56
142
  */
@@ -197,10 +283,11 @@ export async function handleNavStatusRoutes(ctx: RouteContext): Promise<boolean>
197
283
  const projectPath = url.searchParams.get('path') || initialPath;
198
284
 
199
285
  // Execute all counts (synchronous file reads wrapped in Promise.resolve for consistency)
200
- const [issues, discoveries, skills, rules, claude, hooks] = await Promise.all([
286
+ const [issues, discoveries, skills, commands, rules, claude, hooks] = await Promise.all([
201
287
  Promise.resolve(countIssues(projectPath)),
202
288
  Promise.resolve(countDiscoveries(projectPath)),
203
289
  Promise.resolve(countSkills(projectPath)),
290
+ Promise.resolve(countCommands(projectPath)),
204
291
  Promise.resolve(countRules(projectPath)),
205
292
  Promise.resolve(countClaudeFiles(projectPath)),
206
293
  Promise.resolve(countHooks(projectPath))
@@ -210,6 +297,13 @@ export async function handleNavStatusRoutes(ctx: RouteContext): Promise<boolean>
210
297
  issues: { count: issues },
211
298
  discoveries: { count: discoveries },
212
299
  skills: { count: skills.total, project: skills.project, user: skills.user },
300
+ commands: {
301
+ count: commands.total,
302
+ enabled: commands.enabled,
303
+ disabled: commands.disabled,
304
+ project: commands.project,
305
+ user: commands.user
306
+ },
213
307
  rules: { count: rules.total, project: rules.project, user: rules.user },
214
308
  claude: { count: claude },
215
309
  hooks: { count: hooks.total, global: hooks.global, project: hooks.project },
@@ -0,0 +1,78 @@
1
+ /**
2
+ * Provider Reference Routes Module
3
+ * Handles read-only provider model reference API endpoints
4
+ */
5
+
6
+ import type { RouteContext } from './types.js';
7
+ import {
8
+ PROVIDER_MODELS,
9
+ getAllProviders,
10
+ getProviderModels
11
+ } from '../../config/provider-models.js';
12
+
13
+ /**
14
+ * Handle Provider Reference routes
15
+ * @returns true if route was handled, false otherwise
16
+ */
17
+ export async function handleProviderRoutes(ctx: RouteContext): Promise<boolean> {
18
+ const { pathname, req, res } = ctx;
19
+
20
+ // ========== GET ALL PROVIDERS ==========
21
+ // GET /api/providers
22
+ if (pathname === '/api/providers' && req.method === 'GET') {
23
+ try {
24
+ const providers = getAllProviders().map(id => ({
25
+ id,
26
+ name: PROVIDER_MODELS[id].name,
27
+ modelCount: PROVIDER_MODELS[id].models.length
28
+ }));
29
+
30
+ res.writeHead(200, { 'Content-Type': 'application/json' });
31
+ res.end(JSON.stringify({ success: true, providers }));
32
+ } catch (err) {
33
+ res.writeHead(500, { 'Content-Type': 'application/json' });
34
+ res.end(JSON.stringify({
35
+ success: false,
36
+ error: (err as Error).message
37
+ }));
38
+ }
39
+ return true;
40
+ }
41
+
42
+ // ========== GET MODELS FOR PROVIDER ==========
43
+ // GET /api/providers/:provider/models
44
+ const providerMatch = pathname.match(/^\/api\/providers\/([^\/]+)\/models$/);
45
+ if (providerMatch && req.method === 'GET') {
46
+ const provider = decodeURIComponent(providerMatch[1]);
47
+
48
+ try {
49
+ const models = getProviderModels(provider);
50
+
51
+ if (models.length === 0) {
52
+ res.writeHead(404, { 'Content-Type': 'application/json' });
53
+ res.end(JSON.stringify({
54
+ success: false,
55
+ error: `Provider not found: ${provider}`
56
+ }));
57
+ return true;
58
+ }
59
+
60
+ res.writeHead(200, { 'Content-Type': 'application/json' });
61
+ res.end(JSON.stringify({
62
+ success: true,
63
+ provider,
64
+ providerName: PROVIDER_MODELS[provider].name,
65
+ models
66
+ }));
67
+ } catch (err) {
68
+ res.writeHead(500, { 'Content-Type': 'application/json' });
69
+ res.end(JSON.stringify({
70
+ success: false,
71
+ error: (err as Error).message
72
+ }));
73
+ }
74
+ return true;
75
+ }
76
+
77
+ return false;
78
+ }
@@ -2,51 +2,24 @@
2
2
  * Skills Routes Module
3
3
  * Handles all Skills-related API endpoints
4
4
  */
5
- import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, promises as fsPromises } from 'fs';
5
+ import { readFileSync, existsSync, readdirSync, statSync, unlinkSync, renameSync, promises as fsPromises } from 'fs';
6
6
  import { join } from 'path';
7
7
  import { homedir } from 'os';
8
8
  import { executeCliTool } from '../../tools/cli-executor.js';
9
9
  import { SmartContentFormatter } from '../../tools/cli-output-converter.js';
10
10
  import { validatePath as validateAllowedPath } from '../../utils/path-validator.js';
11
11
  import type { RouteContext } from './types.js';
12
-
13
- type SkillLocation = 'project' | 'user';
14
-
15
- interface ParsedSkillFrontmatter {
16
- name: string;
17
- description: string;
18
- version: string | null;
19
- allowedTools: string[];
20
- content: string;
21
- }
22
-
23
- interface SkillSummary {
24
- name: string;
25
- folderName: string;
26
- description: string;
27
- version: string | null;
28
- allowedTools: string[];
29
- location: SkillLocation;
30
- path: string;
31
- supportingFiles: string[];
32
- }
33
-
34
- interface SkillsConfig {
35
- projectSkills: SkillSummary[];
36
- userSkills: SkillSummary[];
37
- }
38
-
39
- interface SkillInfo {
40
- name: string;
41
- description: string;
42
- version: string | null;
43
- allowedTools: string[];
44
- supportingFiles: string[];
45
- }
46
-
47
- type SkillFolderValidation =
48
- | { valid: true; errors: string[]; skillInfo: SkillInfo }
49
- | { valid: false; errors: string[]; skillInfo: null };
12
+ import type {
13
+ SkillLocation,
14
+ ParsedSkillFrontmatter,
15
+ SkillSummary,
16
+ SkillsConfig,
17
+ SkillInfo,
18
+ SkillFolderValidation,
19
+ DisabledSkillSummary,
20
+ ExtendedSkillsConfig,
21
+ SkillOperationResult
22
+ } from '../../types/skill-types.js';
50
23
 
51
24
  type GenerationType = 'description' | 'template';
52
25
 
@@ -65,6 +38,178 @@ function isRecord(value: unknown): value is Record<string, unknown> {
65
38
 
66
39
  // ========== Skills Helper Functions ==========
67
40
 
41
+ /**
42
+ * Disable a skill by renaming SKILL.md to SKILL.md.disabled
43
+ */
44
+ async function disableSkill(
45
+ skillName: string,
46
+ location: SkillLocation,
47
+ projectPath: string,
48
+ initialPath: string,
49
+ reason?: string // Kept for API compatibility but no longer used
50
+ ): Promise<SkillOperationResult> {
51
+ try {
52
+ // Validate skill name
53
+ if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
54
+ return { success: false, message: 'Invalid skill name', status: 400 };
55
+ }
56
+
57
+ // Get skill directory
58
+ let skillsDir: string;
59
+ if (location === 'project') {
60
+ try {
61
+ const validatedProjectPath = await validateAllowedPath(projectPath, { mustExist: true, allowedDirectories: [initialPath] });
62
+ skillsDir = join(validatedProjectPath, '.claude', 'skills');
63
+ } catch (err) {
64
+ const message = err instanceof Error ? err.message : String(err);
65
+ return { success: false, message: message.includes('Access denied') ? 'Access denied' : 'Invalid path', status: 403 };
66
+ }
67
+ } else {
68
+ skillsDir = join(homedir(), '.claude', 'skills');
69
+ }
70
+
71
+ const skillDir = join(skillsDir, skillName);
72
+ if (!existsSync(skillDir)) {
73
+ return { success: false, message: 'Skill not found', status: 404 };
74
+ }
75
+
76
+ const skillMdPath = join(skillDir, 'SKILL.md');
77
+ if (!existsSync(skillMdPath)) {
78
+ return { success: false, message: 'SKILL.md not found', status: 404 };
79
+ }
80
+
81
+ const disabledPath = join(skillDir, 'SKILL.md.disabled');
82
+ if (existsSync(disabledPath)) {
83
+ return { success: false, message: 'Skill already disabled', status: 409 };
84
+ }
85
+
86
+ // Rename: SKILL.md → SKILL.md.disabled
87
+ renameSync(skillMdPath, disabledPath);
88
+
89
+ return { success: true, message: 'Skill disabled', skillName, location };
90
+ } catch (error) {
91
+ return { success: false, message: (error as Error).message, status: 500 };
92
+ }
93
+ }
94
+
95
+ /**
96
+ * Enable a skill by renaming SKILL.md.disabled back to SKILL.md
97
+ */
98
+ async function enableSkill(
99
+ skillName: string,
100
+ location: SkillLocation,
101
+ projectPath: string,
102
+ initialPath: string
103
+ ): Promise<SkillOperationResult> {
104
+ try {
105
+ // Validate skill name
106
+ if (skillName.includes('/') || skillName.includes('\\') || skillName.includes('..')) {
107
+ return { success: false, message: 'Invalid skill name', status: 400 };
108
+ }
109
+
110
+ // Get skill directory
111
+ let skillsDir: string;
112
+ if (location === 'project') {
113
+ try {
114
+ const validatedProjectPath = await validateAllowedPath(projectPath, { mustExist: true, allowedDirectories: [initialPath] });
115
+ skillsDir = join(validatedProjectPath, '.claude', 'skills');
116
+ } catch (err) {
117
+ const message = err instanceof Error ? err.message : String(err);
118
+ return { success: false, message: message.includes('Access denied') ? 'Access denied' : 'Invalid path', status: 403 };
119
+ }
120
+ } else {
121
+ skillsDir = join(homedir(), '.claude', 'skills');
122
+ }
123
+
124
+ const skillDir = join(skillsDir, skillName);
125
+ if (!existsSync(skillDir)) {
126
+ return { success: false, message: 'Skill not found', status: 404 };
127
+ }
128
+
129
+ const disabledPath = join(skillDir, 'SKILL.md.disabled');
130
+ if (!existsSync(disabledPath)) {
131
+ return { success: false, message: 'Disabled skill not found', status: 404 };
132
+ }
133
+
134
+ const skillMdPath = join(skillDir, 'SKILL.md');
135
+ if (existsSync(skillMdPath)) {
136
+ return { success: false, message: 'Skill already enabled', status: 409 };
137
+ }
138
+
139
+ // Rename: SKILL.md.disabled → SKILL.md
140
+ renameSync(disabledPath, skillMdPath);
141
+
142
+ return { success: true, message: 'Skill enabled', skillName, location };
143
+ } catch (error) {
144
+ return { success: false, message: (error as Error).message, status: 500 };
145
+ }
146
+ }
147
+
148
+ /**
149
+ * Get list of disabled skills by checking for SKILL.md.disabled files
150
+ */
151
+ function getDisabledSkillsList(location: SkillLocation, projectPath: string): DisabledSkillSummary[] {
152
+ const result: DisabledSkillSummary[] = [];
153
+
154
+ // Get skills directory (not a separate disabled directory)
155
+ let skillsDir: string;
156
+ if (location === 'project') {
157
+ skillsDir = join(projectPath, '.claude', 'skills');
158
+ } else {
159
+ skillsDir = join(homedir(), '.claude', 'skills');
160
+ }
161
+
162
+ if (!existsSync(skillsDir)) {
163
+ return result;
164
+ }
165
+
166
+ try {
167
+ const skills = readdirSync(skillsDir, { withFileTypes: true });
168
+ for (const skill of skills) {
169
+ if (skill.isDirectory()) {
170
+ const disabledPath = join(skillsDir, skill.name, 'SKILL.md.disabled');
171
+ if (existsSync(disabledPath)) {
172
+ const content = readFileSync(disabledPath, 'utf8');
173
+ const parsed = parseSkillFrontmatter(content);
174
+ const skillDir = join(skillsDir, skill.name);
175
+ const supportingFiles = getSupportingFiles(skillDir);
176
+
177
+ result.push({
178
+ name: parsed.name || skill.name,
179
+ folderName: skill.name,
180
+ description: parsed.description,
181
+ version: parsed.version,
182
+ allowedTools: parsed.allowedTools,
183
+ location,
184
+ path: skillDir,
185
+ supportingFiles,
186
+ disabledAt: new Date().toISOString(), // Cannot get exact time without config file
187
+ reason: undefined // No longer stored
188
+ });
189
+ }
190
+ }
191
+ }
192
+ } catch (error) {
193
+ console.error(`[Skills] Failed to read disabled skills: ${error}`);
194
+ }
195
+
196
+ return result;
197
+ }
198
+
199
+ /**
200
+ * Get extended skills config including disabled skills
201
+ */
202
+ function getExtendedSkillsConfig(projectPath: string): ExtendedSkillsConfig {
203
+ const baseConfig = getSkillsConfig(projectPath);
204
+ return {
205
+ ...baseConfig,
206
+ disabledProjectSkills: getDisabledSkillsList('project', projectPath),
207
+ disabledUserSkills: getDisabledSkillsList('user', projectPath)
208
+ };
209
+ }
210
+
211
+ // ========== Active Skills Helper Functions ==========
212
+
68
213
  /**
69
214
  * Parse skill frontmatter (YAML header)
70
215
  * @param {string} content - Skill file content
@@ -128,7 +273,8 @@ function getSupportingFiles(skillDir: string): string[] {
128
273
  try {
129
274
  const entries = readdirSync(skillDir, { withFileTypes: true });
130
275
  for (const entry of entries) {
131
- if (entry.name !== 'SKILL.md') {
276
+ // Exclude SKILL.md and SKILL.md.disabled from supporting files
277
+ if (entry.name !== 'SKILL.md' && entry.name !== 'SKILL.md.disabled') {
132
278
  if (entry.isFile()) {
133
279
  files.push(entry.name);
134
280
  } else if (entry.isDirectory()) {
@@ -660,15 +806,23 @@ Create a new Claude Code skill with the following specifications:
660
806
  export async function handleSkillsRoutes(ctx: RouteContext): Promise<boolean> {
661
807
  const { pathname, url, req, res, initialPath, handlePostRequest, broadcastToClients } = ctx;
662
808
 
663
- // API: Get all skills (project and user)
664
- if (pathname === '/api/skills') {
809
+ // API: Get all skills (project and user) - with optional extended format
810
+ if (pathname === '/api/skills' && req.method === 'GET') {
665
811
  const projectPathParam = url.searchParams.get('path') || initialPath;
812
+ const includeDisabled = url.searchParams.get('includeDisabled') === 'true';
666
813
 
667
814
  try {
668
815
  const validatedProjectPath = await validateAllowedPath(projectPathParam, { mustExist: true, allowedDirectories: [initialPath] });
669
- const skillsData = getSkillsConfig(validatedProjectPath);
670
- res.writeHead(200, { 'Content-Type': 'application/json' });
671
- res.end(JSON.stringify(skillsData));
816
+
817
+ if (includeDisabled) {
818
+ const extendedData = getExtendedSkillsConfig(validatedProjectPath);
819
+ res.writeHead(200, { 'Content-Type': 'application/json' });
820
+ res.end(JSON.stringify(extendedData));
821
+ } else {
822
+ const skillsData = getSkillsConfig(validatedProjectPath);
823
+ res.writeHead(200, { 'Content-Type': 'application/json' });
824
+ res.end(JSON.stringify(skillsData));
825
+ }
672
826
  } catch (err) {
673
827
  const message = err instanceof Error ? err.message : String(err);
674
828
  const status = message.includes('Access denied') ? 403 : 400;
@@ -679,6 +833,73 @@ export async function handleSkillsRoutes(ctx: RouteContext): Promise<boolean> {
679
833
  return true;
680
834
  }
681
835
 
836
+ // API: Get disabled skills list
837
+ if (pathname === '/api/skills/disabled' && req.method === 'GET') {
838
+ const projectPathParam = url.searchParams.get('path') || initialPath;
839
+
840
+ try {
841
+ const validatedProjectPath = await validateAllowedPath(projectPathParam, { mustExist: true, allowedDirectories: [initialPath] });
842
+ const disabledProjectSkills = getDisabledSkillsList('project', validatedProjectPath);
843
+ const disabledUserSkills = getDisabledSkillsList('user', validatedProjectPath);
844
+
845
+ res.writeHead(200, { 'Content-Type': 'application/json' });
846
+ res.end(JSON.stringify({ disabledProjectSkills, disabledUserSkills }));
847
+ } catch (err) {
848
+ const message = err instanceof Error ? err.message : String(err);
849
+ const status = message.includes('Access denied') ? 403 : 400;
850
+ res.writeHead(status, { 'Content-Type': 'application/json' });
851
+ res.end(JSON.stringify({ error: status === 403 ? 'Access denied' : 'Invalid path', disabledProjectSkills: [], disabledUserSkills: [] }));
852
+ }
853
+ return true;
854
+ }
855
+
856
+ // API: Disable a skill
857
+ if (pathname.match(/^\/api\/skills\/[^/]+\/disable$/) && req.method === 'POST') {
858
+ const pathParts = pathname.split('/');
859
+ const skillName = decodeURIComponent(pathParts[3]);
860
+
861
+ handlePostRequest(req, res, async (body) => {
862
+ if (!isRecord(body)) {
863
+ return { error: 'Invalid request body', status: 400 };
864
+ }
865
+
866
+ const locationValue = body.location;
867
+ const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
868
+ const reason = typeof body.reason === 'string' ? body.reason : undefined;
869
+
870
+ if (locationValue !== 'project' && locationValue !== 'user') {
871
+ return { error: 'Location is required (project or user)' };
872
+ }
873
+
874
+ const projectPath = projectPathParam || initialPath;
875
+ return disableSkill(skillName, locationValue, projectPath, initialPath, reason);
876
+ });
877
+ return true;
878
+ }
879
+
880
+ // API: Enable a skill
881
+ if (pathname.match(/^\/api\/skills\/[^/]+\/enable$/) && req.method === 'POST') {
882
+ const pathParts = pathname.split('/');
883
+ const skillName = decodeURIComponent(pathParts[3]);
884
+
885
+ handlePostRequest(req, res, async (body) => {
886
+ if (!isRecord(body)) {
887
+ return { error: 'Invalid request body', status: 400 };
888
+ }
889
+
890
+ const locationValue = body.location;
891
+ const projectPathParam = typeof body.projectPath === 'string' ? body.projectPath : undefined;
892
+
893
+ if (locationValue !== 'project' && locationValue !== 'user') {
894
+ return { error: 'Location is required (project or user)' };
895
+ }
896
+
897
+ const projectPath = projectPathParam || initialPath;
898
+ return enableSkill(skillName, locationValue, projectPath, initialPath);
899
+ });
900
+ return true;
901
+ }
902
+
682
903
  // API: List skill directory contents
683
904
  if (pathname.match(/^\/api\/skills\/[^/]+\/dir$/) && req.method === 'GET') {
684
905
  const pathParts = pathname.split('/');