wogiflow 2.4.2 → 2.4.4

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 (210) hide show
  1. package/.claude/commands/wogi-start.md +124 -0
  2. package/.claude/docs/claude-code-compatibility.md +51 -0
  3. package/.claude/docs/explore-agents.md +11 -0
  4. package/.claude/settings.json +12 -1
  5. package/.workflow/models/registry.json +1 -1
  6. package/bin/flow +11 -1
  7. package/lib/workspace-contracts.js +599 -0
  8. package/lib/workspace-intelligence.js +600 -0
  9. package/lib/workspace-messages.js +441 -0
  10. package/lib/workspace-routing.js +485 -0
  11. package/lib/workspace-sync.js +339 -0
  12. package/lib/workspace.js +1073 -0
  13. package/package.json +4 -4
  14. package/scripts/MEMORY-ARCHITECTURE.md +1 -1
  15. package/scripts/base-workflow-step.js +136 -0
  16. package/scripts/flow-adaptive-learning.js +8 -9
  17. package/scripts/flow-aggregate.js +11 -6
  18. package/scripts/flow-api-index.js +4 -6
  19. package/scripts/flow-assumption-detector.js +0 -2
  20. package/scripts/flow-audit.js +15 -2
  21. package/scripts/flow-auto-context.js +8 -12
  22. package/scripts/flow-auto-learn.js +49 -49
  23. package/scripts/flow-background.js +5 -6
  24. package/scripts/flow-bridge-state.js +8 -10
  25. package/scripts/flow-bulk-loop.js +1 -3
  26. package/scripts/flow-bulk-orchestrator.js +1 -3
  27. package/scripts/flow-cascade-completion.js +0 -2
  28. package/scripts/flow-cascade.js +4 -4
  29. package/scripts/flow-checkpoint.js +10 -13
  30. package/scripts/flow-code-intelligence.js +10 -12
  31. package/scripts/flow-community-sync.js +4 -4
  32. package/scripts/flow-community.js +12 -20
  33. package/scripts/flow-config-defaults.js +28 -2
  34. package/scripts/flow-config-interactive.js +9 -5
  35. package/scripts/flow-config-loader.js +49 -92
  36. package/scripts/flow-config-substitution.js +0 -2
  37. package/scripts/flow-context-estimator.js +4 -4
  38. package/scripts/flow-context-init.js +10 -12
  39. package/scripts/flow-context-manager.js +0 -2
  40. package/scripts/flow-context-scoring.js +2 -2
  41. package/scripts/flow-contract-scan.js +6 -9
  42. package/scripts/flow-correct.js +29 -27
  43. package/scripts/flow-correction-detector.js +5 -1
  44. package/scripts/flow-damage-control.js +47 -54
  45. package/scripts/flow-decisions-merge.js +4 -14
  46. package/scripts/flow-diff.js +5 -8
  47. package/scripts/flow-done-gates.js +786 -0
  48. package/scripts/flow-done-report.js +123 -0
  49. package/scripts/flow-done.js +71 -717
  50. package/scripts/flow-entropy-monitor.js +1 -3
  51. package/scripts/flow-eval-calibration.js +257 -0
  52. package/scripts/flow-eval-judge.js +10 -1
  53. package/scripts/flow-eval.js +14 -5
  54. package/scripts/flow-extraction-review.js +1 -0
  55. package/scripts/flow-failure-categories.js +0 -2
  56. package/scripts/flow-figma-confirm.js +5 -9
  57. package/scripts/flow-figma-generate.js +8 -10
  58. package/scripts/flow-figma-index.js +8 -10
  59. package/scripts/flow-figma-match.js +3 -5
  60. package/scripts/flow-figma-mcp-server.js +2 -4
  61. package/scripts/flow-figma-orchestrator.js +2 -3
  62. package/scripts/flow-figma-registry.js +2 -3
  63. package/scripts/flow-framework-resolver.js +0 -2
  64. package/scripts/flow-function-index.js +4 -6
  65. package/scripts/flow-gate-confidence.js +2 -2
  66. package/scripts/flow-gitignore.js +0 -2
  67. package/scripts/flow-guided-edit.js +5 -6
  68. package/scripts/flow-health.js +5 -6
  69. package/scripts/flow-hook-errors.js +6 -0
  70. package/scripts/flow-hook-status.js +263 -0
  71. package/scripts/flow-hooks.js +17 -29
  72. package/scripts/flow-http-client.js +9 -8
  73. package/scripts/flow-hybrid-interactive.js +7 -12
  74. package/scripts/flow-hybrid-test.js +12 -13
  75. package/scripts/flow-instruction-richness.js +1 -1
  76. package/scripts/flow-io.js +21 -4
  77. package/scripts/flow-knowledge-router.js +9 -3
  78. package/scripts/flow-learning-orchestrator.js +318 -13
  79. package/scripts/flow-links.js +5 -7
  80. package/scripts/flow-long-input-association.js +275 -0
  81. package/scripts/flow-long-input-chunking.js +1 -0
  82. package/scripts/flow-long-input-cli.js +0 -2
  83. package/scripts/flow-long-input-complexity.js +0 -2
  84. package/scripts/flow-long-input-constants.js +0 -2
  85. package/scripts/flow-long-input-contradictions.js +351 -0
  86. package/scripts/flow-long-input-detection.js +0 -2
  87. package/scripts/flow-long-input-passes.js +885 -0
  88. package/scripts/flow-long-input-stories.js +1 -1
  89. package/scripts/flow-long-input-voice.js +0 -2
  90. package/scripts/flow-long-input.js +425 -3005
  91. package/scripts/flow-loop-retry-learning.js +2 -3
  92. package/scripts/flow-lsp.js +3 -3
  93. package/scripts/flow-mcp-docs.js +3 -4
  94. package/scripts/flow-memory-db.js +6 -8
  95. package/scripts/flow-memory-sync.js +18 -11
  96. package/scripts/flow-metrics.js +1 -2
  97. package/scripts/flow-model-adapter.js +2 -3
  98. package/scripts/flow-model-config.js +72 -104
  99. package/scripts/flow-model-router.js +2 -2
  100. package/scripts/flow-model-types.js +0 -2
  101. package/scripts/flow-multi-approach.js +5 -6
  102. package/scripts/flow-orchestrate-context.js +3 -7
  103. package/scripts/flow-orchestrate-rollback.js +3 -8
  104. package/scripts/flow-orchestrate-state.js +8 -14
  105. package/scripts/flow-orchestrate-templates.js +2 -6
  106. package/scripts/flow-orchestrate-validator.js +5 -9
  107. package/scripts/flow-orchestrate.js +126 -103
  108. package/scripts/flow-output.js +0 -2
  109. package/scripts/flow-parallel.js +1 -1
  110. package/scripts/flow-paths.js +23 -2
  111. package/scripts/flow-pattern-enforcer.js +30 -28
  112. package/scripts/flow-pattern-extractor.js +3 -4
  113. package/scripts/flow-pending.js +0 -2
  114. package/scripts/flow-permissions.js +2 -3
  115. package/scripts/flow-plugin-registry.js +10 -12
  116. package/scripts/flow-prd-manager.js +1 -1
  117. package/scripts/flow-progress.js +7 -9
  118. package/scripts/flow-prompt-composer.js +3 -3
  119. package/scripts/flow-prompt-template.js +2 -2
  120. package/scripts/flow-providers.js +7 -4
  121. package/scripts/flow-registry-manager.js +7 -12
  122. package/scripts/flow-regression.js +9 -11
  123. package/scripts/flow-roadmap.js +2 -2
  124. package/scripts/flow-run-trace.js +16 -15
  125. package/scripts/flow-safety.js +2 -5
  126. package/scripts/flow-scanner-base.js +5 -7
  127. package/scripts/flow-scenario-engine.js +1 -5
  128. package/scripts/flow-security.js +29 -0
  129. package/scripts/flow-session-end.js +32 -41
  130. package/scripts/flow-session-learning.js +53 -49
  131. package/scripts/flow-setup-hooks.js +2 -3
  132. package/scripts/flow-skill-create.js +7 -12
  133. package/scripts/flow-skill-generator.js +12 -16
  134. package/scripts/flow-skill-learn.js +17 -8
  135. package/scripts/flow-skill-matcher.js +1 -2
  136. package/scripts/flow-spec-generator.js +2 -4
  137. package/scripts/flow-stack-wizard.js +5 -7
  138. package/scripts/flow-standards-learner.js +35 -16
  139. package/scripts/flow-start.js +2 -0
  140. package/scripts/flow-stats-collector.js +2 -2
  141. package/scripts/flow-status.js +10 -10
  142. package/scripts/flow-statusline-setup.js +2 -2
  143. package/scripts/flow-step-changelog.js +2 -3
  144. package/scripts/flow-step-comments.js +66 -81
  145. package/scripts/flow-step-complexity.js +50 -70
  146. package/scripts/flow-step-coverage.js +3 -5
  147. package/scripts/flow-step-knowledge.js +2 -3
  148. package/scripts/flow-step-pr-tests.js +64 -74
  149. package/scripts/flow-step-regression.js +3 -5
  150. package/scripts/flow-step-review.js +86 -103
  151. package/scripts/flow-step-security.js +111 -121
  152. package/scripts/flow-step-silent-failures.js +56 -83
  153. package/scripts/flow-step-simplifier.js +52 -70
  154. package/scripts/flow-story.js +4 -7
  155. package/scripts/flow-strict-adherence.js +3 -4
  156. package/scripts/flow-task-checkpoint.js +36 -5
  157. package/scripts/flow-task-enforcer.js +2 -24
  158. package/scripts/flow-tech-debt.js +1 -1
  159. package/scripts/flow-template-extractor.js +1 -0
  160. package/scripts/flow-templates.js +11 -13
  161. package/scripts/flow-test-api.js +9 -13
  162. package/scripts/flow-test-discovery.js +1 -1
  163. package/scripts/flow-test-generate.js +5 -9
  164. package/scripts/flow-test-integrity.js +3 -7
  165. package/scripts/flow-test-ui.js +5 -9
  166. package/scripts/flow-testing-deps.js +1 -3
  167. package/scripts/flow-tiered-learning.js +4 -4
  168. package/scripts/flow-todowrite-sync.js +1 -1
  169. package/scripts/flow-tokens.js +0 -2
  170. package/scripts/flow-verification-profile.js +6 -10
  171. package/scripts/flow-verify.js +12 -16
  172. package/scripts/flow-version-check.js +4 -12
  173. package/scripts/flow-webmcp-generator.js +3 -5
  174. package/scripts/flow-workflow-steps.js +0 -2
  175. package/scripts/flow-workflow.js +9 -11
  176. package/scripts/hooks/adapters/claude-code.js +31 -0
  177. package/scripts/hooks/core/config-change.js +1 -0
  178. package/scripts/hooks/core/extension-registry.js +0 -2
  179. package/scripts/hooks/core/instructions-loaded.js +1 -1
  180. package/scripts/hooks/core/observation-capture.js +5 -5
  181. package/scripts/hooks/core/phase-gate.js +5 -0
  182. package/scripts/hooks/core/post-compact.js +1 -12
  183. package/scripts/hooks/core/research-gate.js +2 -12
  184. package/scripts/hooks/core/routing-gate.js +6 -0
  185. package/scripts/hooks/core/task-completed.js +12 -0
  186. package/scripts/hooks/core/task-created.js +83 -0
  187. package/scripts/hooks/core/worktree-lifecycle.js +1 -1
  188. package/scripts/hooks/entry/claude-code/config-change.js +6 -29
  189. package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
  190. package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
  191. package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
  192. package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
  193. package/scripts/hooks/entry/claude-code/session-end.js +4 -28
  194. package/scripts/hooks/entry/claude-code/session-start.js +205 -243
  195. package/scripts/hooks/entry/claude-code/setup.js +8 -49
  196. package/scripts/hooks/entry/claude-code/stop.js +40 -72
  197. package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
  198. package/scripts/hooks/entry/claude-code/task-created.js +15 -0
  199. package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
  200. package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
  201. package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
  202. package/scripts/hooks/entry/shared/hook-runner.js +99 -0
  203. package/scripts/hooks/entry/shared/read-stdin.js +0 -2
  204. package/scripts/postinstall.js +2 -0
  205. package/scripts/registries/api-registry.js +0 -2
  206. package/scripts/registries/component-registry.js +5 -9
  207. package/scripts/registries/contract-scanner.js +2 -9
  208. package/scripts/registries/function-registry.js +0 -2
  209. package/scripts/registries/schema-registry.js +14 -18
  210. package/scripts/registries/service-registry.js +23 -27
