wogiflow 1.0.0

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 (221) hide show
  1. package/.workflow/agents/reviewer.md +81 -0
  2. package/.workflow/agents/security.md +94 -0
  3. package/.workflow/agents/story-writer.md +58 -0
  4. package/.workflow/bridges/base-bridge.js +395 -0
  5. package/.workflow/bridges/claude-bridge.js +434 -0
  6. package/.workflow/bridges/index.js +130 -0
  7. package/.workflow/lib/assumption-detector.js +481 -0
  8. package/.workflow/lib/config-substitution.js +371 -0
  9. package/.workflow/lib/failure-categories.js +478 -0
  10. package/.workflow/state/app-map.md.template +15 -0
  11. package/.workflow/state/architecture.md.template +24 -0
  12. package/.workflow/state/component-index.json.template +5 -0
  13. package/.workflow/state/decisions.md.template +15 -0
  14. package/.workflow/state/feedback-patterns.md.template +9 -0
  15. package/.workflow/state/knowledge-sync.json.template +6 -0
  16. package/.workflow/state/progress.md.template +14 -0
  17. package/.workflow/state/ready.json.template +7 -0
  18. package/.workflow/state/request-log.md.template +14 -0
  19. package/.workflow/state/session-state.json.template +11 -0
  20. package/.workflow/state/stack.md.template +33 -0
  21. package/.workflow/state/testing.md.template +36 -0
  22. package/.workflow/templates/claude-md.hbs +257 -0
  23. package/.workflow/templates/correction-report.md +67 -0
  24. package/.workflow/templates/gemini-md.hbs +52 -0
  25. package/README.md +1802 -0
  26. package/bin/flow +205 -0
  27. package/lib/index.js +33 -0
  28. package/lib/installer.js +467 -0
  29. package/lib/release-channel.js +269 -0
  30. package/lib/skill-registry.js +526 -0
  31. package/lib/upgrader.js +401 -0
  32. package/lib/utils.js +305 -0
  33. package/package.json +64 -0
  34. package/scripts/flow +985 -0
  35. package/scripts/flow-adaptive-learning.js +1259 -0
  36. package/scripts/flow-aggregate.js +488 -0
  37. package/scripts/flow-archive +133 -0
  38. package/scripts/flow-auto-context.js +1015 -0
  39. package/scripts/flow-auto-learn.js +615 -0
  40. package/scripts/flow-bridge.js +223 -0
  41. package/scripts/flow-browser-suggest.js +316 -0
  42. package/scripts/flow-bug.js +247 -0
  43. package/scripts/flow-cascade.js +711 -0
  44. package/scripts/flow-changelog +85 -0
  45. package/scripts/flow-checkpoint.js +483 -0
  46. package/scripts/flow-cli.js +403 -0
  47. package/scripts/flow-code-intelligence.js +760 -0
  48. package/scripts/flow-complexity.js +502 -0
  49. package/scripts/flow-config-set.js +152 -0
  50. package/scripts/flow-constants.js +157 -0
  51. package/scripts/flow-context +152 -0
  52. package/scripts/flow-context-init.js +482 -0
  53. package/scripts/flow-context-monitor.js +384 -0
  54. package/scripts/flow-context-scoring.js +886 -0
  55. package/scripts/flow-correct.js +458 -0
  56. package/scripts/flow-damage-control.js +985 -0
  57. package/scripts/flow-deps +101 -0
  58. package/scripts/flow-diff.js +700 -0
  59. package/scripts/flow-done +151 -0
  60. package/scripts/flow-done.js +489 -0
  61. package/scripts/flow-durable-session.js +1541 -0
  62. package/scripts/flow-entropy-monitor.js +345 -0
  63. package/scripts/flow-export-profile +349 -0
  64. package/scripts/flow-export-scanner.js +1046 -0
  65. package/scripts/flow-figma-confirm.js +400 -0
  66. package/scripts/flow-figma-extract.js +496 -0
  67. package/scripts/flow-figma-generate.js +683 -0
  68. package/scripts/flow-figma-index.js +909 -0
  69. package/scripts/flow-figma-match.js +617 -0
  70. package/scripts/flow-figma-mcp-server.js +518 -0
  71. package/scripts/flow-figma-pipeline.js +414 -0
  72. package/scripts/flow-file-ops.js +301 -0
  73. package/scripts/flow-gate-confidence.js +825 -0
  74. package/scripts/flow-guided-edit.js +659 -0
  75. package/scripts/flow-health +185 -0
  76. package/scripts/flow-health.js +413 -0
  77. package/scripts/flow-hooks.js +556 -0
  78. package/scripts/flow-http-client.js +249 -0
  79. package/scripts/flow-hybrid-detect.js +167 -0
  80. package/scripts/flow-hybrid-interactive.js +591 -0
  81. package/scripts/flow-hybrid-test.js +152 -0
  82. package/scripts/flow-import-profile +439 -0
  83. package/scripts/flow-init +253 -0
  84. package/scripts/flow-instruction-richness.js +827 -0
  85. package/scripts/flow-jira-integration.js +579 -0
  86. package/scripts/flow-knowledge-router.js +522 -0
  87. package/scripts/flow-knowledge-sync.js +589 -0
  88. package/scripts/flow-linear-integration.js +631 -0
  89. package/scripts/flow-links.js +774 -0
  90. package/scripts/flow-log-manager.js +559 -0
  91. package/scripts/flow-loop-enforcer.js +1246 -0
  92. package/scripts/flow-loop-retry-learning.js +630 -0
  93. package/scripts/flow-lsp.js +923 -0
  94. package/scripts/flow-map-index +348 -0
  95. package/scripts/flow-map-sync +201 -0
  96. package/scripts/flow-memory-blocks.js +668 -0
  97. package/scripts/flow-memory-compactor.js +350 -0
  98. package/scripts/flow-memory-db.js +1110 -0
  99. package/scripts/flow-memory-sync.js +484 -0
  100. package/scripts/flow-metrics.js +353 -0
  101. package/scripts/flow-migrate-ids.js +370 -0
  102. package/scripts/flow-model-adapter.js +802 -0
  103. package/scripts/flow-model-router.js +884 -0
  104. package/scripts/flow-models.js +1231 -0
  105. package/scripts/flow-morning.js +517 -0
  106. package/scripts/flow-multi-approach.js +660 -0
  107. package/scripts/flow-new-feature +86 -0
  108. package/scripts/flow-onboard +1042 -0
  109. package/scripts/flow-orchestrate-llm.js +459 -0
  110. package/scripts/flow-orchestrate.js +3592 -0
  111. package/scripts/flow-output.js +123 -0
  112. package/scripts/flow-parallel-detector.js +399 -0
  113. package/scripts/flow-parallel-dispatch.js +987 -0
  114. package/scripts/flow-parallel.js +428 -0
  115. package/scripts/flow-pattern-enforcer.js +600 -0
  116. package/scripts/flow-prd-manager.js +282 -0
  117. package/scripts/flow-progress.js +323 -0
  118. package/scripts/flow-project-analyzer.js +975 -0
  119. package/scripts/flow-prompt-composer.js +487 -0
  120. package/scripts/flow-providers.js +1381 -0
  121. package/scripts/flow-queue.js +308 -0
  122. package/scripts/flow-ready +82 -0
  123. package/scripts/flow-ready.js +189 -0
  124. package/scripts/flow-regression.js +396 -0
  125. package/scripts/flow-response-parser.js +450 -0
  126. package/scripts/flow-resume.js +284 -0
  127. package/scripts/flow-rules-sync.js +439 -0
  128. package/scripts/flow-run-trace.js +718 -0
  129. package/scripts/flow-safety.js +587 -0
  130. package/scripts/flow-search +104 -0
  131. package/scripts/flow-security.js +481 -0
  132. package/scripts/flow-session-end +106 -0
  133. package/scripts/flow-session-end.js +437 -0
  134. package/scripts/flow-session-state.js +671 -0
  135. package/scripts/flow-setup-hooks +216 -0
  136. package/scripts/flow-setup-hooks.js +377 -0
  137. package/scripts/flow-skill-create.js +329 -0
  138. package/scripts/flow-skill-creator.js +572 -0
  139. package/scripts/flow-skill-generator.js +1046 -0
  140. package/scripts/flow-skill-learn.js +880 -0
  141. package/scripts/flow-skill-matcher.js +578 -0
  142. package/scripts/flow-spec-generator.js +820 -0
  143. package/scripts/flow-stack-wizard.js +895 -0
  144. package/scripts/flow-standup +162 -0
  145. package/scripts/flow-start +74 -0
  146. package/scripts/flow-start.js +235 -0
  147. package/scripts/flow-status +110 -0
  148. package/scripts/flow-status.js +301 -0
  149. package/scripts/flow-step-browser.js +83 -0
  150. package/scripts/flow-step-changelog.js +217 -0
  151. package/scripts/flow-step-comments.js +306 -0
  152. package/scripts/flow-step-complexity.js +234 -0
  153. package/scripts/flow-step-coverage.js +218 -0
  154. package/scripts/flow-step-knowledge.js +193 -0
  155. package/scripts/flow-step-pr-tests.js +364 -0
  156. package/scripts/flow-step-regression.js +89 -0
  157. package/scripts/flow-step-review.js +516 -0
  158. package/scripts/flow-step-security.js +162 -0
  159. package/scripts/flow-step-silent-failures.js +290 -0
  160. package/scripts/flow-step-simplifier.js +346 -0
  161. package/scripts/flow-story +105 -0
  162. package/scripts/flow-story.js +500 -0
  163. package/scripts/flow-suspend.js +252 -0
  164. package/scripts/flow-sync-daemon.js +654 -0
  165. package/scripts/flow-task-analyzer.js +606 -0
  166. package/scripts/flow-team-dashboard.js +748 -0
  167. package/scripts/flow-team-sync.js +752 -0
  168. package/scripts/flow-team.js +977 -0
  169. package/scripts/flow-tech-options.js +528 -0
  170. package/scripts/flow-templates.js +812 -0
  171. package/scripts/flow-tiered-learning.js +728 -0
  172. package/scripts/flow-trace +204 -0
  173. package/scripts/flow-transcript-chunking.js +1106 -0
  174. package/scripts/flow-transcript-digest.js +7918 -0
  175. package/scripts/flow-transcript-language.js +465 -0
  176. package/scripts/flow-transcript-parsing.js +1085 -0
  177. package/scripts/flow-transcript-stories.js +2194 -0
  178. package/scripts/flow-update-map +224 -0
  179. package/scripts/flow-utils.js +2242 -0
  180. package/scripts/flow-verification.js +644 -0
  181. package/scripts/flow-verify.js +1177 -0
  182. package/scripts/flow-voice-input.js +638 -0
  183. package/scripts/flow-watch +168 -0
  184. package/scripts/flow-workflow-steps.js +521 -0
  185. package/scripts/flow-workflow.js +1029 -0
  186. package/scripts/flow-worktree.js +489 -0
  187. package/scripts/hooks/adapters/base-adapter.js +102 -0
  188. package/scripts/hooks/adapters/claude-code.js +359 -0
  189. package/scripts/hooks/adapters/index.js +79 -0
  190. package/scripts/hooks/core/component-check.js +341 -0
  191. package/scripts/hooks/core/index.js +35 -0
  192. package/scripts/hooks/core/loop-check.js +241 -0
  193. package/scripts/hooks/core/session-context.js +294 -0
  194. package/scripts/hooks/core/task-gate.js +177 -0
  195. package/scripts/hooks/core/validation.js +230 -0
  196. package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
  197. package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
  198. package/scripts/hooks/entry/claude-code/session-end.js +87 -0
  199. package/scripts/hooks/entry/claude-code/session-start.js +46 -0
  200. package/scripts/hooks/entry/claude-code/stop.js +43 -0
  201. package/scripts/postinstall.js +139 -0
  202. package/templates/browser-test-flow.json +56 -0
  203. package/templates/bug-report.md +43 -0
  204. package/templates/component-detail.md +42 -0
  205. package/templates/component.stories.tsx +49 -0
  206. package/templates/context/constraints.md +83 -0
  207. package/templates/context/conventions.md +177 -0
  208. package/templates/context/stack.md +60 -0
  209. package/templates/correction-report.md +90 -0
  210. package/templates/feature-proposal.md +35 -0
  211. package/templates/hybrid/_base.md +254 -0
  212. package/templates/hybrid/_patterns.md +45 -0
  213. package/templates/hybrid/create-component.md +127 -0
  214. package/templates/hybrid/create-file.md +56 -0
  215. package/templates/hybrid/create-hook.md +145 -0
  216. package/templates/hybrid/create-service.md +70 -0
  217. package/templates/hybrid/fix-bug.md +33 -0
  218. package/templates/hybrid/modify-file.md +55 -0
  219. package/templates/story.md +68 -0
  220. package/templates/task.json +56 -0
  221. package/templates/trace.md +69 -0
