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,802 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Model-Specific Adapters
5
+ *
6
+ * Manages per-model learning and prompt adjustments.
7
+ * Different models (Claude Opus, Sonnet, Ollama, LM Studio) have different
8
+ * strengths and weaknesses. This module:
9
+ *
10
+ * 1. Detects current model from config/environment
11
+ * 2. Loads model-specific adapter with prompt adjustments
12
+ * 3. Records per-model success/failure patterns
13
+ * 4. Auto-learns from repeated mistakes (updates adapter file)
14
+ *
15
+ * Uses multi-model approach with per-model learning.
16
+ *
17
+ * Usage as module:
18
+ * const { getCurrentModel, getPromptAdjustments, recordModelResult } = require('./flow-model-adapter');
19
+ * const model = getCurrentModel();
20
+ * const adjustments = getPromptAdjustments(model);
21
+ *
22
+ * Usage as CLI:
23
+ * flow model-adapter # Show current model info
24
+ * flow model-adapter --list # List available adapters
25
+ * flow model-adapter --stats # Show per-model statistics
26
+ */
27
+
28
+ const fs = require('fs');
29
+ const path = require('path');
30
+ const { getProjectRoot, getConfig, PATHS, colors } = require('./flow-utils');
31
+
32
+ const PROJECT_ROOT = getProjectRoot();
33
+ const ADAPTERS_DIR = path.join(PROJECT_ROOT, '.workflow', 'model-adapters');
34
+ const MODEL_STATS_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'model-stats.json');
35
+
36
+ // ============================================================
37
+ // Model Detection
38
+ // ============================================================
39
+
40
+ /**
41
+ * Known model patterns for identification
42
+ */
43
+ const MODEL_PATTERNS = {
44
+ 'claude-opus': ['claude-opus', 'opus', 'claude-3-opus', 'claude-opus-4'],
45
+ 'claude-sonnet': ['claude-sonnet', 'sonnet', 'claude-3-sonnet', 'claude-sonnet-4'],
46
+ 'claude-haiku': ['claude-haiku', 'haiku', 'claude-3-haiku'],
47
+ 'gpt-4': ['gpt-4', 'gpt-4-turbo', 'gpt-4o'],
48
+ 'gpt-3.5': ['gpt-3.5', 'gpt-3.5-turbo'],
49
+ 'ollama-nemotron': ['nemotron', 'nvidia-nemotron'],
50
+ 'ollama-qwen': ['qwen', 'qwen-coder', 'qwen3'],
51
+ 'ollama-deepseek': ['deepseek', 'deepseek-coder'],
52
+ 'ollama-codellama': ['codellama', 'code-llama'],
53
+ 'ollama-mistral': ['mistral', 'mixtral'],
54
+ 'lm-studio': ['lm-studio', 'lmstudio']
55
+ };
56
+
57
+ /**
58
+ * Get current model from config or environment
59
+ */
60
+ function getCurrentModel() {
61
+ const config = getConfig();
62
+
63
+ // Check hybrid mode config first
64
+ if (config.hybrid?.enabled) {
65
+ // New config structure: hybrid.executor.model
66
+ if (config.hybrid.executor?.model) {
67
+ return normalizeModelName(config.hybrid.executor.model);
68
+ }
69
+ // Legacy config structure: hybrid.model directly
70
+ if (config.hybrid.model) {
71
+ return normalizeModelName(config.hybrid.model);
72
+ }
73
+ }
74
+
75
+ // Check environment variable
76
+ if (process.env.CLAUDE_MODEL) {
77
+ return normalizeModelName(process.env.CLAUDE_MODEL);
78
+ }
79
+
80
+ // Check modelAdapters config
81
+ if (config.modelAdapters?.currentModel) {
82
+ return normalizeModelName(config.modelAdapters.currentModel);
83
+ }
84
+
85
+ // Default to claude-opus (most capable)
86
+ return 'claude-opus';
87
+ }
88
+
89
+ /**
90
+ * Normalize model name to standard format
91
+ */
92
+ function normalizeModelName(modelName) {
93
+ const lower = modelName.toLowerCase();
94
+
95
+ for (const [standard, patterns] of Object.entries(MODEL_PATTERNS)) {
96
+ for (const pattern of patterns) {
97
+ if (lower.includes(pattern)) {
98
+ return standard;
99
+ }
100
+ }
101
+ }
102
+
103
+ // Return as-is if no pattern matches
104
+ return lower.replace(/[^a-z0-9-]/g, '-');
105
+ }
106
+
107
+ /**
108
+ * Get model family (claude, gpt, ollama, lm-studio)
109
+ */
110
+ function getModelFamily(modelName) {
111
+ const model = normalizeModelName(modelName);
112
+
113
+ if (model.startsWith('claude-')) return 'claude';
114
+ if (model.startsWith('gpt-')) return 'openai';
115
+ if (model.startsWith('ollama-')) return 'ollama';
116
+ if (model.startsWith('lm-studio')) return 'lm-studio';
117
+
118
+ return 'unknown';
119
+ }
120
+
121
+ // ============================================================
122
+ // Adapter Loading
123
+ // ============================================================
124
+
125
+ /**
126
+ * Get path to adapter file for a model
127
+ */
128
+ function getAdapterPath(modelName) {
129
+ const normalized = normalizeModelName(modelName);
130
+ return path.join(ADAPTERS_DIR, `${normalized}.md`);
131
+ }
132
+
133
+ /**
134
+ * Load adapter file content
135
+ */
136
+ function loadAdapter(modelName) {
137
+ const adapterPath = getAdapterPath(modelName);
138
+
139
+ if (!fs.existsSync(adapterPath)) {
140
+ // Try loading family adapter
141
+ const family = getModelFamily(modelName);
142
+ const familyPath = path.join(ADAPTERS_DIR, `${family}-default.md`);
143
+
144
+ if (fs.existsSync(familyPath)) {
145
+ return {
146
+ path: familyPath,
147
+ content: fs.readFileSync(familyPath, 'utf-8'),
148
+ isDefault: true
149
+ };
150
+ }
151
+
152
+ // Load template as last resort
153
+ const templatePath = path.join(ADAPTERS_DIR, '_template.md');
154
+ if (fs.existsSync(templatePath)) {
155
+ return {
156
+ path: templatePath,
157
+ content: fs.readFileSync(templatePath, 'utf-8'),
158
+ isTemplate: true
159
+ };
160
+ }
161
+
162
+ return null;
163
+ }
164
+
165
+ return {
166
+ path: adapterPath,
167
+ content: fs.readFileSync(adapterPath, 'utf-8'),
168
+ isDefault: false
169
+ };
170
+ }
171
+
172
+ /**
173
+ * Parse adapter file into structured data
174
+ */
175
+ function parseAdapter(content) {
176
+ const adapter = {
177
+ name: '',
178
+ strengths: [],
179
+ weaknesses: [],
180
+ promptAdjustments: [],
181
+ antiPatterns: [],
182
+ knownIssues: [],
183
+ learnings: []
184
+ };
185
+
186
+ if (!content) return adapter;
187
+
188
+ // Extract name from first heading
189
+ const nameMatch = content.match(/^#\s+(.+)/m);
190
+ if (nameMatch) {
191
+ adapter.name = nameMatch[1].trim();
192
+ }
193
+
194
+ // Extract sections
195
+ const sections = content.split(/^##\s+/m);
196
+
197
+ for (const section of sections) {
198
+ const lines = section.split('\n');
199
+ const title = lines[0]?.toLowerCase().trim() || '';
200
+ const body = lines.slice(1).join('\n');
201
+
202
+ if (title.includes('strength')) {
203
+ adapter.strengths = extractListItems(body);
204
+ } else if (title.includes('weakness')) {
205
+ adapter.weaknesses = extractListItems(body);
206
+ } else if (title.includes('prompt') || title.includes('adjustment')) {
207
+ adapter.promptAdjustments = extractListItems(body);
208
+ } else if (title.includes('anti-pattern') || title.includes('avoid')) {
209
+ adapter.antiPatterns = extractListItems(body);
210
+ } else if (title.includes('known issue') || title.includes('bug')) {
211
+ adapter.knownIssues = extractListItems(body);
212
+ } else if (title.includes('learning') || title.includes('mistake')) {
213
+ adapter.learnings = extractLearnings(body);
214
+ }
215
+ }
216
+
217
+ return adapter;
218
+ }
219
+
220
+ /**
221
+ * Extract bullet list items from markdown
222
+ */
223
+ function extractListItems(text) {
224
+ const items = [];
225
+ const lines = text.split('\n');
226
+
227
+ for (const line of lines) {
228
+ const match = line.match(/^[-*]\s+(.+)/);
229
+ if (match) {
230
+ items.push(match[1].trim());
231
+ }
232
+ }
233
+
234
+ return items;
235
+ }
236
+
237
+ /**
238
+ * Extract learning entries (date + content)
239
+ */
240
+ function extractLearnings(text) {
241
+ const learnings = [];
242
+ const entries = text.split(/^###\s+/m);
243
+
244
+ for (const entry of entries) {
245
+ if (!entry.trim()) continue;
246
+
247
+ const lines = entry.split('\n');
248
+ const title = lines[0]?.trim() || '';
249
+ const body = lines.slice(1).join('\n').trim();
250
+
251
+ if (title) {
252
+ learnings.push({
253
+ title,
254
+ body,
255
+ date: extractDateFromTitle(title)
256
+ });
257
+ }
258
+ }
259
+
260
+ return learnings;
261
+ }
262
+
263
+ /**
264
+ * Extract date from learning title (e.g., "2024-01-15 - Fixed import issue")
265
+ */
266
+ function extractDateFromTitle(title) {
267
+ const match = title.match(/^(\d{4}-\d{2}-\d{2})/);
268
+ return match ? match[1] : null;
269
+ }
270
+
271
+ // ============================================================
272
+ // Prompt Adjustments
273
+ // ============================================================
274
+
275
+ /**
276
+ * Get prompt adjustments for current model
277
+ * Returns guidance to prepend to prompts
278
+ */
279
+ function getPromptAdjustments(modelName = null) {
280
+ const model = modelName || getCurrentModel();
281
+ const adapterData = loadAdapter(model);
282
+
283
+ if (!adapterData) {
284
+ return {
285
+ model,
286
+ adjustments: [],
287
+ guidance: ''
288
+ };
289
+ }
290
+
291
+ const adapter = parseAdapter(adapterData.content);
292
+ const guidance = [];
293
+
294
+ // Add weakness-based guidance
295
+ if (adapter.weaknesses.length > 0) {
296
+ guidance.push('Be especially careful with:');
297
+ for (const weakness of adapter.weaknesses.slice(0, 3)) {
298
+ guidance.push(`- ${weakness}`);
299
+ }
300
+ }
301
+
302
+ // Add anti-patterns
303
+ if (adapter.antiPatterns.length > 0) {
304
+ guidance.push('');
305
+ guidance.push('Avoid these patterns:');
306
+ for (const pattern of adapter.antiPatterns.slice(0, 3)) {
307
+ guidance.push(`- ${pattern}`);
308
+ }
309
+ }
310
+
311
+ // Add recent learnings (last 3)
312
+ const recentLearnings = adapter.learnings.slice(-3);
313
+ if (recentLearnings.length > 0) {
314
+ guidance.push('');
315
+ guidance.push('Recent learnings to remember:');
316
+ for (const learning of recentLearnings) {
317
+ guidance.push(`- ${learning.title}`);
318
+ }
319
+ }
320
+
321
+ return {
322
+ model,
323
+ adapterPath: adapterData.path,
324
+ isDefault: adapterData.isDefault || false,
325
+ isTemplate: adapterData.isTemplate || false,
326
+ adjustments: adapter.promptAdjustments,
327
+ guidance: guidance.join('\n'),
328
+ strengths: adapter.strengths,
329
+ weaknesses: adapter.weaknesses
330
+ };
331
+ }
332
+
333
+ // ============================================================
334
+ // Model Statistics & Learning
335
+ // ============================================================
336
+
337
+ /**
338
+ * Load model statistics
339
+ */
340
+ function loadModelStats() {
341
+ try {
342
+ if (fs.existsSync(MODEL_STATS_PATH)) {
343
+ return JSON.parse(fs.readFileSync(MODEL_STATS_PATH, 'utf-8'));
344
+ }
345
+ } catch {
346
+ // Ignore errors
347
+ }
348
+
349
+ return {
350
+ version: '1.0.0',
351
+ lastUpdated: new Date().toISOString(),
352
+ models: {}
353
+ };
354
+ }
355
+
356
+ /**
357
+ * Save model statistics
358
+ */
359
+ function saveModelStats(stats) {
360
+ stats.lastUpdated = new Date().toISOString();
361
+ const dir = path.dirname(MODEL_STATS_PATH);
362
+ if (!fs.existsSync(dir)) {
363
+ fs.mkdirSync(dir, { recursive: true });
364
+ }
365
+ fs.writeFileSync(MODEL_STATS_PATH, JSON.stringify(stats, null, 2));
366
+ }
367
+
368
+ /**
369
+ * Record a model execution result
370
+ * @param {string} modelName - Model that was used
371
+ * @param {object} result - { taskType, success, errorType?, errorContext? }
372
+ */
373
+ function recordModelResult(modelName, result) {
374
+ const config = getConfig();
375
+ // Enable tracking if modelAdapters.enabled is true OR if hybrid mode is enabled
376
+ // This allows stats to be collected without explicit modelAdapters config
377
+ const trackingEnabled = config.modelAdapters?.enabled !== false &&
378
+ (config.modelAdapters?.enabled || config.hybrid?.enabled);
379
+ if (!trackingEnabled) return;
380
+
381
+ const stats = loadModelStats();
382
+ const model = normalizeModelName(modelName);
383
+
384
+ // Initialize model entry
385
+ if (!stats.models[model]) {
386
+ stats.models[model] = {
387
+ totalTasks: 0,
388
+ successes: 0,
389
+ failures: 0,
390
+ taskTypes: {},
391
+ errorTypes: {},
392
+ recentErrors: []
393
+ };
394
+ }
395
+
396
+ const modelStats = stats.models[model];
397
+ modelStats.totalTasks++;
398
+
399
+ if (result.success) {
400
+ modelStats.successes++;
401
+ } else {
402
+ modelStats.failures++;
403
+
404
+ // Track error types
405
+ if (result.errorType) {
406
+ modelStats.errorTypes[result.errorType] = (modelStats.errorTypes[result.errorType] || 0) + 1;
407
+ }
408
+
409
+ // Add to recent errors (keep last 20)
410
+ modelStats.recentErrors.unshift({
411
+ timestamp: new Date().toISOString(),
412
+ taskType: result.taskType || 'unknown',
413
+ errorType: result.errorType || 'unknown',
414
+ errorContext: result.errorContext || null
415
+ });
416
+ modelStats.recentErrors = modelStats.recentErrors.slice(0, 20);
417
+
418
+ // Check for repeated errors (trigger auto-learning)
419
+ if (config.modelAdapters?.autoLearn) {
420
+ checkAndAutoLearn(model, modelStats);
421
+ }
422
+ }
423
+
424
+ // Track task types
425
+ if (result.taskType) {
426
+ if (!modelStats.taskTypes[result.taskType]) {
427
+ modelStats.taskTypes[result.taskType] = { total: 0, success: 0 };
428
+ }
429
+ modelStats.taskTypes[result.taskType].total++;
430
+ if (result.success) {
431
+ modelStats.taskTypes[result.taskType].success++;
432
+ }
433
+ }
434
+
435
+ saveModelStats(stats);
436
+ }
437
+
438
+ /**
439
+ * Check for repeated errors and auto-learn
440
+ */
441
+ function checkAndAutoLearn(modelName, modelStats) {
442
+ const recentErrors = modelStats.recentErrors.slice(0, 10);
443
+
444
+ // Group by error type
445
+ const errorCounts = {};
446
+ for (const error of recentErrors) {
447
+ const key = error.errorType;
448
+ if (!errorCounts[key]) {
449
+ errorCounts[key] = { count: 0, contexts: [] };
450
+ }
451
+ errorCounts[key].count++;
452
+ if (error.errorContext) {
453
+ errorCounts[key].contexts.push(error.errorContext);
454
+ }
455
+ }
456
+
457
+ // Find errors that occurred 3+ times
458
+ const repeatedErrors = Object.entries(errorCounts)
459
+ .filter(([_, data]) => data.count >= 3)
460
+ .map(([type, data]) => ({ type, ...data }));
461
+
462
+ if (repeatedErrors.length > 0) {
463
+ addLearningToAdapter(modelName, repeatedErrors);
464
+ }
465
+ }
466
+
467
+ /**
468
+ * Store a single learning/correction in model adapter file
469
+ * Used by knowledge-router for direct learning storage
470
+ * @param {string} modelName - Model identifier
471
+ * @param {string} learning - The learning text
472
+ * @param {object} context - Optional context { taskId, sourceContext, trigger }
473
+ */
474
+ function storeSingleLearning(modelName, learning, context = {}) {
475
+ const adapterPath = getAdapterPath(modelName);
476
+ let content = '';
477
+
478
+ if (fs.existsSync(adapterPath)) {
479
+ content = fs.readFileSync(adapterPath, 'utf-8');
480
+ } else {
481
+ // Ensure directory exists
482
+ const dir = path.dirname(adapterPath);
483
+ if (!fs.existsSync(dir)) {
484
+ fs.mkdirSync(dir, { recursive: true });
485
+ }
486
+
487
+ // Create from template or minimal
488
+ const templatePath = path.join(ADAPTERS_DIR, '_template.md');
489
+ if (fs.existsSync(templatePath)) {
490
+ content = fs.readFileSync(templatePath, 'utf-8')
491
+ .replace('{{MODEL_NAME}}', modelName);
492
+ } else {
493
+ content = `# ${modelName} Adapter\n\n## Learnings\n`;
494
+ }
495
+ }
496
+
497
+ const date = new Date().toISOString().split('T')[0];
498
+ const source = context.taskId || context.sourceContext || 'user-correction';
499
+ const trigger = context.trigger || 'knowledge-router';
500
+
501
+ const learningEntry = `
502
+ ### ${date} - ${source}
503
+
504
+ **Trigger**: ${trigger}
505
+
506
+ ${learning}
507
+
508
+ ---
509
+ `;
510
+
511
+ // Find Learnings section or append at end
512
+ if (content.includes('## Learnings')) {
513
+ const learningsIdx = content.indexOf('## Learnings');
514
+ const nextSectionIdx = content.indexOf('\n## ', learningsIdx + 1);
515
+
516
+ if (nextSectionIdx !== -1) {
517
+ content = content.slice(0, nextSectionIdx) + learningEntry + content.slice(nextSectionIdx);
518
+ } else {
519
+ content += learningEntry;
520
+ }
521
+ } else {
522
+ content += '\n## Learnings\n' + learningEntry;
523
+ }
524
+
525
+ fs.writeFileSync(adapterPath, content);
526
+
527
+ return {
528
+ success: true,
529
+ file: adapterPath,
530
+ message: `Stored as ${modelName}-specific learning`
531
+ };
532
+ }
533
+
534
+ /**
535
+ * Add learning entry to adapter file
536
+ */
537
+ function addLearningToAdapter(modelName, errors) {
538
+ const adapterPath = getAdapterPath(modelName);
539
+ let content = '';
540
+
541
+ if (fs.existsSync(adapterPath)) {
542
+ content = fs.readFileSync(adapterPath, 'utf-8');
543
+ } else {
544
+ // Create from template
545
+ const templatePath = path.join(ADAPTERS_DIR, '_template.md');
546
+ if (fs.existsSync(templatePath)) {
547
+ content = fs.readFileSync(templatePath, 'utf-8')
548
+ .replace('{{MODEL_NAME}}', modelName);
549
+ } else {
550
+ content = `# ${modelName} Adapter\n\n## Learnings\n`;
551
+ }
552
+ }
553
+
554
+ // Add new learning entry
555
+ const date = new Date().toISOString().split('T')[0];
556
+ const learningEntry = [];
557
+
558
+ learningEntry.push(`\n### ${date} - Auto-learned from repeated errors\n`);
559
+
560
+ for (const error of errors) {
561
+ learningEntry.push(`**Error Type**: ${error.type} (occurred ${error.count} times)`);
562
+ if (error.contexts.length > 0) {
563
+ learningEntry.push(`**Context**: ${error.contexts[0]}`);
564
+ }
565
+ learningEntry.push('');
566
+ }
567
+
568
+ // Find Learnings section or append at end
569
+ if (content.includes('## Learnings')) {
570
+ content = content.replace(
571
+ /(## Learnings.*?)(?=\n## |$)/s,
572
+ `$1${learningEntry.join('\n')}`
573
+ );
574
+ } else {
575
+ content += '\n## Learnings\n' + learningEntry.join('\n');
576
+ }
577
+
578
+ // Ensure directory exists
579
+ const dir = path.dirname(adapterPath);
580
+ if (!fs.existsSync(dir)) {
581
+ fs.mkdirSync(dir, { recursive: true });
582
+ }
583
+
584
+ fs.writeFileSync(adapterPath, content);
585
+ }
586
+
587
+ /**
588
+ * Get statistics for all models
589
+ */
590
+ function getAllModelStats() {
591
+ const stats = loadModelStats();
592
+
593
+ return Object.entries(stats.models).map(([model, data]) => ({
594
+ model,
595
+ totalTasks: data.totalTasks,
596
+ successRate: data.totalTasks > 0
597
+ ? ((data.successes / data.totalTasks) * 100).toFixed(1) + '%'
598
+ : 'N/A',
599
+ topErrors: Object.entries(data.errorTypes)
600
+ .sort((a, b) => b[1] - a[1])
601
+ .slice(0, 3)
602
+ .map(([type, count]) => `${type} (${count})`),
603
+ taskBreakdown: Object.entries(data.taskTypes)
604
+ .map(([type, info]) => ({
605
+ type,
606
+ total: info.total,
607
+ successRate: info.total > 0 ? ((info.success / info.total) * 100).toFixed(0) + '%' : 'N/A'
608
+ }))
609
+ }));
610
+ }
611
+
612
+ // ============================================================
613
+ // CLI & Formatting
614
+ // ============================================================
615
+
616
+ /**
617
+ * List available adapters
618
+ */
619
+ function listAdapters() {
620
+ if (!fs.existsSync(ADAPTERS_DIR)) {
621
+ return [];
622
+ }
623
+
624
+ return fs.readdirSync(ADAPTERS_DIR)
625
+ .filter(f => f.endsWith('.md') && !f.startsWith('_'))
626
+ .map(f => f.replace('.md', ''));
627
+ }
628
+
629
+ /**
630
+ * Format adapter info for display
631
+ */
632
+ function formatAdapterInfo(modelName) {
633
+ const adjustments = getPromptAdjustments(modelName);
634
+ let output = '';
635
+
636
+ output += `${colors.cyan}Model: ${adjustments.model}${colors.reset}\n`;
637
+
638
+ if (adjustments.isTemplate) {
639
+ output += `${colors.yellow}Using template (no specific adapter)${colors.reset}\n`;
640
+ } else if (adjustments.isDefault) {
641
+ output += `${colors.dim}Using family default adapter${colors.reset}\n`;
642
+ } else {
643
+ output += `${colors.green}Using model-specific adapter${colors.reset}\n`;
644
+ }
645
+
646
+ output += `Path: ${adjustments.adapterPath || 'N/A'}\n\n`;
647
+
648
+ if (adjustments.strengths.length > 0) {
649
+ output += `${colors.green}Strengths:${colors.reset}\n`;
650
+ for (const s of adjustments.strengths.slice(0, 5)) {
651
+ output += ` • ${s}\n`;
652
+ }
653
+ output += '\n';
654
+ }
655
+
656
+ if (adjustments.weaknesses.length > 0) {
657
+ output += `${colors.yellow}Weaknesses:${colors.reset}\n`;
658
+ for (const w of adjustments.weaknesses.slice(0, 5)) {
659
+ output += ` • ${w}\n`;
660
+ }
661
+ output += '\n';
662
+ }
663
+
664
+ if (adjustments.adjustments.length > 0) {
665
+ output += `${colors.bold}Prompt Adjustments:${colors.reset}\n`;
666
+ for (const a of adjustments.adjustments) {
667
+ output += ` • ${a}\n`;
668
+ }
669
+ }
670
+
671
+ return output;
672
+ }
673
+
674
+ /**
675
+ * Format statistics for display
676
+ */
677
+ function formatStats() {
678
+ const allStats = getAllModelStats();
679
+ let output = '';
680
+
681
+ output += `${colors.cyan}Model Statistics${colors.reset}\n`;
682
+ output += `${'═'.repeat(50)}\n\n`;
683
+
684
+ if (allStats.length === 0) {
685
+ output += `${colors.dim}No statistics recorded yet.${colors.reset}\n`;
686
+ return output;
687
+ }
688
+
689
+ for (const modelStat of allStats) {
690
+ const icon = parseFloat(modelStat.successRate) >= 90
691
+ ? colors.green + '●' + colors.reset
692
+ : parseFloat(modelStat.successRate) >= 70
693
+ ? colors.yellow + '●' + colors.reset
694
+ : colors.red + '●' + colors.reset;
695
+
696
+ output += `${icon} ${colors.bold}${modelStat.model}${colors.reset}\n`;
697
+ output += ` Tasks: ${modelStat.totalTasks} | Success: ${modelStat.successRate}\n`;
698
+
699
+ if (modelStat.topErrors.length > 0) {
700
+ output += ` Top errors: ${modelStat.topErrors.join(', ')}\n`;
701
+ }
702
+
703
+ if (modelStat.taskBreakdown.length > 0) {
704
+ output += ` By type: `;
705
+ output += modelStat.taskBreakdown
706
+ .map(t => `${t.type}(${t.successRate})`)
707
+ .join(', ');
708
+ output += '\n';
709
+ }
710
+
711
+ output += '\n';
712
+ }
713
+
714
+ return output;
715
+ }
716
+
717
+ function showHelp() {
718
+ console.log(`
719
+ Wogi Flow - Model Adapters
720
+
721
+ Manages per-model learning and prompt adjustments for different LLMs.
722
+
723
+ Usage:
724
+ flow model-adapter Show current model info
725
+ flow model-adapter --list List available adapters
726
+ flow model-adapter --stats Show per-model statistics
727
+ flow model-adapter --json Output current model as JSON
728
+ flow model-adapter [model] Show info for specific model
729
+
730
+ Options:
731
+ --list List all available adapter files
732
+ --stats Show success/failure statistics per model
733
+ --json Output as JSON
734
+ --help, -h Show this help
735
+
736
+ Examples:
737
+ flow model-adapter # Show current model
738
+ flow model-adapter claude-sonnet # Show Sonnet adapter
739
+ flow model-adapter --stats # View all model stats
740
+ `);
741
+ }
742
+
743
+ function main() {
744
+ const args = process.argv.slice(2);
745
+
746
+ if (args.includes('--help') || args.includes('-h')) {
747
+ showHelp();
748
+ process.exit(0);
749
+ }
750
+
751
+ if (args.includes('--list')) {
752
+ const adapters = listAdapters();
753
+ console.log(`${colors.cyan}Available Adapters:${colors.reset}`);
754
+ if (adapters.length === 0) {
755
+ console.log(` ${colors.dim}No adapters found. Create one in .workflow/model-adapters/${colors.reset}`);
756
+ } else {
757
+ for (const adapter of adapters) {
758
+ console.log(` • ${adapter}`);
759
+ }
760
+ }
761
+ process.exit(0);
762
+ }
763
+
764
+ if (args.includes('--stats')) {
765
+ console.log(formatStats());
766
+ process.exit(0);
767
+ }
768
+
769
+ // Get model name from args or detect
770
+ const modelArg = args.find(a => !a.startsWith('--'));
771
+ const modelName = modelArg || getCurrentModel();
772
+
773
+ if (args.includes('--json')) {
774
+ console.log(JSON.stringify(getPromptAdjustments(modelName), null, 2));
775
+ process.exit(0);
776
+ }
777
+
778
+ console.log(formatAdapterInfo(modelName));
779
+ }
780
+
781
+ // ============================================================
782
+ // Exports
783
+ // ============================================================
784
+
785
+ module.exports = {
786
+ getCurrentModel,
787
+ normalizeModelName,
788
+ getModelFamily,
789
+ loadAdapter,
790
+ parseAdapter,
791
+ getPromptAdjustments,
792
+ recordModelResult,
793
+ getAllModelStats,
794
+ listAdapters,
795
+ addLearningToAdapter,
796
+ storeSingleLearning,
797
+ getAdapterPath
798
+ };
799
+
800
+ if (require.main === module) {
801
+ main();
802
+ }