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,290 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Silent Failure Hunter Step
5
+ *
6
+ * Detects code patterns that silently swallow errors or failures:
7
+ * - Empty catch blocks
8
+ * - Catch blocks that only log
9
+ * - Async functions without error handling
10
+ * - Promise chains without .catch()
11
+ * - try-finally without catch
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { getProjectRoot, colors } = require('./flow-utils');
17
+
18
+ const PROJECT_ROOT = getProjectRoot();
19
+
20
+ /**
21
+ * Run silent failure detection as a workflow step
22
+ *
23
+ * @param {object} options
24
+ * @param {string[]} options.files - Files modified
25
+ * @param {object} options.stepConfig - Step configuration
26
+ * @param {string} options.mode - Step mode (block/warn/prompt/auto)
27
+ * @returns {object} - { passed: boolean, message: string, details?: object[] }
28
+ */
29
+ async function run(options = {}) {
30
+ const { files = [], stepConfig = {} } = options;
31
+ const checkEmptyCatch = stepConfig.checkEmptyCatch !== false;
32
+ const checkLogOnlyCatch = stepConfig.checkLogOnlyCatch !== false;
33
+ const checkUnhandledAsync = stepConfig.checkUnhandledAsync !== false;
34
+ const checkPromiseChains = stepConfig.checkPromiseChains !== false;
35
+
36
+ // Filter to analyzable files
37
+ const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
38
+ const analyzableFiles = files.filter(f =>
39
+ analyzableExtensions.some(ext => f.endsWith(ext)) &&
40
+ !f.includes('.test.') &&
41
+ !f.includes('.spec.') &&
42
+ !f.includes('.d.ts')
43
+ );
44
+
45
+ if (analyzableFiles.length === 0) {
46
+ return { passed: true, message: 'No files to analyze' };
47
+ }
48
+
49
+ const issues = [];
50
+
51
+ for (const file of analyzableFiles) {
52
+ const filePath = path.join(PROJECT_ROOT, file);
53
+ if (!fs.existsSync(filePath)) continue;
54
+
55
+ try {
56
+ const content = fs.readFileSync(filePath, 'utf8');
57
+ const fileIssues = analyzeForSilentFailures(content, file, {
58
+ checkEmptyCatch,
59
+ checkLogOnlyCatch,
60
+ checkUnhandledAsync,
61
+ checkPromiseChains,
62
+ });
63
+ issues.push(...fileIssues);
64
+ } catch (err) {
65
+ // Skip unreadable files
66
+ }
67
+ }
68
+
69
+ if (issues.length === 0) {
70
+ return {
71
+ passed: true,
72
+ message: 'No silent failure patterns detected',
73
+ };
74
+ }
75
+
76
+ // Report issues
77
+ console.log(colors.yellow + '\n Silent Failure Patterns Detected:' + colors.reset);
78
+ for (const issue of issues) {
79
+ const icon = issue.severity === 'high' ? '\u{1F534}' : '\u{1F7E1}';
80
+ console.log(` ${icon} ${issue.file}:${issue.line}`);
81
+ console.log(` ${issue.type}: ${issue.message}`);
82
+ if (issue.suggestion) {
83
+ console.log(colors.dim + ` \u{2192} ${issue.suggestion}` + colors.reset);
84
+ }
85
+ }
86
+
87
+ const highSeverity = issues.filter(i => i.severity === 'high');
88
+
89
+ return {
90
+ passed: highSeverity.length === 0,
91
+ message: `${issues.length} silent failure pattern(s) found (${highSeverity.length} high severity)`,
92
+ details: issues,
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Analyze code for silent failure patterns
98
+ */
99
+ function analyzeForSilentFailures(content, fileName, config) {
100
+ const issues = [];
101
+ const lines = content.split('\n');
102
+
103
+ // Track context for multi-line analysis
104
+ let inTryBlock = false;
105
+ let inCatchBlock = false;
106
+ let catchStartLine = 0;
107
+ let catchContent = [];
108
+ let braceDepth = 0;
109
+
110
+ for (let i = 0; i < lines.length; i++) {
111
+ const line = lines[i];
112
+ const trimmed = line.trim();
113
+
114
+ // Detect try-catch structure
115
+ if (/\btry\s*\{/.test(line)) {
116
+ inTryBlock = true;
117
+ }
118
+
119
+ if (inTryBlock && /\bcatch\s*\([^)]*\)\s*\{/.test(line)) {
120
+ inCatchBlock = true;
121
+ catchStartLine = i + 1;
122
+ catchContent = [];
123
+ braceDepth = 1;
124
+ continue;
125
+ }
126
+
127
+ if (inCatchBlock) {
128
+ catchContent.push(trimmed);
129
+ braceDepth += (line.match(/\{/g) || []).length;
130
+ braceDepth -= (line.match(/\}/g) || []).length;
131
+
132
+ if (braceDepth <= 0) {
133
+ // Analyze the complete catch block
134
+ const catchIssues = analyzeCatchBlock(catchContent, catchStartLine, fileName, config);
135
+ issues.push(...catchIssues);
136
+
137
+ inCatchBlock = false;
138
+ inTryBlock = false;
139
+ catchContent = [];
140
+ }
141
+ continue;
142
+ }
143
+
144
+ // Check for try-finally without catch (config.checkUnhandledAsync)
145
+ if (config.checkUnhandledAsync && /\btry\s*\{/.test(line)) {
146
+ // Look ahead for finally without catch
147
+ const tryContext = content.substring(content.indexOf(line));
148
+ if (/try\s*\{[^}]*\}\s*finally/.test(tryContext) && !/catch\s*\(/.test(tryContext.substring(0, tryContext.indexOf('finally')))) {
149
+ issues.push({
150
+ file: fileName,
151
+ line: i + 1,
152
+ type: 'Try-Finally Without Catch',
153
+ severity: 'medium',
154
+ message: 'try-finally without catch will not handle errors',
155
+ suggestion: 'Add a catch block or let errors propagate intentionally',
156
+ });
157
+ }
158
+ }
159
+
160
+ // Check for unhandled promise chains (config.checkPromiseChains)
161
+ if (config.checkPromiseChains) {
162
+ // Look for .then() without .catch()
163
+ if (/\.then\s*\(/.test(line) && !line.includes('.catch(') && !line.includes('await')) {
164
+ // Check if .catch() is on the next few lines
165
+ const nextLines = lines.slice(i, i + 5).join(' ');
166
+ if (!nextLines.includes('.catch(') && !nextLines.includes('.finally(')) {
167
+ issues.push({
168
+ file: fileName,
169
+ line: i + 1,
170
+ type: 'Unhandled Promise Chain',
171
+ severity: 'medium',
172
+ message: 'Promise chain without .catch() handler',
173
+ suggestion: 'Add .catch() handler or use async/await with try-catch',
174
+ });
175
+ }
176
+ }
177
+ }
178
+
179
+ // Check for async functions without try-catch (config.checkUnhandledAsync)
180
+ if (config.checkUnhandledAsync) {
181
+ const asyncMatch = line.match(/async\s+(?:function\s+)?(\w+)?/);
182
+ if (asyncMatch && !line.includes('test') && !line.includes('spec')) {
183
+ // Find the function body
184
+ const funcStart = i;
185
+ let funcBraceDepth = 0;
186
+ let funcStarted = false;
187
+ let funcContent = '';
188
+
189
+ for (let j = i; j < Math.min(i + 100, lines.length); j++) {
190
+ funcContent += lines[j] + '\n';
191
+ funcBraceDepth += (lines[j].match(/\{/g) || []).length;
192
+ funcBraceDepth -= (lines[j].match(/\}/g) || []).length;
193
+
194
+ if (lines[j].includes('{')) funcStarted = true;
195
+ if (funcStarted && funcBraceDepth === 0) break;
196
+ }
197
+
198
+ // Check if function has await but no try-catch
199
+ if (/\bawait\b/.test(funcContent) && !/\btry\s*\{/.test(funcContent)) {
200
+ const funcName = asyncMatch[1] || 'anonymous';
201
+ // Don't flag if it's a short function (likely a wrapper)
202
+ if (funcContent.split('\n').length > 5) {
203
+ issues.push({
204
+ file: fileName,
205
+ line: funcStart + 1,
206
+ type: 'Unhandled Async',
207
+ severity: 'medium',
208
+ message: `Async function "${funcName}" uses await without try-catch`,
209
+ suggestion: 'Wrap await calls in try-catch or document that errors propagate',
210
+ });
211
+ }
212
+ }
213
+ }
214
+ }
215
+ }
216
+
217
+ return issues;
218
+ }
219
+
220
+ /**
221
+ * Analyze a catch block for silent failure patterns
222
+ */
223
+ function analyzeCatchBlock(catchLines, startLine, fileName, config) {
224
+ const issues = [];
225
+ const catchBody = catchLines.join('\n').trim();
226
+
227
+ // Remove the closing brace if present
228
+ const cleanBody = catchBody.replace(/\}\s*$/, '').trim();
229
+
230
+ // Check for empty catch block
231
+ if (config.checkEmptyCatch && cleanBody.length === 0) {
232
+ issues.push({
233
+ file: fileName,
234
+ line: startLine,
235
+ type: 'Empty Catch Block',
236
+ severity: 'high',
237
+ message: 'Empty catch block silently swallows all errors',
238
+ suggestion: 'Log the error, rethrow it, or handle it appropriately',
239
+ });
240
+ return issues;
241
+ }
242
+
243
+ // Check for catch blocks that only log
244
+ if (config.checkLogOnlyCatch) {
245
+ const onlyLogging = /^(?:console\.(?:log|error|warn)|logger\.(?:log|error|warn|info))\s*\(/i.test(cleanBody);
246
+ const hasOtherStatements = cleanBody.split(';').filter(s => {
247
+ const t = s.trim();
248
+ return t.length > 0 && !/^console\.|^logger\.|^\/\//.test(t);
249
+ }).length > 0;
250
+
251
+ if (onlyLogging && !hasOtherStatements) {
252
+ issues.push({
253
+ file: fileName,
254
+ line: startLine,
255
+ type: 'Log-Only Catch',
256
+ severity: 'medium',
257
+ message: 'Catch block only logs error without handling it',
258
+ suggestion: 'Consider if error should be rethrown or if recovery logic is needed',
259
+ });
260
+ }
261
+ }
262
+
263
+ // Check for catch blocks that swallow specific errors
264
+ if (/return\s*(?:null|undefined|false|''|""|``)/.test(cleanBody)) {
265
+ issues.push({
266
+ file: fileName,
267
+ line: startLine,
268
+ type: 'Error Suppression',
269
+ severity: 'medium',
270
+ message: 'Catch block returns falsy value, hiding error from caller',
271
+ suggestion: 'Document this behavior or throw a typed error',
272
+ });
273
+ }
274
+
275
+ // Check for catch block with only a comment
276
+ if (/^\/[/*]/.test(cleanBody) && cleanBody.split('\n').every(l => l.trim().startsWith('//') || l.trim().startsWith('*') || l.trim() === '')) {
277
+ issues.push({
278
+ file: fileName,
279
+ line: startLine,
280
+ type: 'Comment-Only Catch',
281
+ severity: 'high',
282
+ message: 'Catch block contains only comments - errors are silently ignored',
283
+ suggestion: 'Add proper error handling or explicit ignore marker',
284
+ });
285
+ }
286
+
287
+ return issues;
288
+ }
289
+
290
+ module.exports = { run, analyzeForSilentFailures };
@@ -0,0 +1,346 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Code Simplifier Step
5
+ *
6
+ * AI-powered code simplification suggestions.
7
+ * Analyzes: nested conditionals, long functions, deep coupling.
8
+ * Provides specific refactoring suggestions with file:line refs.
9
+ *
10
+ * This is a QUALITATIVE analysis (vs codeComplexityCheck which is QUANTITATIVE).
11
+ * Both can be enabled together for comprehensive analysis.
12
+ */
13
+
14
+ const fs = require('fs');
15
+ const path = require('path');
16
+ const { getProjectRoot, colors } = require('./flow-utils');
17
+
18
+ const PROJECT_ROOT = getProjectRoot();
19
+
20
+ /**
21
+ * Run code simplifier analysis step
22
+ *
23
+ * @param {object} options
24
+ * @param {string[]} options.files - Files modified
25
+ * @param {object} options.stepConfig - Step configuration
26
+ * @param {string} options.mode - Step mode (block/warn/prompt/auto)
27
+ * @returns {object} - { passed: boolean, message: string, details?: object[] }
28
+ */
29
+ async function run(options = {}) {
30
+ const { files = [], stepConfig = {} } = options;
31
+ const maxFunctionLines = stepConfig.maxFunctionLines || 50;
32
+ const maxNestingDepth = stepConfig.maxNestingDepth || 3;
33
+ const suggestExtraction = stepConfig.suggestExtraction !== false;
34
+
35
+ // Filter to analyzable files
36
+ const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
37
+ const analyzableFiles = files.filter(f =>
38
+ analyzableExtensions.some(ext => f.endsWith(ext)) &&
39
+ !f.includes('.test.') &&
40
+ !f.includes('.spec.') &&
41
+ !f.includes('.d.ts')
42
+ );
43
+
44
+ if (analyzableFiles.length === 0) {
45
+ return { passed: true, message: 'No analyzable files modified' };
46
+ }
47
+
48
+ const suggestions = [];
49
+
50
+ for (const file of analyzableFiles) {
51
+ const filePath = path.join(PROJECT_ROOT, file);
52
+ if (!fs.existsSync(filePath)) continue;
53
+
54
+ try {
55
+ const content = fs.readFileSync(filePath, 'utf8');
56
+ const fileSuggestions = analyzeForSimplification(content, file, {
57
+ maxFunctionLines,
58
+ maxNestingDepth,
59
+ suggestExtraction,
60
+ });
61
+ suggestions.push(...fileSuggestions);
62
+ } catch (err) {
63
+ // Skip files that can't be analyzed
64
+ }
65
+ }
66
+
67
+ if (suggestions.length === 0) {
68
+ return {
69
+ passed: true,
70
+ message: 'No simplification suggestions',
71
+ };
72
+ }
73
+
74
+ // Report suggestions
75
+ console.log(colors.cyan + '\n Code Simplification Suggestions:' + colors.reset);
76
+ for (const suggestion of suggestions) {
77
+ const icon = suggestion.severity === 'high' ? '\u{1F534}' : '\u{1F7E1}';
78
+ console.log(` ${icon} ${suggestion.file}:${suggestion.line}`);
79
+ console.log(` ${suggestion.type}: ${suggestion.message}`);
80
+ if (suggestion.suggestion) {
81
+ console.log(colors.dim + ` \u{2192} ${suggestion.suggestion}` + colors.reset);
82
+ }
83
+ }
84
+
85
+ // Mode determines if this blocks
86
+ const highSeverity = suggestions.filter(s => s.severity === 'high');
87
+
88
+ return {
89
+ passed: highSeverity.length === 0,
90
+ message: `${suggestions.length} simplification suggestion(s) (${highSeverity.length} high severity)`,
91
+ details: suggestions,
92
+ };
93
+ }
94
+
95
+ /**
96
+ * Analyze code for simplification opportunities
97
+ */
98
+ function analyzeForSimplification(content, fileName, config) {
99
+ const suggestions = [];
100
+ const lines = content.split('\n');
101
+
102
+ // Track function boundaries and analyze each
103
+ let inFunction = false;
104
+ let functionName = '';
105
+ let functionStart = 0;
106
+ let functionLines = [];
107
+ let braceCount = 0;
108
+
109
+ for (let i = 0; i < lines.length; i++) {
110
+ const line = lines[i];
111
+
112
+ // Detect function start
113
+ const funcMatch = line.match(/(?:function\s+(\w+)|(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\(?.*?\)?\s*=>|(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{)/);
114
+ if (funcMatch && !inFunction) {
115
+ inFunction = true;
116
+ functionName = funcMatch[1] || funcMatch[2] || funcMatch[3] || 'anonymous';
117
+ functionStart = i + 1;
118
+ functionLines = [line];
119
+ braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
120
+
121
+ // Arrow function without braces
122
+ if (line.includes('=>') && !line.includes('{')) {
123
+ inFunction = false;
124
+ continue;
125
+ }
126
+ continue;
127
+ }
128
+
129
+ if (inFunction) {
130
+ functionLines.push(line);
131
+ braceCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
132
+
133
+ if (braceCount <= 0) {
134
+ // Analyze the complete function
135
+ const funcSuggestions = analyzeFunctionForSimplification(
136
+ functionLines,
137
+ functionName,
138
+ functionStart,
139
+ fileName,
140
+ config
141
+ );
142
+ suggestions.push(...funcSuggestions);
143
+ inFunction = false;
144
+ functionLines = [];
145
+ }
146
+ }
147
+
148
+ // Check for deeply nested structures anywhere
149
+ const nestingDepth = countNestingDepth(line);
150
+ if (nestingDepth > config.maxNestingDepth) {
151
+ suggestions.push({
152
+ file: fileName,
153
+ line: i + 1,
154
+ type: 'Deep Nesting',
155
+ severity: nestingDepth > config.maxNestingDepth + 2 ? 'high' : 'medium',
156
+ message: `Nesting depth ${nestingDepth} exceeds threshold ${config.maxNestingDepth}`,
157
+ suggestion: 'Consider early returns, guard clauses, or extracting to helper functions',
158
+ });
159
+ }
160
+ }
161
+
162
+ // Check for code duplication patterns
163
+ const duplicationSuggestions = findDuplicationPatterns(content, fileName);
164
+ suggestions.push(...duplicationSuggestions);
165
+
166
+ return suggestions;
167
+ }
168
+
169
+ /**
170
+ * Analyze a single function for simplification
171
+ */
172
+ function analyzeFunctionForSimplification(lines, funcName, startLine, fileName, config) {
173
+ const suggestions = [];
174
+ const content = lines.join('\n');
175
+
176
+ // Check function length
177
+ if (lines.length > config.maxFunctionLines) {
178
+ suggestions.push({
179
+ file: fileName,
180
+ line: startLine,
181
+ type: 'Long Function',
182
+ severity: lines.length > config.maxFunctionLines * 2 ? 'high' : 'medium',
183
+ message: `Function "${funcName}" is ${lines.length} lines (max ${config.maxFunctionLines})`,
184
+ suggestion: config.suggestExtraction
185
+ ? 'Consider extracting logical sections into separate functions'
186
+ : 'Consider breaking into smaller functions',
187
+ });
188
+ }
189
+
190
+ // Check for multiple responsibilities (many different operations)
191
+ const operations = countOperationTypes(content);
192
+ if (operations.types > 4) {
193
+ suggestions.push({
194
+ file: fileName,
195
+ line: startLine,
196
+ type: 'Multiple Responsibilities',
197
+ severity: 'medium',
198
+ message: `Function "${funcName}" appears to have ${operations.types} different operation types`,
199
+ suggestion: 'Consider Single Responsibility Principle - one function, one purpose',
200
+ });
201
+ }
202
+
203
+ // Check for nested callbacks (callback hell)
204
+ const callbackDepth = countCallbackDepth(content);
205
+ if (callbackDepth > 2) {
206
+ suggestions.push({
207
+ file: fileName,
208
+ line: startLine,
209
+ type: 'Callback Nesting',
210
+ severity: callbackDepth > 4 ? 'high' : 'medium',
211
+ message: `Function "${funcName}" has ${callbackDepth} levels of callback nesting`,
212
+ suggestion: 'Consider using async/await, Promise.all, or extracting callbacks',
213
+ });
214
+ }
215
+
216
+ // Check for complex conditionals
217
+ const complexConditions = findComplexConditions(content);
218
+ for (const condition of complexConditions) {
219
+ suggestions.push({
220
+ file: fileName,
221
+ line: startLine + condition.lineOffset,
222
+ type: 'Complex Condition',
223
+ severity: condition.complexity > 4 ? 'high' : 'medium',
224
+ message: `Complex conditional with ${condition.complexity} clauses`,
225
+ suggestion: 'Consider extracting to a named boolean variable or function',
226
+ });
227
+ }
228
+
229
+ return suggestions;
230
+ }
231
+
232
+ /**
233
+ * Count nesting depth at a line
234
+ */
235
+ function countNestingDepth(line) {
236
+ const leading = line.match(/^(\s*)/)?.[1] || '';
237
+ // Approximate: 2 spaces or 1 tab per level
238
+ const spaces = leading.replace(/\t/g, ' ').length;
239
+ return Math.floor(spaces / 2);
240
+ }
241
+
242
+ /**
243
+ * Count different operation types in code
244
+ */
245
+ function countOperationTypes(content) {
246
+ const types = new Set();
247
+
248
+ if (/fetch|axios|http/i.test(content)) types.add('api');
249
+ if (/querySelector|getElementById|document\./i.test(content)) types.add('dom');
250
+ if (/localStorage|sessionStorage|cookie/i.test(content)) types.add('storage');
251
+ if (/console\.|log\(/i.test(content)) types.add('logging');
252
+ if (/fs\.|readFile|writeFile/i.test(content)) types.add('filesystem');
253
+ if (/\.map\(|\.filter\(|\.reduce\(/i.test(content)) types.add('transformation');
254
+ if (/throw\s+|new Error|reject\(/i.test(content)) types.add('error-handling');
255
+ if (/setState|useState|dispatch/i.test(content)) types.add('state');
256
+
257
+ return { types: types.size, list: Array.from(types) };
258
+ }
259
+
260
+ /**
261
+ * Count callback nesting depth
262
+ */
263
+ function countCallbackDepth(content) {
264
+ let maxDepth = 0;
265
+ let currentDepth = 0;
266
+
267
+ // Count by looking for callback patterns
268
+ const lines = content.split('\n');
269
+ for (const line of lines) {
270
+ if (/\(\s*(?:function|\([^)]*\)\s*=>|async\s*\()/.test(line)) {
271
+ currentDepth++;
272
+ maxDepth = Math.max(maxDepth, currentDepth);
273
+ }
274
+ if (/\}\s*\)/.test(line)) {
275
+ currentDepth = Math.max(0, currentDepth - 1);
276
+ }
277
+ }
278
+
279
+ return maxDepth;
280
+ }
281
+
282
+ /**
283
+ * Find complex conditional expressions
284
+ */
285
+ function findComplexConditions(content) {
286
+ const conditions = [];
287
+ const lines = content.split('\n');
288
+
289
+ for (let i = 0; i < lines.length; i++) {
290
+ const line = lines[i];
291
+ if (/\bif\s*\(/.test(line) || /\?\s*[^:]+:/.test(line)) {
292
+ const andOr = (line.match(/&&|\|\|/g) || []).length;
293
+ const ternary = (line.match(/\?[^:]*:/g) || []).length;
294
+ const complexity = andOr + ternary;
295
+
296
+ if (complexity >= 3) {
297
+ conditions.push({
298
+ lineOffset: i,
299
+ complexity,
300
+ });
301
+ }
302
+ }
303
+ }
304
+
305
+ return conditions;
306
+ }
307
+
308
+ /**
309
+ * Find potential code duplication patterns
310
+ */
311
+ function findDuplicationPatterns(content, fileName) {
312
+ const suggestions = [];
313
+ const lines = content.split('\n');
314
+
315
+ // Look for repeated patterns (simplified)
316
+ const patterns = {};
317
+ for (let i = 0; i < lines.length; i++) {
318
+ const line = lines[i].trim();
319
+ if (line.length > 20 && !line.startsWith('//') && !line.startsWith('*')) {
320
+ // Normalize the line for comparison
321
+ const normalized = line.replace(/\s+/g, ' ').replace(/['"`][^'"`]*['"`]/g, '""');
322
+ if (!patterns[normalized]) {
323
+ patterns[normalized] = [];
324
+ }
325
+ patterns[normalized].push(i + 1);
326
+ }
327
+ }
328
+
329
+ // Report lines that appear 3+ times
330
+ for (const [pattern, lineNumbers] of Object.entries(patterns)) {
331
+ if (lineNumbers.length >= 3) {
332
+ suggestions.push({
333
+ file: fileName,
334
+ line: lineNumbers[0],
335
+ type: 'Code Duplication',
336
+ severity: lineNumbers.length >= 5 ? 'high' : 'medium',
337
+ message: `Similar code appears ${lineNumbers.length} times (lines: ${lineNumbers.slice(0, 5).join(', ')}...)`,
338
+ suggestion: 'Consider extracting to a reusable function or constant',
339
+ });
340
+ }
341
+ }
342
+
343
+ return suggestions;
344
+ }
345
+
346
+ module.exports = { run, analyzeForSimplification };