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,1259 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Adaptive Learning for Hybrid Mode
5
+ *
6
+ * When a task fails with an executor model, this module:
7
+ * 1. Analyzes WHY it failed
8
+ * 2. Refines the prompt based on the failure
9
+ * 3. Tracks what refinements were needed
10
+ * 4. On success, updates the model adapter with learnings
11
+ *
12
+ * This creates a feedback loop that improves prompts over time.
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { execFileSync } = require('child_process');
18
+ const { getProjectRoot, colors } = require('./flow-utils');
19
+ const { storeSingleLearning, getAdapterPath } = require('./flow-model-adapter');
20
+ const {
21
+ FailureCategory,
22
+ detectCategory,
23
+ shouldEscalate: checkShouldEscalate
24
+ } = require('../.workflow/lib/failure-categories');
25
+ const { validateRepoFormat, safeGitCommand, sanitizeCommitMessage } = require('./flow-security');
26
+
27
+ const PROJECT_ROOT = getProjectRoot();
28
+ const LEARNING_LOG_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'adaptive-learning.json');
29
+ const STRATEGY_STATS_PATH = path.join(PROJECT_ROOT, '.workflow', 'state', 'strategy-effectiveness.json');
30
+
31
+ // ============================================================
32
+ // Failure Analysis
33
+ // ============================================================
34
+
35
+ /**
36
+ * Error categories with detection patterns and fix strategies
37
+ * Now uses centralized FailureCategory from .workflow/lib/failure-categories.js
38
+ *
39
+ * Legacy mapping for backward compatibility - maps to centralized categories
40
+ */
41
+ const ERROR_CATEGORIES = {
42
+ IMPORT_ERROR: FailureCategory.IMPORT_ERROR,
43
+ TYPE_ERROR: FailureCategory.TYPE_ERROR,
44
+ SYNTAX_ERROR: FailureCategory.SYNTAX_ERROR,
45
+ MARKDOWN_POLLUTION: FailureCategory.MARKDOWN_POLLUTION,
46
+ INCOMPLETE_OUTPUT: FailureCategory.INCOMPLETE_OUTPUT,
47
+ HALLUCINATION: FailureCategory.HALLUCINATION,
48
+ // Additional categories from centralized module
49
+ PARSE_ERROR: FailureCategory.PARSE_ERROR,
50
+ RUNTIME_ERROR: FailureCategory.RUNTIME_ERROR,
51
+ RATE_LIMIT: FailureCategory.RATE_LIMIT,
52
+ API_ERROR: FailureCategory.API_ERROR,
53
+ CONTEXT_OVERFLOW: FailureCategory.CONTEXT_OVERFLOW,
54
+ CAPABILITY_MISMATCH: FailureCategory.CAPABILITY_MISMATCH,
55
+ MISSING_CONTEXT: FailureCategory.MISSING_CONTEXT,
56
+ PATTERN_VIOLATION: FailureCategory.PATTERN_VIOLATION,
57
+ UNKNOWN: FailureCategory.UNKNOWN
58
+ };
59
+
60
+ /**
61
+ * Analyze a failure and categorize it
62
+ * Uses centralized detectCategory for consistent categorization
63
+ * @param {string} error - Error message or output
64
+ * @param {string} output - Model's output
65
+ * @param {object} context - Task context
66
+ * @returns {object} Failure analysis
67
+ */
68
+ function analyzeFailure(error, output, context = {}) {
69
+ const errorStr = String(error);
70
+ const outputStr = String(output || '');
71
+
72
+ // Use centralized detection
73
+ const detection = detectCategory(errorStr, outputStr);
74
+
75
+ const analysis = {
76
+ timestamp: new Date().toISOString(),
77
+ categories: detection.all.map(match => ({
78
+ category: match.category,
79
+ strategy: match.strategy,
80
+ description: match.description,
81
+ severity: match.severity,
82
+ escalate: match.escalate,
83
+ matchedPattern: match.matchedPattern
84
+ })),
85
+ primaryCategory: detection.primary.category,
86
+ strategy: detection.primary.strategy,
87
+ severity: detection.primary.severity,
88
+ shouldEscalate: detection.shouldEscalate,
89
+ details: {},
90
+ rawError: errorStr.slice(0, 500),
91
+ outputSample: outputStr.slice(0, 300)
92
+ };
93
+
94
+ // Extract specific details based on category
95
+ analysis.details = extractErrorDetails(errorStr, analysis.primaryCategory);
96
+
97
+ return analysis;
98
+ }
99
+
100
+ /**
101
+ * Extract specific details from error based on category
102
+ */
103
+ function extractErrorDetails(error, category) {
104
+ const details = {};
105
+
106
+ switch (category) {
107
+ case 'IMPORT_ERROR': {
108
+ // Extract module name
109
+ const moduleMatch = error.match(/(?:module|from) ['"]([^'"]+)['"]/i);
110
+ if (moduleMatch) details.moduleName = moduleMatch[1];
111
+
112
+ // Extract export name
113
+ const exportMatch = error.match(/(?:member|export) ['"]?(\w+)['"]?/i);
114
+ if (exportMatch) details.exportName = exportMatch[1];
115
+ break;
116
+ }
117
+ case 'TYPE_ERROR': {
118
+ // Extract type names
119
+ const typeMatch = error.match(/type ['"]?([^'"]+)['"]? is not assignable to ['"]?([^'"]+)['"]?/i);
120
+ if (typeMatch) {
121
+ details.actualType = typeMatch[1];
122
+ details.expectedType = typeMatch[2];
123
+ }
124
+
125
+ // Extract property name
126
+ const propMatch = error.match(/property ['"]?(\w+)['"]?/i);
127
+ if (propMatch) details.propertyName = propMatch[1];
128
+ break;
129
+ }
130
+ case 'SYNTAX_ERROR': {
131
+ // Extract line number if present
132
+ const lineMatch = error.match(/line (\d+)/i);
133
+ if (lineMatch) details.line = parseInt(lineMatch[1]);
134
+ break;
135
+ }
136
+ }
137
+
138
+ return details;
139
+ }
140
+
141
+ // ============================================================
142
+ // Prompt Refinement
143
+ // ============================================================
144
+
145
+ /**
146
+ * Refinement strategies for different error types
147
+ */
148
+ const REFINEMENT_STRATEGIES = {
149
+ import_fix: {
150
+ prefix: `CRITICAL: Your previous output had IMPORT ERRORS.
151
+ Pay close attention to the "Available Imports" section below.
152
+ Use EXACT import paths and export names as shown.
153
+ Do NOT guess or create new import paths.`,
154
+ suffix: `Remember: Use ONLY imports from the "Available Imports" section. Copy them exactly.`
155
+ },
156
+
157
+ type_fix: {
158
+ prefix: `CRITICAL: Your previous output had TYPE ERRORS.
159
+ Pay close attention to the type definitions provided.
160
+ Match prop types EXACTLY as defined.
161
+ Do NOT add optional properties that aren't in the type.`,
162
+ suffix: `Remember: Match types exactly. If unsure, use the simplest valid type.`
163
+ },
164
+
165
+ syntax_fix: {
166
+ prefix: `CRITICAL: Your previous output had SYNTAX ERRORS.
167
+ Output ONLY valid TypeScript/JavaScript code.
168
+ NO markdown fences. NO explanatory text. NO preamble.
169
+ Start directly with the code (import statements or first line of code).`,
170
+ suffix: `Remember: Pure code only. The output will be saved directly to a file.`
171
+ },
172
+
173
+ format_fix: {
174
+ prefix: `CRITICAL: Your previous output included MARKDOWN or EXPLANATIONS.
175
+ Output ONLY the raw code. NO markdown fences (\`\`\`).
176
+ NO "Here's the code" text. NO explanations before or after.
177
+ Start IMMEDIATELY with the first line of code.`,
178
+ suffix: `CRITICAL: Start your response with actual code (import or first statement). Nothing else.`
179
+ },
180
+
181
+ completion_fix: {
182
+ prefix: `CRITICAL: Your previous output was INCOMPLETE.
183
+ You MUST output the COMPLETE file content.
184
+ Do NOT use "..." or "// rest of code" placeholders.
185
+ Do NOT truncate. Include EVERY line.`,
186
+ suffix: `Remember: Complete code only. No placeholders, no truncation.`
187
+ },
188
+
189
+ context_fix: {
190
+ prefix: `CRITICAL: Your previous output referenced NON-EXISTENT code.
191
+ Use ONLY components, hooks, and utilities that exist in the project.
192
+ Check the "Available Components" and "Available Imports" sections.
193
+ Do NOT invent or hallucinate imports or function names.`,
194
+ suffix: `Remember: Only use what's explicitly listed in the context. When unsure, keep it simple.`
195
+ },
196
+
197
+ generic_fix: {
198
+ prefix: `CRITICAL: Your previous attempt failed. Please try again more carefully.
199
+ Follow the instructions exactly. Output only what is requested.`,
200
+ suffix: `Take your time and ensure the output is correct.`
201
+ },
202
+
203
+ // Additional strategies for new centralized categories
204
+ wait_retry: {
205
+ prefix: `NOTE: The previous attempt encountered a temporary issue (rate limit or timeout).
206
+ This is a retry - the same approach should work.`,
207
+ suffix: `Proceed normally - the temporary issue should be resolved.`
208
+ },
209
+
210
+ retry: {
211
+ prefix: `NOTE: The previous attempt failed due to a transient error.
212
+ Please try again with the same approach.`,
213
+ suffix: `This should work on retry.`
214
+ },
215
+
216
+ context_reduction: {
217
+ prefix: `CRITICAL: Your previous output exceeded context limits.
218
+ Please provide a MORE CONCISE response.
219
+ Focus only on the essential code - no explanations.
220
+ If the task is complex, break it into smaller parts.`,
221
+ suffix: `Keep your response as short as possible while still being complete.`
222
+ },
223
+
224
+ escalate: {
225
+ prefix: `NOTE: This task requires capabilities that may need escalation to a more advanced model.
226
+ Please attempt the task anyway.`,
227
+ suffix: `If unable to complete, indicate what capability is missing.`
228
+ },
229
+
230
+ context_load: {
231
+ prefix: `CRITICAL: Your previous attempt was missing necessary context.
232
+ The required components/files are now provided below.
233
+ Use ONLY what is explicitly available.`,
234
+ suffix: `Remember: Use the context provided, don't assume or invent.`
235
+ },
236
+
237
+ clarify: {
238
+ prefix: `NOTE: The requirements were unclear in the previous attempt.
239
+ Please focus on the CORE functionality described.
240
+ If unsure, implement the simplest interpretation.`,
241
+ suffix: `When in doubt, keep it simple and focused on the main requirement.`
242
+ },
243
+
244
+ pattern_fix: {
245
+ prefix: `CRITICAL: Your previous output didn't follow project patterns.
246
+ Review the patterns and conventions described below.
247
+ Match the existing code style exactly.`,
248
+ suffix: `Remember: Follow project conventions - check naming, structure, and patterns.`
249
+ }
250
+ };
251
+
252
+ /**
253
+ * Refine prompt based on failure analysis
254
+ * @param {string} originalPrompt - Original prompt
255
+ * @param {object} failure - Failure analysis from analyzeFailure()
256
+ * @param {array} previousAttempts - Previous failure analyses
257
+ * @returns {object} Refined prompt and metadata
258
+ */
259
+ function refinePromptForRetry(originalPrompt, failure, previousAttempts = []) {
260
+ const strategy = failure.strategy || 'generic_fix';
261
+ const refinements = REFINEMENT_STRATEGIES[strategy] || REFINEMENT_STRATEGIES.generic_fix;
262
+
263
+ // Build refinement context
264
+ let refinementContext = '';
265
+
266
+ // Add specific error details
267
+ if (failure.details && Object.keys(failure.details).length > 0) {
268
+ refinementContext += '\n\nSPECIFIC ISSUE:\n';
269
+ for (const [key, value] of Object.entries(failure.details)) {
270
+ refinementContext += `- ${key}: ${value}\n`;
271
+ }
272
+ }
273
+
274
+ // Add learnings from previous attempts
275
+ if (previousAttempts.length > 0) {
276
+ refinementContext += '\n\nPREVIOUS ERRORS TO AVOID:\n';
277
+ const uniqueCategories = [...new Set(previousAttempts.map(a => a.primaryCategory))];
278
+ for (const cat of uniqueCategories) {
279
+ const catConfig = ERROR_CATEGORIES[cat];
280
+ if (catConfig) {
281
+ refinementContext += `- ${catConfig.description}\n`;
282
+ }
283
+ }
284
+ }
285
+
286
+ // Construct refined prompt
287
+ const refinedPrompt = `${refinements.prefix}
288
+ ${refinementContext}
289
+ ---
290
+
291
+ ${originalPrompt}
292
+
293
+ ---
294
+ ${refinements.suffix}`;
295
+
296
+ return {
297
+ prompt: refinedPrompt,
298
+ strategy,
299
+ addedInstructions: {
300
+ prefix: refinements.prefix,
301
+ suffix: refinements.suffix,
302
+ context: refinementContext
303
+ },
304
+ attemptNumber: previousAttempts.length + 2 // +2 because first attempt was 1
305
+ };
306
+ }
307
+
308
+ // ============================================================
309
+ // Success Recording
310
+ // ============================================================
311
+
312
+ /**
313
+ * Record a successful recovery to the model adapter
314
+ * @param {string} modelName - Model identifier
315
+ * @param {array} failures - Array of failure analyses that led to success
316
+ * @param {object} successContext - What finally worked
317
+ */
318
+ function recordSuccessfulRecovery(modelName, failures, successContext = {}) {
319
+ if (!failures || failures.length === 0) return;
320
+
321
+ // Track strategy effectiveness for all strategies used
322
+ const strategiesUsed = [...new Set(failures.map(f => f.strategy))];
323
+ for (const strategy of strategiesUsed) {
324
+ trackStrategyEffectiveness(modelName, strategy, true);
325
+ }
326
+
327
+ // Group failures by category
328
+ const categoryCounts = {};
329
+ for (const failure of failures) {
330
+ const cat = failure.primaryCategory;
331
+ if (!categoryCounts[cat]) {
332
+ categoryCounts[cat] = { count: 0, details: [], strategy: failure.strategy };
333
+ }
334
+ categoryCounts[cat].count++;
335
+ if (failure.details && Object.keys(failure.details).length > 0) {
336
+ categoryCounts[cat].details.push(failure.details);
337
+ }
338
+ }
339
+
340
+ // Generate learning entry (with deduplication)
341
+ const learnings = [];
342
+ const date = new Date().toISOString().split('T')[0];
343
+ let newLearningsCount = 0;
344
+
345
+ for (const [category, data] of Object.entries(categoryCounts)) {
346
+ const config = ERROR_CATEGORIES[category];
347
+ if (!config) continue;
348
+
349
+ // Skip if we already have a recent learning for this category
350
+ if (isDuplicateLearning(modelName, category, data.details)) {
351
+ continue;
352
+ }
353
+
354
+ newLearningsCount++;
355
+ learnings.push(`### ${date} - Learned from ${data.count} ${category} failures`);
356
+ learnings.push('');
357
+ learnings.push(`**Issue**: ${config.description}`);
358
+ learnings.push(`**Strategy**: ${config.strategy}`);
359
+
360
+ // Add specific guidance based on category
361
+ const guidance = generateGuidanceFromFailures(category, data.details);
362
+ if (guidance) {
363
+ learnings.push(`**Do**: ${guidance.do}`);
364
+ learnings.push(`**Don't**: ${guidance.dont}`);
365
+ }
366
+
367
+ learnings.push('');
368
+ }
369
+
370
+ // Always log to adaptive learning log (for stats)
371
+ logLearning(modelName, failures, successContext);
372
+
373
+ if (learnings.length > 0) {
374
+ // Add to model adapter file
375
+ const learningText = learnings.join('\n');
376
+ storeSingleLearning(modelName, learningText, {
377
+ taskId: successContext.taskId || 'adaptive-recovery',
378
+ trigger: 'adaptive-learning',
379
+ sourceContext: `Recovered after ${failures.length} failures`
380
+ });
381
+
382
+ console.log(`${colors.green} ✅ ${newLearningsCount} new learning(s) recorded to ${modelName} adapter${colors.reset}`);
383
+ } else {
384
+ console.log(`${colors.dim} ℹ️ No new learnings (duplicates skipped)${colors.reset}`);
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Generate specific do/don't guidance from failure details
390
+ */
391
+ function generateGuidanceFromFailures(category, details) {
392
+ switch (category) {
393
+ case 'IMPORT_ERROR': {
394
+ const modules = details.map(d => d.moduleName).filter(Boolean);
395
+ const exports = details.map(d => d.exportName).filter(Boolean);
396
+ return {
397
+ do: 'Copy import paths exactly from the "Available Imports" section',
398
+ dont: modules.length > 0
399
+ ? `Don't use these incorrect paths: ${modules.slice(0, 3).join(', ')}`
400
+ : 'Don\'t guess import paths'
401
+ };
402
+ }
403
+ case 'TYPE_ERROR': {
404
+ return {
405
+ do: 'Match prop types exactly as defined in the interface',
406
+ dont: 'Don\'t add extra properties or change required/optional status'
407
+ };
408
+ }
409
+ case 'MARKDOWN_POLLUTION': {
410
+ return {
411
+ do: 'Start response immediately with code (import statement or first line)',
412
+ dont: 'Don\'t include markdown fences, "Here\'s the code", or explanations'
413
+ };
414
+ }
415
+ case 'INCOMPLETE_OUTPUT': {
416
+ return {
417
+ do: 'Output complete, working code with all functions implemented',
418
+ dont: 'Don\'t use "...", "// rest of code", or other placeholders'
419
+ };
420
+ }
421
+ default:
422
+ return null;
423
+ }
424
+ }
425
+
426
+ /**
427
+ * Log learning to persistent file for analysis
428
+ */
429
+ function logLearning(modelName, failures, context) {
430
+ let log = { entries: [] };
431
+
432
+ if (fs.existsSync(LEARNING_LOG_PATH)) {
433
+ try {
434
+ log = JSON.parse(fs.readFileSync(LEARNING_LOG_PATH, 'utf-8'));
435
+ } catch (err) {
436
+ log = { entries: [] };
437
+ }
438
+ }
439
+
440
+ log.entries.push({
441
+ timestamp: new Date().toISOString(),
442
+ model: modelName,
443
+ failureCount: failures.length,
444
+ categories: failures.map(f => f.primaryCategory),
445
+ strategies: failures.map(f => f.strategy),
446
+ context: context.taskId || 'unknown',
447
+ recovered: true
448
+ });
449
+
450
+ // Keep last 100 entries
451
+ if (log.entries.length > 100) {
452
+ log.entries = log.entries.slice(-100);
453
+ }
454
+
455
+ const dir = path.dirname(LEARNING_LOG_PATH);
456
+ if (!fs.existsSync(dir)) {
457
+ fs.mkdirSync(dir, { recursive: true });
458
+ }
459
+
460
+ fs.writeFileSync(LEARNING_LOG_PATH, JSON.stringify(log, null, 2));
461
+ }
462
+
463
+ // ============================================================
464
+ // Strategy Effectiveness Tracking
465
+ // ============================================================
466
+
467
+ /**
468
+ * Track which strategies lead to successful recovery
469
+ * This helps us learn which refinement strategies work best per model
470
+ */
471
+ function trackStrategyEffectiveness(modelName, strategy, succeeded) {
472
+ let stats = {};
473
+
474
+ if (fs.existsSync(STRATEGY_STATS_PATH)) {
475
+ try {
476
+ stats = JSON.parse(fs.readFileSync(STRATEGY_STATS_PATH, 'utf-8'));
477
+ } catch (err) {
478
+ stats = {};
479
+ }
480
+ }
481
+
482
+ if (!stats[modelName]) {
483
+ stats[modelName] = {};
484
+ }
485
+
486
+ if (!stats[modelName][strategy]) {
487
+ stats[modelName][strategy] = { successes: 0, failures: 0 };
488
+ }
489
+
490
+ if (succeeded) {
491
+ stats[modelName][strategy].successes++;
492
+ } else {
493
+ stats[modelName][strategy].failures++;
494
+ }
495
+
496
+ // Calculate effectiveness rate (guard against division by zero)
497
+ const s = stats[modelName][strategy];
498
+ const total = s.successes + s.failures;
499
+ s.rate = total > 0 ? s.successes / total : 0;
500
+
501
+ const dir = path.dirname(STRATEGY_STATS_PATH);
502
+ if (!fs.existsSync(dir)) {
503
+ fs.mkdirSync(dir, { recursive: true });
504
+ }
505
+
506
+ fs.writeFileSync(STRATEGY_STATS_PATH, JSON.stringify(stats, null, 2));
507
+ }
508
+
509
+ /**
510
+ * Get strategy effectiveness for a model
511
+ */
512
+ function getStrategyEffectiveness(modelName) {
513
+ if (!fs.existsSync(STRATEGY_STATS_PATH)) {
514
+ return null;
515
+ }
516
+
517
+ try {
518
+ const stats = JSON.parse(fs.readFileSync(STRATEGY_STATS_PATH, 'utf-8'));
519
+ return stats[modelName] || null;
520
+ } catch (err) {
521
+ return null;
522
+ }
523
+ }
524
+
525
+ /**
526
+ * Get the best strategy for a model and error category
527
+ */
528
+ function getBestStrategy(modelName, category) {
529
+ const effectiveness = getStrategyEffectiveness(modelName);
530
+ if (!effectiveness) return null;
531
+
532
+ const categoryStrategy = ERROR_CATEGORIES[category]?.strategy;
533
+ if (categoryStrategy && effectiveness[categoryStrategy]) {
534
+ const rate = effectiveness[categoryStrategy].rate;
535
+ if (rate < 0.5) {
536
+ // This strategy isn't working well, try generic
537
+ return 'generic_fix';
538
+ }
539
+ }
540
+
541
+ return categoryStrategy;
542
+ }
543
+
544
+ // ============================================================
545
+ // Learning Deduplication
546
+ // ============================================================
547
+
548
+ /**
549
+ * Check if a similar learning already exists
550
+ */
551
+ function isDuplicateLearning(modelName, category, details) {
552
+ const adapterPath = getAdapterPath(modelName);
553
+ if (!fs.existsSync(adapterPath)) return false;
554
+
555
+ const content = fs.readFileSync(adapterPath, 'utf-8');
556
+ const learningsMatch = content.match(/## Learnings\n([\s\S]*?)(?=\n## |$)/);
557
+ if (!learningsMatch) return false;
558
+
559
+ const learnings = learningsMatch[1];
560
+
561
+ // Check if we have a learning for this category in the last 7 days
562
+ const sevenDaysAgo = new Date();
563
+ sevenDaysAgo.setDate(sevenDaysAgo.getDate() - 7);
564
+ const dateStr = sevenDaysAgo.toISOString().split('T')[0];
565
+
566
+ // Find entries with both a recent date AND the category on the same line or nearby
567
+ // Pattern: ### YYYY-MM-DD followed by category on same line
568
+ const categoryEscaped = category.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
569
+ const recentCategoryPattern = new RegExp(`### (\\d{4}-\\d{2}-\\d{2}).*?${categoryEscaped}`, 'gi');
570
+ const recentMatches = learnings.match(recentCategoryPattern);
571
+
572
+ if (recentMatches) {
573
+ for (const match of recentMatches) {
574
+ const dateMatch = match.match(/(\d{4}-\d{2}-\d{2})/);
575
+ if (dateMatch && dateMatch[1] >= dateStr) {
576
+ return true; // Recent duplicate found - same date+category header
577
+ }
578
+ }
579
+ }
580
+
581
+ return false;
582
+ }
583
+
584
+ // ============================================================
585
+ // Adaptive Retry Loop
586
+ // ============================================================
587
+
588
+ /**
589
+ * Adaptive retry wrapper for executor calls
590
+ * @param {function} executeFn - Function that executes the task: (prompt) => output
591
+ * @param {function} validateFn - Function that validates output: (output) => { success, error }
592
+ * @param {string} originalPrompt - Original prompt
593
+ * @param {object} options - Options: maxRetries, modelName, taskContext
594
+ * @returns {object} Result with output, success, failures, learningsRecorded
595
+ */
596
+ async function adaptiveRetry(executeFn, validateFn, originalPrompt, options = {}) {
597
+ const {
598
+ maxRetries = 5,
599
+ modelName = 'unknown',
600
+ taskContext = {}
601
+ } = options;
602
+
603
+ const failures = [];
604
+ let currentPrompt = originalPrompt;
605
+ let lastOutput = null;
606
+ let lastError = null;
607
+
608
+ for (let attempt = 1; attempt <= maxRetries; attempt++) {
609
+ try {
610
+ // Execute with current prompt
611
+ const output = await executeFn(currentPrompt);
612
+ lastOutput = output;
613
+
614
+ // Validate output
615
+ const validation = await validateFn(output);
616
+
617
+ if (validation.success) {
618
+ // Success! Record learnings if we had failures
619
+ if (failures.length > 0) {
620
+ recordSuccessfulRecovery(modelName, failures, {
621
+ taskId: taskContext.taskId,
622
+ attemptsTaken: attempt,
623
+ finalPromptLength: currentPrompt.length
624
+ });
625
+ }
626
+
627
+ return {
628
+ success: true,
629
+ output,
630
+ attempts: attempt,
631
+ failures,
632
+ learningsRecorded: failures.length > 0
633
+ };
634
+ }
635
+
636
+ // Failed validation
637
+ lastError = validation.error;
638
+
639
+ } catch (error) {
640
+ lastError = error.message || String(error);
641
+ }
642
+
643
+ // Analyze failure
644
+ const failure = analyzeFailure(lastError, lastOutput, taskContext);
645
+ failures.push(failure);
646
+
647
+ console.log(`${colors.yellow} ⚠️ Attempt ${attempt} failed: ${failure.primaryCategory}${colors.reset}`);
648
+
649
+ // Refine prompt for next attempt
650
+ if (attempt < maxRetries) {
651
+ const refined = refinePromptForRetry(originalPrompt, failure, failures.slice(0, -1));
652
+ currentPrompt = refined.prompt;
653
+ console.log(`${colors.dim} 📝 Refining prompt with ${refined.strategy} strategy${colors.reset}`);
654
+ }
655
+ }
656
+
657
+ // All attempts failed
658
+ return {
659
+ success: false,
660
+ output: lastOutput,
661
+ error: lastError,
662
+ attempts: maxRetries,
663
+ failures,
664
+ learningsRecorded: false
665
+ };
666
+ }
667
+
668
+ // ============================================================
669
+ // Community Sharing - Export Learnings
670
+ // ============================================================
671
+
672
+ /**
673
+ * Export aggregated learnings for contribution to wogi-flow
674
+ * This allows users to share their model-specific learnings with the community
675
+ */
676
+ function exportLearningsForSharing() {
677
+ const exportData = {
678
+ exportedAt: new Date().toISOString(),
679
+ version: '1.0',
680
+ models: {}
681
+ };
682
+
683
+ // Load learning log
684
+ if (!fs.existsSync(LEARNING_LOG_PATH)) {
685
+ return { success: false, error: 'No learning data found' };
686
+ }
687
+
688
+ const log = JSON.parse(fs.readFileSync(LEARNING_LOG_PATH, 'utf-8'));
689
+
690
+ // Aggregate by model
691
+ for (const entry of log.entries) {
692
+ const model = entry.model;
693
+ if (!exportData.models[model]) {
694
+ exportData.models[model] = {
695
+ totalRecoveries: 0,
696
+ categoryFrequency: {},
697
+ learnings: []
698
+ };
699
+ }
700
+
701
+ exportData.models[model].totalRecoveries++;
702
+
703
+ for (const cat of entry.categories) {
704
+ exportData.models[model].categoryFrequency[cat] =
705
+ (exportData.models[model].categoryFrequency[cat] || 0) + 1;
706
+ }
707
+ }
708
+
709
+ // Load model adapter files and extract learnings sections
710
+ const adaptersDir = path.join(PROJECT_ROOT, '.workflow', 'model-adapters');
711
+ if (fs.existsSync(adaptersDir)) {
712
+ const adapterFiles = fs.readdirSync(adaptersDir).filter(f => f.endsWith('.md') && !f.startsWith('_'));
713
+
714
+ for (const file of adapterFiles) {
715
+ const modelName = file.replace('.md', '');
716
+ const content = fs.readFileSync(path.join(adaptersDir, file), 'utf-8');
717
+
718
+ // Extract learnings section
719
+ const learningsMatch = content.match(/## Learnings\n([\s\S]*?)(?=\n## |$)/);
720
+ if (learningsMatch && learningsMatch[1].trim()) {
721
+ if (!exportData.models[modelName]) {
722
+ exportData.models[modelName] = {
723
+ totalRecoveries: 0,
724
+ categoryFrequency: {},
725
+ learnings: []
726
+ };
727
+ }
728
+
729
+ // Parse individual learnings
730
+ const learningBlocks = learningsMatch[1].split(/\n### /).filter(Boolean);
731
+ for (const block of learningBlocks) {
732
+ if (block.includes('Auto-learned') || block.includes('adaptive-learning')) {
733
+ exportData.models[modelName].learnings.push(block.trim());
734
+ }
735
+ }
736
+ }
737
+ }
738
+ }
739
+
740
+ // Generate summary
741
+ exportData.summary = {
742
+ totalModels: Object.keys(exportData.models).length,
743
+ totalLearnings: Object.values(exportData.models).reduce((sum, m) => sum + m.learnings.length, 0),
744
+ topIssues: getTopIssues(exportData.models)
745
+ };
746
+
747
+ return { success: true, data: exportData };
748
+ }
749
+
750
+ /**
751
+ * Get top issues across all models
752
+ */
753
+ function getTopIssues(models) {
754
+ const allCategories = {};
755
+
756
+ for (const model of Object.values(models)) {
757
+ for (const [cat, count] of Object.entries(model.categoryFrequency)) {
758
+ allCategories[cat] = (allCategories[cat] || 0) + count;
759
+ }
760
+ }
761
+
762
+ return Object.entries(allCategories)
763
+ .sort((a, b) => b[1] - a[1])
764
+ .slice(0, 5)
765
+ .map(([category, count]) => ({
766
+ category,
767
+ count,
768
+ description: ERROR_CATEGORIES[category]?.description || 'Unknown'
769
+ }));
770
+ }
771
+
772
+ /**
773
+ * Format export for PR submission
774
+ */
775
+ function formatExportForPR(exportData) {
776
+ const lines = [
777
+ '# Model Adapter Learnings Contribution',
778
+ '',
779
+ `Exported: ${exportData.exportedAt}`,
780
+ `Models: ${exportData.summary.totalModels}`,
781
+ `Learnings: ${exportData.summary.totalLearnings}`,
782
+ '',
783
+ '## Top Issues Encountered',
784
+ ''
785
+ ];
786
+
787
+ for (const issue of exportData.summary.topIssues) {
788
+ lines.push(`- **${issue.category}** (${issue.count}x): ${issue.description}`);
789
+ }
790
+
791
+ lines.push('', '## Per-Model Learnings', '');
792
+
793
+ for (const [model, data] of Object.entries(exportData.models)) {
794
+ if (data.learnings.length === 0) continue;
795
+
796
+ lines.push(`### ${model}`, '');
797
+ lines.push(`Recoveries: ${data.totalRecoveries}`, '');
798
+
799
+ for (const learning of data.learnings.slice(0, 5)) {
800
+ lines.push('```');
801
+ lines.push(learning);
802
+ lines.push('```');
803
+ lines.push('');
804
+ }
805
+ }
806
+
807
+ return lines.join('\n');
808
+ }
809
+
810
+ // ============================================================
811
+ // Automatic PR Contribution
812
+ // ============================================================
813
+
814
+ /**
815
+ * Check if gh CLI is available and authenticated
816
+ */
817
+ function checkGitHubCLI() {
818
+ try {
819
+ execFileSync('gh', ['auth', 'status'], { stdio: 'pipe' });
820
+ return { available: true };
821
+ } catch (err) {
822
+ return { available: false, error: 'gh CLI not authenticated. Run: gh auth login' };
823
+ }
824
+ }
825
+
826
+ /**
827
+ * Create a PR with learnings to the wogi-flow repository
828
+ * @param {string} upstreamRepo - The upstream repo (e.g., 'owner/wogi-flow')
829
+ * @param {object} options - Options
830
+ */
831
+ async function contributeLearnings(upstreamRepo = 'your-org/wogi-flow', options = {}) {
832
+ const { dryRun = false } = options;
833
+
834
+ // Check prerequisites
835
+ const ghCheck = checkGitHubCLI();
836
+ if (!ghCheck.available) {
837
+ return { success: false, error: ghCheck.error };
838
+ }
839
+
840
+ // Export learnings
841
+ const exportResult = exportLearningsForSharing();
842
+ if (!exportResult.success) {
843
+ return { success: false, error: 'No learnings to contribute' };
844
+ }
845
+
846
+ const { data } = exportResult;
847
+ if (data.summary.totalLearnings === 0) {
848
+ return { success: false, error: 'No new learnings to contribute' };
849
+ }
850
+
851
+ // Generate unique branch name
852
+ const timestamp = Date.now();
853
+ const branchName = `learnings-contribution-${timestamp}`;
854
+
855
+ if (dryRun) {
856
+ console.log(`${colors.cyan}[DRY RUN] Would create PR with:${colors.reset}`);
857
+ console.log(` Branch: ${branchName}`);
858
+ console.log(` Models: ${data.summary.totalModels}`);
859
+ console.log(` Learnings: ${data.summary.totalLearnings}`);
860
+ return { success: true, dryRun: true };
861
+ }
862
+
863
+ try {
864
+ // Create contribution file
865
+ const contributionDir = path.join(PROJECT_ROOT, '.workflow', 'contributions');
866
+ if (!fs.existsSync(contributionDir)) {
867
+ fs.mkdirSync(contributionDir, { recursive: true });
868
+ }
869
+
870
+ const contributionFile = path.join(contributionDir, `contribution-${timestamp}.md`);
871
+ fs.writeFileSync(contributionFile, formatExportForPR(data));
872
+
873
+ // Also create/update model adapter patches
874
+ const patchesDir = path.join(contributionDir, 'patches');
875
+ if (!fs.existsSync(patchesDir)) {
876
+ fs.mkdirSync(patchesDir, { recursive: true });
877
+ }
878
+
879
+ for (const [model, modelData] of Object.entries(data.models)) {
880
+ if (modelData.learnings.length === 0) continue;
881
+
882
+ const patchContent = [
883
+ `# Learnings for ${model}`,
884
+ '',
885
+ `Contributed: ${data.exportedAt}`,
886
+ `Recoveries: ${modelData.totalRecoveries}`,
887
+ '',
888
+ '## New Learnings',
889
+ '',
890
+ ...modelData.learnings.map(l => `### ${l}`),
891
+ ''
892
+ ].join('\n');
893
+
894
+ fs.writeFileSync(path.join(patchesDir, `${model}.md`), patchContent);
895
+ }
896
+
897
+ console.log(`${colors.green}✅ Contribution files created in ${contributionDir}${colors.reset}`);
898
+ console.log('');
899
+ console.log(`${colors.cyan}To submit a PR:${colors.reset}`);
900
+ console.log(` 1. Fork ${upstreamRepo} on GitHub`);
901
+ console.log(` 2. Clone your fork`);
902
+ console.log(` 3. Copy files from ${contributionDir} to .workflow/model-adapters/`);
903
+ console.log(` 4. Commit and push`);
904
+ console.log(` 5. Create PR with title: "model-adapters: community learnings"`);
905
+ console.log('');
906
+ console.log(`${colors.dim}Or run with --auto-pr to attempt automatic PR creation (requires fork)${colors.reset}`);
907
+
908
+ return {
909
+ success: true,
910
+ contributionDir,
911
+ models: Object.keys(data.models),
912
+ learnings: data.summary.totalLearnings
913
+ };
914
+
915
+ } catch (err) {
916
+ return { success: false, error: err.message };
917
+ }
918
+ }
919
+
920
+ /**
921
+ * Attempt to create PR automatically using gh CLI
922
+ * This requires the user to have forked the wogi-flow repo
923
+ */
924
+ async function createAutoPR(upstreamRepo, options = {}) {
925
+ const { forkRepo } = options;
926
+
927
+ if (!forkRepo) {
928
+ return { success: false, error: 'Fork repo required. Use --fork=username/wogi-flow' };
929
+ }
930
+
931
+ // Validate repository formats to prevent injection
932
+ if (!validateRepoFormat(forkRepo)) {
933
+ return { success: false, error: 'Invalid fork repo format. Expected: owner/repo' };
934
+ }
935
+ if (!validateRepoFormat(upstreamRepo)) {
936
+ return { success: false, error: 'Invalid upstream repo format. Expected: owner/repo' };
937
+ }
938
+
939
+ const ghCheck = checkGitHubCLI();
940
+ if (!ghCheck.available) {
941
+ return { success: false, error: ghCheck.error };
942
+ }
943
+
944
+ // Export and prepare
945
+ const contribution = await contributeLearnings(upstreamRepo, { dryRun: false });
946
+ if (!contribution.success) {
947
+ return contribution;
948
+ }
949
+
950
+ const timestamp = Date.now();
951
+ const branchName = `learnings-${timestamp}`;
952
+
953
+ try {
954
+ // Clone fork, create branch, add files, push, create PR
955
+ const tempDir = path.join(PROJECT_ROOT, '.workflow', 'temp-pr');
956
+
957
+ console.log(`${colors.cyan}Creating PR automatically...${colors.reset}`);
958
+
959
+ // Clone the fork using safe git command (array arguments prevent injection)
960
+ execFileSync('git', ['clone', '--depth', '1', `https://github.com/${forkRepo}.git`, tempDir], { stdio: 'pipe' });
961
+
962
+ // Create branch
963
+ safeGitCommand(['checkout', '-b', branchName], { cwd: tempDir });
964
+
965
+ // Copy contribution files
966
+ const patchesDir = path.join(contribution.contributionDir, 'patches');
967
+ const targetDir = path.join(tempDir, '.workflow', 'model-adapters');
968
+
969
+ if (fs.existsSync(patchesDir)) {
970
+ const patches = fs.readdirSync(patchesDir);
971
+ for (const patch of patches) {
972
+ const src = path.join(patchesDir, patch);
973
+ const dest = path.join(targetDir, patch);
974
+
975
+ // Append learnings to existing adapter or create new
976
+ if (fs.existsSync(dest)) {
977
+ const existing = fs.readFileSync(dest, 'utf-8');
978
+ const addition = fs.readFileSync(src, 'utf-8');
979
+ // Append new learnings section
980
+ fs.writeFileSync(dest, existing + '\n' + addition);
981
+ } else {
982
+ fs.copyFileSync(src, dest);
983
+ }
984
+ }
985
+ }
986
+
987
+ // Commit using safe git commands
988
+ safeGitCommand(['add', '.'], { cwd: tempDir });
989
+ safeGitCommand(['commit', '-m', 'model-adapters: community learnings from adaptive learning'], { cwd: tempDir });
990
+
991
+ // Push
992
+ safeGitCommand(['push', 'origin', branchName], { cwd: tempDir });
993
+
994
+ // Create PR using gh with proper argument handling
995
+ const prTitle = 'model-adapters: community learnings contribution';
996
+ const prBody = `## Community Learnings Contribution
997
+
998
+ This PR adds learnings from adaptive learning sessions.
999
+
1000
+ **Models:** ${contribution.models.join(', ')}
1001
+ **New Learnings:** ${contribution.learnings}
1002
+
1003
+ These learnings help improve prompt refinement for all wogi-flow users.
1004
+
1005
+ ---
1006
+ *Auto-generated by wogi-flow adaptive learning system*`;
1007
+
1008
+ const forkOwner = forkRepo.split('/')[0];
1009
+ execFileSync('gh', [
1010
+ 'pr', 'create',
1011
+ '--repo', upstreamRepo,
1012
+ '--head', `${forkOwner}:${branchName}`,
1013
+ '--title', prTitle,
1014
+ '--body', prBody
1015
+ ], { cwd: tempDir, stdio: 'inherit' });
1016
+
1017
+ // Cleanup
1018
+ fs.rmSync(tempDir, { recursive: true, force: true });
1019
+
1020
+ console.log(`${colors.green}✅ PR created successfully!${colors.reset}`);
1021
+ return { success: true };
1022
+
1023
+ } catch (err) {
1024
+ return { success: false, error: err.message };
1025
+ }
1026
+ }
1027
+
1028
+ // ============================================================
1029
+ // Exports
1030
+ // ============================================================
1031
+
1032
+ module.exports = {
1033
+ // Core functions
1034
+ analyzeFailure,
1035
+ refinePromptForRetry,
1036
+ recordSuccessfulRecovery,
1037
+ adaptiveRetry,
1038
+
1039
+ // Constants for external use
1040
+ ERROR_CATEGORIES,
1041
+ REFINEMENT_STRATEGIES,
1042
+
1043
+ // Re-export centralized failure categories for convenience
1044
+ FailureCategory,
1045
+ detectCategory,
1046
+ shouldEscalate: checkShouldEscalate,
1047
+
1048
+ // Utilities
1049
+ extractErrorDetails,
1050
+ generateGuidanceFromFailures,
1051
+ logLearning,
1052
+
1053
+ // Strategy effectiveness
1054
+ trackStrategyEffectiveness,
1055
+ getStrategyEffectiveness,
1056
+ getBestStrategy,
1057
+
1058
+ // Deduplication
1059
+ isDuplicateLearning,
1060
+
1061
+ // Community sharing
1062
+ exportLearningsForSharing,
1063
+ formatExportForPR,
1064
+ contributeLearnings,
1065
+ createAutoPR,
1066
+ checkGitHubCLI
1067
+ };
1068
+
1069
+ // ============================================================
1070
+ // CLI
1071
+ // ============================================================
1072
+
1073
+ if (require.main === module) {
1074
+ (async () => {
1075
+ const args = process.argv.slice(2);
1076
+ const command = args[0];
1077
+
1078
+ switch (command) {
1079
+ case 'stats': {
1080
+ // Show learning statistics
1081
+ if (fs.existsSync(LEARNING_LOG_PATH)) {
1082
+ const log = JSON.parse(fs.readFileSync(LEARNING_LOG_PATH, 'utf-8'));
1083
+ console.log('\n📊 Adaptive Learning Statistics\n');
1084
+ console.log(`Total recoveries: ${log.entries.length}`);
1085
+
1086
+ // Group by model
1087
+ const byModel = {};
1088
+ for (const entry of log.entries) {
1089
+ if (!byModel[entry.model]) {
1090
+ byModel[entry.model] = { count: 0, categories: {} };
1091
+ }
1092
+ byModel[entry.model].count++;
1093
+ for (const cat of entry.categories) {
1094
+ byModel[entry.model].categories[cat] = (byModel[entry.model].categories[cat] || 0) + 1;
1095
+ }
1096
+ }
1097
+
1098
+ console.log('\nBy model:');
1099
+ for (const [model, data] of Object.entries(byModel)) {
1100
+ console.log(`\n ${model}: ${data.count} recoveries`);
1101
+ for (const [cat, count] of Object.entries(data.categories)) {
1102
+ console.log(` - ${cat}: ${count}`);
1103
+ }
1104
+ }
1105
+ } else {
1106
+ console.log('No adaptive learning data yet.');
1107
+ }
1108
+ break;
1109
+ }
1110
+
1111
+ case 'test': {
1112
+ // Test failure analysis
1113
+ const testErrors = [
1114
+ "Cannot find module '@/components/Button'",
1115
+ "Type 'string' is not assignable to type 'number'",
1116
+ "Unexpected token, expected '}'",
1117
+ "```typescript\nimport React from 'react'",
1118
+ "// ... rest of component"
1119
+ ];
1120
+
1121
+ console.log('\n🧪 Testing Failure Analysis\n');
1122
+ for (const error of testErrors) {
1123
+ const analysis = analyzeFailure(error, '');
1124
+ console.log(`Error: "${error.slice(0, 50)}..."`);
1125
+ console.log(` Category: ${analysis.primaryCategory}`);
1126
+ console.log(` Strategy: ${analysis.strategy}`);
1127
+ console.log('');
1128
+ }
1129
+ break;
1130
+ }
1131
+
1132
+ case 'export': {
1133
+ // Export learnings for community contribution
1134
+ console.log('\n📤 Exporting Learnings for Community Contribution\n');
1135
+
1136
+ const result = exportLearningsForSharing();
1137
+
1138
+ if (!result.success) {
1139
+ console.log(`${colors.yellow}No learning data to export yet.${colors.reset}`);
1140
+ console.log('Run some hybrid mode tasks first to generate learnings.');
1141
+ process.exit(1);
1142
+ }
1143
+
1144
+ const { data } = result;
1145
+ console.log(`Models with learnings: ${data.summary.totalModels}`);
1146
+ console.log(`Total learnings: ${data.summary.totalLearnings}`);
1147
+
1148
+ if (data.summary.topIssues.length > 0) {
1149
+ console.log('\nTop issues encountered:');
1150
+ for (const issue of data.summary.topIssues) {
1151
+ console.log(` - ${issue.category} (${issue.count}x): ${issue.description}`);
1152
+ }
1153
+ }
1154
+
1155
+ // Save export file
1156
+ const exportPath = path.join(PROJECT_ROOT, '.workflow', 'learnings-export.json');
1157
+ fs.writeFileSync(exportPath, JSON.stringify(data, null, 2));
1158
+ console.log(`\n${colors.green}✅ Exported to: ${exportPath}${colors.reset}`);
1159
+
1160
+ // Also create PR-ready markdown
1161
+ const prPath = path.join(PROJECT_ROOT, '.workflow', 'learnings-contribution.md');
1162
+ fs.writeFileSync(prPath, formatExportForPR(data));
1163
+ console.log(`${colors.green}✅ PR-ready format: ${prPath}${colors.reset}`);
1164
+
1165
+ console.log(`
1166
+ ${colors.cyan}To contribute these learnings to wogi-flow:${colors.reset}
1167
+ 1. Fork https://github.com/your-org/wogi-flow
1168
+ 2. Copy ${prPath} content to your PR
1169
+ 3. Submit PR with title: "model-adapters: community learnings"
1170
+
1171
+ Or use: flow hybrid learning contribute --auto-pr --fork=yourusername/wogi-flow
1172
+ `);
1173
+ break;
1174
+ }
1175
+
1176
+ case 'contribute': {
1177
+ // Contribute learnings via PR
1178
+ console.log('\n🤝 Contributing Learnings to Community\n');
1179
+
1180
+ const autoPR = args.includes('--auto-pr');
1181
+ const forkArg = args.find(a => a.startsWith('--fork='));
1182
+ const forkRepo = forkArg ? forkArg.split('=')[1] : null;
1183
+ const upstreamArg = args.find(a => a.startsWith('--upstream='));
1184
+ const upstreamRepo = upstreamArg ? upstreamArg.split('=')[1] : 'your-org/wogi-flow';
1185
+
1186
+ if (autoPR) {
1187
+ if (!forkRepo) {
1188
+ console.log(`${colors.red}Error: --fork=username/wogi-flow required for auto-PR${colors.reset}`);
1189
+ console.log('Example: flow hybrid learning contribute --auto-pr --fork=myuser/wogi-flow');
1190
+ process.exit(1);
1191
+ }
1192
+
1193
+ const result = await createAutoPR(upstreamRepo, { forkRepo });
1194
+ if (!result.success) {
1195
+ console.log(`${colors.red}Error: ${result.error}${colors.reset}`);
1196
+ process.exit(1);
1197
+ }
1198
+ } else {
1199
+ const result = await contributeLearnings(upstreamRepo);
1200
+ if (!result.success) {
1201
+ console.log(`${colors.red}Error: ${result.error}${colors.reset}`);
1202
+ process.exit(1);
1203
+ }
1204
+ }
1205
+ break;
1206
+ }
1207
+
1208
+ case 'effectiveness': {
1209
+ // Show strategy effectiveness stats
1210
+ console.log('\n📈 Strategy Effectiveness\n');
1211
+
1212
+ if (!fs.existsSync(STRATEGY_STATS_PATH)) {
1213
+ console.log('No strategy effectiveness data yet.');
1214
+ console.log('Run some hybrid mode tasks first to generate data.');
1215
+ break;
1216
+ }
1217
+
1218
+ const stats = JSON.parse(fs.readFileSync(STRATEGY_STATS_PATH, 'utf-8'));
1219
+
1220
+ for (const [model, strategies] of Object.entries(stats)) {
1221
+ console.log(`\n${colors.cyan}${model}${colors.reset}`);
1222
+ for (const [strategy, data] of Object.entries(strategies)) {
1223
+ const rate = (data.rate * 100).toFixed(0);
1224
+ const color = data.rate > 0.7 ? colors.green : data.rate > 0.5 ? colors.yellow : colors.red;
1225
+ console.log(` ${strategy}: ${color}${rate}%${colors.reset} (${data.successes}/${data.successes + data.failures})`);
1226
+ }
1227
+ }
1228
+ break;
1229
+ }
1230
+
1231
+ default:
1232
+ console.log(`
1233
+ Wogi Flow - Adaptive Learning
1234
+
1235
+ Usage:
1236
+ node flow-adaptive-learning.js <command>
1237
+
1238
+ Commands:
1239
+ stats Show learning statistics
1240
+ test Test failure analysis with sample errors
1241
+ export Export learnings for community contribution
1242
+ contribute Create contribution files for PR
1243
+ effectiveness Show strategy effectiveness per model
1244
+
1245
+ Options for contribute:
1246
+ --auto-pr Automatically create GitHub PR
1247
+ --fork=user/repo Your fork of wogi-flow (required for --auto-pr)
1248
+ --upstream=user/repo Upstream repo (default: your-org/wogi-flow)
1249
+
1250
+ This module enables hybrid mode to learn from failures:
1251
+ 1. When executor fails, analyze WHY
1252
+ 2. Refine prompt based on failure category
1253
+ 3. Retry with improved instructions
1254
+ 4. On success, update model adapter with learnings
1255
+ 5. Share learnings with the community via PR
1256
+ `);
1257
+ }
1258
+ })();
1259
+ }