wogiflow 2.4.2 → 2.4.3
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/docs/claude-code-compatibility.md +27 -0
- package/.claude/settings.json +1 -1
- package/.workflow/models/registry.json +1 -1
- 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 +0 -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.js +5 -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 +2 -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/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/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/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
|
@@ -0,0 +1,786 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Wogi Flow - Quality Gate Handlers
|
|
5
|
+
*
|
|
6
|
+
* Individual gate handler functions extracted from flow-done.js runQualityGates.
|
|
7
|
+
* Each handler follows: function(context) => { passed, message, details }
|
|
8
|
+
*
|
|
9
|
+
* Context object shape:
|
|
10
|
+
* { taskId, taskType, normalizedType, config, gates,
|
|
11
|
+
* spawnSync, getModifiedFiles, truncateOutput,
|
|
12
|
+
* fileExists, readFile, readJson, safeJsonParse, safeJsonParseString, validateTaskId,
|
|
13
|
+
* color, success, warn, error,
|
|
14
|
+
* verificationProfile, getOutstandingFindings }
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
const fs = require('node:fs');
|
|
18
|
+
const path = require('node:path');
|
|
19
|
+
const { PATHS } = require('./flow-utils');
|
|
20
|
+
|
|
21
|
+
// v2.1 task enforcement
|
|
22
|
+
const { canExitLoop, getActiveLoop } = require('./flow-task-enforcer');
|
|
23
|
+
|
|
24
|
+
// v1.9.1 quality gate wiring
|
|
25
|
+
let wiringVerifier;
|
|
26
|
+
try {
|
|
27
|
+
wiringVerifier = require('./flow-wiring-verifier');
|
|
28
|
+
} catch (_err) {
|
|
29
|
+
wiringVerifier = null;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
let standardsGate;
|
|
33
|
+
try {
|
|
34
|
+
standardsGate = require('./flow-standards-gate');
|
|
35
|
+
} catch (_err) {
|
|
36
|
+
standardsGate = null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// v1.10 smart test discovery
|
|
40
|
+
let testDiscovery;
|
|
41
|
+
try {
|
|
42
|
+
testDiscovery = require('./flow-test-discovery');
|
|
43
|
+
} catch (_err) {
|
|
44
|
+
testDiscovery = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
// v1.9.7 registry manager — lazy-loaded
|
|
48
|
+
let _registryManagerModule = undefined;
|
|
49
|
+
function getRegistryManager() {
|
|
50
|
+
if (_registryManagerModule === undefined) {
|
|
51
|
+
try {
|
|
52
|
+
_registryManagerModule = require('./flow-registry-manager');
|
|
53
|
+
} catch (_err) {
|
|
54
|
+
_registryManagerModule = null;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
return _registryManagerModule;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ============================================================
|
|
61
|
+
// Gate Handlers
|
|
62
|
+
// ============================================================
|
|
63
|
+
|
|
64
|
+
function testsGate(ctx) {
|
|
65
|
+
if (!ctx.config.scripts?.test) {
|
|
66
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} tests (not configured to run)`);
|
|
67
|
+
return { passed: true, skipped: true };
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
console.log(' Running tests...');
|
|
71
|
+
const result = ctx.spawnSync('npm', ['test'], {
|
|
72
|
+
encoding: 'utf-8',
|
|
73
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
if (result.status === 0) {
|
|
77
|
+
ctx.success('tests passed');
|
|
78
|
+
return { passed: true };
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
ctx.error('tests failed');
|
|
82
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
83
|
+
if (errorOutput) {
|
|
84
|
+
console.log(ctx.color('dim', ' Error output:'));
|
|
85
|
+
const truncated = ctx.truncateOutput(errorOutput, 20, 1000);
|
|
86
|
+
truncated.split('\n').forEach(line => {
|
|
87
|
+
console.log(ctx.color('dim', ` ${line}`));
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return { passed: false, errorOutput };
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function lintGate(ctx) {
|
|
94
|
+
console.log(' Running lint...');
|
|
95
|
+
let result = ctx.spawnSync('npm', ['run', 'lint'], {
|
|
96
|
+
encoding: 'utf-8',
|
|
97
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (result.status !== 0) {
|
|
101
|
+
console.log(` ${ctx.color('yellow', '\u27F3')} lint issues found, attempting auto-fix...`);
|
|
102
|
+
ctx.spawnSync('npm', ['run', 'lint', '--', '--fix'], {
|
|
103
|
+
encoding: 'utf-8',
|
|
104
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
result = ctx.spawnSync('npm', ['run', 'lint'], {
|
|
108
|
+
encoding: 'utf-8',
|
|
109
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
if (result.status === 0) {
|
|
113
|
+
ctx.success('lint passed (auto-fixed)');
|
|
114
|
+
return { passed: true };
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
ctx.error('lint failed (manual fix required)');
|
|
118
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
119
|
+
if (errorOutput) {
|
|
120
|
+
console.log(ctx.color('dim', ' Remaining issues:'));
|
|
121
|
+
const truncated = ctx.truncateOutput(errorOutput, 15, 800);
|
|
122
|
+
truncated.split('\n').forEach(line => {
|
|
123
|
+
console.log(ctx.color('dim', ` ${line}`));
|
|
124
|
+
});
|
|
125
|
+
}
|
|
126
|
+
return { passed: false, errorOutput };
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ctx.success('lint passed');
|
|
130
|
+
return { passed: true };
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
function typecheckGate(ctx) {
|
|
134
|
+
console.log(' Running typecheck...');
|
|
135
|
+
const result = ctx.spawnSync('npm', ['run', 'typecheck'], {
|
|
136
|
+
encoding: 'utf-8',
|
|
137
|
+
stdio: ['pipe', 'pipe', 'pipe']
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
if (result.status === 0) {
|
|
141
|
+
ctx.success('typecheck passed');
|
|
142
|
+
return { passed: true };
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
ctx.error('typecheck failed');
|
|
146
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
147
|
+
if (errorOutput) {
|
|
148
|
+
console.log(ctx.color('dim', ' Type errors:'));
|
|
149
|
+
const truncated = ctx.truncateOutput(errorOutput, 20, 1000);
|
|
150
|
+
truncated.split('\n').forEach(line => {
|
|
151
|
+
console.log(ctx.color('dim', ` ${line}`));
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
return { passed: false, errorOutput };
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
function requestLogEntryGate(ctx) {
|
|
158
|
+
try {
|
|
159
|
+
const content = ctx.readFile(PATHS.requestLog, '');
|
|
160
|
+
if (content.includes(ctx.taskId)) {
|
|
161
|
+
ctx.success('requestLogEntry (found in request-log)');
|
|
162
|
+
} else {
|
|
163
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} requestLogEntry (add entry to request-log.md)`);
|
|
164
|
+
}
|
|
165
|
+
} catch (_err) {
|
|
166
|
+
if (process.env.DEBUG) console.error(`[DEBUG] requestLogEntry check: ${_err.message}`);
|
|
167
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} requestLogEntry (could not check)`);
|
|
168
|
+
}
|
|
169
|
+
// requestLogEntry is a soft gate — never fails
|
|
170
|
+
return { passed: true };
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function registryUpdateGate(ctx, gateName) {
|
|
174
|
+
if (gateName === 'appMapUpdate') {
|
|
175
|
+
ctx.warn("appMapUpdate is deprecated \u2014 update config.json qualityGates to use 'registryUpdate'");
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const registryMod = getRegistryManager();
|
|
179
|
+
if (!registryMod) {
|
|
180
|
+
ctx.warn('registryUpdate (registry manager not available \u2014 verify manually)');
|
|
181
|
+
return { passed: true };
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
try {
|
|
185
|
+
console.log(' Running registry update check...');
|
|
186
|
+
const modifiedFiles = ctx.getModifiedFiles();
|
|
187
|
+
|
|
188
|
+
const mapFiles = ['app-map.md', 'function-map.md', 'api-map.md', 'schema-map.md', 'service-map.md'];
|
|
189
|
+
const beforeHashes = {};
|
|
190
|
+
for (const mf of mapFiles) {
|
|
191
|
+
const mapPath = path.join(PATHS.state, mf);
|
|
192
|
+
try {
|
|
193
|
+
beforeHashes[mf] = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
|
|
194
|
+
} catch (_err) {
|
|
195
|
+
beforeHashes[mf] = 0;
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const { RegistryManager } = registryMod;
|
|
200
|
+
const manager = new RegistryManager();
|
|
201
|
+
manager.loadPlugins();
|
|
202
|
+
manager.detectStack();
|
|
203
|
+
manager.activatePlugins();
|
|
204
|
+
|
|
205
|
+
if (manager.activePlugins.length === 0) {
|
|
206
|
+
ctx.success('registryUpdate (no active registry plugins)');
|
|
207
|
+
return { passed: true };
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const scanResult = ctx.spawnSync('node', [
|
|
211
|
+
'-e',
|
|
212
|
+
`const {RegistryManager} = require(${JSON.stringify(path.join(__dirname, 'flow-registry-manager'))});
|
|
213
|
+
const m = new RegistryManager(); m.loadPlugins(); m.detectStack(); m.activatePlugins();
|
|
214
|
+
m.scanAll().then(r => { process.stderr.write('SCAN_RESULT:' + JSON.stringify(r)); process.exit(0); })
|
|
215
|
+
.catch(err => { process.stderr.write('SCAN_RESULT:' + JSON.stringify({error: err.message})); process.exit(1); });`
|
|
216
|
+
], {
|
|
217
|
+
encoding: 'utf-8',
|
|
218
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
219
|
+
timeout: 30000,
|
|
220
|
+
cwd: process.cwd()
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
if (scanResult.status !== 0) {
|
|
224
|
+
ctx.warn('registryUpdate (scan error \u2014 degraded to manual check)');
|
|
225
|
+
if (process.env.DEBUG) console.error(`[DEBUG] registry scan stderr: ${scanResult.stderr}`);
|
|
226
|
+
return { passed: true };
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const stderrOutput = scanResult.stderr || '';
|
|
230
|
+
const markerIdx = stderrOutput.indexOf('SCAN_RESULT:');
|
|
231
|
+
const jsonStr = markerIdx >= 0 ? stderrOutput.slice(markerIdx + 'SCAN_RESULT:'.length) : '{}';
|
|
232
|
+
const results = ctx.safeJsonParseString(jsonStr, {});
|
|
233
|
+
|
|
234
|
+
const updatedMaps = [];
|
|
235
|
+
for (const mf of mapFiles) {
|
|
236
|
+
const mapPath = path.join(PATHS.state, mf);
|
|
237
|
+
try {
|
|
238
|
+
const afterMtime = fs.existsSync(mapPath) ? fs.statSync(mapPath).mtimeMs : 0;
|
|
239
|
+
if (afterMtime > beforeHashes[mf]) {
|
|
240
|
+
updatedMaps.push(mf);
|
|
241
|
+
}
|
|
242
|
+
} catch (_err) {
|
|
243
|
+
// ignore
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const relevantExtensions = ['.js', '.ts', '.jsx', '.tsx', '.vue', '.svelte'];
|
|
248
|
+
const codeFiles = modifiedFiles.filter(f => relevantExtensions.some(ext => f.endsWith(ext)));
|
|
249
|
+
const nonTestFiles = codeFiles.filter(f => !f.includes('test') && !f.includes('spec') && !f.includes('__test'));
|
|
250
|
+
|
|
251
|
+
const activeIds = manager.activePlugins.map(p => p.constructor.id);
|
|
252
|
+
const scanSummary = Object.entries(results)
|
|
253
|
+
.filter(([_id, r]) => r.success && !r.empty)
|
|
254
|
+
.map(([id]) => id);
|
|
255
|
+
|
|
256
|
+
if (updatedMaps.length > 0) {
|
|
257
|
+
ctx.success(`registryUpdate (auto-scanned: ${updatedMaps.join(', ')} updated)`);
|
|
258
|
+
} else if (scanSummary.length > 0) {
|
|
259
|
+
ctx.success(`registryUpdate (scanned ${activeIds.join(', ')} \u2014 maps already current)`);
|
|
260
|
+
} else if (nonTestFiles.length === 0) {
|
|
261
|
+
ctx.success('registryUpdate (no registrable code files modified)');
|
|
262
|
+
} else {
|
|
263
|
+
ctx.success('registryUpdate (scanned \u2014 no new entries found)');
|
|
264
|
+
}
|
|
265
|
+
return { passed: true };
|
|
266
|
+
} catch (err) {
|
|
267
|
+
ctx.warn(`registryUpdate (error: ${ctx.truncateOutput(err.message, 3, 200)} \u2014 verify manually)`);
|
|
268
|
+
return { passed: true };
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
function loopCompleteGate(ctx) {
|
|
273
|
+
const activeLoop = getActiveLoop();
|
|
274
|
+
if (!activeLoop) {
|
|
275
|
+
ctx.success('loopComplete (no active loop session)');
|
|
276
|
+
return { passed: true };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
const exitResult = canExitLoop();
|
|
280
|
+
if (exitResult.canExit) {
|
|
281
|
+
ctx.success(`loopComplete (${exitResult.reason})`);
|
|
282
|
+
return { passed: true };
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
ctx.error(`loopComplete (${exitResult.pending ?? 0} pending, ${exitResult.failed ?? 0} failed)`);
|
|
286
|
+
return { passed: false, errorOutput: exitResult.message || 'Loop not complete' };
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
function noNewFeaturesGate(ctx) {
|
|
290
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} noNewFeatures (verify no behavior changes)`);
|
|
291
|
+
return { passed: true };
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
/**
|
|
295
|
+
* Check removal impact — extracted helper for nesting reduction.
|
|
296
|
+
* Returns a sub-gate result if there are orphaned refs, or null if clean/not applicable.
|
|
297
|
+
*/
|
|
298
|
+
function checkRemovalImpact(ctx) {
|
|
299
|
+
if (typeof wiringVerifier?.verifyRemovalImpact !== 'function') return null;
|
|
300
|
+
|
|
301
|
+
const modifiedFiles = ctx.getModifiedFiles();
|
|
302
|
+
if (modifiedFiles.length === 0) return null;
|
|
303
|
+
|
|
304
|
+
console.log(' Running removal impact check...');
|
|
305
|
+
const removalResult = wiringVerifier.verifyRemovalImpact(modifiedFiles);
|
|
306
|
+
if (removalResult.identifiersChecked === 0) return null;
|
|
307
|
+
|
|
308
|
+
if (removalResult.passed) {
|
|
309
|
+
ctx.success(`removalImpact (${removalResult.identifiersChecked} removed identifiers verified)`);
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
const orphanCount = removalResult.orphanedRefs.length;
|
|
314
|
+
ctx.error(`removalImpact (${orphanCount} orphaned reference${orphanCount !== 1 ? 's' : ''} to removed exports)`);
|
|
315
|
+
for (const ref of removalResult.orphanedRefs.slice(0, 5)) {
|
|
316
|
+
console.log(ctx.color('dim', ` - "${ref.identifier}" removed from ${ref.removedFrom}, still used in ${ref.totalRefs} file${ref.totalRefs !== 1 ? 's' : ''}`));
|
|
317
|
+
for (const consumer of ref.referencedBy.slice(0, 2)) {
|
|
318
|
+
console.log(ctx.color('dim', ` \u2192 ${consumer.file}`));
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return {
|
|
322
|
+
passed: false,
|
|
323
|
+
errorOutput: `${orphanCount} removed export${orphanCount !== 1 ? 's' : ''} still referenced by consumers`
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function integrationWiringGate(ctx) {
|
|
328
|
+
if (!wiringVerifier || typeof wiringVerifier.verifyWiring !== 'function') {
|
|
329
|
+
ctx.warn('integrationWiring (verifier module not available \u2014 install flow-wiring-verifier.js)');
|
|
330
|
+
if (process.env.DEBUG) console.error('[DEBUG] wiringVerifier module failed to load or missing verifyWiring export');
|
|
331
|
+
return { passed: true };
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
console.log(' Running integration wiring check...');
|
|
336
|
+
const result = wiringVerifier.verifyWiring(ctx.taskId);
|
|
337
|
+
const gateResult = { passed: true, subGates: {} };
|
|
338
|
+
|
|
339
|
+
if (!result.passed) {
|
|
340
|
+
const unwiredCount = result.unwired?.length ?? 0;
|
|
341
|
+
ctx.error(`integrationWiring (${unwiredCount} unwired file${unwiredCount !== 1 ? 's' : ''})`);
|
|
342
|
+
if (result.unwired) {
|
|
343
|
+
for (const item of result.unwired.slice(0, 5)) {
|
|
344
|
+
console.log(ctx.color('dim', ` - ${item.file || item}`));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
gateResult.passed = false;
|
|
348
|
+
gateResult.errorOutput = `${unwiredCount} files created but not imported/used anywhere`;
|
|
349
|
+
} else {
|
|
350
|
+
ctx.success(`integrationWiring (${result.verified ?? 0} files verified)`);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// v1.9.3: Removal impact check
|
|
354
|
+
const removalSub = checkRemovalImpact(ctx);
|
|
355
|
+
if (removalSub) {
|
|
356
|
+
gateResult.subGates.removalImpact = removalSub;
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return gateResult;
|
|
360
|
+
} catch (err) {
|
|
361
|
+
ctx.warn(`integrationWiring (verifier error \u2014 degraded to manual check: ${ctx.truncateOutput(err.message, 3, 200)})`);
|
|
362
|
+
return { passed: true };
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function standardsComplianceGate(ctx) {
|
|
367
|
+
if (!standardsGate || typeof standardsGate.runTaskStandardsCheck !== 'function') {
|
|
368
|
+
ctx.warn('standardsCompliance (checker module not available \u2014 install flow-standards-gate.js)');
|
|
369
|
+
if (process.env.DEBUG) console.error('[DEBUG] standardsGate module failed to load or missing runTaskStandardsCheck export');
|
|
370
|
+
return { passed: true };
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
try {
|
|
374
|
+
console.log(' Running standards compliance check...');
|
|
375
|
+
const modifiedFiles = ctx.getModifiedFiles();
|
|
376
|
+
const taskContext = { id: ctx.taskId, type: ctx.normalizedType };
|
|
377
|
+
const result = standardsGate.runTaskStandardsCheck(taskContext, modifiedFiles);
|
|
378
|
+
const mustFixCount = result.violations?.filter(v => v.severity === 'MUST_FIX' || v.severity === 'high').length ?? 0;
|
|
379
|
+
|
|
380
|
+
if (mustFixCount === 0) {
|
|
381
|
+
ctx.success(`standardsCompliance (${result.violations?.length ?? 0} suggestions, 0 must-fix)`);
|
|
382
|
+
return { passed: true };
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
ctx.error(`standardsCompliance (${mustFixCount} must-fix violation${mustFixCount !== 1 ? 's' : ''})`);
|
|
386
|
+
for (const v of (result.violations ?? []).filter(v => v.severity === 'MUST_FIX' || v.severity === 'high').slice(0, 5)) {
|
|
387
|
+
console.log(ctx.color('dim', ` - ${v.file || ''}:${v.line || ''} ${v.issue || v.message || ''}`));
|
|
388
|
+
}
|
|
389
|
+
return { passed: false, errorOutput: `${mustFixCount} standards violations require fixing` };
|
|
390
|
+
} catch (err) {
|
|
391
|
+
ctx.warn(`standardsCompliance (checker error \u2014 degraded to manual check: ${ctx.truncateOutput(err.message, 3, 200)})`);
|
|
392
|
+
return { passed: true };
|
|
393
|
+
}
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function outstandingFindingsGate(ctx) {
|
|
397
|
+
const outstanding = ctx.getOutstandingFindings();
|
|
398
|
+
if (!outstanding.hasOutstanding) {
|
|
399
|
+
ctx.success('outstandingFindings (no unresolved critical/high findings)');
|
|
400
|
+
return { passed: true };
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
ctx.error(`outstandingFindings (${outstanding.count} unresolved finding${outstanding.count !== 1 ? 's' : ''} from last review)`);
|
|
404
|
+
for (const f of outstanding.findings.slice(0, 5)) {
|
|
405
|
+
console.log(ctx.color('dim', ` - [${f.severity}] ${f.file || ''}:${f.line || ''} ${f.issue || ''}`));
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
passed: false,
|
|
409
|
+
errorOutput: `${outstanding.count} unresolved critical/high findings from last review. Fix them or waive with /wogi-triage.`
|
|
410
|
+
};
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function preReleaseGate(ctx) {
|
|
414
|
+
console.log(' Running pre-release checks...');
|
|
415
|
+
let preReleaseFailed = false;
|
|
416
|
+
|
|
417
|
+
const outstanding = ctx.getOutstandingFindings();
|
|
418
|
+
if (outstanding.hasOutstanding) {
|
|
419
|
+
ctx.error(`preRelease: ${outstanding.count} unresolved findings from last review`);
|
|
420
|
+
preReleaseFailed = true;
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
if (ctx.config.scripts?.lint) {
|
|
424
|
+
const lintResult = ctx.spawnSync('npm', ['run', 'lint'], {
|
|
425
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
|
|
426
|
+
});
|
|
427
|
+
if (lintResult.status !== 0) {
|
|
428
|
+
ctx.error('preRelease: lint failed');
|
|
429
|
+
preReleaseFailed = true;
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (ctx.config.scripts?.typecheck) {
|
|
434
|
+
const tcResult = ctx.spawnSync('npm', ['run', 'typecheck'], {
|
|
435
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
|
|
436
|
+
});
|
|
437
|
+
if (tcResult.status !== 0) {
|
|
438
|
+
ctx.error('preRelease: typecheck failed');
|
|
439
|
+
preReleaseFailed = true;
|
|
440
|
+
}
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
if (!preReleaseFailed) {
|
|
444
|
+
ctx.success('preRelease (codebase is releasable)');
|
|
445
|
+
return { passed: true };
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
return { passed: false, errorOutput: 'Codebase is not in a releasable state' };
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function learningEnforcementGate(ctx) {
|
|
452
|
+
try {
|
|
453
|
+
const feedbackPath = path.join(PATHS.state, 'feedback-patterns.md');
|
|
454
|
+
const content = ctx.readFile(feedbackPath, '');
|
|
455
|
+
if (content.includes(ctx.taskId)) {
|
|
456
|
+
ctx.success('learningEnforcement (pattern recorded in feedback-patterns.md)');
|
|
457
|
+
} else {
|
|
458
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} learningEnforcement (add bug pattern to feedback-patterns.md)`);
|
|
459
|
+
}
|
|
460
|
+
} catch (_err) {
|
|
461
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} learningEnforcement (could not check feedback-patterns.md)`);
|
|
462
|
+
}
|
|
463
|
+
return { passed: true };
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
function resolutionPopulatedGate(ctx) {
|
|
467
|
+
try {
|
|
468
|
+
const changesDir = path.join(PATHS.workflow, 'changes');
|
|
469
|
+
const specPath = path.join(changesDir, `${ctx.taskId}.md`);
|
|
470
|
+
const content = ctx.readFile(specPath, '');
|
|
471
|
+
if (content) {
|
|
472
|
+
const lower = content.toLowerCase();
|
|
473
|
+
if (lower.includes('resolution') || lower.includes('root cause') || lower.includes('fix')) {
|
|
474
|
+
ctx.success('resolutionPopulated (resolution documented in spec)');
|
|
475
|
+
} else {
|
|
476
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} resolutionPopulated (add resolution/root cause to spec)`);
|
|
477
|
+
}
|
|
478
|
+
} else {
|
|
479
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} resolutionPopulated (no spec file found)`);
|
|
480
|
+
}
|
|
481
|
+
} catch (_err) {
|
|
482
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} resolutionPopulated (could not check)`);
|
|
483
|
+
}
|
|
484
|
+
return { passed: true };
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function smokeTestGate(ctx) {
|
|
488
|
+
try {
|
|
489
|
+
const modifiedFiles = ctx.getModifiedFiles();
|
|
490
|
+
const jsFiles = modifiedFiles.filter(f => f.endsWith('.js'));
|
|
491
|
+
|
|
492
|
+
if (jsFiles.length === 0) {
|
|
493
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} smokeTest (no JS files modified \u2014 nothing to check)`);
|
|
494
|
+
return { passed: true };
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
let allPassed = true;
|
|
498
|
+
for (const file of jsFiles) {
|
|
499
|
+
const result = ctx.spawnSync('node', ['--check', file], {
|
|
500
|
+
encoding: 'utf-8', stdio: ['pipe', 'pipe', 'pipe']
|
|
501
|
+
});
|
|
502
|
+
if (result.status !== 0) {
|
|
503
|
+
ctx.error(`smokeTest: syntax error in ${file}`);
|
|
504
|
+
allPassed = false;
|
|
505
|
+
break;
|
|
506
|
+
}
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
if (allPassed) {
|
|
510
|
+
ctx.success(`smokeTest (${jsFiles.length} file${jsFiles.length !== 1 ? 's' : ''} pass syntax check)`);
|
|
511
|
+
return { passed: true };
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
return { passed: false, errorOutput: 'Syntax errors in modified files' };
|
|
515
|
+
} catch (err) {
|
|
516
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} smokeTest (could not run: ${ctx.truncateOutput(err.message, 3, 200)})`);
|
|
517
|
+
return { passed: true };
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
function generatedTestsPassGate(ctx) {
|
|
522
|
+
if (!ctx.config.testing?.enabled || !ctx.config.testing?.generation?.autoGenerate) {
|
|
523
|
+
console.log(` ${ctx.color('dim', '\u00B7')} generatedTestsPass (testing disabled)`);
|
|
524
|
+
return { passed: true };
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (!ctx.validateTaskId(ctx.taskId).valid) {
|
|
528
|
+
ctx.warn('generatedTestsPass (invalid task ID)');
|
|
529
|
+
return { passed: true };
|
|
530
|
+
}
|
|
531
|
+
|
|
532
|
+
const testDir = path.join(PATHS.workflow, 'tests', 'generated', ctx.taskId);
|
|
533
|
+
if (!fs.existsSync(testDir)) {
|
|
534
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} generatedTestsPass (no generated tests found)`);
|
|
535
|
+
return { passed: true };
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if (!ctx.config.scripts?.test) {
|
|
539
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} generatedTestsPass (no test command configured)`);
|
|
540
|
+
return { passed: true };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
console.log(' Running generated tests...');
|
|
544
|
+
const result = ctx.spawnSync('npm', ['test', '--', testDir], {
|
|
545
|
+
encoding: 'utf-8',
|
|
546
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
547
|
+
timeout: 120000
|
|
548
|
+
});
|
|
549
|
+
|
|
550
|
+
if (result.status === 0) {
|
|
551
|
+
ctx.success('generatedTestsPass');
|
|
552
|
+
return { passed: true };
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
ctx.error('generatedTestsPass');
|
|
556
|
+
const errorOutput = result.stderr || result.stdout || '';
|
|
557
|
+
if (errorOutput) {
|
|
558
|
+
console.log(ctx.color('dim', ' Error output:'));
|
|
559
|
+
const truncated = ctx.truncateOutput(errorOutput, 15, 800);
|
|
560
|
+
truncated.split('\n').forEach(line => {
|
|
561
|
+
console.log(ctx.color('dim', ` ${line}`));
|
|
562
|
+
});
|
|
563
|
+
}
|
|
564
|
+
return { passed: false, errorOutput };
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
function verificationGate(ctx, gateName) {
|
|
568
|
+
const isUI = gateName === 'uiVerification';
|
|
569
|
+
|
|
570
|
+
// Project-type-aware gating
|
|
571
|
+
const detected = ctx.config.testing?.detected;
|
|
572
|
+
if (detected?.projectType) {
|
|
573
|
+
const pt = detected.projectType;
|
|
574
|
+
if (isUI && (pt === 'backend' || pt === 'library')) {
|
|
575
|
+
console.log(` ${ctx.color('dim', '\u00B7')} ${gateName} (not applicable \u2014 ${pt} project)`);
|
|
576
|
+
return { passed: true, skipped: true };
|
|
577
|
+
}
|
|
578
|
+
if (!isUI && (pt === 'frontend' || pt === 'library')) {
|
|
579
|
+
console.log(` ${ctx.color('dim', '\u00B7')} ${gateName} (not applicable \u2014 ${pt} project)`);
|
|
580
|
+
return { passed: true, skipped: true };
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
const gateModes = isUI ? ['ui', 'full', 'auto'] : ['api', 'full', 'auto'];
|
|
585
|
+
const testingMode = ctx.config.testing?.mode ?? 'off';
|
|
586
|
+
|
|
587
|
+
if (!ctx.config.testing?.enabled || !gateModes.includes(testingMode)) {
|
|
588
|
+
console.log(` ${ctx.color('dim', '\u00B7')} ${gateName} (testing disabled or mode excludes ${isUI ? 'UI' : 'API'})`);
|
|
589
|
+
return { passed: true };
|
|
590
|
+
}
|
|
591
|
+
|
|
592
|
+
if (!ctx.validateTaskId(ctx.taskId).valid) {
|
|
593
|
+
ctx.warn(`${gateName} (invalid task ID)`);
|
|
594
|
+
return { passed: true };
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
try {
|
|
598
|
+
const label = isUI ? 'UI' : 'API';
|
|
599
|
+
console.log(` Running ${label} verification...`);
|
|
600
|
+
const scriptPath = isUI
|
|
601
|
+
? path.join(__dirname, 'flow-test-ui.js')
|
|
602
|
+
: path.join(__dirname, 'flow-test-api.js');
|
|
603
|
+
const fnName = isUI ? 'runUITests' : 'runAPITests';
|
|
604
|
+
const testResult = ctx.spawnSync('node', ['-e', [
|
|
605
|
+
`const { ${fnName} } = require(${JSON.stringify(scriptPath)});`,
|
|
606
|
+
`${fnName}(${JSON.stringify(ctx.taskId)}).then(r => {`,
|
|
607
|
+
' process.stdout.write(JSON.stringify(r));',
|
|
608
|
+
' process.exit(r.summary && r.summary.failed > 0 ? 1 : 0);',
|
|
609
|
+
'}).catch(err => {',
|
|
610
|
+
' process.stdout.write(JSON.stringify({ error: err.message, summary: { passed: 0, failed: 0, total: 0 } }));',
|
|
611
|
+
' process.exit(2);',
|
|
612
|
+
'});'
|
|
613
|
+
].join('\n')], {
|
|
614
|
+
encoding: 'utf-8',
|
|
615
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
616
|
+
cwd: process.cwd(),
|
|
617
|
+
timeout: 120000
|
|
618
|
+
});
|
|
619
|
+
|
|
620
|
+
if (testResult.status === 2) {
|
|
621
|
+
const parsed = ctx.safeJsonParseString(testResult.stdout, {});
|
|
622
|
+
const errMsg = parsed.error || testResult.stderr?.trim()?.slice(0, 200) || 'Unknown error';
|
|
623
|
+
ctx.warn(`${gateName} (error: ${errMsg})`);
|
|
624
|
+
return { passed: true };
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
const report = ctx.safeJsonParseString(testResult.stdout ?? '{}', {});
|
|
628
|
+
const summary = report.summary ?? { passed: 0, failed: 0, total: 0 };
|
|
629
|
+
|
|
630
|
+
if (summary.failed === 0) {
|
|
631
|
+
ctx.success(`${gateName} (${summary.passed}/${summary.total} passed)`);
|
|
632
|
+
return { passed: true };
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
ctx.error(`${gateName} (${summary.failed} failed)`);
|
|
636
|
+
const failedItems = isUI
|
|
637
|
+
? (report.assertions ?? []).filter(a => a.status === 'failed')
|
|
638
|
+
: (report.endpoints ?? []).flatMap(e => (e.tests ?? []).filter(t => t.status === 'failed'));
|
|
639
|
+
for (const item of failedItems.slice(0, 5)) {
|
|
640
|
+
console.log(ctx.color('dim', ` - ${item.description || item.name || 'test failed'}`));
|
|
641
|
+
}
|
|
642
|
+
return { passed: false, errorOutput: JSON.stringify(failedItems) };
|
|
643
|
+
} catch (err) {
|
|
644
|
+
ctx.warn(`${gateName} (error: ${err.message})`);
|
|
645
|
+
return { passed: true };
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
function uiVerificationGateAfter(ctx, gateName) {
|
|
650
|
+
// Scenario verification check (only for API verification)
|
|
651
|
+
if (gateName !== 'apiVerification') return null;
|
|
652
|
+
if (!ctx.validateTaskId(ctx.taskId).valid) return null;
|
|
653
|
+
|
|
654
|
+
const scenarioReportPath = path.join(PATHS.workflow, 'verifications', `${ctx.taskId}-scenarios.json`);
|
|
655
|
+
if (!fs.existsSync(scenarioReportPath)) return null;
|
|
656
|
+
|
|
657
|
+
try {
|
|
658
|
+
const scenarioReport = ctx.safeJsonParse(scenarioReportPath, null);
|
|
659
|
+
if (!scenarioReport?.summary) return null;
|
|
660
|
+
|
|
661
|
+
const ss = scenarioReport.summary;
|
|
662
|
+
if (ss.failed === 0 && ss.total > 0) {
|
|
663
|
+
ctx.success(`scenarioVerification (${ss.passed}/${ss.total} scenarios passed)`);
|
|
664
|
+
} else if (ss.failed > 0) {
|
|
665
|
+
ctx.error(`scenarioVerification (${ss.failed} scenarios failed)`);
|
|
666
|
+
const failedScenarios = (scenarioReport.scenarios ?? []).filter(s => !s.passed);
|
|
667
|
+
for (const sc of failedScenarios.slice(0, 5)) {
|
|
668
|
+
console.log(ctx.color('dim', ` - ${sc.name || 'unnamed scenario'}: ${sc.error || 'assertions failed'}`));
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
} catch (_err) {
|
|
672
|
+
// Non-fatal
|
|
673
|
+
}
|
|
674
|
+
return null;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function testDiscoveryGate(ctx) {
|
|
678
|
+
const discoveryConfig = ctx.config.testing?.discovery ?? {};
|
|
679
|
+
if (!discoveryConfig.enabled) {
|
|
680
|
+
console.log(` ${ctx.color('dim', '\u00B7')} testDiscovery (disabled \u2014 set testing.discovery.enabled in config)`);
|
|
681
|
+
return { passed: true };
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
if (!testDiscovery || typeof testDiscovery.runTestDiscoveryGate !== 'function') {
|
|
685
|
+
ctx.warn('testDiscovery (module not available \u2014 install flow-test-discovery.js)');
|
|
686
|
+
return { passed: true };
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
try {
|
|
690
|
+
console.log(' Running test discovery gate...');
|
|
691
|
+
const discoveryResult = testDiscovery.runTestDiscoveryGate(ctx.taskId, PATHS.root);
|
|
692
|
+
|
|
693
|
+
if (discoveryResult.passed) {
|
|
694
|
+
ctx.success(`testDiscovery (${discoveryResult.message})`);
|
|
695
|
+
return { passed: true };
|
|
696
|
+
}
|
|
697
|
+
|
|
698
|
+
ctx.error(`testDiscovery (${discoveryResult.message})`);
|
|
699
|
+
if (discoveryResult.report?.passToPass?.failed) {
|
|
700
|
+
for (const f of discoveryResult.report.passToPass.failed.slice(0, 5)) {
|
|
701
|
+
console.log(ctx.color('dim', ` - ${f}`));
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
return { passed: false, errorOutput: discoveryResult.message };
|
|
705
|
+
} catch (err) {
|
|
706
|
+
ctx.warn(`testDiscovery (error: ${ctx.truncateOutput(err.message, 3, 200)})`);
|
|
707
|
+
return { passed: true };
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
function unknownGate(ctx, gateName) {
|
|
712
|
+
console.log(` ${ctx.color('yellow', '\u25CB')} ${gateName} (manual check)`);
|
|
713
|
+
return { passed: true };
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// ============================================================
|
|
717
|
+
// Gate Registry
|
|
718
|
+
// ============================================================
|
|
719
|
+
|
|
720
|
+
const GATE_REGISTRY = {
|
|
721
|
+
tests: testsGate,
|
|
722
|
+
lint: lintGate,
|
|
723
|
+
typecheck: typecheckGate,
|
|
724
|
+
requestLogEntry: requestLogEntryGate,
|
|
725
|
+
appMapUpdate: registryUpdateGate,
|
|
726
|
+
registryUpdate: registryUpdateGate,
|
|
727
|
+
loopComplete: loopCompleteGate,
|
|
728
|
+
noNewFeatures: noNewFeaturesGate,
|
|
729
|
+
integrationWiring: integrationWiringGate,
|
|
730
|
+
standardsCompliance: standardsComplianceGate,
|
|
731
|
+
outstandingFindings: outstandingFindingsGate,
|
|
732
|
+
preRelease: preReleaseGate,
|
|
733
|
+
learningEnforcement: learningEnforcementGate,
|
|
734
|
+
resolutionPopulated: resolutionPopulatedGate,
|
|
735
|
+
smokeTest: smokeTestGate,
|
|
736
|
+
generatedTestsPass: generatedTestsPassGate,
|
|
737
|
+
uiVerification: verificationGate,
|
|
738
|
+
apiVerification: verificationGate,
|
|
739
|
+
testDiscovery: testDiscoveryGate,
|
|
740
|
+
};
|
|
741
|
+
|
|
742
|
+
/**
|
|
743
|
+
* Run a single gate by name.
|
|
744
|
+
* @param {string} gateName
|
|
745
|
+
* @param {object} ctx - Gate context
|
|
746
|
+
* @returns {{ passed: boolean, errorOutput?: string, subGates?: object }}
|
|
747
|
+
*/
|
|
748
|
+
function runGate(gateName, ctx) {
|
|
749
|
+
const handler = GATE_REGISTRY[gateName];
|
|
750
|
+
if (!handler) {
|
|
751
|
+
return unknownGate(ctx, gateName);
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const result = handler(ctx, gateName);
|
|
755
|
+
|
|
756
|
+
// Post-gate hooks (e.g., scenario verification after apiVerification)
|
|
757
|
+
if (gateName === 'uiVerification' || gateName === 'apiVerification') {
|
|
758
|
+
uiVerificationGateAfter(ctx, gateName);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
return result;
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
module.exports = {
|
|
765
|
+
GATE_REGISTRY,
|
|
766
|
+
runGate,
|
|
767
|
+
// Export individual handlers for direct testing
|
|
768
|
+
testsGate,
|
|
769
|
+
lintGate,
|
|
770
|
+
typecheckGate,
|
|
771
|
+
requestLogEntryGate,
|
|
772
|
+
registryUpdateGate,
|
|
773
|
+
loopCompleteGate,
|
|
774
|
+
noNewFeaturesGate,
|
|
775
|
+
integrationWiringGate,
|
|
776
|
+
standardsComplianceGate,
|
|
777
|
+
outstandingFindingsGate,
|
|
778
|
+
preReleaseGate,
|
|
779
|
+
learningEnforcementGate,
|
|
780
|
+
resolutionPopulatedGate,
|
|
781
|
+
smokeTestGate,
|
|
782
|
+
generatedTestsPassGate,
|
|
783
|
+
verificationGate,
|
|
784
|
+
testDiscoveryGate,
|
|
785
|
+
unknownGate,
|
|
786
|
+
};
|