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,364 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - PR Test Analyzer Step
5
+ *
6
+ * Analyzes test coverage and quality for modified files.
7
+ * Checks: coverage for modified files, test quality (mocks, edge cases),
8
+ * and ensures tests exist for new functionality.
9
+ */
10
+
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { getProjectRoot, colors } = require('./flow-utils');
14
+
15
+ const PROJECT_ROOT = getProjectRoot();
16
+
17
+ /**
18
+ * Run PR test analysis as a workflow step
19
+ *
20
+ * @param {object} options
21
+ * @param {string[]} options.files - Files modified
22
+ * @param {object} options.stepConfig - Step configuration
23
+ * @param {string} options.mode - Step mode (block/warn/prompt/auto)
24
+ * @returns {object} - { passed: boolean, message: string, details?: object }
25
+ */
26
+ async function run(options = {}) {
27
+ const { files = [], stepConfig = {} } = options;
28
+ const checkCoverage = stepConfig.checkCoverage !== false;
29
+ const checkQuality = stepConfig.checkQuality !== false;
30
+ const minCoverageForModified = stepConfig.minCoverageForModified || 70;
31
+
32
+ // Filter to source files (not test files)
33
+ const sourceExtensions = ['.js', '.ts', '.jsx', '.tsx'];
34
+ const sourceFiles = files.filter(f =>
35
+ sourceExtensions.some(ext => f.endsWith(ext)) &&
36
+ !f.includes('.test.') &&
37
+ !f.includes('.spec.') &&
38
+ !f.includes('.d.ts') &&
39
+ !f.includes('__tests__') &&
40
+ !f.includes('__mocks__')
41
+ );
42
+
43
+ if (sourceFiles.length === 0) {
44
+ return { passed: true, message: 'No source files to analyze' };
45
+ }
46
+
47
+ const issues = [];
48
+ const coverageReport = {};
49
+ const qualityReport = {};
50
+
51
+ // Check coverage for modified files
52
+ if (checkCoverage) {
53
+ const coverageIssues = await analyzeCoverage(sourceFiles, minCoverageForModified);
54
+ issues.push(...coverageIssues.issues);
55
+ Object.assign(coverageReport, coverageIssues.report);
56
+ }
57
+
58
+ // Check test quality
59
+ if (checkQuality) {
60
+ const qualityIssues = await analyzeTestQuality(sourceFiles, files);
61
+ issues.push(...qualityIssues.issues);
62
+ Object.assign(qualityReport, qualityIssues.report);
63
+ }
64
+
65
+ // Report findings
66
+ if (issues.length > 0) {
67
+ console.log(colors.yellow + '\n PR Test Analysis Issues:' + colors.reset);
68
+ for (const issue of issues) {
69
+ const icon = issue.severity === 'high' ? '\u{1F534}' : '\u{1F7E1}';
70
+ console.log(` ${icon} ${issue.file}`);
71
+ console.log(` ${issue.type}: ${issue.message}`);
72
+ if (issue.suggestion) {
73
+ console.log(colors.dim + ` \u{2192} ${issue.suggestion}` + colors.reset);
74
+ }
75
+ }
76
+ }
77
+
78
+ const highSeverity = issues.filter(i => i.severity === 'high');
79
+
80
+ return {
81
+ passed: highSeverity.length === 0,
82
+ message: issues.length === 0
83
+ ? `Test analysis passed (${sourceFiles.length} files)`
84
+ : `${issues.length} issue(s) found (${highSeverity.length} high severity)`,
85
+ details: {
86
+ issues,
87
+ coverage: coverageReport,
88
+ quality: qualityReport,
89
+ },
90
+ };
91
+ }
92
+
93
+ /**
94
+ * Analyze test coverage for modified files
95
+ */
96
+ async function analyzeCoverage(sourceFiles, minCoverage) {
97
+ const issues = [];
98
+ const report = { checked: [], missing: [], below: [] };
99
+
100
+ // Check if coverage report exists
101
+ const coveragePath = path.join(PROJECT_ROOT, 'coverage', 'coverage-summary.json');
102
+ let coverageData = null;
103
+
104
+ if (fs.existsSync(coveragePath)) {
105
+ try {
106
+ coverageData = JSON.parse(fs.readFileSync(coveragePath, 'utf8'));
107
+ } catch (err) {
108
+ // Can't read coverage
109
+ }
110
+ }
111
+
112
+ for (const file of sourceFiles) {
113
+ const filePath = path.join(PROJECT_ROOT, file);
114
+ if (!fs.existsSync(filePath)) continue;
115
+
116
+ // Find corresponding test file
117
+ const testPatterns = [
118
+ file.replace(/\.(js|ts|jsx|tsx)$/, '.test.$1'),
119
+ file.replace(/\.(js|ts|jsx|tsx)$/, '.spec.$1'),
120
+ file.replace(/src\//, 'src/__tests__/').replace(/\.(js|ts|jsx|tsx)$/, '.test.$1'),
121
+ file.replace(/src\//, 'tests/').replace(/\.(js|ts|jsx|tsx)$/, '.test.$1'),
122
+ ];
123
+
124
+ const hasTest = testPatterns.some(pattern => {
125
+ const testPath = path.join(PROJECT_ROOT, pattern);
126
+ return fs.existsSync(testPath);
127
+ });
128
+
129
+ if (!hasTest) {
130
+ // Check if file exports anything testable
131
+ const content = fs.readFileSync(filePath, 'utf8');
132
+ const hasExports = /export\s+(?:default\s+)?(?:function|class|const|let|var)|module\.exports/.test(content);
133
+
134
+ if (hasExports) {
135
+ issues.push({
136
+ file,
137
+ type: 'Missing Tests',
138
+ severity: 'high',
139
+ message: 'No test file found for source file with exports',
140
+ suggestion: `Create ${testPatterns[0]}`,
141
+ });
142
+ report.missing.push(file);
143
+ }
144
+ continue;
145
+ }
146
+
147
+ report.checked.push(file);
148
+
149
+ // Check coverage if available
150
+ if (coverageData) {
151
+ const absPath = path.resolve(PROJECT_ROOT, file);
152
+ const fileCoverage = coverageData[absPath];
153
+
154
+ if (fileCoverage) {
155
+ const lineCoverage = fileCoverage.lines?.pct || 0;
156
+ const branchCoverage = fileCoverage.branches?.pct || 0;
157
+ const avgCoverage = (lineCoverage + branchCoverage) / 2;
158
+
159
+ if (avgCoverage < minCoverage) {
160
+ issues.push({
161
+ file,
162
+ type: 'Low Coverage',
163
+ severity: avgCoverage < minCoverage / 2 ? 'high' : 'medium',
164
+ message: `Coverage ${avgCoverage.toFixed(1)}% is below ${minCoverage}% threshold`,
165
+ suggestion: 'Add tests for uncovered lines and branches',
166
+ });
167
+ report.below.push({ file, coverage: avgCoverage });
168
+ }
169
+ }
170
+ }
171
+ }
172
+
173
+ return { issues, report };
174
+ }
175
+
176
+ /**
177
+ * Analyze test quality for modified files
178
+ */
179
+ async function analyzeTestQuality(sourceFiles, allFiles) {
180
+ const issues = [];
181
+ const report = { analyzed: [], concerns: [] };
182
+
183
+ // Find test files in the changeset
184
+ const testFiles = allFiles.filter(f =>
185
+ f.includes('.test.') || f.includes('.spec.') || f.includes('__tests__')
186
+ );
187
+
188
+ for (const testFile of testFiles) {
189
+ const testPath = path.join(PROJECT_ROOT, testFile);
190
+ if (!fs.existsSync(testPath)) continue;
191
+
192
+ try {
193
+ const content = fs.readFileSync(testPath, 'utf8');
194
+ report.analyzed.push(testFile);
195
+
196
+ // Check for test quality issues
197
+ const qualityChecks = analyzeTestFile(content, testFile);
198
+ issues.push(...qualityChecks);
199
+
200
+ if (qualityChecks.length > 0) {
201
+ report.concerns.push({ file: testFile, issues: qualityChecks.length });
202
+ }
203
+ } catch (err) {
204
+ // Skip unreadable files
205
+ }
206
+ }
207
+
208
+ // Also check source files for testability concerns
209
+ for (const sourceFile of sourceFiles) {
210
+ const sourcePath = path.join(PROJECT_ROOT, sourceFile);
211
+ if (!fs.existsSync(sourcePath)) continue;
212
+
213
+ try {
214
+ const content = fs.readFileSync(sourcePath, 'utf8');
215
+ const testabilityIssues = checkTestability(content, sourceFile);
216
+ issues.push(...testabilityIssues);
217
+ } catch (err) {
218
+ // Skip unreadable files
219
+ }
220
+ }
221
+
222
+ return { issues, report };
223
+ }
224
+
225
+ /**
226
+ * Analyze a test file for quality issues
227
+ */
228
+ function analyzeTestFile(content, fileName) {
229
+ const issues = [];
230
+
231
+ // Check for empty tests
232
+ const emptyTests = content.match(/(?:it|test)\s*\([^)]+,\s*(?:async\s*)?\(\s*\)\s*=>\s*\{\s*\}\s*\)/g);
233
+ if (emptyTests) {
234
+ issues.push({
235
+ file: fileName,
236
+ type: 'Empty Tests',
237
+ severity: 'high',
238
+ message: `${emptyTests.length} empty test(s) found`,
239
+ suggestion: 'Add assertions to empty tests or remove them',
240
+ });
241
+ }
242
+
243
+ // Check for skipped tests
244
+ const skippedTests = (content.match(/(?:it|test|describe)\.skip/g) || []).length;
245
+ if (skippedTests > 0) {
246
+ issues.push({
247
+ file: fileName,
248
+ type: 'Skipped Tests',
249
+ severity: 'medium',
250
+ message: `${skippedTests} skipped test(s)`,
251
+ suggestion: 'Fix or remove skipped tests before PR',
252
+ });
253
+ }
254
+
255
+ // Check for focused tests (only)
256
+ const focusedTests = (content.match(/(?:it|test|describe)\.only/g) || []).length;
257
+ if (focusedTests > 0) {
258
+ issues.push({
259
+ file: fileName,
260
+ type: 'Focused Tests',
261
+ severity: 'high',
262
+ message: `${focusedTests} focused test(s) - will skip other tests`,
263
+ suggestion: 'Remove .only before committing',
264
+ });
265
+ }
266
+
267
+ // Check for proper assertions
268
+ const testCount = (content.match(/(?:it|test)\s*\(/g) || []).length;
269
+ const assertionCount = (content.match(/expect\s*\(|assert\./g) || []).length;
270
+
271
+ if (testCount > 0 && assertionCount < testCount) {
272
+ issues.push({
273
+ file: fileName,
274
+ type: 'Missing Assertions',
275
+ severity: 'medium',
276
+ message: `${testCount} tests but only ${assertionCount} assertions`,
277
+ suggestion: 'Each test should have at least one assertion',
278
+ });
279
+ }
280
+
281
+ // Check for excessive mocking
282
+ const mockCount = (content.match(/jest\.mock|vi\.mock|sinon\.stub|\.mockImplementation/g) || []).length;
283
+ if (mockCount > 10) {
284
+ issues.push({
285
+ file: fileName,
286
+ type: 'Excessive Mocking',
287
+ severity: 'medium',
288
+ message: `${mockCount} mocks - tests may be too coupled to implementation`,
289
+ suggestion: 'Consider testing behavior rather than implementation',
290
+ });
291
+ }
292
+
293
+ // Check for hardcoded timeouts
294
+ if (/setTimeout\s*\(\s*[^,]+,\s*\d{4,}/.test(content)) {
295
+ issues.push({
296
+ file: fileName,
297
+ type: 'Hardcoded Timeout',
298
+ severity: 'medium',
299
+ message: 'Test uses hardcoded timeout - may cause flaky tests',
300
+ suggestion: 'Use fake timers or waitFor utilities instead',
301
+ });
302
+ }
303
+
304
+ // Check for missing error case tests
305
+ const hasErrorTests = /error|throw|reject|fail|catch/i.test(content);
306
+ if (!hasErrorTests && testCount > 3) {
307
+ issues.push({
308
+ file: fileName,
309
+ type: 'Missing Error Tests',
310
+ severity: 'medium',
311
+ message: 'No error/edge case tests found',
312
+ suggestion: 'Add tests for error conditions and edge cases',
313
+ });
314
+ }
315
+
316
+ return issues;
317
+ }
318
+
319
+ /**
320
+ * Check source file for testability concerns
321
+ */
322
+ function checkTestability(content, fileName) {
323
+ const issues = [];
324
+
325
+ // Check for hard-to-test patterns
326
+ const singletonPattern = /(?:let|var)\s+instance\s*=\s*null.*getInstance/s;
327
+ if (singletonPattern.test(content)) {
328
+ issues.push({
329
+ file: fileName,
330
+ type: 'Singleton Pattern',
331
+ severity: 'medium',
332
+ message: 'Singleton pattern makes testing difficult',
333
+ suggestion: 'Consider dependency injection instead',
334
+ });
335
+ }
336
+
337
+ // Check for direct environment access
338
+ const envAccess = (content.match(/process\.env\.\w+/g) || []).length;
339
+ if (envAccess > 3) {
340
+ issues.push({
341
+ file: fileName,
342
+ type: 'Direct Env Access',
343
+ severity: 'medium',
344
+ message: `${envAccess} direct process.env accesses`,
345
+ suggestion: 'Consider using a config module for easier test mocking',
346
+ });
347
+ }
348
+
349
+ // Check for complex constructors
350
+ const complexConstructor = /constructor\s*\([^)]*\)\s*\{[^}]{300,}\}/s;
351
+ if (complexConstructor.test(content)) {
352
+ issues.push({
353
+ file: fileName,
354
+ type: 'Complex Constructor',
355
+ severity: 'medium',
356
+ message: 'Constructor has complex logic - hard to test',
357
+ suggestion: 'Move initialization logic to separate methods',
358
+ });
359
+ }
360
+
361
+ return issues;
362
+ }
363
+
364
+ module.exports = { run, analyzeCoverage, analyzeTestQuality };
@@ -0,0 +1,89 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Regression Test Step
5
+ *
6
+ * Workflow step wrapper for regression testing.
7
+ * Tests random completed tasks to catch regressions.
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+ const path = require('path');
12
+ const { getProjectRoot } = require('./flow-utils');
13
+
14
+ const PROJECT_ROOT = getProjectRoot();
15
+
16
+ /**
17
+ * Run regression tests as a workflow step
18
+ *
19
+ * @param {object} options
20
+ * @param {string} options.taskId - Current task ID
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 { stepConfig = {}, mode } = options;
27
+ const sampleSize = stepConfig.sampleSize || 3;
28
+
29
+ try {
30
+ // Run regression tests
31
+ const regressionScript = path.join(PROJECT_ROOT, 'scripts', 'flow-regression.js');
32
+ const result = execSync(`node "${regressionScript}" --count ${sampleSize} --json`, {
33
+ cwd: PROJECT_ROOT,
34
+ encoding: 'utf8',
35
+ stdio: ['pipe', 'pipe', 'pipe'],
36
+ });
37
+
38
+ // Parse result
39
+ let parsed;
40
+ try {
41
+ parsed = JSON.parse(result);
42
+ } catch (err) {
43
+ // Non-JSON output means success with no issues
44
+ return { passed: true, message: 'Regression tests passed' };
45
+ }
46
+
47
+ if (parsed.failures && parsed.failures.length > 0) {
48
+ return {
49
+ passed: false,
50
+ message: `${parsed.failures.length} regression test(s) failed`,
51
+ details: parsed.failures,
52
+ };
53
+ }
54
+
55
+ return {
56
+ passed: true,
57
+ message: `Tested ${parsed.tested || sampleSize} completed tasks, all passed`,
58
+ };
59
+
60
+ } catch (error) {
61
+ // Check if it's a test failure or script error
62
+ if (error.stdout) {
63
+ try {
64
+ const parsed = JSON.parse(error.stdout);
65
+ if (parsed.failures) {
66
+ return {
67
+ passed: false,
68
+ message: `${parsed.failures.length} regression test(s) failed`,
69
+ details: parsed.failures,
70
+ };
71
+ }
72
+ } catch (err) {
73
+ // Not JSON, treat as error
74
+ }
75
+ }
76
+
77
+ // No completed tasks to test is not a failure
78
+ if (error.message && error.message.includes('No completed tasks')) {
79
+ return { passed: true, message: 'No completed tasks to test' };
80
+ }
81
+
82
+ return {
83
+ passed: false,
84
+ message: `Regression test error: ${error.message}`,
85
+ };
86
+ }
87
+ }
88
+
89
+ module.exports = { run };