@@ -0,0 +1,526 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow Skill Registry
5
+ *
6
+ * Handles skill installation and management with `flow skill`.
7
+ * Fetches skills from a GitHub-based registry, validates them,
8
+ * and installs them to the project's .claude/skills/ directory.
9
+ *
10
+ * @module lib/skill-registry
11
+ */
12
+
13
+ const fs = require('fs');
14
+ const path = require('path');
15
+
16
+ // Shared utilities
17
+ const {
18
+ findProjectRoot,
19
+ safeJsonParse,
20
+ safeReadJson,
21
+ httpsGet,
22
+ validatePath,
23
+ safeWriteFile
24
+ } = require('./utils');
25
+
26
+ // Registry configuration
27
+ const REGISTRY_CONFIG = {
28
+ baseUrl: 'https://raw.githubusercontent.com/Wogi-Git/wogi-flow-skills',
29
+ branch: 'main',
30
+ manifestFile: 'manifest.json',
31
+ indexFile: 'index.json'
32
+ };
33
+
34
+ // Local cache settings
35
+ const CACHE_DIR = '.workflow/cache/skills';
36
+ const CACHE_TTL = 3600000; // 1 hour in milliseconds
37
+
38
+ /**
39
+ * Parse command line arguments with bounds checking
40
+ * @param {string[]} args - Command line arguments
41
+ * @returns {Object} Parsed options
42
+ */
43
+ function parseArgs(args) {
44
+ const options = {
45
+ command: args[0] || 'list',
46
+ skillName: args[1] || null,
47
+ version: null,
48
+ force: false,
49
+ help: false
50
+ };
51
+
52
+ for (let i = 0; i < args.length; i++) {
53
+ const arg = args[i];
54
+
55
+ if (arg === '--version' || arg === '-v') {
56
+ // Bounds check before accessing next argument
57
+ if (i + 1 >= args.length) {
58
+ console.error('Error: --version requires a value');
59
+ options.help = true;
60
+ break;
61
+ }
62
+ options.version = args[++i];
63
+ } else if (arg === '--force' || arg === '-f') {
64
+ options.force = true;
65
+ } else if (arg === '--help' || arg === '-h') {
66
+ options.help = true;
67
+ }
68
+ }
69
+
70
+ return options;
71
+ }
72
+
73
+ /**
74
+ * Show help message
75
+ */
76
+ function showHelp() {
77
+ console.log(`
78
+ Usage: flow skill <command> [options]
79
+
80
+ Manage skills from the Wogi Flow registry.
81
+
82
+ Commands:
83
+ list List available skills from registry
84
+ add <name> Install a skill
85
+ remove <name> Remove an installed skill
86
+ update [name] Update skill(s) to latest version
87
+ info <name> Show skill details
88
+
89
+ Options:
90
+ --version, -v <ver> Install specific version
91
+ --force, -f Force reinstall or overwrite
92
+ --help, -h Show this help message
93
+
94
+ Examples:
95
+ flow skill list # List all available skills
96
+ flow skill add react # Install react skill
97
+ flow skill add nestjs -v 1.2.0 # Install specific version
98
+ flow skill remove react # Remove skill
99
+ flow skill update # Update all skills
100
+ flow skill info react # Show skill details
101
+ `);
102
+ }
103
+
104
+ // findProjectRoot and httpsGet are imported from ./utils
105
+
106
+ /**
107
+ * Fetch with caching
108
+ * @param {string} url - URL to fetch
109
+ * @param {string} cacheKey - Cache key
110
+ * @param {string} projectRoot - Project root directory
111
+ * @returns {Promise<string>} Response body
112
+ */
113
+ async function fetchWithCache(url, cacheKey, projectRoot) {
114
+ const cacheDir = path.join(projectRoot, CACHE_DIR);
115
+ const cachePath = path.join(cacheDir, `${cacheKey}.json`);
116
+
117
+ // Check cache
118
+ if (fs.existsSync(cachePath)) {
119
+ const cached = safeReadJson(cachePath);
120
+ if (cached && Date.now() - cached.timestamp < CACHE_TTL) {
121
+ return cached.data;
122
+ }
123
+ }
124
+
125
+ // Fetch fresh
126
+ const data = await httpsGet(url);
127
+
128
+ // Save to cache
129
+ try {
130
+ fs.mkdirSync(cacheDir, { recursive: true });
131
+ fs.writeFileSync(cachePath, JSON.stringify({
132
+ timestamp: Date.now(),
133
+ data
134
+ }));
135
+ } catch {
136
+ // Cache write failed, continue anyway
137
+ }
138
+
139
+ return data;
140
+ }
141
+
142
+ /**
143
+ * Fetch skill index from registry
144
+ * @param {string} projectRoot - Project root directory
145
+ * @returns {Promise<Object>} Skill index
146
+ */
147
+ async function fetchSkillIndex(projectRoot) {
148
+ const url = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/${REGISTRY_CONFIG.indexFile}`;
149
+
150
+ try {
151
+ const data = await fetchWithCache(url, 'index', projectRoot);
152
+ const parsed = safeJsonParse(data);
153
+ if (!parsed) {
154
+ throw new Error('Invalid index data');
155
+ }
156
+ return parsed;
157
+ } catch (err) {
158
+ // Return mock index for development/offline
159
+ return {
160
+ version: '1.0',
161
+ skills: {
162
+ react: {
163
+ name: 'react',
164
+ title: 'React',
165
+ description: 'React component patterns and best practices',
166
+ version: '1.0.0',
167
+ author: 'Wogi-Git'
168
+ },
169
+ nestjs: {
170
+ name: 'nestjs',
171
+ title: 'NestJS',
172
+ description: 'NestJS module patterns with entities, DTOs, services',
173
+ version: '1.0.0',
174
+ author: 'Wogi-Git'
175
+ },
176
+ python: {
177
+ name: 'python',
178
+ title: 'Python',
179
+ description: 'Python/FastAPI patterns and best practices',
180
+ version: '1.0.0',
181
+ author: 'Wogi-Git'
182
+ }
183
+ }
184
+ };
185
+ }
186
+ }
187
+
188
+ /**
189
+ * Fetch skill manifest from registry
190
+ * @param {string} skillName - Skill name
191
+ * @param {string} projectRoot - Project root directory
192
+ * @returns {Promise<Object>} Skill manifest
193
+ */
194
+ async function fetchSkillManifest(skillName, projectRoot) {
195
+ const url = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/skills/${skillName}/${REGISTRY_CONFIG.manifestFile}`;
196
+
197
+ try {
198
+ const data = await fetchWithCache(url, `manifest-${skillName}`, projectRoot);
199
+ const parsed = safeJsonParse(data);
200
+ if (!parsed) {
201
+ throw new Error('Invalid manifest data');
202
+ }
203
+ return parsed;
204
+ } catch (err) {
205
+ throw new Error(`Skill '${skillName}' not found in registry`);
206
+ }
207
+ }
208
+
209
+ /**
210
+ * Download skill files
211
+ * @param {string} skillName - Skill name
212
+ * @param {Object} manifest - Skill manifest
213
+ * @param {string} projectRoot - Project root directory
214
+ * @returns {Promise<Object>} Downloaded files
215
+ */
216
+ async function downloadSkillFiles(skillName, manifest, projectRoot) {
217
+ const files = {};
218
+ const baseUrl = `${REGISTRY_CONFIG.baseUrl}/${REGISTRY_CONFIG.branch}/skills/${skillName}`;
219
+
220
+ // Standard skill files
221
+ const standardFiles = ['skill.md', 'patterns.md', 'anti-patterns.md', 'learnings.md'];
222
+ const filesToDownload = manifest.files || standardFiles;
223
+
224
+ for (const file of filesToDownload) {
225
+ try {
226
+ const url = `${baseUrl}/${file}`;
227
+ const content = await httpsGet(url);
228
+ files[file] = content;
229
+ } catch {
230
+ // File doesn't exist, skip
231
+ }
232
+ }
233
+
234
+ return files;
235
+ }
236
+
237
+ /**
238
+ * Get installed skills
239
+ * @param {string} projectRoot - Project root directory
240
+ * @returns {Object} Installed skills map
241
+ */
242
+ function getInstalledSkills(projectRoot) {
243
+ const skillsDir = path.join(projectRoot, '.claude', 'skills');
244
+ const installed = {};
245
+
246
+ if (!fs.existsSync(skillsDir)) {
247
+ return installed;
248
+ }
249
+
250
+ const entries = fs.readdirSync(skillsDir, { withFileTypes: true });
251
+
252
+ for (const entry of entries) {
253
+ if (entry.isDirectory()) {
254
+ const manifestPath = path.join(skillsDir, entry.name, 'manifest.json');
255
+ const manifest = safeReadJson(manifestPath);
256
+ if (manifest) {
257
+ installed[entry.name] = manifest;
258
+ } else {
259
+ installed[entry.name] = { name: entry.name, version: 'unknown' };
260
+ }
261
+ }
262
+ }
263
+
264
+ return installed;
265
+ }
266
+
267
+ /**
268
+ * List available skills
269
+ * @param {string} projectRoot - Project root directory
270
+ */
271
+ async function listSkills(projectRoot) {
272
+ console.log('\nšŸ“¦ Available Skills\n');
273
+
274
+ const index = await fetchSkillIndex(projectRoot);
275
+ const installed = getInstalledSkills(projectRoot);
276
+
277
+ const skills = Object.values(index.skills || {});
278
+
279
+ if (skills.length === 0) {
280
+ console.log(' No skills available in registry');
281
+ return;
282
+ }
283
+
284
+ for (const skill of skills) {
285
+ const isInstalled = installed[skill.name];
286
+ const status = isInstalled ? 'āœ“' : ' ';
287
+ const versionInfo = isInstalled
288
+ ? `(installed: ${isInstalled.version})`
289
+ : `(v${skill.version})`;
290
+
291
+ console.log(` ${status} ${skill.name.padEnd(15)} ${versionInfo}`);
292
+ console.log(` ${skill.description}`);
293
+ }
294
+
295
+ console.log('\nUse `flow skill add <name>` to install a skill');
296
+ }
297
+
298
+ /**
299
+ * Install a skill
300
+ * @param {string} skillName - Skill name
301
+ * @param {string} projectRoot - Project root directory
302
+ * @param {Object} options - Installation options
303
+ */
304
+ async function addSkill(skillName, projectRoot, options) {
305
+ const skillsDir = path.join(projectRoot, '.claude', 'skills', skillName);
306
+
307
+ // Check if already installed
308
+ if (fs.existsSync(skillsDir) && !options.force) {
309
+ console.log(`Skill '${skillName}' is already installed.`);
310
+ console.log('Use --force to reinstall.');
311
+ return;
312
+ }
313
+
314
+ console.log(`\nInstalling skill: ${skillName}\n`);
315
+
316
+ // Fetch manifest
317
+ let manifest;
318
+ try {
319
+ manifest = await fetchSkillManifest(skillName, projectRoot);
320
+ } catch (err) {
321
+ // Use index info if manifest not found
322
+ const index = await fetchSkillIndex(projectRoot);
323
+ if (index.skills && index.skills[skillName]) {
324
+ manifest = index.skills[skillName];
325
+ } else {
326
+ console.error(`Error: ${err.message}`);
327
+ process.exit(1);
328
+ }
329
+ }
330
+
331
+ // Download files
332
+ console.log(' Downloading files...');
333
+ const files = await downloadSkillFiles(skillName, manifest, projectRoot);
334
+
335
+ if (Object.keys(files).length === 0) {
336
+ console.error('Error: No skill files found');
337
+ process.exit(1);
338
+ }
339
+
340
+ // Create skill directory
341
+ fs.mkdirSync(skillsDir, { recursive: true });
342
+
343
+ // Write files with path validation (prevents path traversal)
344
+ for (const [filename, content] of Object.entries(files)) {
345
+ // Use basename to prevent path traversal attacks like "../../../etc/passwd"
346
+ const safeFilename = path.basename(filename);
347
+ const targetPath = validatePath(skillsDir, safeFilename);
348
+ if (!targetPath) {
349
+ console.error(` Warning: Skipping invalid filename '${filename}'`);
350
+ continue;
351
+ }
352
+ fs.writeFileSync(targetPath, content);
353
+ }
354
+
355
+ // Write manifest (safe - we control the filename)
356
+ const localManifest = {
357
+ ...manifest,
358
+ installedAt: new Date().toISOString(),
359
+ installedVersion: manifest.version
360
+ };
361
+ fs.writeFileSync(
362
+ path.join(skillsDir, 'manifest.json'),
363
+ JSON.stringify(localManifest, null, 2)
364
+ );
365
+
366
+ console.log(` āœ“ Installed ${Object.keys(files).length} files`);
367
+ console.log(`\nāœ… Skill '${skillName}' installed successfully!\n`);
368
+ console.log(`Files: .claude/skills/${skillName}/`);
369
+ }
370
+
371
+ /**
372
+ * Remove a skill
373
+ * @param {string} skillName - Skill name
374
+ * @param {string} projectRoot - Project root directory
375
+ */
376
+ function removeSkill(skillName, projectRoot) {
377
+ const skillsDir = path.join(projectRoot, '.claude', 'skills', skillName);
378
+
379
+ if (!fs.existsSync(skillsDir)) {
380
+ console.log(`Skill '${skillName}' is not installed.`);
381
+ return;
382
+ }
383
+
384
+ // Remove directory recursively
385
+ fs.rmSync(skillsDir, { recursive: true });
386
+
387
+ console.log(`āœ“ Removed skill: ${skillName}`);
388
+ }
389
+
390
+ /**
391
+ * Update skills
392
+ * @param {string|null} skillName - Skill name or null for all
393
+ * @param {string} projectRoot - Project root directory
394
+ */
395
+ async function updateSkills(skillName, projectRoot) {
396
+ const installed = getInstalledSkills(projectRoot);
397
+
398
+ if (Object.keys(installed).length === 0) {
399
+ console.log('No skills installed.');
400
+ return;
401
+ }
402
+
403
+ const skillsToUpdate = skillName
404
+ ? [skillName]
405
+ : Object.keys(installed);
406
+
407
+ console.log('\nšŸ”„ Updating skills...\n');
408
+
409
+ const index = await fetchSkillIndex(projectRoot);
410
+
411
+ for (const name of skillsToUpdate) {
412
+ if (!installed[name]) {
413
+ console.log(` ⚠ ${name}: not installed`);
414
+ continue;
415
+ }
416
+
417
+ const registryVersion = index.skills?.[name]?.version || 'unknown';
418
+ const installedVersion = installed[name].version || 'unknown';
419
+
420
+ if (registryVersion === installedVersion && registryVersion !== 'unknown') {
421
+ console.log(` āœ“ ${name}: up to date (${installedVersion})`);
422
+ } else {
423
+ console.log(` ↑ ${name}: ${installedVersion} → ${registryVersion}`);
424
+ await addSkill(name, projectRoot, { force: true });
425
+ }
426
+ }
427
+ }
428
+
429
+ /**
430
+ * Show skill info
431
+ * @param {string} skillName - Skill name
432
+ * @param {string} projectRoot - Project root directory
433
+ */
434
+ async function showSkillInfo(skillName, projectRoot) {
435
+ const index = await fetchSkillIndex(projectRoot);
436
+ const installed = getInstalledSkills(projectRoot);
437
+
438
+ const skill = index.skills?.[skillName];
439
+
440
+ if (!skill) {
441
+ console.log(`Skill '${skillName}' not found in registry.`);
442
+ return;
443
+ }
444
+
445
+ console.log(`\nšŸ“¦ ${skill.title || skill.name}\n`);
446
+ console.log(` Name: ${skill.name}`);
447
+ console.log(` Version: ${skill.version}`);
448
+ console.log(` Author: ${skill.author || 'Unknown'}`);
449
+ console.log(` Description: ${skill.description}`);
450
+
451
+ if (installed[skillName]) {
452
+ console.log(`\n Status: Installed (v${installed[skillName].version})`);
453
+ if (installed[skillName].installedAt) {
454
+ console.log(` Installed: ${installed[skillName].installedAt}`);
455
+ }
456
+ } else {
457
+ console.log(`\n Status: Not installed`);
458
+ }
459
+
460
+ console.log('');
461
+ }
462
+
463
+ /**
464
+ * Main skill registry function
465
+ * @param {string[]} args - Command line arguments
466
+ */
467
+ async function skill(args) {
468
+ const options = parseArgs(args);
469
+
470
+ if (options.help) {
471
+ showHelp();
472
+ return;
473
+ }
474
+
475
+ const projectRoot = findProjectRoot();
476
+
477
+ if (!projectRoot) {
478
+ console.error('Error: Not in a Wogi Flow project');
479
+ console.error('Use `flow init` to initialize a new project');
480
+ process.exit(1);
481
+ }
482
+
483
+ switch (options.command) {
484
+ case 'list':
485
+ await listSkills(projectRoot);
486
+ break;
487
+
488
+ case 'add':
489
+ if (!options.skillName) {
490
+ console.error('Error: Please specify a skill name');
491
+ console.error('Usage: flow skill add <name>');
492
+ process.exit(1);
493
+ }
494
+ await addSkill(options.skillName, projectRoot, options);
495
+ break;
496
+
497
+ case 'remove':
498
+ if (!options.skillName) {
499
+ console.error('Error: Please specify a skill name');
500
+ console.error('Usage: flow skill remove <name>');
501
+ process.exit(1);
502
+ }
503
+ removeSkill(options.skillName, projectRoot);
504
+ break;
505
+
506
+ case 'update':
507
+ await updateSkills(options.skillName, projectRoot);
508
+ break;
509
+
510
+ case 'info':
511
+ if (!options.skillName) {
512
+ console.error('Error: Please specify a skill name');
513
+ console.error('Usage: flow skill info <name>');
514
+ process.exit(1);
515
+ }
516
+ await showSkillInfo(options.skillName, projectRoot);
517
+ break;
518
+
519
+ default:
520
+ console.error(`Unknown command: ${options.command}`);
521
+ showHelp();
522
+ process.exit(1);
523
+ }
524
+ }
525
+
526
+ module.exports = { skill };