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,516 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Code Review Step
5
+ *
6
+ * Hybrid code review: multi-agent for big/high-risk tasks, simple for small/low-risk.
7
+ * Uses confidence scoring (0-100) and only reports issues with confidence >= threshold.
8
+ *
9
+ * Multi-agent review runs 3 parallel perspectives:
10
+ * 1. Architecture/Design review
11
+ * 2. Implementation/Logic review
12
+ * 3. Security/Edge cases review
13
+ */
14
+
15
+ const fs = require('fs');
16
+ const path = require('path');
17
+ const { getProjectRoot, colors } = require('./flow-utils');
18
+
19
+ const PROJECT_ROOT = getProjectRoot();
20
+
21
+ // High-risk patterns that trigger multi-agent review
22
+ const HIGH_RISK_PATTERNS = [
23
+ 'auth', 'authentication', 'authorization',
24
+ 'payment', 'billing', 'checkout',
25
+ 'security', 'crypto', 'encrypt', 'decrypt',
26
+ 'password', 'credential', 'secret', 'token',
27
+ 'admin', 'permission', 'role',
28
+ 'database', 'migration', 'schema',
29
+ ];
30
+
31
+ /**
32
+ * Run code review as a workflow step
33
+ *
34
+ * @param {object} options
35
+ * @param {string[]} options.files - Files modified
36
+ * @param {object} options.stepConfig - Step configuration
37
+ * @param {string} options.mode - Step mode (block/warn/prompt/auto)
38
+ * @param {string} options.taskType - Type of task (feature/bugfix/refactor)
39
+ * @returns {object} - { passed: boolean, message: string, details?: object[] }
40
+ */
41
+ async function run(options = {}) {
42
+ const { files = [], stepConfig = {}, taskType } = options;
43
+ const multiAgentThreshold = stepConfig.multiAgentThreshold || 5;
44
+ const confidenceThreshold = stepConfig.confidenceThreshold || 80;
45
+ const highRiskPatterns = stepConfig.highRiskPatterns || HIGH_RISK_PATTERNS;
46
+
47
+ // Filter to reviewable files
48
+ const reviewableExtensions = ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs'];
49
+ const reviewableFiles = files.filter(f =>
50
+ reviewableExtensions.some(ext => f.endsWith(ext)) &&
51
+ !f.includes('.test.') &&
52
+ !f.includes('.spec.') &&
53
+ !f.includes('.d.ts')
54
+ );
55
+
56
+ if (reviewableFiles.length === 0) {
57
+ return { passed: true, message: 'No reviewable files modified' };
58
+ }
59
+
60
+ // Determine if high-risk
61
+ const isHighRisk = taskType === 'refactor' ||
62
+ reviewableFiles.some(f => highRiskPatterns.some(p => f.toLowerCase().includes(p)));
63
+
64
+ // Choose review mode
65
+ const useMultiAgent = reviewableFiles.length > multiAgentThreshold || isHighRisk;
66
+
67
+ let issues;
68
+ if (useMultiAgent) {
69
+ console.log(colors.cyan + ' Running multi-agent code review...' + colors.reset);
70
+ issues = await runMultiAgentReview(reviewableFiles, stepConfig);
71
+ } else {
72
+ console.log(colors.cyan + ' Running simple code review...' + colors.reset);
73
+ issues = await runSimpleReview(reviewableFiles, stepConfig);
74
+ }
75
+
76
+ // Filter by confidence threshold
77
+ const reportableIssues = issues.filter(i => i.confidence >= confidenceThreshold);
78
+
79
+ if (reportableIssues.length === 0) {
80
+ return {
81
+ passed: true,
82
+ message: useMultiAgent
83
+ ? `Multi-agent review passed (${reviewableFiles.length} files)`
84
+ : `Simple review passed (${reviewableFiles.length} files)`,
85
+ };
86
+ }
87
+
88
+ // Report issues
89
+ console.log(colors.yellow + '\n Code Review Issues:' + colors.reset);
90
+
91
+ const criticalIssues = reportableIssues.filter(i => i.severity === 'critical');
92
+ const importantIssues = reportableIssues.filter(i => i.severity === 'important');
93
+
94
+ if (criticalIssues.length > 0) {
95
+ console.log(colors.red + ' Critical:' + colors.reset);
96
+ for (const issue of criticalIssues) {
97
+ printIssue(issue);
98
+ }
99
+ }
100
+
101
+ if (importantIssues.length > 0) {
102
+ console.log(colors.yellow + ' Important:' + colors.reset);
103
+ for (const issue of importantIssues) {
104
+ printIssue(issue);
105
+ }
106
+ }
107
+
108
+ // Critical issues block, important issues warn
109
+ const hasCritical = criticalIssues.length > 0;
110
+
111
+ // Auto-capture learnings from issues found
112
+ if (reportableIssues.length > 0) {
113
+ try {
114
+ const { captureFromSessionReview } = require('./flow-auto-learn');
115
+ captureFromSessionReview(reportableIssues);
116
+ } catch (err) {
117
+ // Auto-learn not available or failed - continue silently
118
+ }
119
+ }
120
+
121
+ return {
122
+ passed: !hasCritical,
123
+ message: `${reportableIssues.length} issue(s) found (${criticalIssues.length} critical, ${importantIssues.length} important)`,
124
+ details: reportableIssues,
125
+ };
126
+ }
127
+
128
+ /**
129
+ * Print a single issue
130
+ */
131
+ function printIssue(issue) {
132
+ const confidenceColor = issue.confidence >= 90 ? colors.red : colors.yellow;
133
+ console.log(` ${issue.file}:${issue.line}`);
134
+ console.log(` ${issue.description}`);
135
+ console.log(` ${confidenceColor}Confidence: ${issue.confidence}%${colors.reset}`);
136
+ if (issue.fix) {
137
+ console.log(colors.dim + ` Fix: ${issue.fix}` + colors.reset);
138
+ }
139
+ }
140
+
141
+ /**
142
+ * Run multi-agent review (3 perspectives)
143
+ */
144
+ async function runMultiAgentReview(files, config) {
145
+ const allIssues = [];
146
+
147
+ for (const file of files) {
148
+ const filePath = path.join(PROJECT_ROOT, file);
149
+ if (!fs.existsSync(filePath)) continue;
150
+
151
+ try {
152
+ const content = fs.readFileSync(filePath, 'utf8');
153
+
154
+ // Run all 3 perspectives
155
+ const architectureIssues = reviewArchitecture(content, file);
156
+ const implementationIssues = reviewImplementation(content, file);
157
+ const securityIssues = reviewSecurity(content, file);
158
+
159
+ // Merge and dedupe issues
160
+ const fileIssues = mergeIssues([
161
+ ...architectureIssues,
162
+ ...implementationIssues,
163
+ ...securityIssues,
164
+ ]);
165
+
166
+ allIssues.push(...fileIssues);
167
+ } catch (err) {
168
+ // Skip unreadable files
169
+ }
170
+ }
171
+
172
+ return allIssues;
173
+ }
174
+
175
+ /**
176
+ * Run simple review (single pass)
177
+ */
178
+ async function runSimpleReview(files, config) {
179
+ const allIssues = [];
180
+
181
+ for (const file of files) {
182
+ const filePath = path.join(PROJECT_ROOT, file);
183
+ if (!fs.existsSync(filePath)) continue;
184
+
185
+ try {
186
+ const content = fs.readFileSync(filePath, 'utf8');
187
+ const fileIssues = runBasicChecks(content, file);
188
+ allIssues.push(...fileIssues);
189
+ } catch (err) {
190
+ // Skip unreadable files
191
+ }
192
+ }
193
+
194
+ return allIssues;
195
+ }
196
+
197
+ /**
198
+ * Architecture/Design review perspective
199
+ */
200
+ function reviewArchitecture(content, fileName) {
201
+ const issues = [];
202
+ const lines = content.split('\n');
203
+
204
+ // Check for god objects (too many methods/properties)
205
+ const methodCount = (content.match(/(?:function\s+\w+|(?:const|let|var)\s+\w+\s*=\s*(?:async\s*)?\()/g) || []).length;
206
+ if (methodCount > 15) {
207
+ issues.push({
208
+ file: fileName,
209
+ line: 1,
210
+ perspective: 'architecture',
211
+ severity: methodCount > 25 ? 'critical' : 'important',
212
+ confidence: Math.min(95, 70 + methodCount),
213
+ description: `File has ${methodCount} functions - consider splitting into modules`,
214
+ fix: 'Extract related functions into separate modules with clear responsibilities',
215
+ });
216
+ }
217
+
218
+ // Check for circular dependency hints
219
+ const imports = content.match(/(?:require|import).*['"](\.\.?\/[^'"]+)['"]/g) || [];
220
+ const uniqueImports = new Set(imports);
221
+ if (uniqueImports.size > 10) {
222
+ issues.push({
223
+ file: fileName,
224
+ line: 1,
225
+ perspective: 'architecture',
226
+ severity: 'important',
227
+ confidence: 75,
228
+ description: `High import count (${uniqueImports.size}) may indicate tight coupling`,
229
+ fix: 'Review dependencies and consider introducing facades or reorganizing modules',
230
+ });
231
+ }
232
+
233
+ // Check for mixed concerns
234
+ const hasDOM = /querySelector|getElementById|document\./i.test(content);
235
+ const hasAPI = /fetch|axios|http/i.test(content);
236
+ const hasDB = /query|findOne|insertOne|mongoose|prisma/i.test(content);
237
+
238
+ const concerns = [hasDOM, hasAPI, hasDB].filter(Boolean).length;
239
+ if (concerns >= 2) {
240
+ issues.push({
241
+ file: fileName,
242
+ line: 1,
243
+ perspective: 'architecture',
244
+ severity: 'important',
245
+ confidence: 80,
246
+ description: 'File mixes multiple concerns (UI/API/DB)',
247
+ fix: 'Separate into distinct layers: presentation, business logic, data access',
248
+ });
249
+ }
250
+
251
+ return issues;
252
+ }
253
+
254
+ /**
255
+ * Implementation/Logic review perspective
256
+ */
257
+ function reviewImplementation(content, fileName) {
258
+ const issues = [];
259
+ const lines = content.split('\n');
260
+
261
+ // Check for magic numbers
262
+ for (let i = 0; i < lines.length; i++) {
263
+ const line = lines[i];
264
+ // Skip comments, imports, and obvious cases
265
+ if (line.trim().startsWith('//') || line.includes('import') || line.includes('require')) continue;
266
+
267
+ const magicMatch = line.match(/[^a-zA-Z0-9_](\d{2,})[^a-zA-Z0-9_]/);
268
+ if (magicMatch) {
269
+ const num = parseInt(magicMatch[1]);
270
+ // Skip common values like 100, 1000, ports, etc.
271
+ if (![100, 1000, 10000, 3000, 8080, 8000, 443, 80].includes(num)) {
272
+ issues.push({
273
+ file: fileName,
274
+ line: i + 1,
275
+ perspective: 'implementation',
276
+ severity: 'important',
277
+ confidence: 70,
278
+ description: `Magic number ${num} - consider using a named constant`,
279
+ fix: `Extract to a descriptively named constant: const MEANINGFUL_NAME = ${num}`,
280
+ });
281
+ }
282
+ }
283
+ }
284
+
285
+ // Check for deeply nested logic
286
+ let maxIndent = 0;
287
+ for (let i = 0; i < lines.length; i++) {
288
+ const indent = (lines[i].match(/^(\s*)/)?.[1] || '').replace(/\t/g, ' ').length;
289
+ if (indent > maxIndent) maxIndent = indent;
290
+ if (indent >= 16) { // 8+ levels
291
+ issues.push({
292
+ file: fileName,
293
+ line: i + 1,
294
+ perspective: 'implementation',
295
+ severity: 'critical',
296
+ confidence: 95,
297
+ description: 'Deeply nested code (8+ levels)',
298
+ fix: 'Use early returns, extract functions, or restructure logic',
299
+ });
300
+ break; // Only report once per file
301
+ }
302
+ }
303
+
304
+ // Check for duplicate string literals
305
+ const stringLiterals = content.match(/['"][^'"]{10,}['"]/g) || [];
306
+ const literalCounts = {};
307
+ for (const lit of stringLiterals) {
308
+ literalCounts[lit] = (literalCounts[lit] || 0) + 1;
309
+ }
310
+ for (const [lit, count] of Object.entries(literalCounts)) {
311
+ if (count >= 3) {
312
+ issues.push({
313
+ file: fileName,
314
+ line: 1,
315
+ perspective: 'implementation',
316
+ severity: 'important',
317
+ confidence: 85,
318
+ description: `String literal appears ${count} times - extract to constant`,
319
+ fix: `Create a constant: const MESSAGE = ${lit}`,
320
+ });
321
+ }
322
+ }
323
+
324
+ return issues;
325
+ }
326
+
327
+ /**
328
+ * Security/Edge cases review perspective
329
+ */
330
+ function reviewSecurity(content, fileName) {
331
+ const issues = [];
332
+ const lines = content.split('\n');
333
+
334
+ // Check for unsafe operations
335
+ const unsafePatterns = [
336
+ { pattern: /eval\s*\(/i, desc: 'eval() is dangerous - allows code injection', severity: 'critical', confidence: 100 },
337
+ { pattern: /innerHTML\s*=/i, desc: 'innerHTML can cause XSS - use textContent or sanitize', severity: 'critical', confidence: 95 },
338
+ { pattern: /dangerouslySetInnerHTML/i, desc: 'dangerouslySetInnerHTML requires careful sanitization', severity: 'important', confidence: 85 },
339
+ { pattern: /new Function\s*\(/i, desc: 'new Function() is similar to eval - avoid if possible', severity: 'critical', confidence: 95 },
340
+ { pattern: /document\.write/i, desc: 'document.write can be exploited - use DOM methods', severity: 'important', confidence: 90 },
341
+ { pattern: /exec\s*\(\s*[^)]*\+/i, desc: 'String concatenation in exec() may allow command injection', severity: 'critical', confidence: 90 },
342
+ { pattern: /execSync\s*\(\s*[^)]*\+/i, desc: 'String concatenation in execSync() may allow command injection', severity: 'critical', confidence: 90 },
343
+ ];
344
+
345
+ for (let i = 0; i < lines.length; i++) {
346
+ const line = lines[i];
347
+ for (const { pattern, desc, severity, confidence } of unsafePatterns) {
348
+ if (pattern.test(line)) {
349
+ issues.push({
350
+ file: fileName,
351
+ line: i + 1,
352
+ perspective: 'security',
353
+ severity,
354
+ confidence,
355
+ description: desc,
356
+ fix: 'Review and use safer alternatives',
357
+ });
358
+ }
359
+ }
360
+ }
361
+
362
+ // Check for missing error handling in async
363
+ const asyncFunctions = content.match(/async\s+(?:function\s+)?(\w+)/g) || [];
364
+ for (const asyncFunc of asyncFunctions) {
365
+ // Simple heuristic: check if there's a try-catch nearby
366
+ const funcName = asyncFunc.replace(/async\s+(?:function\s+)?/, '');
367
+ const funcIndex = content.indexOf(asyncFunc);
368
+ const funcContext = content.substring(funcIndex, funcIndex + 500);
369
+ if (!funcContext.includes('try') && !funcContext.includes('catch')) {
370
+ issues.push({
371
+ file: fileName,
372
+ line: content.substring(0, funcIndex).split('\n').length,
373
+ perspective: 'security',
374
+ severity: 'important',
375
+ confidence: 70,
376
+ description: `Async function "${funcName}" may lack error handling`,
377
+ fix: 'Add try-catch or ensure errors are handled by the caller',
378
+ });
379
+ }
380
+ }
381
+
382
+ // Check for hardcoded credentials
383
+ const credPatterns = [
384
+ /(?:password|passwd|pwd)\s*[:=]\s*['"][^'"]+['"]/i,
385
+ /(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]+['"]/i,
386
+ /(?:secret|token)\s*[:=]\s*['"][A-Za-z0-9+/=]{20,}['"]/i,
387
+ ];
388
+
389
+ for (let i = 0; i < lines.length; i++) {
390
+ const line = lines[i];
391
+ // Skip comments and env references
392
+ if (line.trim().startsWith('//') || line.includes('process.env') || line.includes('.env')) continue;
393
+
394
+ for (const pattern of credPatterns) {
395
+ if (pattern.test(line)) {
396
+ issues.push({
397
+ file: fileName,
398
+ line: i + 1,
399
+ perspective: 'security',
400
+ severity: 'critical',
401
+ confidence: 90,
402
+ description: 'Potential hardcoded credential detected',
403
+ fix: 'Use environment variables or a secrets manager',
404
+ });
405
+ break;
406
+ }
407
+ }
408
+ }
409
+
410
+ return issues;
411
+ }
412
+
413
+ /**
414
+ * Run basic checks (for simple review mode)
415
+ */
416
+ function runBasicChecks(content, fileName) {
417
+ const issues = [];
418
+ const lines = content.split('\n');
419
+
420
+ // Check for console.log left in code
421
+ for (let i = 0; i < lines.length; i++) {
422
+ if (/console\.(log|debug|info)\s*\(/.test(lines[i]) && !lines[i].trim().startsWith('//')) {
423
+ issues.push({
424
+ file: fileName,
425
+ line: i + 1,
426
+ perspective: 'basic',
427
+ severity: 'important',
428
+ confidence: 80,
429
+ description: 'console.log left in code',
430
+ fix: 'Remove or replace with proper logging',
431
+ });
432
+ }
433
+ }
434
+
435
+ // Check for TODO/FIXME comments
436
+ for (let i = 0; i < lines.length; i++) {
437
+ if (/\b(TODO|FIXME|XXX|HACK)\b/i.test(lines[i])) {
438
+ issues.push({
439
+ file: fileName,
440
+ line: i + 1,
441
+ perspective: 'basic',
442
+ severity: 'important',
443
+ confidence: 75,
444
+ description: 'Unresolved TODO/FIXME comment',
445
+ fix: 'Address the TODO or create a task to track it',
446
+ });
447
+ }
448
+ }
449
+
450
+ // Check for empty catch blocks
451
+ const emptyCatch = content.match(/catch\s*\([^)]*\)\s*\{\s*\}/g);
452
+ if (emptyCatch) {
453
+ issues.push({
454
+ file: fileName,
455
+ line: 1,
456
+ perspective: 'basic',
457
+ severity: 'critical',
458
+ confidence: 95,
459
+ description: 'Empty catch block swallows errors silently',
460
+ fix: 'Log the error or rethrow it',
461
+ });
462
+ }
463
+
464
+ // Check for debugger statements
465
+ for (let i = 0; i < lines.length; i++) {
466
+ if (/\bdebugger\b/.test(lines[i])) {
467
+ issues.push({
468
+ file: fileName,
469
+ line: i + 1,
470
+ perspective: 'basic',
471
+ severity: 'critical',
472
+ confidence: 100,
473
+ description: 'debugger statement left in code',
474
+ fix: 'Remove the debugger statement',
475
+ });
476
+ }
477
+ }
478
+
479
+ return issues;
480
+ }
481
+
482
+ /**
483
+ * Merge and dedupe issues from multiple perspectives
484
+ */
485
+ function mergeIssues(issues) {
486
+ // Group by file:line
487
+ const grouped = {};
488
+ for (const issue of issues) {
489
+ const key = `${issue.file}:${issue.line}`;
490
+ if (!grouped[key]) {
491
+ grouped[key] = [];
492
+ }
493
+ grouped[key].push(issue);
494
+ }
495
+
496
+ // For each group, keep highest confidence version
497
+ const merged = [];
498
+ for (const group of Object.values(grouped)) {
499
+ // Sort by confidence descending
500
+ group.sort((a, b) => b.confidence - a.confidence);
501
+
502
+ // If multiple perspectives found same issue, boost confidence
503
+ if (group.length > 1) {
504
+ const best = group[0];
505
+ best.confidence = Math.min(100, best.confidence + (group.length - 1) * 5);
506
+ best.perspectives = group.map(i => i.perspective);
507
+ merged.push(best);
508
+ } else {
509
+ merged.push(group[0]);
510
+ }
511
+ }
512
+
513
+ return merged;
514
+ }
515
+
516
+ module.exports = { run, runMultiAgentReview, runSimpleReview };
@@ -0,0 +1,162 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Wogi Flow - Security Scan Step
5
+ *
6
+ * Workflow step for security scanning.
7
+ * Runs npm audit and checks for common vulnerabilities.
8
+ */
9
+
10
+ const { execSync } = require('child_process');
11
+ const fs = require('fs');
12
+ const path = require('path');
13
+ const { getProjectRoot } = require('./flow-utils');
14
+
15
+ const PROJECT_ROOT = getProjectRoot();
16
+
17
+ /**
18
+ * Run security scan 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 = {}, mode } = options;
28
+ const severity = stepConfig.severity || 'high';
29
+ const issues = [];
30
+
31
+ // 1. Check for secrets in modified files
32
+ const secretPatterns = [
33
+ /(?:api[_-]?key|apikey)\s*[:=]\s*['"][^'"]+['"]/gi,
34
+ /(?:secret|password|passwd|pwd)\s*[:=]\s*['"][^'"]+['"]/gi,
35
+ /(?:access[_-]?token|auth[_-]?token)\s*[:=]\s*['"][^'"]+['"]/gi,
36
+ /-----BEGIN\s+(?:RSA\s+)?PRIVATE\s+KEY-----/i,
37
+ /(?:aws[_-]?access[_-]?key[_-]?id)\s*[:=]\s*['"][A-Z0-9]+['"]/gi,
38
+ ];
39
+
40
+ for (const file of files) {
41
+ const filePath = path.join(PROJECT_ROOT, file);
42
+ if (!fs.existsSync(filePath)) continue;
43
+
44
+ // Skip test files and config examples
45
+ if (file.includes('.test.') || file.includes('.spec.') || file.includes('.example')) {
46
+ continue;
47
+ }
48
+
49
+ try {
50
+ const content = fs.readFileSync(filePath, 'utf8');
51
+ for (const pattern of secretPatterns) {
52
+ if (pattern.test(content)) {
53
+ issues.push({
54
+ type: 'secret',
55
+ severity: 'high',
56
+ file,
57
+ message: 'Potential secret or credential detected',
58
+ });
59
+ break; // One issue per file is enough
60
+ }
61
+ }
62
+ } catch (err) {
63
+ // Skip unreadable files
64
+ }
65
+ }
66
+
67
+ // 2. Run npm audit if package.json was modified
68
+ const packageModified = files.some(f => f.endsWith('package.json') || f.endsWith('package-lock.json'));
69
+
70
+ if (packageModified || stepConfig.alwaysAudit) {
71
+ try {
72
+ const auditResult = execSync('npm audit --json 2>/dev/null', {
73
+ cwd: PROJECT_ROOT,
74
+ encoding: 'utf8',
75
+ stdio: ['pipe', 'pipe', 'pipe'],
76
+ });
77
+
78
+ const audit = JSON.parse(auditResult);
79
+
80
+ if (audit.metadata && audit.metadata.vulnerabilities) {
81
+ const vulns = audit.metadata.vulnerabilities;
82
+
83
+ if (severity === 'critical' && vulns.critical > 0) {
84
+ issues.push({
85
+ type: 'npm_audit',
86
+ severity: 'critical',
87
+ message: `${vulns.critical} critical vulnerabilities found`,
88
+ count: vulns.critical,
89
+ });
90
+ } else if (severity === 'high' && (vulns.critical > 0 || vulns.high > 0)) {
91
+ const count = vulns.critical + vulns.high;
92
+ issues.push({
93
+ type: 'npm_audit',
94
+ severity: 'high',
95
+ message: `${count} high/critical vulnerabilities found`,
96
+ count,
97
+ });
98
+ } else if (severity === 'moderate') {
99
+ const count = vulns.critical + vulns.high + vulns.moderate;
100
+ if (count > 0) {
101
+ issues.push({
102
+ type: 'npm_audit',
103
+ severity: 'moderate',
104
+ message: `${count} moderate+ vulnerabilities found`,
105
+ count,
106
+ });
107
+ }
108
+ }
109
+ }
110
+ } catch (err) {
111
+ // npm audit failed or returned non-zero
112
+ if (e.stdout) {
113
+ try {
114
+ const audit = JSON.parse(e.stdout);
115
+ if (audit.metadata && audit.metadata.vulnerabilities) {
116
+ const vulns = audit.metadata.vulnerabilities;
117
+ const count = vulns.critical + vulns.high;
118
+ if (count > 0 && (severity === 'high' || severity === 'critical')) {
119
+ issues.push({
120
+ type: 'npm_audit',
121
+ severity: 'high',
122
+ message: `${count} high/critical vulnerabilities`,
123
+ count,
124
+ });
125
+ }
126
+ }
127
+ } catch (parseError) {
128
+ // Ignore parse errors
129
+ }
130
+ }
131
+ }
132
+ }
133
+
134
+ // 3. Evaluate results
135
+ if (issues.length === 0) {
136
+ return { passed: true, message: 'Security scan passed' };
137
+ }
138
+
139
+ // Filter by severity for blocking
140
+ const blockingIssues = issues.filter(i => {
141
+ if (severity === 'critical') return i.severity === 'critical';
142
+ if (severity === 'high') return i.severity === 'high' || i.severity === 'critical';
143
+ return true;
144
+ });
145
+
146
+ if (blockingIssues.length > 0) {
147
+ return {
148
+ passed: false,
149
+ message: `${blockingIssues.length} security issue(s) found`,
150
+ details: blockingIssues,
151
+ };
152
+ }
153
+
154
+ // Non-blocking issues
155
+ return {
156
+ passed: true,
157
+ message: `${issues.length} low-severity issue(s) found`,
158
+ details: issues,
159
+ };
160
+ }
161
+
162
+ module.exports = { run };