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,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
|
+
}
|