@@ -43,14 +43,6 @@ const { autoArchiveIfNeeded } = require('./flow-log-manager');
43
43
  // v1.9.0 regression testing (legacy - now in workflow steps)
44
44
  const { runRegressionTests } = require('./flow-regression');
45
45
 
46
- // v1.10 smart test discovery + SWE-bench dual gate
47
- let testDiscovery;
48
- try {
49
- testDiscovery = require('./flow-test-discovery');
50
- } catch (err) {
51
- testDiscovery = null;
52
- }
53
-
54
46
  // v2.2 modular workflow steps
55
47
  const { runSteps, getAllSteps } = require('./flow-workflow-steps');
56
48
 
@@ -60,9 +52,6 @@ const { loadDurableSession, archiveDurableSession } = require('./flow-durable-se
60
52
  // v5.1 prompt capture and clarification learning
61
53
  const { processTaskCompletion } = require('./flow-prompt-capture');
62
54
 
63
- // v2.1 task enforcement as explicit quality gate
64
- const { canExitLoop, getActiveLoop } = require('./flow-task-enforcer');
65
-
66
55
  // v2.5 checkpoint system
67
56
  const { Checkpoint } = require('./flow-checkpoint');
68
57
 
@@ -91,56 +80,18 @@ const {
91
80
  // v3.1 spec verification gate
92
81
  const { verifySpecDeliverables, formatVerificationResults } = require('./flow-spec-verifier');
93
82
 
94
- // v1.9.1 quality gate wiring — verifiers that were built but never called
95
- let wiringVerifier;
96
- try {
97
- wiringVerifier = require('./flow-wiring-verifier');
98
- } catch (err) {
99
- wiringVerifier = null;
100
- }
101
-
102
- let standardsGate;
103
- try {
104
- standardsGate = require('./flow-standards-gate');
105
- } catch (err) {
106
- standardsGate = null;
107
- }
108
-
109
- // v3.1 recursive error recovery (with hypothesis generation)
110
- let errorRecovery;
111
- try {
112
- errorRecovery = require('./flow-error-recovery');
113
- } catch (err) {
114
- // Module optional - graceful degradation
115
- errorRecovery = null;
116
- }
117
-
118
- let hypothesisGenerator;
119
- try {
120
- hypothesisGenerator = require('./flow-hypothesis-generator');
121
- } catch (err) {
122
- hypothesisGenerator = null;
123
- }
124
-
125
83
  // v5.2 verification profiles
126
84
  const { loadProfile: loadVerificationProfile } = require('./flow-verification-profile');
127
85
 
128
- // v1.9.7 registry map update gate lazy-loaded to avoid startup cost
129
- // Loaded inside the registryUpdate gate branch only
130
- let _registryManagerModule = undefined; // undefined = not yet loaded, null = load failed
131
- function getRegistryManager() {
132
- if (_registryManagerModule === undefined) {
133
- try {
134
- _registryManagerModule = require('./flow-registry-manager');
135
- } catch (err) {
136
- _registryManagerModule = null;
137
- }
138
- }
139
- return _registryManagerModule;
140
- }
141
-
142
- // Path for last failure artifact
143
- const LAST_FAILURE_PATH = path.join(PATHS.state, 'last-failure.json');
86
+ // v2.3 extracted gate handlers and report formatting
87
+ const { runGate } = require('./flow-done-gates');
88
+ const {
89
+ LAST_FAILURE_PATH,
90
+ printFailureSummary,
91
+ saveFailureArtifact,
92
+ printErrorRecoveryAnalysis,
93
+ printFinalFailureMessage,
94
+ } = require('./flow-done-report');
144
95
 
145
96
  /**
146
97
  * Get files modified in current task (from git)
@@ -221,10 +172,12 @@ function checkOutstandingFindings() {
221
172
  }
222
173
 
223
174
  /**
224
- * Run quality gates from config
175
+ * Run quality gates from config.
176
+ *
177
+ * Orchestration layer — delegates to individual gate handlers in flow-done-gates.js
178
+ * and report formatting to flow-done-report.js.
225
179
  */
226
180
  function runQualityGates(taskId, taskType) {
227
- // Validate taskId before using in any path construction
228
181
  if (taskId && !validateTaskId(taskId).valid) {
229
182
  console.log(color('red', `Invalid task ID format: ${String(taskId).slice(0, 30)}`));
230
183
  return { passed: false, failed: ['invalidTaskId'], errors: { invalidTaskId: 'Task ID failed validation' } };
@@ -237,17 +190,12 @@ function runQualityGates(taskId, taskType) {
237
190
  console.log(color('yellow', 'Running quality gates...'));
238
191
  console.log('');
239
192
 
240
- // Load verification profile — warn if missing and testing gates are configured
241
193
  const verificationProfile = loadVerificationProfile();
242
-
243
194
  const config = getConfig();
244
- // Use task-type-specific gates, fall back to feature gates, then empty
245
- const normalizedType = (taskType || 'feature').toLowerCase();
195
+ const normalizedType = (taskType ?? 'feature').toLowerCase();
246
196
  const gates = config.qualityGates?.[normalizedType]?.require
247
- || config.qualityGates?.feature?.require
248
- || [];
249
- const failed = [];
250
- const errors = {}; // Store error output for correction artifact
197
+ ?? config.qualityGates?.feature?.require
198
+ ?? [];
251
199
 
252
200
  // Warn if testing gates are present but no verification profile exists
253
201
  if (!verificationProfile) {
@@ -258,7 +206,7 @@ function runQualityGates(taskId, taskType) {
258
206
  }
259
207
  }
260
208
 
261
- // Cache outstanding findings result used by both outstandingFindings and preRelease gates
209
+ // Cache outstanding findings — shared by outstandingFindings and preRelease gates
262
210
  let cachedOutstandingFindings = null;
263
211
  function getOutstandingFindings() {
264
212
  if (!cachedOutstandingFindings) {
@@ -267,597 +215,57 @@ function runQualityGates(taskId, taskType) {
267
215
  return cachedOutstandingFindings;
268
216
  }
269
217
 
270
- for (const gate of gates) {
271
- if (gate === 'tests') {
272
- if (config.scripts?.test) {
273
- console.log(' Running tests...');
274
- const result = spawnSync('npm', ['test'], {
275
- encoding: 'utf-8',
276
- stdio: ['pipe', 'pipe', 'pipe']
277
- });
278
- if (result.status === 0) {
279
- success(`tests passed`);
280
- } else {
281
- error(`tests failed`);
282
- // Capture error output
283
- const errorOutput = result.stderr || result.stdout || '';
284
- if (errorOutput) {
285
- console.log(color('dim', ' Error output:'));
286
- const truncated = truncateOutput(errorOutput, 20, 1000);
287
- truncated.split('\n').forEach(line => {
288
- console.log(color('dim', ` ${line}`));
289
- });
290
- }
291
- errors.tests = errorOutput;
292
- failed.push('tests');
293
- }
294
- } else {
295
- console.log(` ${color('yellow', '○')} tests (not configured to run)`);
296
- }
297
- } else if (gate === 'lint') {
298
- console.log(' Running lint...');
299
- let result = spawnSync('npm', ['run', 'lint'], {
300
- encoding: 'utf-8',
301
- stdio: ['pipe', 'pipe', 'pipe']
302
- });
303
-
304
- if (result.status !== 0) {
305
- // Try auto-fix
306
- console.log(` ${color('yellow', '⟳')} lint issues found, attempting auto-fix...`);
307
- spawnSync('npm', ['run', 'lint', '--', '--fix'], {
308
- encoding: 'utf-8',
309
- stdio: ['pipe', 'pipe', 'pipe']
310
- });
311
-
312
- // Re-run lint to check if issues are fixed
313
- result = spawnSync('npm', ['run', 'lint'], {
314
- encoding: 'utf-8',
315
- stdio: ['pipe', 'pipe', 'pipe']
316
- });
317
-
318
- if (result.status === 0) {
319
- success(`lint passed (auto-fixed)`);
320
- } else {
321
- error(`lint failed (manual fix required)`);
322
- const errorOutput = result.stderr || result.stdout || '';
323
- if (errorOutput) {
324
- console.log(color('dim', ' Remaining issues:'));
325
- const truncated = truncateOutput(errorOutput, 15, 800);
326
- truncated.split('\n').forEach(line => {
327
- console.log(color('dim', ` ${line}`));
328
- });
329
- }
330
- errors.lint = errorOutput;
331
- failed.push('lint');
332
- }
333
- } else {
334
- success(`lint passed`);
335
- }
336
- } else if (gate === 'typecheck') {
337
- console.log(' Running typecheck...');
338
- const result = spawnSync('npm', ['run', 'typecheck'], {
339
- encoding: 'utf-8',
340
- stdio: ['pipe', 'pipe', 'pipe']
341
- });
342
- if (result.status === 0) {
343
- success(`typecheck passed`);
344
- } else {
345
- error(`typecheck failed`);
346
- const errorOutput = result.stderr || result.stdout || '';
347
- if (errorOutput) {
348
- console.log(color('dim', ' Type errors:'));
349
- const truncated = truncateOutput(errorOutput, 20, 1000);
350
- truncated.split('\n').forEach(line => {
351
- console.log(color('dim', ` ${line}`));
352
- });
353
- }
354
- errors.typecheck = errorOutput;
355
- failed.push('typecheck');
356
- }
357
- } else if (gate === 'requestLogEntry') {
358
- // Check if request-log has an entry for this task
359
- try {
360
- const content = readFile(PATHS.requestLog, '');
361
- if (content.includes(taskId)) {
362
- success(`requestLogEntry (found in request-log)`);
363
- } else {
364
- console.log(` ${color('yellow', '○')} requestLogEntry (add entry to request-log.md)`);
365
- }
366
- } catch (err) {
367
- if (process.env.DEBUG) console.error(`[DEBUG] requestLogEntry check: ${err.message}`);
368
- console.log(` ${color('yellow', '○')} requestLogEntry (could not check)`);
369
- }
370
- } else if (gate === 'appMapUpdate' || gate === 'registryUpdate') {
371
- // v1.9.7: Programmatic registry scan — replaces manual "verify manually" no-op.
372
- // Runs flow-registry-manager scan on all active registries, comparing modified files
373
- // against registry entries to detect missing registrations.
374
- // v1.9.8: Deprecation warning for old gate name
375
- if (gate === 'appMapUpdate') {
376
- warn(`appMapUpdate is deprecated — update config.json qualityGates to use 'registryUpdate'`);
377
- }
378
- const registryMod = getRegistryManager();
379
- if (registryMod) {
380
- try {
381
- console.log(' Running registry update check...');
382
- const modifiedFiles = getModifiedFiles();
383
-
384
- // Get map file timestamps BEFORE scan (to detect changes)
385
- const mapFiles = ['app-map.md', 'function-map.md', 'api-map.md', 'schema-map.md', 'service-map.md'];
386
- const beforeHashes = {};
387
- for (const mf of mapFiles) {
388
- const mapPath = path.join(PATHS.state, mf);
389
- try {
390
- beforeHashes[mf] = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
391
- } catch (err) {
392
- beforeHashes[mf] = 0;
393
- }
394
- }
395
-
396
- // Detect active plugins to check if scan is needed (lightweight, no async)
397
- const { RegistryManager } = registryMod;
398
- const manager = new RegistryManager();
399
- manager.loadPlugins();
400
- manager.detectStack();
401
- manager.activatePlugins();
402
-
403
- // Only scan if there are active plugins
404
- if (manager.activePlugins.length > 0) {
405
- // scanAll is async but we need sync behavior in quality gates
406
- // Use spawnSync to run the scan as a child process
407
- // v1.9.8: Added cwd, write JSON to stderr to avoid stdout pollution from require() side-effects
408
- const scanResult = spawnSync('node', [
409
- '-e',
410
- `const {RegistryManager} = require(${JSON.stringify(path.join(__dirname, 'flow-registry-manager'))});
411
- const m = new RegistryManager(); m.loadPlugins(); m.detectStack(); m.activatePlugins();
412
- m.scanAll().then(r => { process.stderr.write('SCAN_RESULT:' + JSON.stringify(r)); process.exit(0); })
413
- .catch(err => { process.stderr.write('SCAN_RESULT:' + JSON.stringify({error: err.message})); process.exit(1); });`
414
- ], {
415
- encoding: 'utf-8',
416
- stdio: ['pipe', 'pipe', 'pipe'],
417
- timeout: 30000,
418
- cwd: process.cwd()
419
- });
420
-
421
- if (scanResult.status === 0) {
422
- // Extract scan result from stderr (after SCAN_RESULT: marker) to avoid stdout pollution
423
- const stderrOutput = scanResult.stderr || '';
424
- const markerIdx = stderrOutput.indexOf('SCAN_RESULT:');
425
- const jsonStr = markerIdx >= 0 ? stderrOutput.slice(markerIdx + 'SCAN_RESULT:'.length) : '{}';
426
- const results = safeJsonParseString(jsonStr, {});
427
-
428
- // Check which map files were updated
429
- const updatedMaps = [];
430
- for (const mf of mapFiles) {
431
- const mapPath = path.join(PATHS.state, mf);
432
- try {
433
- const afterMtime = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
434
- if (afterMtime > beforeHashes[mf]) {
435
- updatedMaps.push(mf);
436
- }
437
- } catch (err) {
438
- // ignore
439
- }
440
- }
441
-
442
- // Check if modified files include patterns that should be in registries
443
- const relevantExtensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
444
- const codeFiles = modifiedFiles.filter(f => relevantExtensions.some(ext => f.endsWith(ext)));
445
- const nonTestFiles = codeFiles.filter(f => !f.includes('test') && !f.includes('spec') && !f.includes('__test'));
446
-
447
- const activeIds = manager.activePlugins.map(p => p.constructor.id);
448
- const scanSummary = Object.entries(results)
449
- .filter(([id, r]) => r.success && !r.empty)
450
- .map(([id]) => id);
451
-
452
- if (updatedMaps.length > 0) {
453
- success(`registryUpdate (auto-scanned: ${updatedMaps.join(', ')} updated)`);
454
- } else if (scanSummary.length > 0) {
455
- success(`registryUpdate (scanned ${activeIds.join(', ')} — maps already current)`);
456
- } else if (nonTestFiles.length === 0) {
457
- success(`registryUpdate (no registrable code files modified)`);
458
- } else {
459
- success(`registryUpdate (scanned — no new entries found)`);
460
- }
461
- } else {
462
- warn(`registryUpdate (scan error — degraded to manual check)`);
463
- if (process.env.DEBUG) console.error(`[DEBUG] registry scan stderr: ${scanResult.stderr}`);
464
- }
465
- } else {
466
- success(`registryUpdate (no active registry plugins)`);
467
- }
468
- } catch (err) {
469
- // Graceful degradation — don't block task completion on scan errors
470
- warn(`registryUpdate (error: ${truncateOutput(err.message, 3, 200)} — verify manually)`);
471
- }
472
- } else {
473
- warn(`registryUpdate (registry manager not available — verify manually)`);
474
- }
475
- } else if (gate === 'loopComplete') {
476
- // v2.1: Explicit loop completion check
477
- const activeLoop = getActiveLoop();
478
- if (!activeLoop) {
479
- // No active loop - either completed or not used
480
- success(`loopComplete (no active loop session)`);
481
- } else {
482
- const exitResult = canExitLoop();
483
- if (exitResult.canExit) {
484
- success(`loopComplete (${exitResult.reason})`);
485
- } else {
486
- error(`loopComplete (${exitResult.pending || 0} pending, ${exitResult.failed || 0} failed)`);
487
- errors.loopComplete = exitResult.message || 'Loop not complete';
488
- failed.push('loopComplete');
489
- }
490
- }
491
- } else if (gate === 'noNewFeatures') {
492
- // Refactor-specific gate - manual check
493
- console.log(` ${color('yellow', '○')} noNewFeatures (verify no behavior changes)`);
494
- } else if (gate === 'integrationWiring') {
495
- // v1.9.1: Actually call the wiring verifier instead of falling through
496
- if (wiringVerifier && typeof wiringVerifier.verifyWiring === 'function') {
497
- try {
498
- console.log(' Running integration wiring check...');
499
- const result = wiringVerifier.verifyWiring(taskId);
500
- if (result.passed) {
501
- success(`integrationWiring (${result.verified || 0} files verified)`);
502
- } else {
503
- const unwiredCount = result.unwired?.length || 0;
504
- error(`integrationWiring (${unwiredCount} unwired file${unwiredCount !== 1 ? 's' : ''})`);
505
- if (result.unwired) {
506
- for (const item of result.unwired.slice(0, 5)) {
507
- console.log(color('dim', ` - ${item.file || item}`));
508
- }
509
- }
510
- errors.integrationWiring = `${unwiredCount} files created but not imported/used anywhere`;
511
- failed.push('integrationWiring');
512
- }
513
-
514
- // v1.9.3: Removal impact check — verify removed exports aren't still referenced
515
- if (typeof wiringVerifier.verifyRemovalImpact === 'function') {
516
- const modifiedFiles = getModifiedFiles();
517
- if (modifiedFiles.length > 0) {
518
- console.log(' Running removal impact check...');
519
- const removalResult = wiringVerifier.verifyRemovalImpact(modifiedFiles);
520
- if (removalResult.identifiersChecked > 0) {
521
- if (removalResult.passed) {
522
- success(`removalImpact (${removalResult.identifiersChecked} removed identifiers verified)`);
523
- } else {
524
- const orphanCount = removalResult.orphanedRefs.length;
525
- error(`removalImpact (${orphanCount} orphaned reference${orphanCount !== 1 ? 's' : ''} to removed exports)`);
526
- for (const ref of removalResult.orphanedRefs.slice(0, 5)) {
527
- console.log(color('dim', ` - "${ref.identifier}" removed from ${ref.removedFrom}, still used in ${ref.totalRefs} file${ref.totalRefs !== 1 ? 's' : ''}`));
528
- for (const consumer of ref.referencedBy.slice(0, 2)) {
529
- console.log(color('dim', ` → ${consumer.file}`));
530
- }
531
- }
532
- errors.removalImpact = `${orphanCount} removed export${orphanCount !== 1 ? 's' : ''} still referenced by consumers`;
533
- failed.push('removalImpact');
534
- }
535
- }
536
- }
537
- }
538
- } catch (err) {
539
- // Graceful degradation: verifier error is not a hard failure — gate passes with warning
540
- warn(`integrationWiring (verifier error — degraded to manual check: ${truncateOutput(err.message, 3, 200)})`);
541
- }
542
- } else {
543
- warn(`integrationWiring (verifier module not available — install flow-wiring-verifier.js)`);
544
- if (process.env.DEBUG) console.error('[DEBUG] wiringVerifier module failed to load or missing verifyWiring export');
545
- }
546
- } else if (gate === 'standardsCompliance') {
547
- // v1.9.1: Actually call the standards gate instead of falling through
548
- if (standardsGate && typeof standardsGate.runTaskStandardsCheck === 'function') {
549
- try {
550
- console.log(' Running standards compliance check...');
551
- const modifiedFiles = getModifiedFiles();
552
- const taskContext = { id: taskId, type: normalizedType };
553
- const result = standardsGate.runTaskStandardsCheck(taskContext, modifiedFiles);
554
- const mustFixCount = result.violations?.filter(v => v.severity === 'MUST_FIX' || v.severity === 'high').length || 0;
555
- if (mustFixCount === 0) {
556
- success(`standardsCompliance (${result.violations?.length || 0} suggestions, 0 must-fix)`);
557
- } else {
558
- error(`standardsCompliance (${mustFixCount} must-fix violation${mustFixCount !== 1 ? 's' : ''})`);
559
- for (const v of (result.violations || []).filter(v => v.severity === 'MUST_FIX' || v.severity === 'high').slice(0, 5)) {
560
- console.log(color('dim', ` - ${v.file || ''}:${v.line || ''} ${v.issue || v.message || ''}`));
561
- }
562
- errors.standardsCompliance = `${mustFixCount} standards violations require fixing`;
563
- failed.push('standardsCompliance');
564
- }
565
- } catch (err) {
566
- // Graceful degradation: checker error is not a hard failure — gate passes with warning
567
- warn(`standardsCompliance (checker error — degraded to manual check: ${truncateOutput(err.message, 3, 200)})`);
568
- }
569
- } else {
570
- warn(`standardsCompliance (checker module not available — install flow-standards-gate.js)`);
571
- if (process.env.DEBUG) console.error('[DEBUG] standardsGate module failed to load or missing runTaskStandardsCheck export');
572
- }
573
- } else if (gate === 'outstandingFindings') {
574
- // v1.9.1: Check for unresolved MUST_FIX findings from last review
575
- const outstanding = getOutstandingFindings();
576
- if (!outstanding.hasOutstanding) {
577
- success(`outstandingFindings (no unresolved critical/high findings)`);
578
- } else {
579
- error(`outstandingFindings (${outstanding.count} unresolved finding${outstanding.count !== 1 ? 's' : ''} from last review)`);
580
- for (const f of outstanding.findings.slice(0, 5)) {
581
- console.log(color('dim', ` - [${f.severity}] ${f.file || ''}:${f.line || ''} ${f.issue || ''}`));
582
- }
583
- errors.outstandingFindings = `${outstanding.count} unresolved critical/high findings from last review. Fix them or waive with /wogi-triage.`;
584
- failed.push('outstandingFindings');
585
- }
586
- } else if (gate === 'preRelease') {
587
- // v1.9.1: Pre-release gate — verify codebase is in releasable state
588
- console.log(' Running pre-release checks...');
589
- let preReleaseFailed = false;
590
-
591
- // Check outstanding findings (uses cached result)
592
- const outstanding = getOutstandingFindings();
593
- if (outstanding.hasOutstanding) {
594
- error(`preRelease: ${outstanding.count} unresolved findings from last review`);
595
- preReleaseFailed = true;
596
- }
218
+ // Build shared context for gate handlers
219
+ const ctx = {
220
+ taskId,
221
+ taskType,
222
+ normalizedType,
223
+ config,
224
+ gates,
225
+ spawnSync,
226
+ getModifiedFiles,
227
+ truncateOutput,
228
+ fileExists,
229
+ readFile,
230
+ readJson,
231
+ safeJsonParse,
232
+ safeJsonParseString,
233
+ validateTaskId,
234
+ color,
235
+ success,
236
+ warn,
237
+ error,
238
+ verificationProfile,
239
+ getOutstandingFindings,
240
+ };
597
241
 
598
- // Check lint if configured
599
- if (config.scripts?.lint) {
600
- const lintResult = spawnSync('npm', ['run', 'lint'], {
601
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
602
- });
603
- if (lintResult.status !== 0) {
604
- error(`preRelease: lint failed`);
605
- preReleaseFailed = true;
606
- }
607
- }
242
+ const failed = [];
243
+ const errors = {};
608
244
 
609
- // Check typecheck if configured
610
- if (config.scripts?.typecheck) {
611
- const tcResult = spawnSync('npm', ['run', 'typecheck'], {
612
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
613
- });
614
- if (tcResult.status !== 0) {
615
- error(`preRelease: typecheck failed`);
616
- preReleaseFailed = true;
617
- }
618
- }
245
+ for (const gate of gates) {
246
+ const result = runGate(gate, ctx);
619
247
 
620
- if (!preReleaseFailed) {
621
- success(`preRelease (codebase is releasable)`);
622
- } else {
623
- errors.preRelease = 'Codebase is not in a releasable state';
624
- failed.push('preRelease');
248
+ if (!result.passed) {
249
+ failed.push(gate);
250
+ if (result.errorOutput) {
251
+ errors[gate] = result.errorOutput;
625
252
  }
626
- } else if (gate === 'learningEnforcement') {
627
- // v1.9.2: Check that bugfix patterns are recorded in feedback-patterns.md
628
- try {
629
- const feedbackPath = path.join(PATHS.state, 'feedback-patterns.md');
630
- const content = readFile(feedbackPath, '');
631
- if (content.includes(taskId)) {
632
- success(`learningEnforcement (pattern recorded in feedback-patterns.md)`);
633
- } else {
634
- console.log(` ${color('yellow', '○')} learningEnforcement (add bug pattern to feedback-patterns.md)`);
635
- }
636
- } catch (err) {
637
- console.log(` ${color('yellow', '○')} learningEnforcement (could not check feedback-patterns.md)`);
638
- }
639
- } else if (gate === 'resolutionPopulated') {
640
- // v1.9.2: Check that the task's change spec has a resolution/fix section
641
- try {
642
- const changesDir = path.join(PATHS.workflow, 'changes');
643
- const specPath = path.join(changesDir, `${taskId}.md`);
644
- const content = readFile(specPath, '');
645
- if (content) {
646
- if (content.toLowerCase().includes('resolution') || content.toLowerCase().includes('root cause') || content.toLowerCase().includes('fix')) {
647
- success(`resolutionPopulated (resolution documented in spec)`);
648
- } else {
649
- console.log(` ${color('yellow', '○')} resolutionPopulated (add resolution/root cause to spec)`);
650
- }
651
- } else {
652
- console.log(` ${color('yellow', '○')} resolutionPopulated (no spec file found)`);
653
- }
654
- } catch (err) {
655
- console.log(` ${color('yellow', '○')} resolutionPopulated (could not check)`);
656
- }
657
- } else if (gate === 'smokeTest') {
658
- // v1.9.2: Run a basic smoke test — syntax check all modified JS files
659
- try {
660
- const modifiedFiles = getModifiedFiles();
661
- const jsFiles = modifiedFiles.filter(f => f.endsWith('.js'));
662
- if (jsFiles.length === 0) {
663
- console.log(` ${color('yellow', '○')} smokeTest (no JS files modified — nothing to check)`);
664
- } else {
665
- let allPassed = true;
666
- for (const file of jsFiles) {
667
- const result = spawnSync('node', ['--check', file], {
668
- encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
669
- });
670
- if (result.status !== 0) {
671
- error(`smokeTest: syntax error in ${file}`);
672
- allPassed = false;
673
- break;
674
- }
675
- }
676
- if (allPassed) {
677
- success(`smokeTest (${jsFiles.length} file${jsFiles.length !== 1 ? 's' : ''} pass syntax check)`);
678
- } else {
679
- errors.smokeTest = 'Syntax errors in modified files';
680
- failed.push('smokeTest');
681
- }
682
- } // end jsFiles.length > 0
683
- } catch (err) {
684
- console.log(` ${color('yellow', '○')} smokeTest (could not run: ${truncateOutput(err.message, 3, 200)})`);
685
- }
686
- } else if (gate === 'generatedTestsPass') {
687
- if (config.testing?.enabled && config.testing?.generation?.autoGenerate) {
688
- if (!validateTaskId(taskId).valid) {
689
- warn(`generatedTestsPass (invalid task ID)`);
690
- } else {
691
- const testDir = path.join(PATHS.workflow, 'tests', 'generated', taskId);
692
- if (fs.existsSync(testDir)) {
693
- console.log(' Running generated tests...');
694
- if (config.scripts?.test) {
695
- const result = spawnSync('npm', ['test', '--', testDir], {
696
- encoding: 'utf-8',
697
- stdio: ['pipe', 'pipe', 'pipe'],
698
- timeout: 120000
699
- });
700
- if (result.status === 0) {
701
- success(`generatedTestsPass`);
702
- } else {
703
- error(`generatedTestsPass`);
704
- const errorOutput = result.stderr || result.stdout || '';
705
- if (errorOutput) {
706
- console.log(color('dim', ' Error output:'));
707
- const truncated = truncateOutput(errorOutput, 15, 800);
708
- truncated.split('\n').forEach(line => {
709
- console.log(color('dim', ` ${line}`));
710
- });
711
- }
712
- errors.generatedTestsPass = errorOutput;
713
- failed.push('generatedTestsPass');
714
- }
715
- } else {
716
- console.log(` ${color('yellow', '○')} generatedTestsPass (no test command configured)`);
717
- }
718
- } else {
719
- console.log(` ${color('yellow', '○')} generatedTestsPass (no generated tests found)`);
720
- }
721
- }
722
- } else {
723
- console.log(` ${color('dim', '·')} generatedTestsPass (testing disabled)`);
724
- }
725
- } else if (gate === 'uiVerification' || gate === 'apiVerification') {
726
- const isUI = gate === 'uiVerification';
727
-
728
- // Project-type-aware gating: skip irrelevant gates
729
- const detected = config.testing?.detected;
730
- if (detected && detected.projectType) {
731
- const pt = detected.projectType;
732
- if (isUI && (pt === 'backend' || pt === 'library')) {
733
- console.log(` ${color('dim', '·')} ${gate} (not applicable — ${pt} project)`);
734
- continue;
735
- }
736
- if (!isUI && (pt === 'frontend' || pt === 'library')) {
737
- console.log(` ${color('dim', '·')} ${gate} (not applicable — ${pt} project)`);
738
- continue;
739
- }
740
- }
741
-
742
- const gateModes = isUI ? ['ui', 'full', 'auto'] : ['api', 'full', 'auto'];
743
- const testingMode = config.testing?.mode || 'off';
744
- if (config.testing?.enabled && gateModes.includes(testingMode)) {
745
- if (!validateTaskId(taskId).valid) {
746
- warn(`${gate} (invalid task ID)`);
747
- } else {
748
- try {
749
- const label = isUI ? 'UI' : 'API';
750
- console.log(` Running ${label} verification...`);
751
- const scriptPath = isUI
752
- ? path.join(__dirname, 'flow-test-ui.js')
753
- : path.join(__dirname, 'flow-test-api.js');
754
- const fnName = isUI ? 'runUITests' : 'runAPITests';
755
- const testResult = spawnSync('node', ['-e', [
756
- `const { ${fnName} } = require(${JSON.stringify(scriptPath)});`,
757
- `${fnName}(${JSON.stringify(taskId)}).then(r => {`,
758
- ' process.stdout.write(JSON.stringify(r));',
759
- ' process.exit(r.summary && r.summary.failed > 0 ? 1 : 0);',
760
- '}).catch(err => {',
761
- ' process.stdout.write(JSON.stringify({ error: err.message, summary: { passed: 0, failed: 0, total: 0 } }));',
762
- ' process.exit(2);',
763
- '});'
764
- ].join('\n')], {
765
- encoding: 'utf-8',
766
- stdio: ['pipe', 'pipe', 'pipe'],
767
- cwd: process.cwd(),
768
- timeout: 120000
769
- });
770
- if (testResult.status === 2) {
771
- const parsed = safeJsonParseString(testResult.stdout, {});
772
- const errMsg = parsed.error || testResult.stderr?.trim()?.slice(0, 200) || 'Unknown error';
773
- warn(`${gate} (error: ${errMsg})`);
774
- } else {
775
- const report = safeJsonParseString(testResult.stdout || '{}', {});
776
- const summary = report.summary || { passed: 0, failed: 0, total: 0 };
777
- if (summary.failed === 0) {
778
- success(`${gate} (${summary.passed}/${summary.total} passed)`);
779
- } else {
780
- error(`${gate} (${summary.failed} failed)`);
781
- const failedItems = isUI
782
- ? (report.assertions || []).filter(a => a.status === 'failed')
783
- : (report.endpoints || []).flatMap(e => (e.tests || []).filter(t => t.status === 'failed'));
784
- for (const item of failedItems.slice(0, 5)) {
785
- console.log(color('dim', ` - ${item.description || item.name || 'test failed'}`));
786
- }
787
- errors[gate] = JSON.stringify(failedItems);
788
- failed.push(gate);
789
- }
790
- }
791
- } catch (err) {
792
- warn(`${gate} (error: ${err.message})`);
793
- }
253
+ }
794
254
 
795
- // Also check scenario verification results if available
796
- if (!isUI && validateTaskId(taskId).valid) {
797
- const scenarioReportPath = path.join(PATHS.workflow, 'verifications', `${taskId}-scenarios.json`);
798
- if (fs.existsSync(scenarioReportPath)) {
799
- try {
800
- const scenarioReport = safeJsonParse(scenarioReportPath, null);
801
- if (scenarioReport && scenarioReport.summary) {
802
- const ss = scenarioReport.summary;
803
- if (ss.failed === 0 && ss.total > 0) {
804
- success(`scenarioVerification (${ss.passed}/${ss.total} scenarios passed)`);
805
- } else if (ss.failed > 0) {
806
- error(`scenarioVerification (${ss.failed} scenarios failed)`);
807
- const failedScenarios = (scenarioReport.scenarios || []).filter(s => !s.passed);
808
- for (const sc of failedScenarios.slice(0, 5)) {
809
- console.log(color('dim', ` - ${sc.name || 'unnamed scenario'}: ${sc.error || 'assertions failed'}`));
810
- }
811
- }
812
- }
813
- } catch (err) {
814
- // Non-fatal — scenario report parsing failed
815
- }
816
- }
255
+ // Handle sub-gates (e.g., removalImpact from integrationWiring)
256
+ if (result.subGates) {
257
+ for (const [subName, subResult] of Object.entries(result.subGates)) {
258
+ if (!subResult.passed) {
259
+ failed.push(subName);
260
+ if (subResult.errorOutput) {
261
+ errors[subName] = subResult.errorOutput;
817
262
  }
818
263
  }
819
- } else {
820
- console.log(` ${color('dim', '·')} ${gate} (testing disabled or mode excludes ${isUI ? 'UI' : 'API'})`);
821
264
  }
822
- } else if (gate === 'testDiscovery') {
823
- // v1.10: Smart test discovery + SWE-bench dual gate
824
- const discoveryConfig = config.testing?.discovery || {};
825
- if (discoveryConfig.enabled) {
826
- if (testDiscovery && typeof testDiscovery.runTestDiscoveryGate === 'function') {
827
- try {
828
- console.log(' Running test discovery gate...');
829
- const discoveryResult = testDiscovery.runTestDiscoveryGate(taskId, PATHS.root);
830
- if (discoveryResult.passed) {
831
- success(`testDiscovery (${discoveryResult.message})`);
832
- } else {
833
- error(`testDiscovery (${discoveryResult.message})`);
834
- if (discoveryResult.report?.passToPass?.failed) {
835
- for (const f of discoveryResult.report.passToPass.failed.slice(0, 5)) {
836
- console.log(color('dim', ` - ${f}`));
837
- }
838
- }
839
- errors.testDiscovery = discoveryResult.message;
840
- failed.push('testDiscovery');
841
- }
842
- } catch (err) {
843
- warn(`testDiscovery (error: ${truncateOutput(err.message, 3, 200)})`);
844
- }
845
- } else {
846
- warn(`testDiscovery (module not available — install flow-test-discovery.js)`);
847
- }
848
- } else {
849
- console.log(` ${color('dim', '·')} testDiscovery (disabled — set testing.discovery.enabled in config)`);
850
- }
851
- } else {
852
- console.log(` ${color('yellow', '○')} ${gate} (manual check)`);
853
265
  }
854
266
  }
855
267
 
856
- if (failed.length > 0) {
857
- console.log('');
858
- console.log(color('red', `Failed gates: ${failed.join(', ')}`));
859
- }
860
-
268
+ printFailureSummary(failed);
861
269
  return { passed: failed.length === 0, failed, errors };
862
270
  }
863
271
 
@@ -1137,73 +545,15 @@ async function main() {
1137
545
 
1138
546
  // Look up task type for type-specific quality gates
1139
547
  const preGateTask = findTask(taskId);
1140
- const taskTypeForGates = preGateTask?.task?.type || 'feature';
548
+ const taskTypeForGates = preGateTask?.task?.type ?? 'feature';
1141
549
 
1142
550
  // Run quality gates (type-aware since v1.9.1)
1143
551
  const gateResult = runQualityGates(taskId, taskTypeForGates);
1144
552
 
1145
553
  if (!gateResult.passed) {
1146
- // Create correction artifact for AI self-repair
1147
- try {
1148
- writeJson(LAST_FAILURE_PATH, {
1149
- taskId,
1150
- timestamp: new Date().toISOString(),
1151
- failedGates: gateResult.failed,
1152
- errors: gateResult.errors
1153
- });
1154
- console.log('');
1155
- console.log(color('dim', `Failure details saved to: ${LAST_FAILURE_PATH}`));
1156
- } catch (err) {
1157
- if (process.env.DEBUG) console.error(`[DEBUG] Failed to save failure artifact: ${err.message}`);
1158
- }
1159
-
1160
- // v3.1: Error recovery analysis with hypotheses
1161
- if (errorRecovery && doneConfig.errorRecovery?.enabled !== false) {
1162
- console.log('');
1163
- console.log(color('cyan', '━'.repeat(50)));
1164
- console.log(color('cyan', '🔍 Error Recovery Analysis'));
1165
- console.log(color('cyan', '━'.repeat(50)));
1166
-
1167
- // Analyze each failed gate
1168
- for (const gate of gateResult.failed) {
1169
- const errorText = gateResult.errors[gate] || '';
1170
- if (errorText) {
1171
- try {
1172
- // Classify the error
1173
- const classified = errorRecovery.classifyError(errorText);
1174
- const levelName = errorRecovery.getLevelName(classified.level);
1175
- console.log(`${gate}: ${color('yellow', levelName || 'unknown')} error`);
1176
-
1177
- // Get fix suggestions
1178
- const suggestions = errorRecovery.getSuggestedFixes(classified.level, errorText);
1179
- if (suggestions && suggestions.length > 0) {
1180
- console.log(` Suggested fixes:`);
1181
- suggestions.slice(0, 3).forEach(fix => {
1182
- console.log(` → ${fix}`);
1183
- });
1184
- }
1185
-
1186
- // Generate hypotheses if available
1187
- if (hypothesisGenerator) {
1188
- const hypotheses = hypothesisGenerator.generateHypotheses(errorText, classified);
1189
- if (hypotheses && hypotheses.length > 0) {
1190
- console.log(` Hypotheses:`);
1191
- hypotheses.slice(0, 2).forEach(h => {
1192
- console.log(` • ${h.hypothesis} (${Math.round(h.likelihood * 100)}% likelihood)`);
1193
- });
1194
- }
1195
- }
1196
- console.log('');
1197
- } catch (analysisErr) {
1198
- if (process.env.DEBUG) console.error(`[DEBUG] Error analysis: ${analysisErr.message}`);
1199
- }
1200
- }
1201
- }
1202
- }
1203
-
1204
- console.log('');
1205
- error('Quality gates failed. Fix issues before completing.');
1206
- console.log(color('dim', 'Tip: Review the error output above or check .workflow/state/last-failure.json'));
554
+ saveFailureArtifact(taskId, gateResult.failed, gateResult.errors);
555
+ printErrorRecoveryAnalysis(gateResult, doneConfig);
556
+ printFinalFailureMessage();
1207
557
  process.exit(1);
1208
558
  }
1209
559
 
@@ -1311,7 +661,11 @@ async function main() {
1311
661
  if (tableMatch) {
1312
662
  const insertPoint = tableMatch.index + tableMatch[0].length;
1313
663
  const newContent = content.slice(0, insertPoint) + patternEntry + content.slice(insertPoint);
1314
- fs.writeFileSync(feedbackPath, newContent);
664
+ // Route through orchestrator for locking and dedup
665
+ try {
666
+ const { writeToFeedbackPatterns: writeFP } = require('./flow-learning-orchestrator');
667
+ writeFP({ content: newContent, entryText: 'high-refinement-request', caller: 'flow-done/highRefinementFlag' }).catch(() => {});
668
+ } catch (_orcErr) { /* fallback: already computed newContent but orchestrator unavailable */ }
1315
669
  }
1316
670
  }
1317
671
  }
@@ -1375,7 +729,7 @@ async function main() {
1375
729
  // Check if task is a child of any story in this epic
1376
730
  // Use safeJsonParse per security-patterns.md Rule #2
1377
731
  const readyData = safeJsonParse(PATHS.ready, {});
1378
- const allTasks = [...(readyData.ready || []), ...(readyData.inProgress || []), ...(readyData.recentlyCompleted || [])];
732
+ const allTasks = [...(readyData.ready ?? []), ...(readyData.inProgress ?? []), ...(readyData.recentlyCompleted ?? [])];
1379
733
  return allTasks.some(t => t && typeof t === 'object' && t.parent === s && t.id === taskId);
1380
734
  })) {
1381
735
  const progressResult = updateEpicProgress(epic.id);
@@ -1395,7 +749,7 @@ async function main() {
1395
749
  // When a feature completes, auto-complete parent epic if all features done
1396
750
  // When an epic completes, auto-complete parent plan if all epics done
1397
751
  try {
1398
- const taskType = result.task?.type || 'story';
752
+ const taskType = result.task?.type ?? 'story';
1399
753
  cascadeCompletion(taskId, taskType);
1400
754
  } catch (err) {
1401
755
  if (process.env.DEBUG) console.error(`[DEBUG] Cascade completion: ${err.message}`);
@@ -1525,7 +879,7 @@ async function main() {
1525
879
  }
1526
880
 
1527
881
  // v2.0: Refresh component index after task if configured
1528
- const scanOn = config.componentIndex?.scanOn || [];
882
+ const scanOn = config.componentIndex?.scanOn ?? [];
1529
883
  if (config.componentIndex?.autoScan !== false && scanOn.includes('afterTask')) {
1530
884
  try {
1531
885
  console.log(color('dim', '🔄 Refreshing component index...'));
@@ -1543,7 +897,7 @@ async function main() {
1543
897
  }
1544
898
 
1545
899
  // v2.7: Refresh function registry after task if configured
1546
- const funcScanOn = config.functionRegistry?.scanOn || [];
900
+ const funcScanOn = config.functionRegistry?.scanOn ?? [];
1547
901
  if (config.functionRegistry?.enabled && config.functionRegistry?.autoUpdate !== false &&
1548
902
  funcScanOn.includes('afterTask')) {
1549
903
  try {
@@ -1559,7 +913,7 @@ async function main() {
1559
913
  }
1560
914
 
1561
915
  // v2.7: Refresh API registry after task if configured
1562
- const apiScanOn = config.apiRegistry?.scanOn || [];
916
+ const apiScanOn = config.apiRegistry?.scanOn ?? [];
1563
917
  if (config.apiRegistry?.enabled && config.apiRegistry?.autoUpdate !== false &&
1564
918
  apiScanOn.includes('afterTask')) {
1565
919
  try {