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,1046 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Skill Generator for Wogi Flow
5
+ * Fetches documentation via Context7 MCP and generates skill files
6
+ */
7
+
8
+ const fs = require('fs');
9
+ const path = require('path');
10
+
11
+ // Import helper functions from tech options
12
+ let _techOptions = null;
13
+ function getTechOptions() {
14
+ if (!_techOptions) {
15
+ try {
16
+ _techOptions = require('./flow-tech-options');
17
+ } catch (err) {
18
+ _techOptions = { getSkillType: () => 'library', getParentFramework: () => null };
19
+ }
20
+ }
21
+ return _techOptions;
22
+ }
23
+
24
+ // Lazy-load to avoid circular dependency
25
+ let _syncDecisionsToRules = null;
26
+ function syncDecisionsToRules() {
27
+ if (!_syncDecisionsToRules) {
28
+ _syncDecisionsToRules = require('./flow-rules-sync').syncDecisionsToRules;
29
+ }
30
+ return _syncDecisionsToRules();
31
+ }
32
+
33
+ // ============================================
34
+ // CONFIGURATION
35
+ // ============================================
36
+
37
+ const SKILL_TOPICS = {
38
+ patterns: 'best practices common patterns',
39
+ antiPatterns: 'common mistakes anti-patterns pitfalls',
40
+ conventions: 'coding conventions style guide',
41
+ gettingStarted: 'getting started setup installation'
42
+ };
43
+
44
+ // Technology-specific keywords for better doc extraction
45
+ const TECH_KEYWORDS = {
46
+ nextjs: ['app router', 'server components', 'server actions', 'middleware', 'api routes'],
47
+ react: ['hooks', 'context', 'suspense', 'concurrent', 'server components'],
48
+ tailwind: ['utility classes', 'responsive', 'dark mode', 'configuration'],
49
+ prisma: ['schema', 'migrations', 'queries', 'relations', 'transactions'],
50
+ nestjs: ['modules', 'controllers', 'providers', 'guards', 'interceptors'],
51
+ fastapi: ['routes', 'dependencies', 'pydantic', 'async', 'middleware'],
52
+ vue: ['composition api', 'reactivity', 'components', 'directives'],
53
+ svelte: ['stores', 'actions', 'transitions', 'ssr']
54
+ };
55
+
56
+ // ============================================
57
+ // CONTEXT7 MCP INTEGRATION
58
+ // ============================================
59
+
60
+ /**
61
+ * This function is designed to work with Claude Code's MCP infrastructure.
62
+ * When run directly, it outputs instructions for manual Context7 fetching.
63
+ * When used via Claude, Claude should call Context7 MCP tools directly.
64
+ */
65
+ async function fetchDocsViaContext7(technology) {
66
+ // When run via Claude, return null to signal Claude should use MCP
67
+ if (process.env.WOGI_MCP_MODE === 'true') {
68
+ return null; // Claude will use resolve-library-id + get-library-docs
69
+ }
70
+
71
+ // When run directly, we can't call MCP - output instructions
72
+ console.log(`\n 📚 Need to fetch docs for: ${technology.label}`);
73
+ console.log(` Context7 ID: ${technology.context7}`);
74
+
75
+ return {
76
+ technology: technology.value,
77
+ context7Id: technology.context7,
78
+ needsMCPFetch: true
79
+ };
80
+ }
81
+
82
+ // ============================================
83
+ // SKILL FILE GENERATION
84
+ // ============================================
85
+
86
+ function generateSkillMd(tech, docs) {
87
+ const date = new Date().toISOString().split('T')[0];
88
+
89
+ return `---
90
+ name: ${tech.value}
91
+ version: 1.0
92
+ context7: ${tech.context7 || 'null'}
93
+ generated: ${new Date().toISOString()}
94
+ learningCount: 0
95
+ successRate: null
96
+ ---
97
+
98
+ # ${tech.label} Skill
99
+
100
+ **Version**: Auto-generated ${date}
101
+ **Context7 ID**: ${tech.context7 || 'N/A'}
102
+
103
+ ## Overview
104
+
105
+ This skill provides patterns and conventions for ${tech.label} development.
106
+ ${docs?.overview || `Patterns extracted from ${tech.label} documentation.`}
107
+
108
+ ## When to Apply
109
+
110
+ Apply this skill when:
111
+ - Working on files related to ${tech.label}
112
+ - Creating new ${tech.label} components/modules
113
+ - Reviewing ${tech.label} code
114
+
115
+ ## Key Patterns
116
+
117
+ See \`knowledge/patterns.md\` for detailed patterns.
118
+
119
+ ## Anti-Patterns to Avoid
120
+
121
+ See \`knowledge/anti-patterns.md\` for common mistakes.
122
+
123
+ ## Conventions
124
+
125
+ See \`rules/conventions.md\` for coding standards.
126
+
127
+ ---
128
+ *Auto-generated by Wogi Flow Tech Stack Wizard*
129
+ `;
130
+ }
131
+
132
+ function generatePatternsMd(tech, docs) {
133
+ const keywords = TECH_KEYWORDS[tech.value] || [];
134
+ const keywordsSection = keywords.length > 0
135
+ ? `\n## Key Concepts\n\n${keywords.map(k => `- ${k}`).join('\n')}\n`
136
+ : '';
137
+
138
+ return `# ${tech.label} Patterns
139
+
140
+ Best practices and patterns for ${tech.label} development.
141
+ ${keywordsSection}
142
+ ## Patterns
143
+
144
+ ${docs?.patterns || `### Pattern 1: [To be populated]
145
+
146
+ When fetching documentation via Context7, patterns will be extracted automatically.
147
+
148
+ ### Pattern 2: [To be populated]
149
+
150
+ Run \`/wogi-setup-stack --regenerate\` after documentation is fetched.
151
+ `}
152
+
153
+ ## Examples
154
+
155
+ ${docs?.examples || `Examples will be populated from documentation.`}
156
+
157
+ ---
158
+ *Auto-generated - will be enhanced with Context7 documentation*
159
+ `;
160
+ }
161
+
162
+ function generateAntiPatternsMd(tech, docs) {
163
+ return `# ${tech.label} Anti-Patterns
164
+
165
+ Common mistakes and pitfalls to avoid with ${tech.label}.
166
+
167
+ ## Anti-Patterns
168
+
169
+ ${docs?.antiPatterns || `### Anti-Pattern 1: [To be populated]
170
+
171
+ Anti-patterns will be extracted from ${tech.label} documentation.
172
+
173
+ ### Anti-Pattern 2: [To be populated]
174
+
175
+ Common mistakes identified in documentation and community discussions.
176
+ `}
177
+
178
+ ## Why to Avoid
179
+
180
+ ${docs?.whyAvoid || `Each anti-pattern includes reasoning for why it should be avoided.`}
181
+
182
+ ---
183
+ *Auto-generated - will be enhanced with Context7 documentation*
184
+ `;
185
+ }
186
+
187
+ function generateConventionsMd(tech, docs) {
188
+ return `# ${tech.label} Conventions
189
+
190
+ Coding standards and conventions for ${tech.label}.
191
+
192
+ ## Naming Conventions
193
+
194
+ ${docs?.naming || `- Follow ${tech.label} community conventions
195
+ - Use consistent naming across the codebase`}
196
+
197
+ ## File Structure
198
+
199
+ ${docs?.fileStructure || `Follow the recommended ${tech.label} project structure.`}
200
+
201
+ ## Code Style
202
+
203
+ ${docs?.codeStyle || `- Follow official ${tech.label} style guide
204
+ - Use linting tools where available`}
205
+
206
+ ---
207
+ *Auto-generated - will be enhanced with Context7 documentation*
208
+ `;
209
+ }
210
+
211
+ // ============================================
212
+ // HUB-SPOKE SKILL GENERATION
213
+ // ============================================
214
+
215
+ /**
216
+ * Generate ecosystem link file for a hub skill
217
+ * These files link to related spoke skills for token efficiency
218
+ */
219
+ function generateEcosystemLinkMd(category, relatedSkills, parentFramework) {
220
+ const categoryLabels = {
221
+ stateManagement: 'State Management',
222
+ forms: 'Form Handling',
223
+ styling: 'Styling',
224
+ dataFetching: 'Data Fetching',
225
+ animation: 'Animation',
226
+ validation: 'Validation',
227
+ orm: 'ORM / Database',
228
+ auth: 'Authentication'
229
+ };
230
+
231
+ const label = categoryLabels[category] || category;
232
+ const skillLinks = relatedSkills.map(s => `- \`../${s}/skill.md\``).join('\n');
233
+
234
+ return `# ${label} for ${parentFramework}
235
+
236
+ This is an ecosystem link file. For ${label.toLowerCase()} patterns, see the dedicated skill:
237
+
238
+ ${skillLinks || '- No specific skill configured yet'}
239
+
240
+ ## Quick Reference
241
+
242
+ When working with ${label.toLowerCase()} in ${parentFramework}:
243
+ 1. Load the appropriate skill from the list above
244
+ 2. Follow patterns in that skill's \`knowledge/patterns.md\`
245
+ 3. Avoid anti-patterns in \`knowledge/anti-patterns.md\`
246
+
247
+ ---
248
+ *This is a link file - see referenced skills for detailed patterns*
249
+ `;
250
+ }
251
+
252
+ /**
253
+ * Generate skill.md for a hub (framework) skill
254
+ */
255
+ function generateHubSkillMd(tech, docs, ecosystemSkills) {
256
+ const date = new Date().toISOString().split('T')[0];
257
+
258
+ const ecosystemSection = ecosystemSkills.length > 0
259
+ ? `## Ecosystem Skills
260
+
261
+ This framework works with the following ecosystem skills (loaded on-demand):
262
+
263
+ ${ecosystemSkills.map(s => `- **${s.label}**: \`.claude/skills/${s.id}/\``).join('\n')}
264
+
265
+ When you import or work with these libraries, load their dedicated skill for detailed patterns.
266
+ `
267
+ : '';
268
+
269
+ return `---
270
+ name: ${tech.value}
271
+ version: 1.0
272
+ type: framework
273
+ context7: ${tech.context7 || 'null'}
274
+ generated: ${new Date().toISOString()}
275
+ learningCount: 0
276
+ successRate: null
277
+ ---
278
+
279
+ # ${tech.label} Skill
280
+
281
+ **Version**: Auto-generated ${date}
282
+ **Type**: Framework (Hub)
283
+ **Context7 ID**: ${tech.context7 || 'N/A'}
284
+
285
+ ## Overview
286
+
287
+ This is the main skill for ${tech.label} development. It contains core patterns and links to ecosystem skills.
288
+ ${docs?.overview || `Patterns extracted from ${tech.label} documentation.`}
289
+
290
+ ## When to Apply
291
+
292
+ This skill is automatically loaded when:
293
+ - Working on ${tech.label} files
294
+ - Creating new ${tech.label} components/modules
295
+ - Reviewing ${tech.label} code
296
+
297
+ ${ecosystemSection}
298
+ ## Core Patterns
299
+
300
+ See \`knowledge/patterns.md\` for detailed ${tech.label} patterns.
301
+
302
+ ## Anti-Patterns to Avoid
303
+
304
+ See \`knowledge/anti-patterns.md\` for common mistakes.
305
+
306
+ ## Conventions
307
+
308
+ See \`rules/conventions.md\` for coding standards.
309
+
310
+ ---
311
+ *Auto-generated by Wogi Flow Tech Stack Wizard*
312
+ `;
313
+ }
314
+
315
+ /**
316
+ * Generate skill.md for a spoke (library) skill
317
+ */
318
+ function generateSpokeSkillMd(tech, docs, parentFramework) {
319
+ const date = new Date().toISOString().split('T')[0];
320
+
321
+ const parentSection = parentFramework
322
+ ? `## Parent Framework
323
+
324
+ This skill is part of the **${parentFramework}** ecosystem. See \`.claude/skills/${parentFramework}/\` for framework-level patterns.
325
+ `
326
+ : '';
327
+
328
+ return `---
329
+ name: ${tech.value}
330
+ version: 1.0
331
+ type: library
332
+ context7: ${tech.context7 || 'null'}
333
+ parentFramework: ${parentFramework || 'null'}
334
+ generated: ${new Date().toISOString()}
335
+ learningCount: 0
336
+ successRate: null
337
+ ---
338
+
339
+ # ${tech.label} Skill
340
+
341
+ **Version**: Auto-generated ${date}
342
+ **Type**: Library (Spoke)
343
+ **Context7 ID**: ${tech.context7 || 'N/A'}
344
+
345
+ ## Overview
346
+
347
+ This skill provides focused patterns for ${tech.label}.
348
+ ${docs?.overview || `Patterns extracted from ${tech.label} documentation.`}
349
+
350
+ ${parentSection}
351
+ ## When to Load
352
+
353
+ This skill is loaded on-demand when:
354
+ - File imports \`${tech.value}\` or related packages
355
+ - Working on code that uses ${tech.label}
356
+ - User explicitly asks about ${tech.label}
357
+
358
+ ## Patterns
359
+
360
+ See \`knowledge/patterns.md\` for detailed patterns.
361
+
362
+ ## Anti-Patterns
363
+
364
+ See \`knowledge/anti-patterns.md\` for common mistakes.
365
+
366
+ ---
367
+ *Auto-generated by Wogi Flow Tech Stack Wizard*
368
+ *This is a focused library skill - kept small for token efficiency*
369
+ `;
370
+ }
371
+
372
+ // ============================================
373
+ // SKILLS INDEX GENERATION (v2.0 Hub-Spoke)
374
+ // ============================================
375
+
376
+ /**
377
+ * Generate skills index with hub-spoke structure
378
+ * v2.0 format includes: type, ecosystem, loadWith, tokenCost
379
+ */
380
+ function generateSkillsIndex(technologies, selections) {
381
+ const { getSkillType, getParentFramework, ECOSYSTEMS } = getTechOptions();
382
+ const skills = {};
383
+
384
+ // First pass: identify all technologies and their types
385
+ const techMap = new Map();
386
+ for (const tech of technologies) {
387
+ const skillId = tech.value.toLowerCase().replace(/[^a-z0-9]/g, '-');
388
+ const type = getSkillType(tech.value);
389
+ techMap.set(skillId, { tech, type });
390
+ }
391
+
392
+ // Second pass: build skill entries with relationships
393
+ for (const [skillId, { tech, type }] of techMap) {
394
+ const keywords = TECH_KEYWORDS[tech.value] || [];
395
+ const parentFramework = type === 'library' ? getParentFramework(tech.value, selections) : null;
396
+
397
+ // Find ecosystem members for framework skills
398
+ let ecosystem = [];
399
+ if (type === 'framework') {
400
+ for (const [otherId, other] of techMap) {
401
+ if (other.type === 'library') {
402
+ const otherParent = getParentFramework(other.tech.value, selections);
403
+ if (otherParent === tech.value) {
404
+ ecosystem.push(otherId);
405
+ }
406
+ }
407
+ }
408
+ }
409
+
410
+ // Determine loadWith - what skills should be loaded together
411
+ let loadWith = [];
412
+ if (type === 'framework' && ecosystem.length > 0) {
413
+ // Frameworks load with their most commonly used libraries
414
+ const defaultsToLoad = ['zustand', 'react-hook-form', 'tanstack-query', 'typeorm', 'passport'];
415
+ loadWith = ecosystem.filter(id => defaultsToLoad.some(d => id.includes(d)));
416
+ }
417
+
418
+ // Estimate token cost
419
+ const tokenCost = type === 'framework' ? 'medium' : 'low';
420
+
421
+ skills[skillId] = {
422
+ path: `.claude/skills/${skillId}/`,
423
+ label: tech.label,
424
+ context7: tech.context7,
425
+ type,
426
+ ecosystem: ecosystem.length > 0 ? ecosystem : undefined,
427
+ parentFramework: parentFramework || undefined,
428
+ loadWith: loadWith.length > 0 ? loadWith : undefined,
429
+ tokenCost,
430
+ covers: [tech.value, tech.label.toLowerCase(), ...keywords],
431
+ sections: {
432
+ overview: 'skill.md',
433
+ patterns: 'knowledge/patterns.md',
434
+ antiPatterns: 'knowledge/anti-patterns.md',
435
+ conventions: 'rules/conventions.md'
436
+ }
437
+ };
438
+ }
439
+
440
+ // Determine project stack from selections
441
+ const projectStack = technologies.map(t => t.value);
442
+
443
+ return {
444
+ version: '2.0',
445
+ generated: new Date().toISOString(),
446
+ projectType: selections.projectType,
447
+ focus: selections.focus,
448
+ loadingStrategy: {
449
+ framework: 'always',
450
+ library: 'on-demand'
451
+ },
452
+ skills,
453
+ projectStack
454
+ };
455
+ }
456
+
457
+ // ============================================
458
+ // MIGRATION: skills/ -> .claude/skills/ (v2.1.0)
459
+ // ============================================
460
+
461
+ /**
462
+ * Migrate skills from old skills/ location to new .claude/skills/ location
463
+ * Called during skill generation to ensure consistency with Claude Code 2.1.0
464
+ */
465
+ function migrateOldSkills(projectRoot) {
466
+ const oldSkillsDir = path.join(projectRoot, 'skills');
467
+ const newSkillsDir = path.join(projectRoot, '.claude', 'skills');
468
+
469
+ if (!fs.existsSync(oldSkillsDir)) {
470
+ return { migrated: [], skipped: [] };
471
+ }
472
+
473
+ const migrated = [];
474
+ const skipped = [];
475
+
476
+ try {
477
+ const entries = fs.readdirSync(oldSkillsDir, { withFileTypes: true });
478
+
479
+ for (const entry of entries) {
480
+ // Skip non-directories and special files
481
+ if (!entry.isDirectory()) {
482
+ // Handle skills-index.json specially
483
+ if (entry.name === 'skills-index.json') {
484
+ const oldPath = path.join(oldSkillsDir, entry.name);
485
+ const newPath = path.join(newSkillsDir, entry.name);
486
+
487
+ // Only migrate if new one doesn't exist
488
+ if (!fs.existsSync(newPath)) {
489
+ ensureDir(newSkillsDir);
490
+ fs.renameSync(oldPath, newPath);
491
+ migrated.push(`skills-index.json`);
492
+ } else {
493
+ fs.unlinkSync(oldPath);
494
+ skipped.push(`skills-index.json (newer exists in .claude/skills/)`);
495
+ }
496
+ }
497
+ continue;
498
+ }
499
+
500
+ const skillName = entry.name;
501
+ const oldSkillPath = path.join(oldSkillsDir, skillName);
502
+ const newSkillPath = path.join(newSkillsDir, skillName);
503
+
504
+ // Check if skill already exists in new location
505
+ if (fs.existsSync(newSkillPath)) {
506
+ // Skill exists in both places - keep new, remove old
507
+ fs.rmSync(oldSkillPath, { recursive: true, force: true });
508
+ skipped.push(`${skillName} (already in .claude/skills/)`);
509
+ } else {
510
+ // Move skill to new location
511
+ ensureDir(newSkillsDir);
512
+ fs.renameSync(oldSkillPath, newSkillPath);
513
+ migrated.push(skillName);
514
+ }
515
+ }
516
+
517
+ // Clean up empty skills/ directory
518
+ const remainingEntries = fs.readdirSync(oldSkillsDir);
519
+ if (remainingEntries.length === 0) {
520
+ fs.rmdirSync(oldSkillsDir);
521
+ }
522
+
523
+ } catch (error) {
524
+ console.warn(` Warning: Could not migrate old skills: ${error.message}`);
525
+ }
526
+
527
+ return { migrated, skipped };
528
+ }
529
+
530
+ // ============================================
531
+ // FILE WRITING
532
+ // ============================================
533
+
534
+ function ensureDir(dirPath) {
535
+ if (!fs.existsSync(dirPath)) {
536
+ fs.mkdirSync(dirPath, { recursive: true });
537
+ }
538
+ }
539
+
540
+ /**
541
+ * Write skill files with hub-spoke awareness
542
+ * @param {Object} tech - Technology definition
543
+ * @param {Object} docs - Documentation content (optional)
544
+ * @param {string} projectRoot - Project root path
545
+ * @param {Object} skillContext - Additional context { type, parentFramework, ecosystemSkills }
546
+ */
547
+ async function writeSkillFiles(tech, docs, projectRoot, skillContext = {}) {
548
+ const { getSkillType, getParentFramework } = getTechOptions();
549
+
550
+ const skillId = tech.value.toLowerCase().replace(/[^a-z0-9]/g, '-');
551
+ const skillDir = path.join(projectRoot, '.claude', 'skills', skillId);
552
+ const type = skillContext.type || getSkillType(tech.value);
553
+ const parentFramework = skillContext.parentFramework || null;
554
+ const ecosystemSkills = skillContext.ecosystemSkills || [];
555
+
556
+ // Create directory structure
557
+ ensureDir(skillDir);
558
+ ensureDir(path.join(skillDir, 'knowledge'));
559
+ ensureDir(path.join(skillDir, 'rules'));
560
+
561
+ // Framework (hub) skills get an ecosystem/ directory for link files
562
+ if (type === 'framework') {
563
+ ensureDir(path.join(skillDir, 'ecosystem'));
564
+ ensureDir(path.join(skillDir, 'templates'));
565
+ }
566
+
567
+ // Generate appropriate skill.md based on type
568
+ const skillMdContent = type === 'framework'
569
+ ? generateHubSkillMd(tech, docs, ecosystemSkills)
570
+ : generateSpokeSkillMd(tech, docs, parentFramework);
571
+
572
+ // Core files for all skills
573
+ const files = [
574
+ { path: 'skill.md', content: skillMdContent },
575
+ { path: 'knowledge/patterns.md', content: generatePatternsMd(tech, docs) },
576
+ { path: 'knowledge/anti-patterns.md', content: generateAntiPatternsMd(tech, docs) },
577
+ { path: 'knowledge/learnings.md', content: `# ${tech.label} Learnings\n\n*Empty - will be populated as you provide feedback during development.*\n` }
578
+ ];
579
+
580
+ // Framework skills get conventions, library skills are kept minimal
581
+ if (type === 'framework') {
582
+ files.push({ path: 'rules/conventions.md', content: generateConventionsMd(tech, docs) });
583
+ }
584
+
585
+ // Write all files
586
+ for (const file of files) {
587
+ const filePath = path.join(skillDir, file.path);
588
+ fs.writeFileSync(filePath, file.content, 'utf8');
589
+ }
590
+
591
+ return { skillDir, type, skillId };
592
+ }
593
+
594
+ /**
595
+ * Write ecosystem link files for a framework skill
596
+ */
597
+ async function writeEcosystemLinks(frameworkId, ecosystemCategories, projectRoot) {
598
+ const ecosystemDir = path.join(projectRoot, '.claude', 'skills', frameworkId, 'ecosystem');
599
+ ensureDir(ecosystemDir);
600
+
601
+ for (const [category, relatedSkills] of Object.entries(ecosystemCategories)) {
602
+ if (relatedSkills.length > 0) {
603
+ const content = generateEcosystemLinkMd(category, relatedSkills, frameworkId);
604
+ const filePath = path.join(ecosystemDir, `${category}.md`);
605
+ fs.writeFileSync(filePath, content, 'utf8');
606
+ }
607
+ }
608
+ }
609
+
610
+ function updateDecisionsMd(selections, technologies, projectRoot) {
611
+ const decisionsPath = path.join(projectRoot, '.workflow', 'state', 'decisions.md');
612
+
613
+ // Build tech stack section
614
+ const lines = [
615
+ '\n## Tech Stack (auto-generated)\n',
616
+ `*Generated: ${new Date().toISOString().split('T')[0]}*\n`
617
+ ];
618
+
619
+ if (selections.frontend && selections.frontend !== 'none') {
620
+ const frontendOpt = technologies.find(t => t.value === selections.frontend);
621
+ lines.push(`- **Frontend**: ${frontendOpt?.label || selections.frontend}`);
622
+ }
623
+
624
+ if (selections.stateManagement && selections.stateManagement !== 'none') {
625
+ const stateOpt = technologies.find(t => t.value === selections.stateManagement);
626
+ lines.push(`- **State Management**: ${stateOpt?.label || selections.stateManagement}`);
627
+ }
628
+
629
+ if (selections.styling && selections.styling !== 'none') {
630
+ const { STYLING_OPTIONS } = require('./flow-tech-options');
631
+ const styleOpt = STYLING_OPTIONS?.find(o => o.value === selections.styling);
632
+ lines.push(`- **Styling**: ${styleOpt?.label || selections.styling}`);
633
+ }
634
+
635
+ if (selections.backend && selections.backend !== 'none') {
636
+ const backendOpt = technologies.find(t => t.value === selections.backend);
637
+ lines.push(`- **Backend**: ${backendOpt?.label || selections.backend}`);
638
+ }
639
+
640
+ if (selections.database && selections.database !== 'none') {
641
+ const dbOpt = technologies.find(t => t.value === selections.database);
642
+ lines.push(`- **Database**: ${dbOpt?.label || selections.database}`);
643
+ }
644
+
645
+ if (selections.testing && selections.testing !== 'none') {
646
+ const { TESTING_OPTIONS } = require('./flow-tech-options');
647
+ const testOpt = TESTING_OPTIONS?.find(o => o.value === selections.testing);
648
+ lines.push(`- **Testing**: ${testOpt?.label || selections.testing}`);
649
+ }
650
+
651
+ if (selections.additionalTools?.length > 0) {
652
+ lines.push(`- **Additional Tools**: ${selections.additionalTools.join(', ')}`);
653
+ }
654
+
655
+ lines.push('\nSee `.claude/skills/skills-index.json` for detailed patterns.\n');
656
+
657
+ const techStackSection = lines.join('\n');
658
+
659
+ // Read existing decisions.md or create new
660
+ let content = '';
661
+ if (fs.existsSync(decisionsPath)) {
662
+ content = fs.readFileSync(decisionsPath, 'utf8');
663
+
664
+ // Remove existing tech stack section if present
665
+ const techStackRegex = /\n## Tech Stack \(auto-generated\)[\s\S]*?(?=\n## |$)/;
666
+ content = content.replace(techStackRegex, '');
667
+ } else {
668
+ content = `# Project Decisions\n\nProject-specific coding decisions and patterns.\n`;
669
+ ensureDir(path.dirname(decisionsPath));
670
+ }
671
+
672
+ // Append tech stack section
673
+ content = content.trimEnd() + '\n' + techStackSection;
674
+
675
+ fs.writeFileSync(decisionsPath, content, 'utf8');
676
+
677
+ // Sync to .claude/rules/ for Claude Code integration
678
+ syncDecisionsToRules();
679
+ }
680
+
681
+ function updateConfigJson(technologies, projectRoot) {
682
+ const configPath = path.join(projectRoot, '.workflow', 'config.json');
683
+
684
+ if (!fs.existsSync(configPath)) {
685
+ console.log(' Warning: config.json not found, skipping skills registration');
686
+ return;
687
+ }
688
+
689
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
690
+
691
+ // Update skills section
692
+ if (!config.skills) config.skills = {};
693
+ config.skills.installed = technologies.map(t =>
694
+ t.value.toLowerCase().replace(/[^a-z0-9]/g, '-')
695
+ );
696
+
697
+ fs.writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf8');
698
+ }
699
+
700
+ // ============================================
701
+ // MAIN GENERATION FUNCTION
702
+ // ============================================
703
+
704
+ async function generateSkills(technologies, selections) {
705
+ const projectRoot = process.cwd();
706
+ const { getSkillType, getParentFramework } = getTechOptions();
707
+
708
+ // Migrate any skills from old skills/ location to .claude/skills/
709
+ const migration = migrateOldSkills(projectRoot);
710
+ if (migration.migrated.length > 0) {
711
+ console.log('\n Migrated skills from skills/ to .claude/skills/:');
712
+ for (const skill of migration.migrated) {
713
+ console.log(` ✓ ${skill}`);
714
+ }
715
+ }
716
+ if (migration.skipped.length > 0) {
717
+ console.log('\n Cleaned up old skill locations:');
718
+ for (const skill of migration.skipped) {
719
+ console.log(` - ${skill}`);
720
+ }
721
+ }
722
+
723
+ // Categorize technologies into frameworks (hubs) and libraries (spokes)
724
+ const frameworks = [];
725
+ const libraries = [];
726
+
727
+ for (const tech of technologies) {
728
+ const type = getSkillType(tech.value);
729
+ if (type === 'framework') {
730
+ frameworks.push(tech);
731
+ } else {
732
+ libraries.push(tech);
733
+ }
734
+ }
735
+
736
+ console.log('\n Generating skills with hub-spoke structure:');
737
+ console.log(` Frameworks (hubs): ${frameworks.map(f => f.label).join(', ') || 'none'}`);
738
+ console.log(` Libraries (spokes): ${libraries.map(l => l.label).join(', ') || 'none'}`);
739
+
740
+ // Track what needs MCP fetching
741
+ const needsMCPFetch = [];
742
+
743
+ // Build ecosystem mapping: which libraries belong to which frameworks
744
+ const frameworkEcosystems = new Map();
745
+ for (const fw of frameworks) {
746
+ frameworkEcosystems.set(fw.value, {
747
+ tech: fw,
748
+ libraries: [],
749
+ categories: {} // { stateManagement: ['zustand'], forms: ['react-hook-form'], ... }
750
+ });
751
+ }
752
+
753
+ // Associate libraries with their parent frameworks
754
+ for (const lib of libraries) {
755
+ const parent = getParentFramework(lib.value, selections);
756
+ if (parent && frameworkEcosystems.has(parent)) {
757
+ const ecosystem = frameworkEcosystems.get(parent);
758
+ ecosystem.libraries.push(lib);
759
+
760
+ // Categorize the library
761
+ const category = categorizeLibrary(lib.value);
762
+ if (!ecosystem.categories[category]) {
763
+ ecosystem.categories[category] = [];
764
+ }
765
+ ecosystem.categories[category].push(lib.value.toLowerCase().replace(/[^a-z0-9]/g, '-'));
766
+ }
767
+ }
768
+
769
+ // Generate framework (hub) skills first
770
+ for (const fw of frameworks) {
771
+ console.log(`\n Processing framework: ${fw.label} (hub)...`);
772
+
773
+ const docResult = await fetchDocsViaContext7(fw);
774
+ if (docResult?.needsMCPFetch) {
775
+ needsMCPFetch.push(docResult);
776
+ }
777
+
778
+ const ecosystem = frameworkEcosystems.get(fw.value);
779
+ const ecosystemSkills = ecosystem.libraries.map(lib => ({
780
+ id: lib.value.toLowerCase().replace(/[^a-z0-9]/g, '-'),
781
+ label: lib.label
782
+ }));
783
+
784
+ const result = await writeSkillFiles(fw, null, projectRoot, {
785
+ type: 'framework',
786
+ ecosystemSkills
787
+ });
788
+
789
+ // Write ecosystem link files
790
+ if (Object.keys(ecosystem.categories).length > 0) {
791
+ await writeEcosystemLinks(result.skillId, ecosystem.categories, projectRoot);
792
+ console.log(` ✓ Created ecosystem links in: ${result.skillId}/ecosystem/`);
793
+ }
794
+
795
+ console.log(` ✓ Created: ${path.relative(projectRoot, result.skillDir)}`);
796
+ }
797
+
798
+ // Generate library (spoke) skills
799
+ for (const lib of libraries) {
800
+ console.log(`\n Processing library: ${lib.label} (spoke)...`);
801
+
802
+ const docResult = await fetchDocsViaContext7(lib);
803
+ if (docResult?.needsMCPFetch) {
804
+ needsMCPFetch.push(docResult);
805
+ }
806
+
807
+ const parentFramework = getParentFramework(lib.value, selections);
808
+ const result = await writeSkillFiles(lib, null, projectRoot, {
809
+ type: 'library',
810
+ parentFramework
811
+ });
812
+
813
+ console.log(` ✓ Created: ${path.relative(projectRoot, result.skillDir)}`);
814
+ }
815
+
816
+ // Generate skills index (v2.0)
817
+ const skillsIndex = generateSkillsIndex(technologies, selections);
818
+ const indexPath = path.join(projectRoot, '.claude', 'skills', 'skills-index.json');
819
+ ensureDir(path.dirname(indexPath));
820
+ fs.writeFileSync(indexPath, JSON.stringify(skillsIndex, null, 2), 'utf8');
821
+ console.log(`\n ✓ Created: .claude/skills/skills-index.json (v2.0 format)`);
822
+
823
+ // Update decisions.md
824
+ updateDecisionsMd(selections, technologies, projectRoot);
825
+ console.log(` ✓ Updated: .workflow/state/decisions.md`);
826
+
827
+ // Update config.json
828
+ updateConfigJson(technologies, projectRoot);
829
+ console.log(` ✓ Updated: .workflow/config.json`);
830
+
831
+ // If running outside of Claude, provide MCP instructions
832
+ if (needsMCPFetch.length > 0) {
833
+ console.log('\n' + '='.repeat(60));
834
+ console.log(' Documentation Fetch Required');
835
+ console.log('='.repeat(60));
836
+ console.log('\n Skill placeholders created. To populate with real documentation:');
837
+ console.log('\n 1. In Claude Code, run:');
838
+ console.log(' /wogi-setup-stack --fetch-docs\n');
839
+ console.log(' 2. Claude will use Context7 MCP to fetch latest docs for:');
840
+ for (const tech of needsMCPFetch) {
841
+ console.log(` - ${tech.technology} (${tech.context7Id})`);
842
+ }
843
+ console.log('\n 3. Skills will be updated with extracted patterns.');
844
+ console.log();
845
+ }
846
+
847
+ // Summary with hub-spoke info
848
+ console.log('\n' + '─'.repeat(60));
849
+ console.log(' Skills generated with hub-spoke structure:');
850
+ console.log('─'.repeat(60));
851
+ console.log(` Frameworks (always loaded): ${frameworks.length}`);
852
+ console.log(` Libraries (loaded on-demand): ${libraries.length}`);
853
+ console.log('\n Loading strategy:');
854
+ console.log(' • Framework skills load when working on framework files');
855
+ console.log(' • Library skills load when file imports that library');
856
+ console.log(' • This saves tokens while keeping relevant context available');
857
+ console.log();
858
+
859
+ return {
860
+ skillsCreated: technologies.map(t => t.value),
861
+ indexPath,
862
+ needsMCPFetch,
863
+ hubSpokeStats: {
864
+ frameworks: frameworks.length,
865
+ libraries: libraries.length
866
+ }
867
+ };
868
+ }
869
+
870
+ /**
871
+ * Categorize a library into its ecosystem category
872
+ */
873
+ function categorizeLibrary(libValue) {
874
+ const categories = {
875
+ stateManagement: ['zustand', 'redux', 'jotai', 'recoil', 'mobx', 'pinia', 'vuex', 'xstate'],
876
+ forms: ['react-hook-form', 'formik', 'vee-validate', 'formkit'],
877
+ styling: ['tailwind', 'shadcn', 'styled-components', 'emotion', 'vanilla-extract', 'sass'],
878
+ dataFetching: ['tanstack-query', 'swr', 'rtk-query', 'apollo'],
879
+ animation: ['framer-motion', 'react-spring', 'gsap', 'auto-animate'],
880
+ validation: ['zod', 'yup', 'class-validator', 'joi', 'valibot'],
881
+ orm: ['prisma', 'drizzle', 'typeorm', 'mikro-orm', 'mongoose', 'sequelize', 'sqlalchemy'],
882
+ auth: ['next-auth', 'clerk', 'auth0', 'supabase-auth', 'firebase-auth', 'passport', 'lucia'],
883
+ testing: ['vitest', 'jest', 'testing-library', 'playwright', 'cypress', 'pytest']
884
+ };
885
+
886
+ for (const [category, libs] of Object.entries(categories)) {
887
+ if (libs.includes(libValue)) {
888
+ return category;
889
+ }
890
+ }
891
+
892
+ return 'other';
893
+ }
894
+
895
+ /**
896
+ * Enhance a skill with documentation from Context7
897
+ * Called by Claude after fetching docs via MCP
898
+ */
899
+ async function enhanceSkillWithDocs(skillId, docs) {
900
+ const projectRoot = process.cwd();
901
+ const skillDir = path.join(projectRoot, '.claude', 'skills', skillId);
902
+
903
+ if (!fs.existsSync(skillDir)) {
904
+ throw new Error(`Skill directory not found: ${skillDir}`);
905
+ }
906
+
907
+ // Parse the docs to extract patterns, anti-patterns, etc.
908
+ const parsed = parseDocumentation(docs);
909
+
910
+ // Update patterns.md
911
+ if (parsed.patterns) {
912
+ const patternsPath = path.join(skillDir, 'knowledge', 'patterns.md');
913
+ let content = fs.readFileSync(patternsPath, 'utf8');
914
+
915
+ // Replace placeholder patterns
916
+ content = content.replace(
917
+ /## Patterns[\s\S]*?(?=## Examples|---)/,
918
+ `## Patterns\n\n${parsed.patterns}\n\n`
919
+ );
920
+
921
+ if (parsed.examples) {
922
+ content = content.replace(
923
+ /## Examples[\s\S]*?(?=---)/,
924
+ `## Examples\n\n${parsed.examples}\n\n`
925
+ );
926
+ }
927
+
928
+ fs.writeFileSync(patternsPath, content, 'utf8');
929
+ }
930
+
931
+ // Update anti-patterns.md
932
+ if (parsed.antiPatterns) {
933
+ const antiPatternsPath = path.join(skillDir, 'knowledge', 'anti-patterns.md');
934
+ let content = fs.readFileSync(antiPatternsPath, 'utf8');
935
+
936
+ content = content.replace(
937
+ /## Anti-Patterns[\s\S]*?(?=## Why|---)/,
938
+ `## Anti-Patterns\n\n${parsed.antiPatterns}\n\n`
939
+ );
940
+
941
+ fs.writeFileSync(antiPatternsPath, content, 'utf8');
942
+ }
943
+
944
+ // Update conventions.md
945
+ if (parsed.conventions) {
946
+ const conventionsPath = path.join(skillDir, 'rules', 'conventions.md');
947
+ fs.writeFileSync(conventionsPath, parsed.conventions, 'utf8');
948
+ }
949
+
950
+ return { enhanced: true, skillId };
951
+ }
952
+
953
+ /**
954
+ * Parse documentation text to extract patterns, anti-patterns, and conventions
955
+ */
956
+ function parseDocumentation(docs) {
957
+ // Simple parsing - in practice, Claude would do intelligent extraction
958
+ const result = {
959
+ patterns: null,
960
+ antiPatterns: null,
961
+ conventions: null,
962
+ examples: null
963
+ };
964
+
965
+ if (typeof docs === 'string') {
966
+ // Look for common section headers
967
+ const patternsMatch = docs.match(/(?:best practices|patterns|recommended)[\s\S]*?(?=\n#|$)/i);
968
+ if (patternsMatch) result.patterns = patternsMatch[0];
969
+
970
+ const antiMatch = docs.match(/(?:anti-patterns|avoid|common mistakes|pitfalls)[\s\S]*?(?=\n#|$)/i);
971
+ if (antiMatch) result.antiPatterns = antiMatch[0];
972
+
973
+ const examplesMatch = docs.match(/(?:examples|usage|code samples)[\s\S]*?(?=\n#|$)/i);
974
+ if (examplesMatch) result.examples = examplesMatch[0];
975
+ } else if (typeof docs === 'object') {
976
+ // Accept pre-parsed docs
977
+ result.patterns = docs.patterns;
978
+ result.antiPatterns = docs.antiPatterns;
979
+ result.conventions = docs.conventions;
980
+ result.examples = docs.examples;
981
+ }
982
+
983
+ return result;
984
+ }
985
+
986
+ // ============================================
987
+ // EXPORTS
988
+ // ============================================
989
+
990
+ module.exports = {
991
+ generateSkills,
992
+ enhanceSkillWithDocs,
993
+ parseDocumentation,
994
+ generateSkillsIndex,
995
+ migrateOldSkills,
996
+ writeSkillFiles,
997
+ writeEcosystemLinks,
998
+ generateHubSkillMd,
999
+ generateSpokeSkillMd,
1000
+ generateEcosystemLinkMd,
1001
+ categorizeLibrary,
1002
+ SKILL_TOPICS,
1003
+ TECH_KEYWORDS
1004
+ };
1005
+
1006
+ // CLI support
1007
+ if (require.main === module) {
1008
+ const args = process.argv.slice(2);
1009
+
1010
+ if (args.includes('--help')) {
1011
+ console.log(`
1012
+ Skill Generator for Wogi Flow
1013
+
1014
+ Usage:
1015
+ node flow-skill-generator.js [options]
1016
+
1017
+ Options:
1018
+ --from-selections Generate from saved stack-selections.json
1019
+ --help Show this help
1020
+
1021
+ This script is typically called by the Tech Stack Wizard.
1022
+ For manual use, run the wizard first: node flow-stack-wizard.js
1023
+ `);
1024
+ process.exit(0);
1025
+ }
1026
+
1027
+ if (args.includes('--from-selections')) {
1028
+ const selectionsPath = path.join(process.cwd(), '.workflow', 'state', 'stack-selections.json');
1029
+
1030
+ if (!fs.existsSync(selectionsPath)) {
1031
+ console.error('No stack-selections.json found. Run the wizard first.');
1032
+ process.exit(1);
1033
+ }
1034
+
1035
+ const selections = JSON.parse(fs.readFileSync(selectionsPath, 'utf8'));
1036
+ const { collectTechnologiesFromSelections } = require('./flow-tech-options');
1037
+ const technologies = collectTechnologiesFromSelections(selections);
1038
+
1039
+ generateSkills(technologies, selections)
1040
+ .then(() => process.exit(0))
1041
+ .catch((err) => {
1042
+ console.error('Error:', err);
1043
+ process.exit(1);
1044
+ });
1045
+ }
1046
+ }