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,502 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Task Complexity Assessment
5
+ *
6
+ * Analyzes task complexity to guide context richness for the local LLM.
7
+ *
8
+ * Key insight: Local LLM tokens are FREE. The goal is to give the LLM
9
+ * everything it needs for 90%+ success rate. Failed executions cost more
10
+ * (in Claude retry tokens) than generous upfront context.
11
+ *
12
+ * This module helps Claude estimate the MINIMUM context needed for success,
13
+ * not impose artificial limits on token usage.
14
+ *
15
+ * Usage:
16
+ * const { assessTaskComplexity } = require('./flow-complexity');
17
+ * const complexity = assessTaskComplexity(task);
18
+ */
19
+
20
+ // ============================================================
21
+ // Complexity Levels (Guidance, NOT Hard Limits)
22
+ // ============================================================
23
+
24
+ /**
25
+ * These are GUIDANCE for Claude on how much context to include.
26
+ * There are NO hard token limits - local LLM tokens are free.
27
+ *
28
+ * The suggestedTokens is a minimum estimate for 90%+ success rate.
29
+ * Claude should include MORE context if there's any doubt.
30
+ */
31
+ const COMPLEXITY_LEVELS = {
32
+ small: {
33
+ suggestedTokens: 2000,
34
+ description: 'Single file, simple change, no new dependencies',
35
+ contextNeeds: 'Basic imports list, target file only'
36
+ },
37
+ medium: {
38
+ suggestedTokens: 4000,
39
+ description: 'Multi-file changes, moderate complexity, some boilerplate',
40
+ contextNeeds: 'Full imports, props/types for related components, patterns'
41
+ },
42
+ large: {
43
+ suggestedTokens: 6000,
44
+ description: 'Many files, complex logic, tests, error handling',
45
+ contextNeeds: 'All imports with usage examples, related code snippets, full types'
46
+ },
47
+ xl: {
48
+ suggestedTokens: 10000,
49
+ description: 'Architectural changes, many dependencies, extensive boilerplate',
50
+ contextNeeds: 'Everything available - full context, all examples, comprehensive types'
51
+ }
52
+ };
53
+
54
+ // Keep TOKEN_BUDGETS for backward compatibility but without hard limits
55
+ const TOKEN_BUDGETS = {
56
+ small: { default: 2000, description: COMPLEXITY_LEVELS.small.description },
57
+ medium: { default: 4000, description: COMPLEXITY_LEVELS.medium.description },
58
+ large: { default: 6000, description: COMPLEXITY_LEVELS.large.description },
59
+ xl: { default: 10000, description: COMPLEXITY_LEVELS.xl.description }
60
+ };
61
+
62
+ // ============================================================
63
+ // Complexity Keywords and Weights
64
+ // ============================================================
65
+
66
+ /**
67
+ * Keywords that indicate higher complexity
68
+ * Each keyword adds to the complexity score
69
+ */
70
+ const COMPLEXITY_KEYWORDS = {
71
+ // High complexity (weight: 3)
72
+ high: [
73
+ 'refactor', 'migrate', 'authentication', 'authorization', 'auth',
74
+ 'security', 'encryption', 'database', 'migration', 'schema',
75
+ 'integration', 'api', 'websocket', 'realtime', 'cache', 'caching',
76
+ 'payment', 'stripe', 'checkout', 'transaction'
77
+ ],
78
+ // Medium complexity (weight: 2)
79
+ medium: [
80
+ 'validation', 'form', 'crud', 'create', 'update', 'delete',
81
+ 'filter', 'sort', 'pagination', 'search', 'modal', 'dialog',
82
+ 'notification', 'toast', 'error handling', 'loading', 'state',
83
+ 'hook', 'context', 'provider', 'service'
84
+ ],
85
+ // Low complexity (weight: 1)
86
+ low: [
87
+ 'style', 'css', 'color', 'margin', 'padding', 'layout',
88
+ 'text', 'label', 'button', 'icon', 'image', 'link',
89
+ 'import', 'export', 'rename', 'move', 'typo', 'fix'
90
+ ]
91
+ };
92
+
93
+ /**
94
+ * Keywords that indicate simple/small tasks
95
+ */
96
+ const SIMPLICITY_KEYWORDS = [
97
+ 'simple', 'quick', 'small', 'minor', 'typo', 'rename',
98
+ 'comment', 'log', 'console', 'debug', 'cleanup', 'remove unused'
99
+ ];
100
+
101
+ /**
102
+ * Keywords indicating test requirements
103
+ */
104
+ const TEST_KEYWORDS = [
105
+ 'test', 'spec', 'unit test', 'integration test', 'e2e',
106
+ 'coverage', 'mock', 'stub', 'fixture'
107
+ ];
108
+
109
+ // ============================================================
110
+ // Complexity Assessment Functions
111
+ // ============================================================
112
+
113
+ /**
114
+ * Counts file references in task description
115
+ * @param {string} text - Task description or criteria
116
+ * @returns {Object} - File count metrics
117
+ */
118
+ function countFileReferences(text) {
119
+ if (!text) return { total: 0, create: 0, modify: 0 };
120
+
121
+ const lowerText = text.toLowerCase();
122
+
123
+ // Count explicit file mentions
124
+ const filePatterns = [
125
+ /\b\w+\.(tsx?|jsx?|ts|js|css|scss|json|md)\b/gi,
126
+ /create\s+(?:a\s+)?(?:new\s+)?(\w+)\s+(?:file|component|service|hook)/gi,
127
+ /modify\s+(?:the\s+)?(\w+)/gi,
128
+ /update\s+(?:the\s+)?(\w+)/gi,
129
+ /add\s+to\s+(\w+)/gi
130
+ ];
131
+
132
+ let fileCount = 0;
133
+ for (const pattern of filePatterns) {
134
+ const matches = text.match(pattern);
135
+ if (matches) fileCount += matches.length;
136
+ }
137
+
138
+ // Estimate create vs modify
139
+ const createKeywords = ['create', 'new', 'add', 'implement', 'build'];
140
+ const modifyKeywords = ['update', 'modify', 'change', 'fix', 'edit', 'refactor'];
141
+
142
+ const hasCreate = createKeywords.some(k => lowerText.includes(k));
143
+ const hasModify = modifyKeywords.some(k => lowerText.includes(k));
144
+
145
+ // Minimum 1 file if task exists
146
+ const total = Math.max(1, fileCount);
147
+
148
+ return {
149
+ total,
150
+ create: hasCreate ? Math.ceil(total * 0.6) : 0,
151
+ modify: hasModify ? Math.ceil(total * 0.4) : total
152
+ };
153
+ }
154
+
155
+ /**
156
+ * Detects complexity keywords in text
157
+ * @param {string} text - Task description
158
+ * @returns {Object} - Detected keywords by weight
159
+ */
160
+ function detectComplexityKeywords(text) {
161
+ if (!text) return { high: [], medium: [], low: [], simplicity: [] };
162
+
163
+ const lowerText = text.toLowerCase();
164
+
165
+ const detected = {
166
+ high: COMPLEXITY_KEYWORDS.high.filter(k => lowerText.includes(k)),
167
+ medium: COMPLEXITY_KEYWORDS.medium.filter(k => lowerText.includes(k)),
168
+ low: COMPLEXITY_KEYWORDS.low.filter(k => lowerText.includes(k)),
169
+ simplicity: SIMPLICITY_KEYWORDS.filter(k => lowerText.includes(k))
170
+ };
171
+
172
+ return detected;
173
+ }
174
+
175
+ /**
176
+ * Checks if tests are required
177
+ * @param {string} text - Task description
178
+ * @returns {boolean}
179
+ */
180
+ function requiresTests(text) {
181
+ if (!text) return false;
182
+ const lowerText = text.toLowerCase();
183
+ return TEST_KEYWORDS.some(k => lowerText.includes(k));
184
+ }
185
+
186
+ /**
187
+ * Counts acceptance criteria/scenarios
188
+ * @param {Object} task - Task object
189
+ * @returns {number}
190
+ */
191
+ function countAcceptanceCriteria(task) {
192
+ let count = 0;
193
+
194
+ // Check for scenarios in task
195
+ if (task.scenarios) {
196
+ count += Array.isArray(task.scenarios) ? task.scenarios.length : 1;
197
+ }
198
+
199
+ // Check for acceptance criteria
200
+ if (task.acceptanceCriteria) {
201
+ if (Array.isArray(task.acceptanceCriteria)) {
202
+ count += task.acceptanceCriteria.length;
203
+ } else if (typeof task.acceptanceCriteria === 'string') {
204
+ // Count Given/When/Then blocks
205
+ const givenMatches = task.acceptanceCriteria.match(/given\b/gi);
206
+ count += givenMatches ? givenMatches.length : 1;
207
+ }
208
+ }
209
+
210
+ // Check description for scenario patterns
211
+ const description = task.description || task.title || '';
212
+ const scenarioMatches = description.match(/scenario\s*\d*:|given\s|when\s|then\s/gi);
213
+ if (scenarioMatches) {
214
+ count += Math.ceil(scenarioMatches.length / 3); // Given/When/Then = 1 scenario
215
+ }
216
+
217
+ return count || 1; // Minimum 1
218
+ }
219
+
220
+ /**
221
+ * Calculates complexity score and estimates tokens
222
+ * @param {Object} factors - Complexity factors
223
+ * @returns {Object} - Score and token estimate
224
+ */
225
+ function calculateComplexityScore(factors) {
226
+ let score = 0;
227
+ const breakdown = [];
228
+
229
+ // Base score
230
+ score += 1000;
231
+ breakdown.push('Base: 1000');
232
+
233
+ // File-based scoring
234
+ // Note: These are conservative estimates. TypeScript files with types,
235
+ // imports, and boilerplate can easily be 1000+ tokens each.
236
+ // Prefer overestimating since local LLM tokens are free.
237
+ if (factors.fileCount.create > 0) {
238
+ const createTokens = factors.fileCount.create * 1200; // Increased from 800
239
+ score += createTokens;
240
+ breakdown.push(`Create ${factors.fileCount.create} files: +${createTokens}`);
241
+ }
242
+
243
+ if (factors.fileCount.modify > 0) {
244
+ const modifyTokens = factors.fileCount.modify * 600; // Increased from 400
245
+ score += modifyTokens;
246
+ breakdown.push(`Modify ${factors.fileCount.modify} files: +${modifyTokens}`);
247
+ }
248
+
249
+ // Keyword-based scoring
250
+ if (factors.keywords.high.length > 0) {
251
+ const highTokens = factors.keywords.high.length * 500;
252
+ score += highTokens;
253
+ breakdown.push(`High complexity keywords (${factors.keywords.high.join(', ')}): +${highTokens}`);
254
+ }
255
+
256
+ if (factors.keywords.medium.length > 0) {
257
+ const medTokens = factors.keywords.medium.length * 300;
258
+ score += medTokens;
259
+ breakdown.push(`Medium complexity keywords: +${medTokens}`);
260
+ }
261
+
262
+ // Tests
263
+ if (factors.hasTests) {
264
+ score += 1000;
265
+ breakdown.push('Tests required: +1000');
266
+ }
267
+
268
+ // Acceptance criteria complexity
269
+ if (factors.acceptanceCriteriaCount > 3) {
270
+ const acTokens = (factors.acceptanceCriteriaCount - 3) * 200;
271
+ score += acTokens;
272
+ breakdown.push(`Extra acceptance criteria: +${acTokens}`);
273
+ }
274
+
275
+ // Simplicity discount
276
+ if (factors.keywords.simplicity.length > 0 && score > 1500) {
277
+ const discount = Math.min(500, factors.keywords.simplicity.length * 200);
278
+ score -= discount;
279
+ breakdown.push(`Simplicity discount: -${discount}`);
280
+ }
281
+
282
+ return { score, breakdown };
283
+ }
284
+
285
+ /**
286
+ * Determines complexity level from score
287
+ * Uses soft thresholds - these guide context richness, not limit tokens
288
+ * @param {number} score - Calculated score
289
+ * @returns {string} - Complexity level
290
+ */
291
+ function scoreToLevel(score) {
292
+ if (score <= 2000) return 'small';
293
+ if (score <= 4000) return 'medium';
294
+ if (score <= 7000) return 'large';
295
+ return 'xl';
296
+ }
297
+
298
+ /**
299
+ * Main function: Assess task complexity
300
+ *
301
+ * Returns guidance for Claude on how much context to include.
302
+ * The estimatedTokens is a MINIMUM for 90%+ success - Claude should
303
+ * include more if there's any doubt. Local LLM tokens are free!
304
+ *
305
+ * @param {Object} task - Task object with description, title, acceptanceCriteria, etc.
306
+ * @returns {Object} - Complexity assessment with guidance (not limits)
307
+ */
308
+ function assessTaskComplexity(task) {
309
+ // Handle string input (just a description)
310
+ if (typeof task === 'string') {
311
+ task = { description: task };
312
+ }
313
+
314
+ // Combine all text sources for analysis
315
+ const textSources = [
316
+ task.title,
317
+ task.description,
318
+ task.summary,
319
+ Array.isArray(task.acceptanceCriteria) ? task.acceptanceCriteria.join(' ') : task.acceptanceCriteria,
320
+ task.technicalNotes,
321
+ task.notes
322
+ ].filter(Boolean).join(' ');
323
+
324
+ // Gather factors
325
+ const factors = {
326
+ fileCount: countFileReferences(textSources),
327
+ keywords: detectComplexityKeywords(textSources),
328
+ hasTests: requiresTests(textSources),
329
+ acceptanceCriteriaCount: countAcceptanceCriteria(task),
330
+ hasDependencies: /depend|require|need|import from|install/i.test(textSources)
331
+ };
332
+
333
+ // Calculate score
334
+ const { score, breakdown } = calculateComplexityScore(factors);
335
+
336
+ // Determine level (guidance, not limit)
337
+ const level = scoreToLevel(score);
338
+ const complexityInfo = COMPLEXITY_LEVELS[level];
339
+
340
+ // Generate reasoning
341
+ const reasoning = generateReasoning(level, factors, breakdown);
342
+
343
+ // Estimated tokens is a MINIMUM - no upper limit, local LLM is free
344
+ // Add buffer for safety (better to include too much than too little)
345
+ const estimatedTokens = Math.max(score, complexityInfo.suggestedTokens);
346
+
347
+ return {
348
+ level,
349
+ estimatedTokens,
350
+ reasoning,
351
+ // What context the LLM needs for this complexity level
352
+ contextNeeds: complexityInfo.contextNeeds,
353
+ factors: {
354
+ fileCount: factors.fileCount.total,
355
+ filesToCreate: factors.fileCount.create,
356
+ filesToModify: factors.fileCount.modify,
357
+ hasTests: factors.hasTests,
358
+ hasDependencies: factors.hasDependencies,
359
+ acceptanceCriteriaCount: factors.acceptanceCriteriaCount,
360
+ complexityKeywords: [...factors.keywords.high, ...factors.keywords.medium],
361
+ simplicityKeywords: factors.keywords.simplicity
362
+ },
363
+ // Keep for backward compatibility
364
+ budget: TOKEN_BUDGETS[level],
365
+ scoreBreakdown: breakdown
366
+ };
367
+ }
368
+
369
+ /**
370
+ * Generates human-readable reasoning
371
+ */
372
+ function generateReasoning(level, factors, breakdown) {
373
+ const parts = [];
374
+
375
+ // File summary
376
+ if (factors.fileCount.create > 0 && factors.fileCount.modify > 0) {
377
+ parts.push(`Creating ${factors.fileCount.create} and modifying ${factors.fileCount.modify} files`);
378
+ } else if (factors.fileCount.create > 0) {
379
+ parts.push(`Creating ${factors.fileCount.create} new file(s)`);
380
+ } else if (factors.fileCount.modify > 0) {
381
+ parts.push(`Modifying ${factors.fileCount.modify} file(s)`);
382
+ }
383
+
384
+ // Complexity keywords
385
+ if (factors.keywords.high.length > 0) {
386
+ parts.push(`High complexity: ${factors.keywords.high.slice(0, 3).join(', ')}`);
387
+ }
388
+
389
+ // Tests
390
+ if (factors.hasTests) {
391
+ parts.push('Tests required');
392
+ }
393
+
394
+ // Dependencies
395
+ if (factors.hasDependencies) {
396
+ parts.push('Has dependencies');
397
+ }
398
+
399
+ return parts.length > 0 ? parts.join('. ') + '.' : TOKEN_BUDGETS[level].description;
400
+ }
401
+
402
+ /**
403
+ * Gets the suggested token minimum for a level
404
+ */
405
+ function getDefaultTokens(level) {
406
+ return COMPLEXITY_LEVELS[level]?.suggestedTokens || COMPLEXITY_LEVELS.medium.suggestedTokens;
407
+ }
408
+
409
+ /**
410
+ * Ensures tokens meet minimum threshold
411
+ * Note: No upper limit - local LLM tokens are free!
412
+ */
413
+ function clampTokens(tokens, minTokens = 1000) {
414
+ // Only enforce minimum - no maximum, local LLM is free
415
+ return Math.max(minTokens, tokens);
416
+ }
417
+
418
+ // ============================================================
419
+ // Exports
420
+ // ============================================================
421
+
422
+ module.exports = {
423
+ assessTaskComplexity,
424
+ // New exports
425
+ COMPLEXITY_LEVELS,
426
+ // Legacy exports (for backward compatibility)
427
+ TOKEN_BUDGETS,
428
+ COMPLEXITY_KEYWORDS,
429
+ SIMPLICITY_KEYWORDS,
430
+ TEST_KEYWORDS,
431
+ getDefaultTokens,
432
+ clampTokens,
433
+ // Expose internal functions for testing
434
+ countFileReferences,
435
+ detectComplexityKeywords,
436
+ requiresTests,
437
+ countAcceptanceCriteria,
438
+ calculateComplexityScore,
439
+ scoreToLevel
440
+ };
441
+
442
+ // ============================================================
443
+ // CLI for testing
444
+ // ============================================================
445
+
446
+ if (require.main === module) {
447
+ const args = process.argv.slice(2);
448
+
449
+ if (args.length === 0) {
450
+ console.log(`
451
+ Usage: node flow-complexity.js "<task description>"
452
+
453
+ Examples:
454
+ node flow-complexity.js "Add a console.log to the login function"
455
+ node flow-complexity.js "Create a new UserProfile component with tests"
456
+ node flow-complexity.js "Implement role-based access control for all API endpoints"
457
+ `);
458
+ process.exit(0);
459
+ }
460
+
461
+ const taskDescription = args.join(' ');
462
+ const result = assessTaskComplexity(taskDescription);
463
+
464
+ console.log('\n═══════════════════════════════════════════════════════════');
465
+ console.log(' TASK COMPLEXITY ASSESSMENT (No Limits!)');
466
+ console.log('═══════════════════════════════════════════════════════════\n');
467
+
468
+ console.log(`Task: "${taskDescription}"\n`);
469
+
470
+ console.log(`Level: ${result.level.toUpperCase()}`);
471
+ console.log(`Minimum Tokens for 90%+ Success: ${result.estimatedTokens.toLocaleString()}`);
472
+ console.log(`Context Needs: ${result.contextNeeds}`);
473
+ console.log(`\nReasoning: ${result.reasoning}`);
474
+
475
+ console.log('\n───────────────────────────────────────────────────────────');
476
+ console.log(' FACTORS');
477
+ console.log('───────────────────────────────────────────────────────────\n');
478
+
479
+ console.log(`Files to create: ${result.factors.filesToCreate}`);
480
+ console.log(`Files to modify: ${result.factors.filesToModify}`);
481
+ console.log(`Has tests: ${result.factors.hasTests}`);
482
+ console.log(`Has dependencies: ${result.factors.hasDependencies}`);
483
+ console.log(`Acceptance criteria: ${result.factors.acceptanceCriteriaCount}`);
484
+
485
+ if (result.factors.complexityKeywords.length > 0) {
486
+ console.log(`Complexity keywords: ${result.factors.complexityKeywords.join(', ')}`);
487
+ }
488
+
489
+ if (result.factors.simplicityKeywords.length > 0) {
490
+ console.log(`Simplicity keywords: ${result.factors.simplicityKeywords.join(', ')}`);
491
+ }
492
+
493
+ console.log('\n───────────────────────────────────────────────────────────');
494
+ console.log(' SCORE BREAKDOWN');
495
+ console.log('───────────────────────────────────────────────────────────\n');
496
+
497
+ for (const item of result.scoreBreakdown) {
498
+ console.log(` ${item}`);
499
+ }
500
+
501
+ console.log('\n═══════════════════════════════════════════════════════════\n');
502
+ }
@@ -0,0 +1,152 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Config Set
5
+ *
6
+ * Sets a configuration value in config.json with proper cache invalidation.
7
+ * Replaces jq calls in the main flow script to ensure cache consistency.
8
+ *
9
+ * Usage: node flow-config-set.js <key.path> <value>
10
+ *
11
+ * Examples:
12
+ * node flow-config-set.js parallel.enabled true
13
+ * node flow-config-set.js hybrid.model "qwen3"
14
+ * node flow-config-set.js worktree.autoCleanupHours 48
15
+ */
16
+
17
+ const {
18
+ PATHS,
19
+ readJson,
20
+ withLock,
21
+ invalidateConfigCache,
22
+ color,
23
+ success,
24
+ error
25
+ } = require('./flow-utils');
26
+
27
+ /**
28
+ * Parse a value string to the appropriate type
29
+ */
30
+ function parseValue(valueStr) {
31
+ // Boolean
32
+ if (valueStr === 'true') return true;
33
+ if (valueStr === 'false') return false;
34
+
35
+ // Null
36
+ if (valueStr === 'null') return null;
37
+
38
+ // Number
39
+ if (/^-?\d+(\.\d+)?$/.test(valueStr)) {
40
+ return parseFloat(valueStr);
41
+ }
42
+
43
+ // JSON object or array
44
+ if ((valueStr.startsWith('{') && valueStr.endsWith('}')) ||
45
+ (valueStr.startsWith('[') && valueStr.endsWith(']'))) {
46
+ try {
47
+ return JSON.parse(valueStr);
48
+ } catch {
49
+ // Not valid JSON, treat as string
50
+ }
51
+ }
52
+
53
+ // String (remove quotes if present)
54
+ if ((valueStr.startsWith('"') && valueStr.endsWith('"')) ||
55
+ (valueStr.startsWith("'") && valueStr.endsWith("'"))) {
56
+ return valueStr.slice(1, -1);
57
+ }
58
+
59
+ return valueStr;
60
+ }
61
+
62
+ /**
63
+ * Set a nested value in an object using dot notation
64
+ */
65
+ function setNestedValue(obj, path, value) {
66
+ const keys = path.split('.');
67
+ let current = obj;
68
+
69
+ for (let i = 0; i < keys.length - 1; i++) {
70
+ const key = keys[i];
71
+ if (!(key in current) || typeof current[key] !== 'object') {
72
+ current[key] = {};
73
+ }
74
+ current = current[key];
75
+ }
76
+
77
+ const lastKey = keys[keys.length - 1];
78
+ current[lastKey] = value;
79
+
80
+ return obj;
81
+ }
82
+
83
+ /**
84
+ * Get a nested value from an object using dot notation
85
+ */
86
+ function getNestedValue(obj, path) {
87
+ return path.split('.').reduce((o, k) => o?.[k], obj);
88
+ }
89
+
90
+ async function main() {
91
+ const keyPath = process.argv[2];
92
+ const valueStr = process.argv[3];
93
+
94
+ if (!keyPath) {
95
+ console.log('Usage: flow-config-set <key.path> [value]');
96
+ console.log('');
97
+ console.log('Examples:');
98
+ console.log(' flow-config-set parallel.enabled true');
99
+ console.log(' flow-config-set hybrid.model "qwen3"');
100
+ console.log(' flow-config-set worktree.autoCleanupHours 48');
101
+ console.log('');
102
+ console.log('If no value is provided, prints the current value.');
103
+ process.exit(1);
104
+ }
105
+
106
+ // Load config (read-only, no lock needed)
107
+ const config = readJson(PATHS.config, {});
108
+
109
+ // If no value provided, just print current value
110
+ if (valueStr === undefined) {
111
+ const currentValue = getNestedValue(config, keyPath);
112
+ if (currentValue === undefined) {
113
+ console.log(color('yellow', `${keyPath} is not set`));
114
+ } else {
115
+ console.log(JSON.stringify(currentValue, null, 2));
116
+ }
117
+ process.exit(0);
118
+ }
119
+
120
+ // Parse the value
121
+ const value = parseValue(valueStr);
122
+ const oldValue = getNestedValue(config, keyPath);
123
+
124
+ // Use file lock to prevent race conditions during write
125
+ try {
126
+ await withLock(PATHS.config, () => {
127
+ // Re-read config after acquiring lock (may have changed)
128
+ const freshConfig = readJson(PATHS.config, {});
129
+ setNestedValue(freshConfig, keyPath, value);
130
+
131
+ // Write config using fs directly (withLock handles the file)
132
+ const fs = require('fs');
133
+ fs.writeFileSync(PATHS.config, JSON.stringify(freshConfig, null, 2));
134
+ invalidateConfigCache();
135
+ });
136
+
137
+ // Output result
138
+ if (oldValue === undefined) {
139
+ success(`Set ${keyPath} = ${JSON.stringify(value)}`);
140
+ } else {
141
+ success(`Changed ${keyPath}: ${JSON.stringify(oldValue)} → ${JSON.stringify(value)}`);
142
+ }
143
+ } catch (lockError) {
144
+ error(`Failed to update config: ${lockError.message}`);
145
+ process.exit(1);
146
+ }
147
+ }
148
+
149
+ main().catch(err => {
150
+ error(`Unexpected error: ${err.message}`);
151
+ process.exit(1);
152
+ });