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.
- package/.claude/commands/wogi-start.md +124 -0
- package/.claude/docs/claude-code-compatibility.md +51 -0
- package/.claude/docs/explore-agents.md +11 -0
- package/.claude/settings.json +12 -1
- package/.workflow/models/registry.json +1 -1
- package/bin/flow +11 -1
- package/lib/workspace-contracts.js +599 -0
- package/lib/workspace-intelligence.js +600 -0
- package/lib/workspace-messages.js +441 -0
- package/lib/workspace-routing.js +485 -0
- package/lib/workspace-sync.js +339 -0
- package/lib/workspace.js +1073 -0
- package/package.json +4 -4
- package/scripts/MEMORY-ARCHITECTURE.md +1 -1
- package/scripts/base-workflow-step.js +136 -0
- package/scripts/flow-adaptive-learning.js +8 -9
- package/scripts/flow-aggregate.js +11 -6
- package/scripts/flow-api-index.js +4 -6
- package/scripts/flow-assumption-detector.js +0 -2
- package/scripts/flow-audit.js +15 -2
- package/scripts/flow-auto-context.js +8 -12
- package/scripts/flow-auto-learn.js +49 -49
- package/scripts/flow-background.js +5 -6
- package/scripts/flow-bridge-state.js +8 -10
- package/scripts/flow-bulk-loop.js +1 -3
- package/scripts/flow-bulk-orchestrator.js +1 -3
- package/scripts/flow-cascade-completion.js +0 -2
- package/scripts/flow-cascade.js +4 -4
- package/scripts/flow-checkpoint.js +10 -13
- package/scripts/flow-code-intelligence.js +10 -12
- package/scripts/flow-community-sync.js +4 -4
- package/scripts/flow-community.js +12 -20
- package/scripts/flow-config-defaults.js +28 -2
- package/scripts/flow-config-interactive.js +9 -5
- package/scripts/flow-config-loader.js +49 -92
- package/scripts/flow-config-substitution.js +0 -2
- package/scripts/flow-context-estimator.js +4 -4
- package/scripts/flow-context-init.js +10 -12
- package/scripts/flow-context-manager.js +0 -2
- package/scripts/flow-context-scoring.js +2 -2
- package/scripts/flow-contract-scan.js +6 -9
- package/scripts/flow-correct.js +29 -27
- package/scripts/flow-correction-detector.js +5 -1
- package/scripts/flow-damage-control.js +47 -54
- package/scripts/flow-decisions-merge.js +4 -14
- package/scripts/flow-diff.js +5 -8
- package/scripts/flow-done-gates.js +786 -0
- package/scripts/flow-done-report.js +123 -0
- package/scripts/flow-done.js +71 -717
- package/scripts/flow-entropy-monitor.js +1 -3
- package/scripts/flow-eval-calibration.js +257 -0
- package/scripts/flow-eval-judge.js +10 -1
- package/scripts/flow-eval.js +14 -5
- package/scripts/flow-extraction-review.js +1 -0
- package/scripts/flow-failure-categories.js +0 -2
- package/scripts/flow-figma-confirm.js +5 -9
- package/scripts/flow-figma-generate.js +8 -10
- package/scripts/flow-figma-index.js +8 -10
- package/scripts/flow-figma-match.js +3 -5
- package/scripts/flow-figma-mcp-server.js +2 -4
- package/scripts/flow-figma-orchestrator.js +2 -3
- package/scripts/flow-figma-registry.js +2 -3
- package/scripts/flow-framework-resolver.js +0 -2
- package/scripts/flow-function-index.js +4 -6
- package/scripts/flow-gate-confidence.js +2 -2
- package/scripts/flow-gitignore.js +0 -2
- package/scripts/flow-guided-edit.js +5 -6
- package/scripts/flow-health.js +5 -6
- package/scripts/flow-hook-errors.js +6 -0
- package/scripts/flow-hook-status.js +263 -0
- package/scripts/flow-hooks.js +17 -29
- package/scripts/flow-http-client.js +9 -8
- package/scripts/flow-hybrid-interactive.js +7 -12
- package/scripts/flow-hybrid-test.js +12 -13
- package/scripts/flow-instruction-richness.js +1 -1
- package/scripts/flow-io.js +21 -4
- package/scripts/flow-knowledge-router.js +9 -3
- package/scripts/flow-learning-orchestrator.js +318 -13
- package/scripts/flow-links.js +5 -7
- package/scripts/flow-long-input-association.js +275 -0
- package/scripts/flow-long-input-chunking.js +1 -0
- package/scripts/flow-long-input-cli.js +0 -2
- package/scripts/flow-long-input-complexity.js +0 -2
- package/scripts/flow-long-input-constants.js +0 -2
- package/scripts/flow-long-input-contradictions.js +351 -0
- package/scripts/flow-long-input-detection.js +0 -2
- package/scripts/flow-long-input-passes.js +885 -0
- package/scripts/flow-long-input-stories.js +1 -1
- package/scripts/flow-long-input-voice.js +0 -2
- package/scripts/flow-long-input.js +425 -3005
- package/scripts/flow-loop-retry-learning.js +2 -3
- package/scripts/flow-lsp.js +3 -3
- package/scripts/flow-mcp-docs.js +3 -4
- package/scripts/flow-memory-db.js +6 -8
- package/scripts/flow-memory-sync.js +18 -11
- package/scripts/flow-metrics.js +1 -2
- package/scripts/flow-model-adapter.js +2 -3
- package/scripts/flow-model-config.js +72 -104
- package/scripts/flow-model-router.js +2 -2
- package/scripts/flow-model-types.js +0 -2
- package/scripts/flow-multi-approach.js +5 -6
- package/scripts/flow-orchestrate-context.js +3 -7
- package/scripts/flow-orchestrate-rollback.js +3 -8
- package/scripts/flow-orchestrate-state.js +8 -14
- package/scripts/flow-orchestrate-templates.js +2 -6
- package/scripts/flow-orchestrate-validator.js +5 -9
- package/scripts/flow-orchestrate.js +126 -103
- package/scripts/flow-output.js +0 -2
- package/scripts/flow-parallel.js +1 -1
- package/scripts/flow-paths.js +23 -2
- package/scripts/flow-pattern-enforcer.js +30 -28
- package/scripts/flow-pattern-extractor.js +3 -4
- package/scripts/flow-pending.js +0 -2
- package/scripts/flow-permissions.js +2 -3
- package/scripts/flow-plugin-registry.js +10 -12
- package/scripts/flow-prd-manager.js +1 -1
- package/scripts/flow-progress.js +7 -9
- package/scripts/flow-prompt-composer.js +3 -3
- package/scripts/flow-prompt-template.js +2 -2
- package/scripts/flow-providers.js +7 -4
- package/scripts/flow-registry-manager.js +7 -12
- package/scripts/flow-regression.js +9 -11
- package/scripts/flow-roadmap.js +2 -2
- package/scripts/flow-run-trace.js +16 -15
- package/scripts/flow-safety.js +2 -5
- package/scripts/flow-scanner-base.js +5 -7
- package/scripts/flow-scenario-engine.js +1 -5
- package/scripts/flow-security.js +29 -0
- package/scripts/flow-session-end.js +32 -41
- package/scripts/flow-session-learning.js +53 -49
- package/scripts/flow-setup-hooks.js +2 -3
- package/scripts/flow-skill-create.js +7 -12
- package/scripts/flow-skill-generator.js +12 -16
- package/scripts/flow-skill-learn.js +17 -8
- package/scripts/flow-skill-matcher.js +1 -2
- package/scripts/flow-spec-generator.js +2 -4
- package/scripts/flow-stack-wizard.js +5 -7
- package/scripts/flow-standards-learner.js +35 -16
- package/scripts/flow-start.js +2 -0
- package/scripts/flow-stats-collector.js +2 -2
- package/scripts/flow-status.js +10 -10
- package/scripts/flow-statusline-setup.js +2 -2
- package/scripts/flow-step-changelog.js +2 -3
- package/scripts/flow-step-comments.js +66 -81
- package/scripts/flow-step-complexity.js +50 -70
- package/scripts/flow-step-coverage.js +3 -5
- package/scripts/flow-step-knowledge.js +2 -3
- package/scripts/flow-step-pr-tests.js +64 -74
- package/scripts/flow-step-regression.js +3 -5
- package/scripts/flow-step-review.js +86 -103
- package/scripts/flow-step-security.js +111 -121
- package/scripts/flow-step-silent-failures.js +56 -83
- package/scripts/flow-step-simplifier.js +52 -70
- package/scripts/flow-story.js +4 -7
- package/scripts/flow-strict-adherence.js +3 -4
- package/scripts/flow-task-checkpoint.js +36 -5
- package/scripts/flow-task-enforcer.js +2 -24
- package/scripts/flow-tech-debt.js +1 -1
- package/scripts/flow-template-extractor.js +1 -0
- package/scripts/flow-templates.js +11 -13
- package/scripts/flow-test-api.js +9 -13
- package/scripts/flow-test-discovery.js +1 -1
- package/scripts/flow-test-generate.js +5 -9
- package/scripts/flow-test-integrity.js +3 -7
- package/scripts/flow-test-ui.js +5 -9
- package/scripts/flow-testing-deps.js +1 -3
- package/scripts/flow-tiered-learning.js +4 -4
- package/scripts/flow-todowrite-sync.js +1 -1
- package/scripts/flow-tokens.js +0 -2
- package/scripts/flow-verification-profile.js +6 -10
- package/scripts/flow-verify.js +12 -16
- package/scripts/flow-version-check.js +4 -12
- package/scripts/flow-webmcp-generator.js +3 -5
- package/scripts/flow-workflow-steps.js +0 -2
- package/scripts/flow-workflow.js +9 -11
- package/scripts/hooks/adapters/claude-code.js +31 -0
- package/scripts/hooks/core/config-change.js +1 -0
- package/scripts/hooks/core/extension-registry.js +0 -2
- package/scripts/hooks/core/instructions-loaded.js +1 -1
- package/scripts/hooks/core/observation-capture.js +5 -5
- package/scripts/hooks/core/phase-gate.js +5 -0
- package/scripts/hooks/core/post-compact.js +1 -12
- package/scripts/hooks/core/research-gate.js +2 -12
- package/scripts/hooks/core/routing-gate.js +6 -0
- package/scripts/hooks/core/task-completed.js +12 -0
- package/scripts/hooks/core/task-created.js +83 -0
- package/scripts/hooks/core/worktree-lifecycle.js +1 -1
- package/scripts/hooks/entry/claude-code/config-change.js +6 -29
- package/scripts/hooks/entry/claude-code/instructions-loaded.js +5 -30
- package/scripts/hooks/entry/claude-code/post-compact.js +4 -31
- package/scripts/hooks/entry/claude-code/post-tool-use.js +121 -172
- package/scripts/hooks/entry/claude-code/pre-tool-use.js +260 -361
- package/scripts/hooks/entry/claude-code/session-end.js +4 -28
- package/scripts/hooks/entry/claude-code/session-start.js +205 -243
- package/scripts/hooks/entry/claude-code/setup.js +8 -49
- package/scripts/hooks/entry/claude-code/stop.js +40 -72
- package/scripts/hooks/entry/claude-code/task-completed.js +4 -28
- package/scripts/hooks/entry/claude-code/task-created.js +15 -0
- package/scripts/hooks/entry/claude-code/user-prompt-submit.js +113 -195
- package/scripts/hooks/entry/claude-code/worktree-create.js +6 -25
- package/scripts/hooks/entry/claude-code/worktree-remove.js +6 -25
- package/scripts/hooks/entry/shared/hook-runner.js +99 -0
- package/scripts/hooks/entry/shared/read-stdin.js +0 -2
- package/scripts/postinstall.js +2 -0
- package/scripts/registries/api-registry.js +0 -2
- package/scripts/registries/component-registry.js +5 -9
- package/scripts/registries/contract-scanner.js +2 -9
- package/scripts/registries/function-registry.js +0 -2
- package/scripts/registries/schema-registry.js +14 -18
- package/scripts/registries/service-registry.js +23 -27
package/scripts/flow-done.js
CHANGED
|
@@ -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
|
-
//
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
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
|
-
|
|
245
|
-
const normalizedType = (taskType || 'feature').toLowerCase();
|
|
195
|
+
const normalizedType = (taskType ?? 'feature').toLowerCase();
|
|
246
196
|
const gates = config.qualityGates?.[normalizedType]?.require
|
|
247
|
-
|
|
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
|
|
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
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
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
|
-
|
|
599
|
-
|
|
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
|
-
|
|
610
|
-
|
|
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
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
errors
|
|
624
|
-
failed.push('preRelease');
|
|
248
|
+
if (!result.passed) {
|
|
249
|
+
failed.push(gate);
|
|
250
|
+
if (result.errorOutput) {
|
|
251
|
+
errors[gate] = result.errorOutput;
|
|
625
252
|
}
|
|
626
|
-
}
|
|
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
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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 {
|