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,306 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Comment Analyzer Step
5
+ *
6
+ * Analyzes code comments for quality issues:
7
+ * - Stale or misleading comments
8
+ * - TODO/FIXME/HACK markers
9
+ * - JSDoc accuracy vs actual signatures
10
+ * - Commented-out code
11
+ * - Missing documentation for public APIs
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 comment analysis 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 flagTodo = stepConfig.flagTodo !== false;
32
+ const flagFixme = stepConfig.flagFixme !== false;
33
+ const checkJsdoc = stepConfig.checkJsdoc !== false;
34
+ const flagCommentedCode = stepConfig.flagCommentedCode !== false;
35
+ const flagStale = stepConfig.flagStale !== false;
36
+
37
+ // Filter to analyzable files
38
+ const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
39
+ const analyzableFiles = files.filter(f =>
40
+ analyzableExtensions.some(ext => f.endsWith(ext)) &&
41
+ !f.includes('.d.ts')
42
+ );
43
+
44
+ if (analyzableFiles.length === 0) {
45
+ return { passed: true, message: 'No files to analyze' };
46
+ }
47
+
48
+ const issues = [];
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 fileIssues = analyzeComments(content, file, {
57
+ flagTodo,
58
+ flagFixme,
59
+ checkJsdoc,
60
+ flagCommentedCode,
61
+ flagStale,
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: 'Comment analysis passed',
73
+ };
74
+ }
75
+
76
+ // Report issues
77
+ console.log(colors.yellow + '\n Comment Analysis Issues:' + colors.reset);
78
+
79
+ // Group by type
80
+ const grouped = {};
81
+ for (const issue of issues) {
82
+ if (!grouped[issue.type]) grouped[issue.type] = [];
83
+ grouped[issue.type].push(issue);
84
+ }
85
+
86
+ for (const [type, typeIssues] of Object.entries(grouped)) {
87
+ console.log(colors.cyan + ` ${type}:` + colors.reset);
88
+ for (const issue of typeIssues.slice(0, 5)) { // Limit to 5 per type
89
+ console.log(` ${issue.file}:${issue.line}`);
90
+ console.log(` ${issue.message}`);
91
+ }
92
+ if (typeIssues.length > 5) {
93
+ console.log(colors.dim + ` ... and ${typeIssues.length - 5} more` + colors.reset);
94
+ }
95
+ }
96
+
97
+ const highSeverity = issues.filter(i => i.severity === 'high');
98
+
99
+ return {
100
+ passed: highSeverity.length === 0,
101
+ message: `${issues.length} comment issue(s) found (${highSeverity.length} high severity)`,
102
+ details: issues,
103
+ };
104
+ }
105
+
106
+ /**
107
+ * Analyze code for comment quality issues
108
+ */
109
+ function analyzeComments(content, fileName, config) {
110
+ const issues = [];
111
+ const lines = content.split('\n');
112
+
113
+ // Track JSDoc blocks for accuracy checking
114
+ let inJsDoc = false;
115
+ let jsDocContent = [];
116
+ let jsDocStartLine = 0;
117
+
118
+ for (let i = 0; i < lines.length; i++) {
119
+ const line = lines[i];
120
+ const trimmed = line.trim();
121
+
122
+ // Track JSDoc blocks
123
+ if (trimmed.startsWith('/**')) {
124
+ inJsDoc = true;
125
+ jsDocContent = [trimmed];
126
+ jsDocStartLine = i + 1;
127
+ continue;
128
+ }
129
+
130
+ if (inJsDoc) {
131
+ jsDocContent.push(trimmed);
132
+ if (trimmed.includes('*/')) {
133
+ inJsDoc = false;
134
+ // Check JSDoc accuracy if the next line is a function
135
+ if (config.checkJsdoc && i + 1 < lines.length) {
136
+ const nextLine = lines[i + 1];
137
+ const jsDocIssues = checkJsDocAccuracy(jsDocContent, nextLine, jsDocStartLine, fileName);
138
+ issues.push(...jsDocIssues);
139
+ }
140
+ jsDocContent = [];
141
+ }
142
+ continue;
143
+ }
144
+
145
+ // Check for TODO/FIXME/HACK markers
146
+ if (config.flagTodo || config.flagFixme) {
147
+ const todoMatch = trimmed.match(/\b(TODO|FIXME|XXX|HACK|BUG|UNDONE)\b:?\s*(.*)/i);
148
+ if (todoMatch) {
149
+ const marker = todoMatch[1].toUpperCase();
150
+ const isTodo = marker === 'TODO';
151
+ const isFixme = ['FIXME', 'XXX', 'BUG', 'HACK', 'UNDONE'].includes(marker);
152
+
153
+ if ((isTodo && config.flagTodo) || (isFixme && config.flagFixme)) {
154
+ issues.push({
155
+ file: fileName,
156
+ line: i + 1,
157
+ type: marker,
158
+ severity: isFixme ? 'high' : 'medium',
159
+ message: todoMatch[2] || `Unresolved ${marker} marker`,
160
+ });
161
+ }
162
+ }
163
+ }
164
+
165
+ // Check for commented-out code
166
+ if (config.flagCommentedCode) {
167
+ // Single line comment that looks like code
168
+ if (trimmed.startsWith('//') && !trimmed.startsWith('///')) {
169
+ const commentContent = trimmed.substring(2).trim();
170
+ if (looksLikeCode(commentContent)) {
171
+ issues.push({
172
+ file: fileName,
173
+ line: i + 1,
174
+ type: 'Commented Code',
175
+ severity: 'medium',
176
+ message: 'Commented-out code should be removed',
177
+ });
178
+ }
179
+ }
180
+ }
181
+
182
+ // Check for potentially stale comments
183
+ if (config.flagStale) {
184
+ // Comment followed by code that contradicts it
185
+ if (trimmed.startsWith('//') && i + 1 < lines.length) {
186
+ const comment = trimmed.substring(2).trim().toLowerCase();
187
+ const nextLine = lines[i + 1].trim().toLowerCase();
188
+
189
+ const stalePatterns = [
190
+ { comment: /always returns?|never fails?/, code: /throw|error|null|undefined/ },
191
+ { comment: /deprecated|don't use|do not use/, code: /^\s*(?:export\s+)?(?:function|const|class)/ },
192
+ { comment: /temporary|temp\s|remove later/, code: /.+/ }, // Any code after "temporary"
193
+ ];
194
+
195
+ for (const pattern of stalePatterns) {
196
+ if (pattern.comment.test(comment) && pattern.code.test(nextLine)) {
197
+ issues.push({
198
+ file: fileName,
199
+ line: i + 1,
200
+ type: 'Potentially Stale Comment',
201
+ severity: 'medium',
202
+ message: 'Comment may not match the code below it',
203
+ });
204
+ break;
205
+ }
206
+ }
207
+ }
208
+ }
209
+ }
210
+
211
+ return issues;
212
+ }
213
+
214
+ /**
215
+ * Check if a comment looks like commented-out code
216
+ */
217
+ function looksLikeCode(text) {
218
+ // Skip very short comments
219
+ if (text.length < 10) return false;
220
+
221
+ // Skip obvious prose
222
+ if (/^[A-Z][a-z]+\s+[a-z]+/.test(text)) return false; // Starts like a sentence
223
+
224
+ // Code patterns
225
+ const codePatterns = [
226
+ /^\s*(?:const|let|var|function|class|if|for|while|return|import|export)\s/,
227
+ /^\s*\w+\s*[=:]\s*[^=]/, // Assignment
228
+ /^\s*\w+\s*\([^)]*\)\s*[;{]?$/, // Function call
229
+ /^\s*\}\s*(?:else|catch|finally)?\s*\{?\s*$/, // Braces
230
+ /^\s*(?:await|async)\s+/, // Async
231
+ /^\s*\w+\.\w+\(/, // Method call
232
+ /^\s*\/\*|\*\/\s*$/, // Multi-line comment markers
233
+ ];
234
+
235
+ return codePatterns.some(pattern => pattern.test(text));
236
+ }
237
+
238
+ /**
239
+ * Check JSDoc accuracy against function signature
240
+ */
241
+ function checkJsDocAccuracy(jsDocLines, functionLine, startLine, fileName) {
242
+ const issues = [];
243
+ const jsDoc = jsDocLines.join('\n');
244
+
245
+ // Extract @param and @returns from JSDoc
246
+ const jsDocParams = [];
247
+ const paramMatches = jsDoc.matchAll(/@param\s+(?:\{[^}]+\}\s+)?(\w+)/g);
248
+ for (const match of paramMatches) {
249
+ jsDocParams.push(match[1]);
250
+ }
251
+
252
+ const hasReturns = /@returns?/.test(jsDoc);
253
+
254
+ // Parse function signature
255
+ const funcMatch = functionLine.match(/(?:async\s+)?(?:function\s+)?(\w+)?\s*\(([^)]*)\)/);
256
+ if (!funcMatch) return issues;
257
+
258
+ const actualParams = funcMatch[2]
259
+ .split(',')
260
+ .map(p => p.trim().replace(/[:=].*/, '').replace(/^\.\.\./, '').trim())
261
+ .filter(p => p.length > 0);
262
+
263
+ // Check for missing params in JSDoc
264
+ for (const param of actualParams) {
265
+ if (!jsDocParams.includes(param)) {
266
+ issues.push({
267
+ file: fileName,
268
+ line: startLine,
269
+ type: 'Missing @param',
270
+ severity: 'medium',
271
+ message: `JSDoc missing @param for "${param}"`,
272
+ });
273
+ }
274
+ }
275
+
276
+ // Check for extra params in JSDoc
277
+ for (const param of jsDocParams) {
278
+ if (!actualParams.includes(param)) {
279
+ issues.push({
280
+ file: fileName,
281
+ line: startLine,
282
+ type: 'Extra @param',
283
+ severity: 'medium',
284
+ message: `JSDoc has @param "${param}" but function doesn't have this parameter`,
285
+ });
286
+ }
287
+ }
288
+
289
+ // Check for returns mismatch
290
+ const hasReturnStatement = /\breturn\s+[^;]/.test(functionLine) ||
291
+ (functionLine.includes('=>') && !functionLine.includes('=> {'));
292
+
293
+ if (hasReturnStatement && !hasReturns) {
294
+ issues.push({
295
+ file: fileName,
296
+ line: startLine,
297
+ type: 'Missing @returns',
298
+ severity: 'low',
299
+ message: 'Function returns a value but JSDoc has no @returns',
300
+ });
301
+ }
302
+
303
+ return issues;
304
+ }
305
+
306
+ module.exports = { run, analyzeComments };
@@ -0,0 +1,234 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Code Complexity Check Step
5
+ *
6
+ * Analyzes cyclomatic complexity of modified files.
7
+ * Flags functions that exceed the configured threshold.
8
+ */
9
+
10
+ const fs = require('fs');
11
+ const path = require('path');
12
+ const { getProjectRoot, colors } = require('./flow-utils');
13
+
14
+ const PROJECT_ROOT = getProjectRoot();
15
+
16
+ /**
17
+ * Run code complexity check step
18
+ *
19
+ * @param {object} options
20
+ * @param {string[]} options.files - Files modified
21
+ * @param {object} options.stepConfig - Step configuration
22
+ * @param {string} options.mode - Step mode (block/warn/prompt/auto)
23
+ * @returns {object} - { passed: boolean, message: string, details?: object[] }
24
+ */
25
+ async function run(options = {}) {
26
+ const { files = [], stepConfig = {}, mode } = options;
27
+ const threshold = stepConfig.threshold || 10;
28
+
29
+ // Filter to analyzable files
30
+ const analyzableExtensions = ['.js', '.ts', '.jsx', '.tsx'];
31
+ const analyzableFiles = files.filter(f =>
32
+ analyzableExtensions.some(ext => f.endsWith(ext)) &&
33
+ !f.includes('.test.') &&
34
+ !f.includes('.spec.') &&
35
+ !f.includes('.d.ts')
36
+ );
37
+
38
+ if (analyzableFiles.length === 0) {
39
+ return { passed: true, message: 'No analyzable files modified' };
40
+ }
41
+
42
+ const complexFunctions = [];
43
+
44
+ for (const file of analyzableFiles) {
45
+ const filePath = path.join(PROJECT_ROOT, file);
46
+ if (!fs.existsSync(filePath)) continue;
47
+
48
+ try {
49
+ const content = fs.readFileSync(filePath, 'utf8');
50
+ const functions = analyzeComplexity(content, file);
51
+
52
+ for (const func of functions) {
53
+ if (func.complexity > threshold) {
54
+ complexFunctions.push({
55
+ file,
56
+ function: func.name,
57
+ complexity: func.complexity,
58
+ line: func.line,
59
+ suggestion: getSuggestion(func.complexity, threshold),
60
+ });
61
+ }
62
+ }
63
+ } catch (err) {
64
+ // Skip files that can't be analyzed
65
+ }
66
+ }
67
+
68
+ if (complexFunctions.length === 0) {
69
+ return {
70
+ passed: true,
71
+ message: `All functions under complexity threshold (${threshold})`,
72
+ };
73
+ }
74
+
75
+ // Report complex functions
76
+ console.log(colors.yellow + `\n Functions exceeding complexity threshold (${threshold}):` + colors.reset);
77
+ for (const func of complexFunctions) {
78
+ console.log(` ${func.file}:${func.line} - ${func.function} (${func.complexity})`);
79
+ console.log(colors.gray + ` ${func.suggestion}` + colors.reset);
80
+ }
81
+
82
+ return {
83
+ passed: false,
84
+ message: `${complexFunctions.length} function(s) exceed complexity threshold`,
85
+ details: complexFunctions,
86
+ };
87
+ }
88
+
89
+ /**
90
+ * Analyze cyclomatic complexity of functions in code
91
+ *
92
+ * Uses a simplified heuristic approach:
93
+ * - Base complexity: 1
94
+ * - +1 for each: if, else if, for, while, case, catch, &&, ||, ?, ??
95
+ */
96
+ function analyzeComplexity(content, fileName) {
97
+ const functions = [];
98
+
99
+ // Match function declarations and expressions
100
+ const functionPatterns = [
101
+ // function name() {}
102
+ /function\s+(\w+)\s*\([^)]*\)\s*\{/g,
103
+ // const name = function() {}
104
+ /(?:const|let|var)\s+(\w+)\s*=\s*function\s*\([^)]*\)\s*\{/g,
105
+ // const name = () => {}
106
+ /(?:const|let|var)\s+(\w+)\s*=\s*(?:async\s*)?\([^)]*\)\s*=>/g,
107
+ // const name = async () => {}
108
+ /(?:const|let|var)\s+(\w+)\s*=\s*async\s+\([^)]*\)\s*=>/g,
109
+ // method() {} in class
110
+ /^\s*(?:async\s+)?(\w+)\s*\([^)]*\)\s*\{/gm,
111
+ ];
112
+
113
+ // Split content into lines for line number tracking
114
+ const lines = content.split('\n');
115
+
116
+ // Simple approach: find function boundaries and count complexity indicators
117
+ let currentFunction = null;
118
+ let braceCount = 0;
119
+ let functionStart = 0;
120
+ let functionContent = '';
121
+
122
+ for (let i = 0; i < lines.length; i++) {
123
+ const line = lines[i];
124
+
125
+ // Check for function start
126
+ if (!currentFunction) {
127
+ for (const pattern of functionPatterns) {
128
+ pattern.lastIndex = 0;
129
+ const match = pattern.exec(line);
130
+ if (match) {
131
+ currentFunction = match[1] || 'anonymous';
132
+ functionStart = i + 1;
133
+ braceCount = (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
134
+ functionContent = line;
135
+
136
+ // Arrow functions without braces
137
+ if (line.includes('=>') && !line.includes('{')) {
138
+ const complexity = calculateLineComplexity(line);
139
+ functions.push({
140
+ name: currentFunction,
141
+ line: functionStart,
142
+ complexity: 1 + complexity,
143
+ });
144
+ currentFunction = null;
145
+ }
146
+ break;
147
+ }
148
+ }
149
+ } else {
150
+ // Track braces
151
+ braceCount += (line.match(/\{/g) || []).length - (line.match(/\}/g) || []).length;
152
+ functionContent += '\n' + line;
153
+
154
+ // Function ended
155
+ if (braceCount <= 0) {
156
+ const complexity = calculateComplexity(functionContent);
157
+ functions.push({
158
+ name: currentFunction,
159
+ line: functionStart,
160
+ complexity,
161
+ });
162
+ currentFunction = null;
163
+ functionContent = '';
164
+ }
165
+ }
166
+ }
167
+
168
+ return functions;
169
+ }
170
+
171
+ /**
172
+ * Calculate cyclomatic complexity of a code block
173
+ */
174
+ function calculateComplexity(code) {
175
+ let complexity = 1; // Base complexity
176
+
177
+ // Decision points
178
+ const patterns = [
179
+ /\bif\s*\(/g, // if statements
180
+ /\belse\s+if\s*\(/g, // else if (already counted in if, subtract)
181
+ /\bfor\s*\(/g, // for loops
182
+ /\bwhile\s*\(/g, // while loops
183
+ /\bcase\s+[^:]+:/g, // switch cases
184
+ /\bcatch\s*\(/g, // catch blocks
185
+ /\?\s*[^:]+:/g, // ternary operators
186
+ /&&/g, // logical AND
187
+ /\|\|/g, // logical OR
188
+ /\?\?/g, // nullish coalescing
189
+ ];
190
+
191
+ for (const pattern of patterns) {
192
+ const matches = code.match(pattern);
193
+ if (matches) {
194
+ complexity += matches.length;
195
+ }
196
+ }
197
+
198
+ // Subtract double-counted else if
199
+ const elseIfMatches = code.match(/\belse\s+if\s*\(/g);
200
+ if (elseIfMatches) {
201
+ complexity -= elseIfMatches.length;
202
+ }
203
+
204
+ return complexity;
205
+ }
206
+
207
+ /**
208
+ * Calculate complexity for a single line (arrow functions)
209
+ */
210
+ function calculateLineComplexity(line) {
211
+ let complexity = 0;
212
+
213
+ if (line.includes('?') && line.includes(':')) complexity += 1;
214
+ complexity += (line.match(/&&/g) || []).length;
215
+ complexity += (line.match(/\|\|/g) || []).length;
216
+ complexity += (line.match(/\?\?/g) || []).length;
217
+
218
+ return complexity;
219
+ }
220
+
221
+ /**
222
+ * Get suggestion for reducing complexity
223
+ */
224
+ function getSuggestion(complexity, threshold) {
225
+ if (complexity > threshold * 2) {
226
+ return 'Consider breaking into multiple smaller functions';
227
+ }
228
+ if (complexity > threshold * 1.5) {
229
+ return 'Consider extracting some logic into helper functions';
230
+ }
231
+ return 'Consider simplifying conditional logic';
232
+ }
233
+
234
+ module.exports = { run, analyzeComplexity, calculateComplexity };