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.
- package/.workflow/agents/reviewer.md +81 -0
- package/.workflow/agents/security.md +94 -0
- package/.workflow/agents/story-writer.md +58 -0
- package/.workflow/bridges/base-bridge.js +395 -0
- package/.workflow/bridges/claude-bridge.js +434 -0
- package/.workflow/bridges/index.js +130 -0
- package/.workflow/lib/assumption-detector.js +481 -0
- package/.workflow/lib/config-substitution.js +371 -0
- package/.workflow/lib/failure-categories.js +478 -0
- package/.workflow/state/app-map.md.template +15 -0
- package/.workflow/state/architecture.md.template +24 -0
- package/.workflow/state/component-index.json.template +5 -0
- package/.workflow/state/decisions.md.template +15 -0
- package/.workflow/state/feedback-patterns.md.template +9 -0
- package/.workflow/state/knowledge-sync.json.template +6 -0
- package/.workflow/state/progress.md.template +14 -0
- package/.workflow/state/ready.json.template +7 -0
- package/.workflow/state/request-log.md.template +14 -0
- package/.workflow/state/session-state.json.template +11 -0
- package/.workflow/state/stack.md.template +33 -0
- package/.workflow/state/testing.md.template +36 -0
- package/.workflow/templates/claude-md.hbs +257 -0
- package/.workflow/templates/correction-report.md +67 -0
- package/.workflow/templates/gemini-md.hbs +52 -0
- package/README.md +1802 -0
- package/bin/flow +205 -0
- package/lib/index.js +33 -0
- package/lib/installer.js +467 -0
- package/lib/release-channel.js +269 -0
- package/lib/skill-registry.js +526 -0
- package/lib/upgrader.js +401 -0
- package/lib/utils.js +305 -0
- package/package.json +64 -0
- package/scripts/flow +985 -0
- package/scripts/flow-adaptive-learning.js +1259 -0
- package/scripts/flow-aggregate.js +488 -0
- package/scripts/flow-archive +133 -0
- package/scripts/flow-auto-context.js +1015 -0
- package/scripts/flow-auto-learn.js +615 -0
- package/scripts/flow-bridge.js +223 -0
- package/scripts/flow-browser-suggest.js +316 -0
- package/scripts/flow-bug.js +247 -0
- package/scripts/flow-cascade.js +711 -0
- package/scripts/flow-changelog +85 -0
- package/scripts/flow-checkpoint.js +483 -0
- package/scripts/flow-cli.js +403 -0
- package/scripts/flow-code-intelligence.js +760 -0
- package/scripts/flow-complexity.js +502 -0
- package/scripts/flow-config-set.js +152 -0
- package/scripts/flow-constants.js +157 -0
- package/scripts/flow-context +152 -0
- package/scripts/flow-context-init.js +482 -0
- package/scripts/flow-context-monitor.js +384 -0
- package/scripts/flow-context-scoring.js +886 -0
- package/scripts/flow-correct.js +458 -0
- package/scripts/flow-damage-control.js +985 -0
- package/scripts/flow-deps +101 -0
- package/scripts/flow-diff.js +700 -0
- package/scripts/flow-done +151 -0
- package/scripts/flow-done.js +489 -0
- package/scripts/flow-durable-session.js +1541 -0
- package/scripts/flow-entropy-monitor.js +345 -0
- package/scripts/flow-export-profile +349 -0
- package/scripts/flow-export-scanner.js +1046 -0
- package/scripts/flow-figma-confirm.js +400 -0
- package/scripts/flow-figma-extract.js +496 -0
- package/scripts/flow-figma-generate.js +683 -0
- package/scripts/flow-figma-index.js +909 -0
- package/scripts/flow-figma-match.js +617 -0
- package/scripts/flow-figma-mcp-server.js +518 -0
- package/scripts/flow-figma-pipeline.js +414 -0
- package/scripts/flow-file-ops.js +301 -0
- package/scripts/flow-gate-confidence.js +825 -0
- package/scripts/flow-guided-edit.js +659 -0
- package/scripts/flow-health +185 -0
- package/scripts/flow-health.js +413 -0
- package/scripts/flow-hooks.js +556 -0
- package/scripts/flow-http-client.js +249 -0
- package/scripts/flow-hybrid-detect.js +167 -0
- package/scripts/flow-hybrid-interactive.js +591 -0
- package/scripts/flow-hybrid-test.js +152 -0
- package/scripts/flow-import-profile +439 -0
- package/scripts/flow-init +253 -0
- package/scripts/flow-instruction-richness.js +827 -0
- package/scripts/flow-jira-integration.js +579 -0
- package/scripts/flow-knowledge-router.js +522 -0
- package/scripts/flow-knowledge-sync.js +589 -0
- package/scripts/flow-linear-integration.js +631 -0
- package/scripts/flow-links.js +774 -0
- package/scripts/flow-log-manager.js +559 -0
- package/scripts/flow-loop-enforcer.js +1246 -0
- package/scripts/flow-loop-retry-learning.js +630 -0
- package/scripts/flow-lsp.js +923 -0
- package/scripts/flow-map-index +348 -0
- package/scripts/flow-map-sync +201 -0
- package/scripts/flow-memory-blocks.js +668 -0
- package/scripts/flow-memory-compactor.js +350 -0
- package/scripts/flow-memory-db.js +1110 -0
- package/scripts/flow-memory-sync.js +484 -0
- package/scripts/flow-metrics.js +353 -0
- package/scripts/flow-migrate-ids.js +370 -0
- package/scripts/flow-model-adapter.js +802 -0
- package/scripts/flow-model-router.js +884 -0
- package/scripts/flow-models.js +1231 -0
- package/scripts/flow-morning.js +517 -0
- package/scripts/flow-multi-approach.js +660 -0
- package/scripts/flow-new-feature +86 -0
- package/scripts/flow-onboard +1042 -0
- package/scripts/flow-orchestrate-llm.js +459 -0
- package/scripts/flow-orchestrate.js +3592 -0
- package/scripts/flow-output.js +123 -0
- package/scripts/flow-parallel-detector.js +399 -0
- package/scripts/flow-parallel-dispatch.js +987 -0
- package/scripts/flow-parallel.js +428 -0
- package/scripts/flow-pattern-enforcer.js +600 -0
- package/scripts/flow-prd-manager.js +282 -0
- package/scripts/flow-progress.js +323 -0
- package/scripts/flow-project-analyzer.js +975 -0
- package/scripts/flow-prompt-composer.js +487 -0
- package/scripts/flow-providers.js +1381 -0
- package/scripts/flow-queue.js +308 -0
- package/scripts/flow-ready +82 -0
- package/scripts/flow-ready.js +189 -0
- package/scripts/flow-regression.js +396 -0
- package/scripts/flow-response-parser.js +450 -0
- package/scripts/flow-resume.js +284 -0
- package/scripts/flow-rules-sync.js +439 -0
- package/scripts/flow-run-trace.js +718 -0
- package/scripts/flow-safety.js +587 -0
- package/scripts/flow-search +104 -0
- package/scripts/flow-security.js +481 -0
- package/scripts/flow-session-end +106 -0
- package/scripts/flow-session-end.js +437 -0
- package/scripts/flow-session-state.js +671 -0
- package/scripts/flow-setup-hooks +216 -0
- package/scripts/flow-setup-hooks.js +377 -0
- package/scripts/flow-skill-create.js +329 -0
- package/scripts/flow-skill-creator.js +572 -0
- package/scripts/flow-skill-generator.js +1046 -0
- package/scripts/flow-skill-learn.js +880 -0
- package/scripts/flow-skill-matcher.js +578 -0
- package/scripts/flow-spec-generator.js +820 -0
- package/scripts/flow-stack-wizard.js +895 -0
- package/scripts/flow-standup +162 -0
- package/scripts/flow-start +74 -0
- package/scripts/flow-start.js +235 -0
- package/scripts/flow-status +110 -0
- package/scripts/flow-status.js +301 -0
- package/scripts/flow-step-browser.js +83 -0
- package/scripts/flow-step-changelog.js +217 -0
- package/scripts/flow-step-comments.js +306 -0
- package/scripts/flow-step-complexity.js +234 -0
- package/scripts/flow-step-coverage.js +218 -0
- package/scripts/flow-step-knowledge.js +193 -0
- package/scripts/flow-step-pr-tests.js +364 -0
- package/scripts/flow-step-regression.js +89 -0
- package/scripts/flow-step-review.js +516 -0
- package/scripts/flow-step-security.js +162 -0
- package/scripts/flow-step-silent-failures.js +290 -0
- package/scripts/flow-step-simplifier.js +346 -0
- package/scripts/flow-story +105 -0
- package/scripts/flow-story.js +500 -0
- package/scripts/flow-suspend.js +252 -0
- package/scripts/flow-sync-daemon.js +654 -0
- package/scripts/flow-task-analyzer.js +606 -0
- package/scripts/flow-team-dashboard.js +748 -0
- package/scripts/flow-team-sync.js +752 -0
- package/scripts/flow-team.js +977 -0
- package/scripts/flow-tech-options.js +528 -0
- package/scripts/flow-templates.js +812 -0
- package/scripts/flow-tiered-learning.js +728 -0
- package/scripts/flow-trace +204 -0
- package/scripts/flow-transcript-chunking.js +1106 -0
- package/scripts/flow-transcript-digest.js +7918 -0
- package/scripts/flow-transcript-language.js +465 -0
- package/scripts/flow-transcript-parsing.js +1085 -0
- package/scripts/flow-transcript-stories.js +2194 -0
- package/scripts/flow-update-map +224 -0
- package/scripts/flow-utils.js +2242 -0
- package/scripts/flow-verification.js +644 -0
- package/scripts/flow-verify.js +1177 -0
- package/scripts/flow-voice-input.js +638 -0
- package/scripts/flow-watch +168 -0
- package/scripts/flow-workflow-steps.js +521 -0
- package/scripts/flow-workflow.js +1029 -0
- package/scripts/flow-worktree.js +489 -0
- package/scripts/hooks/adapters/base-adapter.js +102 -0
- package/scripts/hooks/adapters/claude-code.js +359 -0
- package/scripts/hooks/adapters/index.js +79 -0
- package/scripts/hooks/core/component-check.js +341 -0
- package/scripts/hooks/core/index.js +35 -0
- package/scripts/hooks/core/loop-check.js +241 -0
- package/scripts/hooks/core/session-context.js +294 -0
- package/scripts/hooks/core/task-gate.js +177 -0
- package/scripts/hooks/core/validation.js +230 -0
- package/scripts/hooks/entry/claude-code/post-tool-use.js +65 -0
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +89 -0
- package/scripts/hooks/entry/claude-code/session-end.js +87 -0
- package/scripts/hooks/entry/claude-code/session-start.js +46 -0
- package/scripts/hooks/entry/claude-code/stop.js +43 -0
- package/scripts/postinstall.js +139 -0
- package/templates/browser-test-flow.json +56 -0
- package/templates/bug-report.md +43 -0
- package/templates/component-detail.md +42 -0
- package/templates/component.stories.tsx +49 -0
- package/templates/context/constraints.md +83 -0
- package/templates/context/conventions.md +177 -0
- package/templates/context/stack.md +60 -0
- package/templates/correction-report.md +90 -0
- package/templates/feature-proposal.md +35 -0
- package/templates/hybrid/_base.md +254 -0
- package/templates/hybrid/_patterns.md +45 -0
- package/templates/hybrid/create-component.md +127 -0
- package/templates/hybrid/create-file.md +56 -0
- package/templates/hybrid/create-hook.md +145 -0
- package/templates/hybrid/create-service.md +70 -0
- package/templates/hybrid/fix-bug.md +33 -0
- package/templates/hybrid/modify-file.md +55 -0
- package/templates/story.md +68 -0
- package/templates/task.json +56 -0
- 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
|
+
}
|