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,578 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Skill Matcher (Priority 1: Model-Invoked Skills)
5
+ *
6
+ * Automatically matches skills to task context based on:
7
+ * - Task description keywords
8
+ * - File patterns being modified
9
+ * - Task type (feature, bugfix, refactor)
10
+ *
11
+ * Uses model-invoked skills approach.
12
+ *
13
+ * Usage:
14
+ * const { matchSkills, loadSkillContext } = require('./flow-skill-matcher');
15
+ * const skills = await matchSkills('implement user authentication');
16
+ * const context = await loadSkillContext(skills);
17
+ */
18
+
19
+ const fs = require('fs');
20
+ const path = require('path');
21
+ const { getProjectRoot, getConfig, PATHS, colors } = require('./flow-utils');
22
+
23
+ const PROJECT_ROOT = getProjectRoot();
24
+ const SKILLS_DIR = path.join(PROJECT_ROOT, '.claude', 'skills');
25
+
26
+ // ============================================================
27
+ // Skill Trigger Definitions
28
+ // ============================================================
29
+
30
+ /**
31
+ * Default trigger patterns for skills
32
+ * These are used if the skill.md doesn't define explicit triggers
33
+ */
34
+ const DEFAULT_TRIGGERS = {
35
+ 'nestjs': {
36
+ keywords: ['nestjs', 'nest', 'module', 'controller', 'service', 'entity', 'dto', 'typeorm', 'backend', 'api'],
37
+ filePatterns: ['*.module.ts', '*.controller.ts', '*.service.ts', '*.entity.ts', '*.dto.ts'],
38
+ taskTypes: ['feature', 'bugfix', 'refactor'],
39
+ categories: ['backend', 'api', 'database']
40
+ },
41
+ 'react': {
42
+ keywords: ['react', 'component', 'hook', 'usestate', 'useeffect', 'jsx', 'tsx', 'frontend', 'ui'],
43
+ filePatterns: ['*.tsx', '*.jsx', 'use*.ts', '*.component.tsx'],
44
+ taskTypes: ['feature', 'bugfix', 'refactor'],
45
+ categories: ['frontend', 'ui', 'component']
46
+ },
47
+ 'python': {
48
+ keywords: ['python', 'pip', 'django', 'flask', 'fastapi', 'pytest', 'pydantic'],
49
+ filePatterns: ['*.py', 'requirements.txt', 'setup.py', 'pyproject.toml'],
50
+ taskTypes: ['feature', 'bugfix', 'refactor'],
51
+ categories: ['backend', 'scripting']
52
+ },
53
+ 'figma-analyzer': {
54
+ keywords: ['figma', 'design', 'ui', 'component', 'design-system', 'tokens'],
55
+ filePatterns: [],
56
+ taskTypes: ['feature'],
57
+ categories: ['design', 'ui']
58
+ },
59
+ 'transcript-digestion': {
60
+ keywords: ['transcript', 'meeting', 'notes', 'spec', 'requirements', 'extract', 'digestion'],
61
+ filePatterns: [],
62
+ taskTypes: ['feature', 'story'],
63
+ categories: ['planning', 'documentation']
64
+ }
65
+ };
66
+
67
+ // ============================================================
68
+ // Skill Loading
69
+ // ============================================================
70
+
71
+ /**
72
+ * Load skill metadata from skill.md
73
+ * Parses YAML frontmatter and extracts trigger configuration
74
+ */
75
+ function loadSkillMetadata(skillName) {
76
+ // Skip template skills
77
+ if (skillName === '_template' || skillName.startsWith('_')) {
78
+ return null;
79
+ }
80
+
81
+ const skillPath = path.join(SKILLS_DIR, skillName, 'skill.md');
82
+
83
+ if (!fs.existsSync(skillPath)) {
84
+ return null;
85
+ }
86
+
87
+ try {
88
+ const content = fs.readFileSync(skillPath, 'utf-8');
89
+ const metadata = { name: skillName };
90
+
91
+ // Parse YAML frontmatter
92
+ const frontmatterMatch = content.match(/^---\n([\s\S]*?)\n---/);
93
+ if (frontmatterMatch) {
94
+ const frontmatter = frontmatterMatch[1];
95
+
96
+ // Extract key-value pairs
97
+ for (const line of frontmatter.split('\n')) {
98
+ const [key, ...valueParts] = line.split(':');
99
+ if (key && valueParts.length > 0) {
100
+ const value = valueParts.join(':').trim();
101
+ metadata[key.trim()] = value;
102
+ }
103
+ }
104
+
105
+ // Skip skills marked as not loadable or templates
106
+ if (metadata.loadable === 'false' || metadata.template === 'true') {
107
+ return null;
108
+ }
109
+ }
110
+
111
+ // Extract triggers section if present
112
+ const triggersMatch = content.match(/## Triggers\n([\s\S]*?)(?=\n## |$)/);
113
+ if (triggersMatch) {
114
+ metadata.triggers = parseTriggersSection(triggersMatch[1]);
115
+ }
116
+
117
+ // Extract file patterns from "File Patterns" section
118
+ const filePatternsMatch = content.match(/## File Patterns\n([\s\S]*?)(?=\n## |$)/);
119
+ if (filePatternsMatch) {
120
+ metadata.filePatterns = parseListSection(filePatternsMatch[1]);
121
+ }
122
+
123
+ // Extract "When to Use" section for keyword hints
124
+ const whenToUseMatch = content.match(/## When to Use\n([\s\S]*?)(?=\n## |$)/);
125
+ if (whenToUseMatch) {
126
+ metadata.whenToUse = whenToUseMatch[1].trim();
127
+ }
128
+
129
+ return metadata;
130
+ } catch (err) {
131
+ console.warn(`Warning: Could not load skill metadata for ${skillName}: ${err.message}`);
132
+ return null;
133
+ }
134
+ }
135
+
136
+ /**
137
+ * Parse triggers section from skill.md
138
+ */
139
+ function parseTriggersSection(section) {
140
+ const triggers = {
141
+ keywords: [],
142
+ filePatterns: [],
143
+ taskTypes: [],
144
+ categories: []
145
+ };
146
+
147
+ const lines = section.split('\n');
148
+ let currentKey = null;
149
+
150
+ for (const line of lines) {
151
+ const trimmed = line.trim();
152
+
153
+ if (trimmed.startsWith('- keywords:')) {
154
+ currentKey = 'keywords';
155
+ const inline = trimmed.replace('- keywords:', '').trim();
156
+ if (inline) {
157
+ triggers.keywords = parseInlineArray(inline);
158
+ }
159
+ } else if (trimmed.startsWith('- file_patterns:') || trimmed.startsWith('- filePatterns:')) {
160
+ currentKey = 'filePatterns';
161
+ const inline = trimmed.replace(/- (file_patterns|filePatterns):/, '').trim();
162
+ if (inline) {
163
+ triggers.filePatterns = parseInlineArray(inline);
164
+ }
165
+ } else if (trimmed.startsWith('- task_types:') || trimmed.startsWith('- taskTypes:')) {
166
+ currentKey = 'taskTypes';
167
+ const inline = trimmed.replace(/- (task_types|taskTypes):/, '').trim();
168
+ if (inline) {
169
+ triggers.taskTypes = parseInlineArray(inline);
170
+ }
171
+ } else if (trimmed.startsWith('- categories:')) {
172
+ currentKey = 'categories';
173
+ const inline = trimmed.replace('- categories:', '').trim();
174
+ if (inline) {
175
+ triggers.categories = parseInlineArray(inline);
176
+ }
177
+ } else if (trimmed.startsWith('- ') && currentKey) {
178
+ const value = trimmed.substring(2).replace(/^["']|["']$/g, '');
179
+ triggers[currentKey].push(value);
180
+ }
181
+ }
182
+
183
+ return triggers;
184
+ }
185
+
186
+ /**
187
+ * Parse inline array like ["a", "b", "c"]
188
+ */
189
+ function parseInlineArray(str) {
190
+ const match = str.match(/\[([^\]]*)\]/);
191
+ if (match) {
192
+ return match[1].split(',').map(s => s.trim().replace(/^["']|["']$/g, '')).filter(Boolean);
193
+ }
194
+ return [];
195
+ }
196
+
197
+ /**
198
+ * Parse list section (bullet points)
199
+ */
200
+ function parseListSection(section) {
201
+ return section
202
+ .split('\n')
203
+ .filter(line => line.trim().startsWith('- '))
204
+ .map(line => line.trim().substring(2).replace(/`/g, ''));
205
+ }
206
+
207
+ /**
208
+ * Get all installed skills with their triggers
209
+ */
210
+ function getAllSkills() {
211
+ const config = getConfig();
212
+ const installedSkills = config.skills?.installed || [];
213
+ const skills = [];
214
+
215
+ for (const skillName of installedSkills) {
216
+ const metadata = loadSkillMetadata(skillName);
217
+ const defaultTriggers = DEFAULT_TRIGGERS[skillName] || {
218
+ keywords: [],
219
+ filePatterns: [],
220
+ taskTypes: ['feature', 'bugfix', 'refactor'],
221
+ categories: []
222
+ };
223
+
224
+ skills.push({
225
+ name: skillName,
226
+ metadata: metadata || {},
227
+ triggers: metadata?.triggers || defaultTriggers,
228
+ filePatterns: metadata?.filePatterns || defaultTriggers.filePatterns
229
+ });
230
+ }
231
+
232
+ return skills;
233
+ }
234
+
235
+ // ============================================================
236
+ // Skill Matching
237
+ // ============================================================
238
+
239
+ /**
240
+ * Match skills to task context
241
+ * Returns ranked list of applicable skills with match scores
242
+ *
243
+ * @param {string} taskDescription - Task description text
244
+ * @param {object} options - Matching options
245
+ * @param {string[]} options.filePaths - Files being modified
246
+ * @param {string} options.taskType - Task type (feature, bugfix, refactor)
247
+ * @param {string[]} options.categories - Task categories
248
+ */
249
+ function matchSkills(taskDescription, options = {}) {
250
+ const config = getConfig();
251
+
252
+ // Check if auto-invoke is enabled
253
+ if (config.skills?.autoInvoke === false) {
254
+ return [];
255
+ }
256
+
257
+ const skills = getAllSkills();
258
+ const matches = [];
259
+
260
+ const descLower = taskDescription.toLowerCase();
261
+ const filePaths = options.filePaths || [];
262
+ const taskType = options.taskType || 'feature';
263
+ const categories = options.categories || [];
264
+
265
+ for (const skill of skills) {
266
+ let score = 0;
267
+ const matchReasons = [];
268
+
269
+ // 1. Keyword matching (highest weight)
270
+ const keywords = skill.triggers.keywords || [];
271
+ for (const keyword of keywords) {
272
+ if (descLower.includes(keyword.toLowerCase())) {
273
+ score += 3;
274
+ matchReasons.push(`keyword: "${keyword}"`);
275
+ }
276
+ }
277
+
278
+ // 2. File pattern matching
279
+ const filePatterns = skill.filePatterns || skill.triggers.filePatterns || [];
280
+ for (const pattern of filePatterns) {
281
+ const regex = patternToRegex(pattern);
282
+ for (const filePath of filePaths) {
283
+ if (regex.test(filePath)) {
284
+ score += 2;
285
+ matchReasons.push(`file pattern: "${pattern}"`);
286
+ break; // Only count once per pattern
287
+ }
288
+ }
289
+ }
290
+
291
+ // 3. Task type matching
292
+ const taskTypes = skill.triggers.taskTypes || [];
293
+ if (taskTypes.includes(taskType)) {
294
+ score += 1;
295
+ matchReasons.push(`task type: "${taskType}"`);
296
+ }
297
+
298
+ // 4. Category matching
299
+ const skillCategories = skill.triggers.categories || [];
300
+ for (const cat of categories) {
301
+ if (skillCategories.includes(cat.toLowerCase())) {
302
+ score += 1;
303
+ matchReasons.push(`category: "${cat}"`);
304
+ }
305
+ }
306
+
307
+ // Only include skills with score > 0
308
+ if (score > 0) {
309
+ matches.push({
310
+ name: skill.name,
311
+ score,
312
+ reasons: matchReasons,
313
+ metadata: skill.metadata
314
+ });
315
+ }
316
+ }
317
+
318
+ // Sort by score descending
319
+ matches.sort((a, b) => b.score - a.score);
320
+
321
+ return matches;
322
+ }
323
+
324
+ /**
325
+ * Convert glob pattern to regex
326
+ */
327
+ function patternToRegex(pattern) {
328
+ const escaped = pattern
329
+ .replace(/[.+^${}()|[\]\\]/g, '\\$&')
330
+ .replace(/\*/g, '.*')
331
+ .replace(/\?/g, '.');
332
+ return new RegExp(escaped, 'i');
333
+ }
334
+
335
+ // ============================================================
336
+ // Skill Context Loading
337
+ // ============================================================
338
+
339
+ /**
340
+ * Load skill context for matched skills
341
+ * Returns combined context from all matched skills
342
+ *
343
+ * @param {Array} matchedSkills - Skills returned from matchSkills()
344
+ * @param {object} options - Loading options
345
+ * @param {number} options.maxSkills - Max skills to load (default: 3)
346
+ * @param {boolean} options.includePatterns - Include patterns.md
347
+ * @param {boolean} options.includeAntiPatterns - Include anti-patterns.md
348
+ * @param {boolean} options.includeLearnings - Include learnings.md
349
+ */
350
+ async function loadSkillContext(matchedSkills, options = {}) {
351
+ const maxSkills = options.maxSkills || 3;
352
+ const includePatterns = options.includePatterns !== false;
353
+ const includeAntiPatterns = options.includeAntiPatterns !== false;
354
+ const includeLearnings = options.includeLearnings !== false;
355
+
356
+ const skillsToLoad = matchedSkills.slice(0, maxSkills);
357
+ const context = {
358
+ skills: [],
359
+ totalTokenEstimate: 0
360
+ };
361
+
362
+ for (const skill of skillsToLoad) {
363
+ const skillDir = path.join(SKILLS_DIR, skill.name);
364
+ const skillContext = {
365
+ name: skill.name,
366
+ score: skill.score,
367
+ reasons: skill.reasons,
368
+ files: {}
369
+ };
370
+
371
+ // Load skill.md (main description)
372
+ const skillMdPath = path.join(skillDir, 'skill.md');
373
+ if (fs.existsSync(skillMdPath)) {
374
+ skillContext.files['skill.md'] = fs.readFileSync(skillMdPath, 'utf-8');
375
+ }
376
+
377
+ // Load knowledge files
378
+ const knowledgeDir = path.join(skillDir, 'knowledge');
379
+ if (fs.existsSync(knowledgeDir)) {
380
+ if (includePatterns) {
381
+ const patternsPath = path.join(knowledgeDir, 'patterns.md');
382
+ if (fs.existsSync(patternsPath)) {
383
+ skillContext.files['patterns.md'] = fs.readFileSync(patternsPath, 'utf-8');
384
+ }
385
+ }
386
+
387
+ if (includeAntiPatterns) {
388
+ const antiPatternsPath = path.join(knowledgeDir, 'anti-patterns.md');
389
+ if (fs.existsSync(antiPatternsPath)) {
390
+ skillContext.files['anti-patterns.md'] = fs.readFileSync(antiPatternsPath, 'utf-8');
391
+ }
392
+ }
393
+
394
+ if (includeLearnings) {
395
+ const learningsPath = path.join(knowledgeDir, 'learnings.md');
396
+ if (fs.existsSync(learningsPath)) {
397
+ skillContext.files['learnings.md'] = fs.readFileSync(learningsPath, 'utf-8');
398
+ }
399
+ }
400
+ }
401
+
402
+ // Load rules/conventions
403
+ const conventionsPath = path.join(skillDir, 'rules', 'conventions.md');
404
+ if (fs.existsSync(conventionsPath)) {
405
+ skillContext.files['conventions.md'] = fs.readFileSync(conventionsPath, 'utf-8');
406
+ }
407
+
408
+ // Estimate tokens (rough: 1 token ≈ 4 chars)
409
+ skillContext.tokenEstimate = Object.values(skillContext.files)
410
+ .reduce((sum, content) => sum + Math.ceil(content.length / 4), 0);
411
+
412
+ context.skills.push(skillContext);
413
+ context.totalTokenEstimate += skillContext.tokenEstimate;
414
+ }
415
+
416
+ return context;
417
+ }
418
+
419
+ /**
420
+ * Format skill context for display/injection
421
+ */
422
+ function formatSkillContext(skillContext) {
423
+ let output = '';
424
+
425
+ for (const skill of skillContext.skills) {
426
+ output += `\n${'='.repeat(60)}\n`;
427
+ output += `## Skill: ${skill.name} (score: ${skill.score})\n`;
428
+ output += `Matched because: ${skill.reasons.join(', ')}\n`;
429
+ output += `${'='.repeat(60)}\n\n`;
430
+
431
+ for (const [filename, content] of Object.entries(skill.files)) {
432
+ output += `### ${filename}\n\n`;
433
+ output += content;
434
+ output += '\n\n';
435
+ }
436
+ }
437
+
438
+ return output;
439
+ }
440
+
441
+ /**
442
+ * Get skill summary for display
443
+ */
444
+ function getSkillSummary(matchedSkills) {
445
+ if (matchedSkills.length === 0) {
446
+ return `${colors.dim}No skills matched for this task${colors.reset}`;
447
+ }
448
+
449
+ let output = `${colors.cyan}🔧 Matched Skills:${colors.reset}\n`;
450
+
451
+ for (const skill of matchedSkills.slice(0, 5)) {
452
+ const scoreBar = '●'.repeat(Math.min(skill.score, 5)) + '○'.repeat(Math.max(0, 5 - skill.score));
453
+ output += ` ${colors.green}${skill.name}${colors.reset} [${scoreBar}]\n`;
454
+ output += ` ${colors.dim}${skill.reasons.slice(0, 3).join(', ')}${colors.reset}\n`;
455
+ }
456
+
457
+ if (matchedSkills.length > 5) {
458
+ output += ` ${colors.dim}... and ${matchedSkills.length - 5} more${colors.reset}\n`;
459
+ }
460
+
461
+ return output;
462
+ }
463
+
464
+ // ============================================================
465
+ // CLI
466
+ // ============================================================
467
+
468
+ function showHelp() {
469
+ console.log(`
470
+ Wogi Flow - Skill Matcher
471
+
472
+ Matches skills to task context for automatic loading.
473
+
474
+ Usage:
475
+ flow skill-match "task description"
476
+ flow skill-match "task description" --files src/auth/*.ts
477
+ flow skill-match --list
478
+
479
+ Options:
480
+ --files <glob> Files being modified (for pattern matching)
481
+ --type <type> Task type (feature, bugfix, refactor)
482
+ --json Output as JSON
483
+ --list List all installed skills with triggers
484
+ --help, -h Show this help
485
+
486
+ Examples:
487
+ flow skill-match "create user authentication module"
488
+ flow skill-match "fix the login component" --type bugfix
489
+ flow skill-match "refactor entities" --files "src/*.entity.ts"
490
+ `);
491
+ }
492
+
493
+ function listSkills() {
494
+ const skills = getAllSkills();
495
+
496
+ console.log(`${colors.bold}Installed Skills:${colors.reset}\n`);
497
+
498
+ for (const skill of skills) {
499
+ console.log(`${colors.cyan}${skill.name}${colors.reset}`);
500
+ console.log(` Keywords: ${(skill.triggers.keywords || []).slice(0, 5).join(', ')}`);
501
+ console.log(` File patterns: ${(skill.filePatterns || []).slice(0, 3).join(', ')}`);
502
+ console.log(` Task types: ${(skill.triggers.taskTypes || []).join(', ')}`);
503
+ console.log('');
504
+ }
505
+ }
506
+
507
+ async function main() {
508
+ const args = process.argv.slice(2);
509
+
510
+ if (args.includes('--help') || args.includes('-h')) {
511
+ showHelp();
512
+ process.exit(0);
513
+ }
514
+
515
+ if (args.includes('--list')) {
516
+ listSkills();
517
+ process.exit(0);
518
+ }
519
+
520
+ const jsonOutput = args.includes('--json');
521
+
522
+ // Extract options
523
+ const filesIndex = args.indexOf('--files');
524
+ const filePaths = filesIndex >= 0 ? args[filesIndex + 1]?.split(',') || [] : [];
525
+
526
+ const typeIndex = args.indexOf('--type');
527
+ const taskType = typeIndex >= 0 ? args[typeIndex + 1] : 'feature';
528
+
529
+ // Get description (everything that's not a flag)
530
+ const description = args
531
+ .filter((a, i) =>
532
+ !a.startsWith('--') &&
533
+ i !== filesIndex + 1 &&
534
+ i !== typeIndex + 1
535
+ )
536
+ .join(' ');
537
+
538
+ if (!description) {
539
+ console.log(`${colors.red}Error: Please provide a task description${colors.reset}`);
540
+ showHelp();
541
+ process.exit(1);
542
+ }
543
+
544
+ const matches = matchSkills(description, { filePaths, taskType });
545
+
546
+ if (jsonOutput) {
547
+ console.log(JSON.stringify(matches, null, 2));
548
+ } else {
549
+ console.log(getSkillSummary(matches));
550
+
551
+ if (matches.length > 0) {
552
+ console.log(`\n${colors.dim}Loading top skill context...${colors.reset}\n`);
553
+ const context = await loadSkillContext(matches, { maxSkills: 1 });
554
+ console.log(`${colors.dim}Estimated tokens: ~${context.totalTokenEstimate}${colors.reset}`);
555
+ }
556
+ }
557
+ }
558
+
559
+ // ============================================================
560
+ // Exports
561
+ // ============================================================
562
+
563
+ module.exports = {
564
+ loadSkillMetadata,
565
+ getAllSkills,
566
+ matchSkills,
567
+ loadSkillContext,
568
+ formatSkillContext,
569
+ getSkillSummary,
570
+ DEFAULT_TRIGGERS
571
+ };
572
+
573
+ if (require.main === module) {
574
+ main().catch(err => {
575
+ console.error(`Error: ${err.message}`);
576
+ process.exit(1);
577
+ });
578
+ }