scene-capability-engine 3.6.45 → 3.6.46
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/CHANGELOG.md +11 -0
- package/docs/releases/README.md +1 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/README.md +1 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/package.json +4 -2
- package/scripts/auto-strategy-router.js +231 -0
- package/scripts/capability-mapping-report.js +339 -0
- package/scripts/check-branding-consistency.js +140 -0
- package/scripts/check-sce-tracking.js +54 -0
- package/scripts/check-skip-allowlist.js +94 -0
- package/scripts/errorbook-registry-health-gate.js +172 -0
- package/scripts/errorbook-release-gate.js +132 -0
- package/scripts/failure-attribution-repair.js +317 -0
- package/scripts/git-managed-gate.js +464 -0
- package/scripts/interactive-approval-event-projection.js +400 -0
- package/scripts/interactive-approval-workflow.js +829 -0
- package/scripts/interactive-authorization-tier-evaluate.js +413 -0
- package/scripts/interactive-change-plan-gate.js +225 -0
- package/scripts/interactive-context-bridge.js +617 -0
- package/scripts/interactive-customization-loop.js +1690 -0
- package/scripts/interactive-dialogue-governance.js +842 -0
- package/scripts/interactive-feedback-log.js +253 -0
- package/scripts/interactive-flow-smoke.js +238 -0
- package/scripts/interactive-flow.js +1059 -0
- package/scripts/interactive-governance-report.js +1112 -0
- package/scripts/interactive-intent-build.js +707 -0
- package/scripts/interactive-loop-smoke.js +215 -0
- package/scripts/interactive-moqui-adapter.js +304 -0
- package/scripts/interactive-plan-build.js +426 -0
- package/scripts/interactive-runtime-policy-evaluate.js +495 -0
- package/scripts/interactive-work-order-build.js +552 -0
- package/scripts/matrix-regression-gate.js +167 -0
- package/scripts/moqui-core-regression-suite.js +397 -0
- package/scripts/moqui-lexicon-audit.js +651 -0
- package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
- package/scripts/moqui-matrix-remediation-queue.js +852 -0
- package/scripts/moqui-metadata-extract.js +1340 -0
- package/scripts/moqui-rebuild-gate.js +167 -0
- package/scripts/moqui-release-summary.js +729 -0
- package/scripts/moqui-standard-rebuild.js +1370 -0
- package/scripts/moqui-template-baseline-report.js +682 -0
- package/scripts/npm-package-runtime-asset-check.js +221 -0
- package/scripts/problem-closure-gate.js +441 -0
- package/scripts/release-asset-integrity-check.js +216 -0
- package/scripts/release-asset-nonempty-normalize.js +166 -0
- package/scripts/release-drift-evaluate.js +223 -0
- package/scripts/release-drift-signals.js +255 -0
- package/scripts/release-governance-snapshot-export.js +132 -0
- package/scripts/release-ops-weekly-summary.js +934 -0
- package/scripts/release-risk-remediation-bundle.js +315 -0
- package/scripts/release-weekly-ops-gate.js +423 -0
- package/scripts/state-migration-reconciliation-gate.js +110 -0
- package/scripts/state-storage-tiering-audit.js +337 -0
- package/scripts/steering-content-audit.js +393 -0
- package/scripts/symbol-evidence-locate.js +366 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs-extra');
|
|
6
|
+
const { spawn, spawnSync } = require('child_process');
|
|
7
|
+
const {
|
|
8
|
+
DEFAULT_BASELINE,
|
|
9
|
+
DEFAULT_OUT,
|
|
10
|
+
DEFAULT_LINES_OUT,
|
|
11
|
+
DEFAULT_MARKDOWN_OUT,
|
|
12
|
+
DEFAULT_BATCH_JSON_OUT,
|
|
13
|
+
DEFAULT_COMMANDS_OUT,
|
|
14
|
+
DEFAULT_TOP_TEMPLATES,
|
|
15
|
+
DEFAULT_PHASE_HIGH_PARALLEL,
|
|
16
|
+
DEFAULT_PHASE_HIGH_AGENT_BUDGET,
|
|
17
|
+
DEFAULT_PHASE_MEDIUM_PARALLEL,
|
|
18
|
+
DEFAULT_PHASE_MEDIUM_AGENT_BUDGET,
|
|
19
|
+
DEFAULT_PHASE_COOLDOWN_SECONDS
|
|
20
|
+
} = require('./moqui-matrix-remediation-queue');
|
|
21
|
+
|
|
22
|
+
const DEFAULT_HIGH_GOALS = '.sce/auto/matrix-remediation.goals.high.json';
|
|
23
|
+
const DEFAULT_MEDIUM_GOALS = '.sce/auto/matrix-remediation.goals.medium.json';
|
|
24
|
+
const DEFAULT_HIGH_LINES = '.sce/auto/matrix-remediation.high.lines';
|
|
25
|
+
const DEFAULT_MEDIUM_LINES = '.sce/auto/matrix-remediation.medium.lines';
|
|
26
|
+
const DEFAULT_CLUSTER_GOALS = '.sce/auto/matrix-remediation.capability-clusters.json';
|
|
27
|
+
const DEFAULT_HIGH_RETRY_MAX_ROUNDS = 3;
|
|
28
|
+
const DEFAULT_MEDIUM_RETRY_MAX_ROUNDS = 2;
|
|
29
|
+
const DEFAULT_PHASE_RECOVERY_ATTEMPTS = 2;
|
|
30
|
+
const DEFAULT_PHASE_RECOVERY_COOLDOWN_SECONDS = 30;
|
|
31
|
+
|
|
32
|
+
function parseArgs(argv) {
|
|
33
|
+
const options = {
|
|
34
|
+
highGoals: DEFAULT_HIGH_GOALS,
|
|
35
|
+
mediumGoals: DEFAULT_MEDIUM_GOALS,
|
|
36
|
+
highLines: DEFAULT_HIGH_LINES,
|
|
37
|
+
mediumLines: DEFAULT_MEDIUM_LINES,
|
|
38
|
+
phaseHighParallel: DEFAULT_PHASE_HIGH_PARALLEL,
|
|
39
|
+
phaseHighAgentBudget: DEFAULT_PHASE_HIGH_AGENT_BUDGET,
|
|
40
|
+
phaseMediumParallel: DEFAULT_PHASE_MEDIUM_PARALLEL,
|
|
41
|
+
phaseMediumAgentBudget: DEFAULT_PHASE_MEDIUM_AGENT_BUDGET,
|
|
42
|
+
phaseCooldownSeconds: DEFAULT_PHASE_COOLDOWN_SECONDS,
|
|
43
|
+
highRetryMaxRounds: DEFAULT_HIGH_RETRY_MAX_ROUNDS,
|
|
44
|
+
mediumRetryMaxRounds: DEFAULT_MEDIUM_RETRY_MAX_ROUNDS,
|
|
45
|
+
phaseRecoveryAttempts: DEFAULT_PHASE_RECOVERY_ATTEMPTS,
|
|
46
|
+
phaseRecoveryCooldownSeconds: DEFAULT_PHASE_RECOVERY_COOLDOWN_SECONDS,
|
|
47
|
+
baseline: null,
|
|
48
|
+
queueOut: DEFAULT_OUT,
|
|
49
|
+
queueLinesOut: DEFAULT_LINES_OUT,
|
|
50
|
+
queueMarkdownOut: DEFAULT_MARKDOWN_OUT,
|
|
51
|
+
queueBatchJsonOut: DEFAULT_BATCH_JSON_OUT,
|
|
52
|
+
queueCommandsOut: DEFAULT_COMMANDS_OUT,
|
|
53
|
+
clusterGoals: null,
|
|
54
|
+
clusterHighGoalsOut: null,
|
|
55
|
+
clusterMediumGoalsOut: null,
|
|
56
|
+
minDeltaAbs: 0,
|
|
57
|
+
topTemplates: DEFAULT_TOP_TEMPLATES,
|
|
58
|
+
noFallbackLines: false,
|
|
59
|
+
continueOnError: false,
|
|
60
|
+
dryRun: false,
|
|
61
|
+
json: false,
|
|
62
|
+
sceBin: null
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
66
|
+
const token = argv[index];
|
|
67
|
+
const next = argv[index + 1];
|
|
68
|
+
if (token === '--high-goals' && next) {
|
|
69
|
+
options.highGoals = next;
|
|
70
|
+
index += 1;
|
|
71
|
+
} else if (token === '--medium-goals' && next) {
|
|
72
|
+
options.mediumGoals = next;
|
|
73
|
+
index += 1;
|
|
74
|
+
} else if (token === '--high-lines' && next) {
|
|
75
|
+
options.highLines = next;
|
|
76
|
+
index += 1;
|
|
77
|
+
} else if (token === '--medium-lines' && next) {
|
|
78
|
+
options.mediumLines = next;
|
|
79
|
+
index += 1;
|
|
80
|
+
} else if (token === '--phase-high-parallel' && next) {
|
|
81
|
+
options.phaseHighParallel = Number(next);
|
|
82
|
+
index += 1;
|
|
83
|
+
} else if (token === '--phase-high-agent-budget' && next) {
|
|
84
|
+
options.phaseHighAgentBudget = Number(next);
|
|
85
|
+
index += 1;
|
|
86
|
+
} else if (token === '--phase-medium-parallel' && next) {
|
|
87
|
+
options.phaseMediumParallel = Number(next);
|
|
88
|
+
index += 1;
|
|
89
|
+
} else if (token === '--phase-medium-agent-budget' && next) {
|
|
90
|
+
options.phaseMediumAgentBudget = Number(next);
|
|
91
|
+
index += 1;
|
|
92
|
+
} else if (token === '--phase-cooldown-seconds' && next) {
|
|
93
|
+
options.phaseCooldownSeconds = Number(next);
|
|
94
|
+
index += 1;
|
|
95
|
+
} else if (token === '--high-retry-max-rounds' && next) {
|
|
96
|
+
options.highRetryMaxRounds = Number(next);
|
|
97
|
+
index += 1;
|
|
98
|
+
} else if (token === '--medium-retry-max-rounds' && next) {
|
|
99
|
+
options.mediumRetryMaxRounds = Number(next);
|
|
100
|
+
index += 1;
|
|
101
|
+
} else if (token === '--phase-recovery-attempts' && next) {
|
|
102
|
+
options.phaseRecoveryAttempts = Number(next);
|
|
103
|
+
index += 1;
|
|
104
|
+
} else if (token === '--phase-recovery-cooldown-seconds' && next) {
|
|
105
|
+
options.phaseRecoveryCooldownSeconds = Number(next);
|
|
106
|
+
index += 1;
|
|
107
|
+
} else if (token === '--baseline' && next) {
|
|
108
|
+
options.baseline = next;
|
|
109
|
+
index += 1;
|
|
110
|
+
} else if (token === '--queue-out' && next) {
|
|
111
|
+
options.queueOut = next;
|
|
112
|
+
index += 1;
|
|
113
|
+
} else if (token === '--queue-lines-out' && next) {
|
|
114
|
+
options.queueLinesOut = next;
|
|
115
|
+
index += 1;
|
|
116
|
+
} else if (token === '--queue-markdown-out' && next) {
|
|
117
|
+
options.queueMarkdownOut = next;
|
|
118
|
+
index += 1;
|
|
119
|
+
} else if (token === '--queue-batch-json-out' && next) {
|
|
120
|
+
options.queueBatchJsonOut = next;
|
|
121
|
+
index += 1;
|
|
122
|
+
} else if (token === '--queue-commands-out' && next) {
|
|
123
|
+
options.queueCommandsOut = next;
|
|
124
|
+
index += 1;
|
|
125
|
+
} else if (token === '--cluster-goals' && next) {
|
|
126
|
+
options.clusterGoals = next;
|
|
127
|
+
index += 1;
|
|
128
|
+
} else if (token === '--cluster-high-goals-out' && next) {
|
|
129
|
+
options.clusterHighGoalsOut = next;
|
|
130
|
+
index += 1;
|
|
131
|
+
} else if (token === '--cluster-medium-goals-out' && next) {
|
|
132
|
+
options.clusterMediumGoalsOut = next;
|
|
133
|
+
index += 1;
|
|
134
|
+
} else if (token === '--min-delta-abs' && next) {
|
|
135
|
+
options.minDeltaAbs = Number(next);
|
|
136
|
+
index += 1;
|
|
137
|
+
} else if (token === '--top-templates' && next) {
|
|
138
|
+
options.topTemplates = Number(next);
|
|
139
|
+
index += 1;
|
|
140
|
+
} else if (token === '--sce-bin' && next) {
|
|
141
|
+
options.sceBin = next;
|
|
142
|
+
index += 1;
|
|
143
|
+
} else if (token === '--no-fallback-lines') {
|
|
144
|
+
options.noFallbackLines = true;
|
|
145
|
+
} else if (token === '--continue-on-error') {
|
|
146
|
+
options.continueOnError = true;
|
|
147
|
+
} else if (token === '--dry-run') {
|
|
148
|
+
options.dryRun = true;
|
|
149
|
+
} else if (token === '--json') {
|
|
150
|
+
options.json = true;
|
|
151
|
+
} else if (token === '-h' || token === '--help') {
|
|
152
|
+
printHelpAndExit(0);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
validatePositiveInt(options.phaseHighParallel, '--phase-high-parallel');
|
|
157
|
+
validatePositiveInt(options.phaseHighAgentBudget, '--phase-high-agent-budget');
|
|
158
|
+
validatePositiveInt(options.phaseMediumParallel, '--phase-medium-parallel');
|
|
159
|
+
validatePositiveInt(options.phaseMediumAgentBudget, '--phase-medium-agent-budget');
|
|
160
|
+
validateNonNegativeInt(options.phaseCooldownSeconds, '--phase-cooldown-seconds');
|
|
161
|
+
validatePositiveInt(options.highRetryMaxRounds, '--high-retry-max-rounds');
|
|
162
|
+
validatePositiveInt(options.mediumRetryMaxRounds, '--medium-retry-max-rounds');
|
|
163
|
+
validatePositiveInt(options.phaseRecoveryAttempts, '--phase-recovery-attempts');
|
|
164
|
+
validateNonNegativeInt(options.phaseRecoveryCooldownSeconds, '--phase-recovery-cooldown-seconds');
|
|
165
|
+
if (!Number.isFinite(options.minDeltaAbs) || options.minDeltaAbs < 0) {
|
|
166
|
+
throw new Error('--min-delta-abs must be a non-negative number.');
|
|
167
|
+
}
|
|
168
|
+
validatePositiveInt(options.topTemplates, '--top-templates');
|
|
169
|
+
|
|
170
|
+
return options;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
function validatePositiveInt(value, name) {
|
|
174
|
+
if (!Number.isFinite(value) || value < 1 || !Number.isInteger(value)) {
|
|
175
|
+
throw new Error(`${name} must be a positive integer.`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function validateNonNegativeInt(value, name) {
|
|
180
|
+
if (!Number.isFinite(value) || value < 0 || !Number.isInteger(value)) {
|
|
181
|
+
throw new Error(`${name} must be a non-negative integer.`);
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function printHelpAndExit(code) {
|
|
186
|
+
const lines = [
|
|
187
|
+
'Usage: node scripts/moqui-matrix-remediation-phased-runner.js [options]',
|
|
188
|
+
'',
|
|
189
|
+
'Options:',
|
|
190
|
+
` --high-goals <path> High-phase goals JSON input (default: ${DEFAULT_HIGH_GOALS})`,
|
|
191
|
+
` --medium-goals <path> Medium-phase goals JSON input (default: ${DEFAULT_MEDIUM_GOALS})`,
|
|
192
|
+
` --high-lines <path> High-phase lines fallback input (default: ${DEFAULT_HIGH_LINES})`,
|
|
193
|
+
` --medium-lines <path> Medium-phase lines fallback input (default: ${DEFAULT_MEDIUM_LINES})`,
|
|
194
|
+
` --phase-high-parallel <n> close-loop-batch parallel for high phase (default: ${DEFAULT_PHASE_HIGH_PARALLEL})`,
|
|
195
|
+
` --phase-high-agent-budget <n> close-loop-batch agent budget for high phase (default: ${DEFAULT_PHASE_HIGH_AGENT_BUDGET})`,
|
|
196
|
+
` --phase-medium-parallel <n> close-loop-batch parallel for medium phase (default: ${DEFAULT_PHASE_MEDIUM_PARALLEL})`,
|
|
197
|
+
` --phase-medium-agent-budget <n> close-loop-batch agent budget for medium phase (default: ${DEFAULT_PHASE_MEDIUM_AGENT_BUDGET})`,
|
|
198
|
+
` --phase-cooldown-seconds <n> Cooldown between phases (default: ${DEFAULT_PHASE_COOLDOWN_SECONDS})`,
|
|
199
|
+
` --high-retry-max-rounds <n> Retry max rounds for high phase (default: ${DEFAULT_HIGH_RETRY_MAX_ROUNDS})`,
|
|
200
|
+
` --medium-retry-max-rounds <n> Retry max rounds for medium phase (default: ${DEFAULT_MEDIUM_RETRY_MAX_ROUNDS})`,
|
|
201
|
+
` --phase-recovery-attempts <n> Max process-level attempts per phase on failure (default: ${DEFAULT_PHASE_RECOVERY_ATTEMPTS})`,
|
|
202
|
+
` --phase-recovery-cooldown-seconds <n> Cooldown between retry attempts of the same phase (default: ${DEFAULT_PHASE_RECOVERY_COOLDOWN_SECONDS})`,
|
|
203
|
+
` --baseline <path> Optional: generate queue package from baseline first (default baseline path: ${DEFAULT_BASELINE})`,
|
|
204
|
+
` --queue-out <path> Queue plan JSON output when --baseline is used (default: ${DEFAULT_OUT})`,
|
|
205
|
+
` --queue-lines-out <path> Queue lines output when --baseline is used (default: ${DEFAULT_LINES_OUT})`,
|
|
206
|
+
` --queue-markdown-out <path> Queue markdown output when --baseline is used (default: ${DEFAULT_MARKDOWN_OUT})`,
|
|
207
|
+
` --queue-batch-json-out <path> Queue aggregate goals JSON output when --baseline is used (default: ${DEFAULT_BATCH_JSON_OUT})`,
|
|
208
|
+
` --queue-commands-out <path> Queue commands markdown output when --baseline is used (default: ${DEFAULT_COMMANDS_OUT})`,
|
|
209
|
+
` --cluster-goals <path> Optional: capability-cluster goals JSON input (for example: ${DEFAULT_CLUSTER_GOALS})`,
|
|
210
|
+
' --cluster-high-goals-out <path> Derived high-phase goals JSON output from capability clusters',
|
|
211
|
+
' --cluster-medium-goals-out <path> Derived medium-phase goals JSON output from capability clusters',
|
|
212
|
+
' --min-delta-abs <n> Pass-through to queue generation when --baseline is used',
|
|
213
|
+
` --top-templates <n> Pass-through top template candidates (default: ${DEFAULT_TOP_TEMPLATES})`,
|
|
214
|
+
' --no-fallback-lines Disable fallback to .lines files when goals JSON is empty/missing',
|
|
215
|
+
' --continue-on-error Continue medium phase even if high phase fails',
|
|
216
|
+
' --sce-bin <path> Override executable (default: current Node + local bin/sce.js)',
|
|
217
|
+
' --dry-run Print planned phased commands without executing',
|
|
218
|
+
' --json Print summary JSON',
|
|
219
|
+
' -h, --help Show this help'
|
|
220
|
+
];
|
|
221
|
+
console.log(lines.join('\n'));
|
|
222
|
+
process.exit(code);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
function resolvePath(cwd, value) {
|
|
226
|
+
return path.isAbsolute(value) ? value : path.resolve(cwd, value);
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
function derivePhasePath(basePath, phaseName) {
|
|
230
|
+
const parsed = path.parse(basePath);
|
|
231
|
+
return path.join(parsed.dir, `${parsed.name}.${phaseName}${parsed.ext}`);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function readGoalsPayload(filePath) {
|
|
235
|
+
const exists = await fs.pathExists(filePath);
|
|
236
|
+
if (!exists) {
|
|
237
|
+
return { exists: false, goals: [] };
|
|
238
|
+
}
|
|
239
|
+
const payload = await fs.readJson(filePath);
|
|
240
|
+
const goals = Array.isArray(payload && payload.goals)
|
|
241
|
+
? payload.goals.map(item => `${item || ''}`.trim()).filter(Boolean)
|
|
242
|
+
: [];
|
|
243
|
+
return { exists: true, goals };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function readLinesPayload(filePath) {
|
|
247
|
+
const exists = await fs.pathExists(filePath);
|
|
248
|
+
if (!exists) {
|
|
249
|
+
return { exists: false, lines: [] };
|
|
250
|
+
}
|
|
251
|
+
const text = await fs.readFile(filePath, 'utf8');
|
|
252
|
+
const lines = text
|
|
253
|
+
.split(/\r?\n/)
|
|
254
|
+
.map(item => item.trim())
|
|
255
|
+
.filter(Boolean);
|
|
256
|
+
return { exists: true, lines };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function dedupeGoals(values = []) {
|
|
260
|
+
const output = [];
|
|
261
|
+
const seen = new Set();
|
|
262
|
+
for (const item of values) {
|
|
263
|
+
const goal = `${item || ''}`.trim();
|
|
264
|
+
if (!goal || seen.has(goal)) {
|
|
265
|
+
continue;
|
|
266
|
+
}
|
|
267
|
+
seen.add(goal);
|
|
268
|
+
output.push(goal);
|
|
269
|
+
}
|
|
270
|
+
return output;
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
async function readClusterGoalsPayload(filePath) {
|
|
274
|
+
const exists = await fs.pathExists(filePath);
|
|
275
|
+
if (!exists) {
|
|
276
|
+
return {
|
|
277
|
+
exists: false,
|
|
278
|
+
cluster_count: 0,
|
|
279
|
+
goal_count: 0,
|
|
280
|
+
high_goals: [],
|
|
281
|
+
medium_goals: []
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
const payload = await fs.readJson(filePath);
|
|
285
|
+
const clusters = Array.isArray(payload && payload.clusters)
|
|
286
|
+
? payload.clusters
|
|
287
|
+
: [];
|
|
288
|
+
const highGoals = [];
|
|
289
|
+
const mediumGoals = [];
|
|
290
|
+
for (const cluster of clusters) {
|
|
291
|
+
const goals = Array.isArray(cluster && cluster.goals)
|
|
292
|
+
? cluster.goals.map(item => `${item || ''}`.trim()).filter(Boolean)
|
|
293
|
+
: [];
|
|
294
|
+
const phase = `${cluster && cluster.recommended_phase ? cluster.recommended_phase : ''}`
|
|
295
|
+
.trim()
|
|
296
|
+
.toLowerCase();
|
|
297
|
+
if (phase === 'high') {
|
|
298
|
+
highGoals.push(...goals);
|
|
299
|
+
} else {
|
|
300
|
+
mediumGoals.push(...goals);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const fallbackGoals = Array.isArray(payload && payload.goals)
|
|
305
|
+
? payload.goals.map(item => `${item || ''}`.trim()).filter(Boolean)
|
|
306
|
+
: [];
|
|
307
|
+
const dedupHigh = dedupeGoals(highGoals);
|
|
308
|
+
const dedupMedium = dedupeGoals([...mediumGoals, ...fallbackGoals]);
|
|
309
|
+
return {
|
|
310
|
+
exists: true,
|
|
311
|
+
cluster_count: clusters.length,
|
|
312
|
+
goal_count: dedupHigh.length + dedupMedium.length,
|
|
313
|
+
high_goals: dedupHigh,
|
|
314
|
+
medium_goals: dedupMedium
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
async function writeGoalsPayload(filePath, goals = []) {
|
|
319
|
+
await fs.ensureDir(path.dirname(filePath));
|
|
320
|
+
await fs.writeJson(filePath, { goals: dedupeGoals(goals) }, { spaces: 2 });
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function prepareClusterGoals(options, runtime, paths) {
|
|
324
|
+
if (!paths.clusterGoalsPath) {
|
|
325
|
+
return {
|
|
326
|
+
status: 'skipped',
|
|
327
|
+
source: null,
|
|
328
|
+
high_goals_path: null,
|
|
329
|
+
medium_goals_path: null,
|
|
330
|
+
cluster_count: 0,
|
|
331
|
+
goal_count: 0,
|
|
332
|
+
high_goal_count: 0,
|
|
333
|
+
medium_goal_count: 0
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
const parsed = await readClusterGoalsPayload(paths.clusterGoalsPath);
|
|
337
|
+
if (!parsed.exists) {
|
|
338
|
+
throw new Error(`cluster goals file not found: ${runtime.toRelative(paths.clusterGoalsPath)}`);
|
|
339
|
+
}
|
|
340
|
+
await writeGoalsPayload(paths.clusterHighGoalsPath, parsed.high_goals);
|
|
341
|
+
await writeGoalsPayload(paths.clusterMediumGoalsPath, parsed.medium_goals);
|
|
342
|
+
return {
|
|
343
|
+
status: 'completed',
|
|
344
|
+
source: runtime.toRelative(paths.clusterGoalsPath),
|
|
345
|
+
high_goals_path: runtime.toRelative(paths.clusterHighGoalsPath),
|
|
346
|
+
medium_goals_path: runtime.toRelative(paths.clusterMediumGoalsPath),
|
|
347
|
+
cluster_count: parsed.cluster_count,
|
|
348
|
+
goal_count: parsed.goal_count,
|
|
349
|
+
high_goal_count: parsed.high_goals.length,
|
|
350
|
+
medium_goal_count: parsed.medium_goals.length
|
|
351
|
+
};
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
async function selectPhaseInput(phaseName, goalsPath, linesPath, allowLinesFallback) {
|
|
355
|
+
const goals = await readGoalsPayload(goalsPath);
|
|
356
|
+
if (goals.exists && goals.goals.length > 0) {
|
|
357
|
+
return {
|
|
358
|
+
phase: phaseName,
|
|
359
|
+
source: 'goals-json',
|
|
360
|
+
format: 'json',
|
|
361
|
+
path: goalsPath,
|
|
362
|
+
count: goals.goals.length
|
|
363
|
+
};
|
|
364
|
+
}
|
|
365
|
+
if (allowLinesFallback) {
|
|
366
|
+
const lines = await readLinesPayload(linesPath);
|
|
367
|
+
if (lines.exists && lines.lines.length > 0) {
|
|
368
|
+
return {
|
|
369
|
+
phase: phaseName,
|
|
370
|
+
source: 'lines-fallback',
|
|
371
|
+
format: 'lines',
|
|
372
|
+
path: linesPath,
|
|
373
|
+
count: lines.lines.length
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return null;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function buildCloseLoopArgs(input, phaseOptions) {
|
|
381
|
+
return [
|
|
382
|
+
'auto',
|
|
383
|
+
'close-loop-batch',
|
|
384
|
+
input.path,
|
|
385
|
+
'--format',
|
|
386
|
+
input.format,
|
|
387
|
+
'--batch-parallel',
|
|
388
|
+
`${phaseOptions.parallel}`,
|
|
389
|
+
'--batch-agent-budget',
|
|
390
|
+
`${phaseOptions.agentBudget}`,
|
|
391
|
+
'--batch-retry-until-complete',
|
|
392
|
+
'--batch-retry-max-rounds',
|
|
393
|
+
`${phaseOptions.retryMaxRounds}`,
|
|
394
|
+
'--json'
|
|
395
|
+
];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
function quoteCliArg(value = '') {
|
|
399
|
+
const text = `${value || ''}`;
|
|
400
|
+
if (!text) {
|
|
401
|
+
return '""';
|
|
402
|
+
}
|
|
403
|
+
if (/^[\w./\\:-]+$/.test(text)) {
|
|
404
|
+
return text;
|
|
405
|
+
}
|
|
406
|
+
return `"${text.replace(/"/g, '\\"')}"`;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function toCommandPreview(bin, args) {
|
|
410
|
+
return `${quoteCliArg(bin)} ${args.map(arg => quoteCliArg(arg)).join(' ')}`.trim();
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
function buildQueueGenerationArgs(options, paths) {
|
|
414
|
+
return [
|
|
415
|
+
path.resolve(__dirname, 'moqui-matrix-remediation-queue.js'),
|
|
416
|
+
'--baseline',
|
|
417
|
+
paths.baselinePath,
|
|
418
|
+
'--out',
|
|
419
|
+
paths.queueOutPath,
|
|
420
|
+
'--lines-out',
|
|
421
|
+
paths.queueLinesOutPath,
|
|
422
|
+
'--markdown-out',
|
|
423
|
+
paths.queueMarkdownOutPath,
|
|
424
|
+
'--batch-json-out',
|
|
425
|
+
paths.queueBatchJsonOutPath,
|
|
426
|
+
'--commands-out',
|
|
427
|
+
paths.queueCommandsOutPath,
|
|
428
|
+
'--phase-high-lines-out',
|
|
429
|
+
paths.highLinesPath,
|
|
430
|
+
'--phase-medium-lines-out',
|
|
431
|
+
paths.mediumLinesPath,
|
|
432
|
+
'--phase-high-goals-out',
|
|
433
|
+
paths.highGoalsPath,
|
|
434
|
+
'--phase-medium-goals-out',
|
|
435
|
+
paths.mediumGoalsPath,
|
|
436
|
+
'--phase-high-parallel',
|
|
437
|
+
`${options.phaseHighParallel}`,
|
|
438
|
+
'--phase-high-agent-budget',
|
|
439
|
+
`${options.phaseHighAgentBudget}`,
|
|
440
|
+
'--phase-medium-parallel',
|
|
441
|
+
`${options.phaseMediumParallel}`,
|
|
442
|
+
'--phase-medium-agent-budget',
|
|
443
|
+
`${options.phaseMediumAgentBudget}`,
|
|
444
|
+
'--phase-cooldown-seconds',
|
|
445
|
+
`${options.phaseCooldownSeconds}`,
|
|
446
|
+
'--min-delta-abs',
|
|
447
|
+
`${options.minDeltaAbs}`,
|
|
448
|
+
'--top-templates',
|
|
449
|
+
`${options.topTemplates}`,
|
|
450
|
+
'--json'
|
|
451
|
+
];
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
function runQueueGeneration(options, runtime, paths) {
|
|
455
|
+
if (!paths.baselinePath) {
|
|
456
|
+
return {
|
|
457
|
+
status: 'skipped',
|
|
458
|
+
exit_code: null,
|
|
459
|
+
command: null,
|
|
460
|
+
queue_summary: null,
|
|
461
|
+
stderr: null
|
|
462
|
+
};
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const commandArgs = buildQueueGenerationArgs(options, paths);
|
|
466
|
+
const command = toCommandPreview(process.execPath, commandArgs);
|
|
467
|
+
const result = spawnSync(process.execPath, commandArgs, {
|
|
468
|
+
cwd: runtime.cwd,
|
|
469
|
+
encoding: 'utf8'
|
|
470
|
+
});
|
|
471
|
+
const code = Number.isFinite(result.status) ? result.status : 1;
|
|
472
|
+
let queueSummary = null;
|
|
473
|
+
if (code === 0) {
|
|
474
|
+
const stdoutText = `${result.stdout || ''}`.trim();
|
|
475
|
+
if (stdoutText) {
|
|
476
|
+
try {
|
|
477
|
+
const parsed = JSON.parse(stdoutText);
|
|
478
|
+
if (parsed && typeof parsed === 'object') {
|
|
479
|
+
queueSummary = {
|
|
480
|
+
selected_regressions: parsed.summary && Number.isFinite(Number(parsed.summary.selected_regressions))
|
|
481
|
+
? Number(parsed.summary.selected_regressions)
|
|
482
|
+
: null,
|
|
483
|
+
phase_high_count: parsed.summary && Number.isFinite(Number(parsed.summary.phase_high_count))
|
|
484
|
+
? Number(parsed.summary.phase_high_count)
|
|
485
|
+
: null,
|
|
486
|
+
phase_medium_count: parsed.summary && Number.isFinite(Number(parsed.summary.phase_medium_count))
|
|
487
|
+
? Number(parsed.summary.phase_medium_count)
|
|
488
|
+
: null
|
|
489
|
+
};
|
|
490
|
+
}
|
|
491
|
+
} catch (_error) {
|
|
492
|
+
queueSummary = null;
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
return {
|
|
498
|
+
status: code === 0 ? 'completed' : 'failed',
|
|
499
|
+
exit_code: code,
|
|
500
|
+
command,
|
|
501
|
+
queue_summary: queueSummary,
|
|
502
|
+
stderr: `${result.stderr || ''}`.trim() || null
|
|
503
|
+
};
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function executeCommand(bin, args, cwd) {
|
|
507
|
+
return new Promise((resolve) => {
|
|
508
|
+
const child = spawn(bin, args, {
|
|
509
|
+
cwd,
|
|
510
|
+
stdio: 'inherit',
|
|
511
|
+
shell: process.platform === 'win32'
|
|
512
|
+
});
|
|
513
|
+
child.on('close', (code) => {
|
|
514
|
+
resolve({ code: Number.isFinite(code) ? code : 1 });
|
|
515
|
+
});
|
|
516
|
+
child.on('error', (error) => {
|
|
517
|
+
resolve({ code: 1, error: error ? error.message : 'spawn failed' });
|
|
518
|
+
});
|
|
519
|
+
});
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
async function sleep(seconds) {
|
|
523
|
+
if (seconds <= 0) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
await new Promise(resolve => setTimeout(resolve, seconds * 1000));
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
function halveWithFloor(value) {
|
|
530
|
+
return Math.max(1, Math.floor(Number(value) / 2));
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
async function runPhases(options, runtime, hooks = {}) {
|
|
534
|
+
const phases = [];
|
|
535
|
+
let failed = false;
|
|
536
|
+
const executeFn = typeof hooks.executeCommand === 'function'
|
|
537
|
+
? hooks.executeCommand
|
|
538
|
+
: executeCommand;
|
|
539
|
+
const sleepFn = typeof hooks.sleep === 'function'
|
|
540
|
+
? hooks.sleep
|
|
541
|
+
: sleep;
|
|
542
|
+
const phaseEntries = [
|
|
543
|
+
{
|
|
544
|
+
name: 'high',
|
|
545
|
+
input: runtime.highInput,
|
|
546
|
+
parallel: options.phaseHighParallel,
|
|
547
|
+
agentBudget: options.phaseHighAgentBudget,
|
|
548
|
+
retryMaxRounds: options.highRetryMaxRounds
|
|
549
|
+
},
|
|
550
|
+
{
|
|
551
|
+
name: 'medium',
|
|
552
|
+
input: runtime.mediumInput,
|
|
553
|
+
parallel: options.phaseMediumParallel,
|
|
554
|
+
agentBudget: options.phaseMediumAgentBudget,
|
|
555
|
+
retryMaxRounds: options.mediumRetryMaxRounds
|
|
556
|
+
}
|
|
557
|
+
];
|
|
558
|
+
const runnerBin = options.sceBin || process.execPath;
|
|
559
|
+
const runnerPrefixArgs = options.sceBin ? [] : [path.resolve(__dirname, '..', 'bin', 'sce.js')];
|
|
560
|
+
|
|
561
|
+
for (let index = 0; index < phaseEntries.length; index += 1) {
|
|
562
|
+
const entry = phaseEntries[index];
|
|
563
|
+
const record = {
|
|
564
|
+
phase: entry.name,
|
|
565
|
+
status: 'skipped',
|
|
566
|
+
reason: null,
|
|
567
|
+
selected_input: entry.input
|
|
568
|
+
? {
|
|
569
|
+
source: entry.input.source,
|
|
570
|
+
format: entry.input.format,
|
|
571
|
+
path: runtime.toRelative(entry.input.path),
|
|
572
|
+
item_count: entry.input.count
|
|
573
|
+
}
|
|
574
|
+
: null,
|
|
575
|
+
command: null,
|
|
576
|
+
exit_code: null,
|
|
577
|
+
attempts: []
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
if (!entry.input) {
|
|
581
|
+
record.reason = 'no-available-goals';
|
|
582
|
+
phases.push(record);
|
|
583
|
+
continue;
|
|
584
|
+
}
|
|
585
|
+
if (failed && !options.continueOnError) {
|
|
586
|
+
record.reason = 'skipped-after-prior-failure';
|
|
587
|
+
phases.push(record);
|
|
588
|
+
continue;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
if (options.dryRun) {
|
|
592
|
+
const args = [...runnerPrefixArgs, ...buildCloseLoopArgs(entry.input, entry)];
|
|
593
|
+
record.command = toCommandPreview(runnerBin, args);
|
|
594
|
+
record.attempts.push({
|
|
595
|
+
attempt: 1,
|
|
596
|
+
status: 'planned',
|
|
597
|
+
command: record.command,
|
|
598
|
+
parallel: entry.parallel,
|
|
599
|
+
agent_budget: entry.agentBudget,
|
|
600
|
+
exit_code: null,
|
|
601
|
+
reason: null
|
|
602
|
+
});
|
|
603
|
+
record.status = 'planned';
|
|
604
|
+
phases.push(record);
|
|
605
|
+
continue;
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
let attemptParallel = entry.parallel;
|
|
609
|
+
let attemptAgentBudget = entry.agentBudget;
|
|
610
|
+
for (let attempt = 1; attempt <= options.phaseRecoveryAttempts; attempt += 1) {
|
|
611
|
+
const phaseOptions = {
|
|
612
|
+
parallel: attemptParallel,
|
|
613
|
+
agentBudget: attemptAgentBudget,
|
|
614
|
+
retryMaxRounds: entry.retryMaxRounds
|
|
615
|
+
};
|
|
616
|
+
const args = [...runnerPrefixArgs, ...buildCloseLoopArgs(entry.input, phaseOptions)];
|
|
617
|
+
const command = toCommandPreview(runnerBin, args);
|
|
618
|
+
if (!record.command) {
|
|
619
|
+
record.command = command;
|
|
620
|
+
}
|
|
621
|
+
const execution = await executeFn(runnerBin, args, runtime.cwd);
|
|
622
|
+
const attemptReason = execution.code === 0 ? null : (execution.error || `exit code ${execution.code}`);
|
|
623
|
+
record.attempts.push({
|
|
624
|
+
attempt,
|
|
625
|
+
status: execution.code === 0 ? 'completed' : 'failed',
|
|
626
|
+
command,
|
|
627
|
+
parallel: attemptParallel,
|
|
628
|
+
agent_budget: attemptAgentBudget,
|
|
629
|
+
exit_code: execution.code,
|
|
630
|
+
reason: attemptReason
|
|
631
|
+
});
|
|
632
|
+
record.exit_code = execution.code;
|
|
633
|
+
|
|
634
|
+
if (execution.code === 0) {
|
|
635
|
+
record.status = 'completed';
|
|
636
|
+
record.reason = null;
|
|
637
|
+
break;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
if (attempt >= options.phaseRecoveryAttempts) {
|
|
641
|
+
record.status = 'failed';
|
|
642
|
+
record.reason = attemptReason;
|
|
643
|
+
failed = true;
|
|
644
|
+
break;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (options.phaseRecoveryCooldownSeconds > 0) {
|
|
648
|
+
await sleepFn(options.phaseRecoveryCooldownSeconds);
|
|
649
|
+
}
|
|
650
|
+
attemptParallel = halveWithFloor(attemptParallel);
|
|
651
|
+
attemptAgentBudget = halveWithFloor(attemptAgentBudget);
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
phases.push(record);
|
|
655
|
+
|
|
656
|
+
const isHighPhase = entry.name === 'high';
|
|
657
|
+
const mediumPlanned = Boolean(phaseEntries[1] && phaseEntries[1].input);
|
|
658
|
+
if (
|
|
659
|
+
isHighPhase &&
|
|
660
|
+
entry.input &&
|
|
661
|
+
mediumPlanned &&
|
|
662
|
+
!options.dryRun &&
|
|
663
|
+
options.phaseCooldownSeconds > 0 &&
|
|
664
|
+
(record.status === 'completed' || options.continueOnError)
|
|
665
|
+
) {
|
|
666
|
+
await sleepFn(options.phaseCooldownSeconds);
|
|
667
|
+
}
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
return phases;
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function main() {
|
|
674
|
+
const options = parseArgs(process.argv.slice(2));
|
|
675
|
+
const cwd = process.cwd();
|
|
676
|
+
const baselinePath = options.baseline
|
|
677
|
+
? resolvePath(cwd, options.baseline)
|
|
678
|
+
: null;
|
|
679
|
+
const highGoalsPath = resolvePath(cwd, options.highGoals);
|
|
680
|
+
const mediumGoalsPath = resolvePath(cwd, options.mediumGoals);
|
|
681
|
+
const highLinesPath = resolvePath(cwd, options.highLines);
|
|
682
|
+
const mediumLinesPath = resolvePath(cwd, options.mediumLines);
|
|
683
|
+
const queueOutPath = resolvePath(cwd, options.queueOut);
|
|
684
|
+
const queueLinesOutPath = resolvePath(cwd, options.queueLinesOut);
|
|
685
|
+
const queueMarkdownOutPath = resolvePath(cwd, options.queueMarkdownOut);
|
|
686
|
+
const queueBatchJsonOutPath = resolvePath(cwd, options.queueBatchJsonOut);
|
|
687
|
+
const queueCommandsOutPath = resolvePath(cwd, options.queueCommandsOut);
|
|
688
|
+
const clusterGoalsPath = options.clusterGoals
|
|
689
|
+
? resolvePath(cwd, options.clusterGoals)
|
|
690
|
+
: null;
|
|
691
|
+
const clusterHighGoalsPath = clusterGoalsPath
|
|
692
|
+
? (
|
|
693
|
+
options.clusterHighGoalsOut
|
|
694
|
+
? resolvePath(cwd, options.clusterHighGoalsOut)
|
|
695
|
+
: derivePhasePath(clusterGoalsPath, 'high')
|
|
696
|
+
)
|
|
697
|
+
: null;
|
|
698
|
+
const clusterMediumGoalsPath = clusterGoalsPath
|
|
699
|
+
? (
|
|
700
|
+
options.clusterMediumGoalsOut
|
|
701
|
+
? resolvePath(cwd, options.clusterMediumGoalsOut)
|
|
702
|
+
: derivePhasePath(clusterGoalsPath, 'medium')
|
|
703
|
+
)
|
|
704
|
+
: null;
|
|
705
|
+
const allowLinesFallback = options.noFallbackLines !== true;
|
|
706
|
+
const runtime = {
|
|
707
|
+
cwd,
|
|
708
|
+
toRelative: (filePath) => path.relative(cwd, filePath) || '.'
|
|
709
|
+
};
|
|
710
|
+
const paths = {
|
|
711
|
+
baselinePath,
|
|
712
|
+
highGoalsPath,
|
|
713
|
+
mediumGoalsPath,
|
|
714
|
+
highLinesPath,
|
|
715
|
+
mediumLinesPath,
|
|
716
|
+
queueOutPath,
|
|
717
|
+
queueLinesOutPath,
|
|
718
|
+
queueMarkdownOutPath,
|
|
719
|
+
queueBatchJsonOutPath,
|
|
720
|
+
queueCommandsOutPath,
|
|
721
|
+
clusterGoalsPath,
|
|
722
|
+
clusterHighGoalsPath,
|
|
723
|
+
clusterMediumGoalsPath
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const prepare = runQueueGeneration(options, runtime, paths);
|
|
727
|
+
if (prepare.status === 'failed') {
|
|
728
|
+
throw new Error(`queue generation failed before phased execution${prepare.stderr ? `: ${prepare.stderr}` : '.'}`);
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const clusterPrepare = await prepareClusterGoals(options, runtime, paths);
|
|
732
|
+
const effectiveHighGoalsPath = clusterPrepare.status === 'completed'
|
|
733
|
+
? clusterHighGoalsPath
|
|
734
|
+
: highGoalsPath;
|
|
735
|
+
const effectiveMediumGoalsPath = clusterPrepare.status === 'completed'
|
|
736
|
+
? clusterMediumGoalsPath
|
|
737
|
+
: mediumGoalsPath;
|
|
738
|
+
|
|
739
|
+
const highInput = await selectPhaseInput('high', effectiveHighGoalsPath, highLinesPath, allowLinesFallback);
|
|
740
|
+
const mediumInput = await selectPhaseInput('medium', effectiveMediumGoalsPath, mediumLinesPath, allowLinesFallback);
|
|
741
|
+
const shouldCooldown = Boolean(highInput && mediumInput && options.phaseCooldownSeconds > 0);
|
|
742
|
+
runtime.highInput = highInput;
|
|
743
|
+
runtime.mediumInput = mediumInput;
|
|
744
|
+
const phases = await runPhases(options, runtime);
|
|
745
|
+
const completedCount = phases.filter(item => item.status === 'completed').length;
|
|
746
|
+
const plannedCount = phases.filter(item => item.status === 'planned').length;
|
|
747
|
+
const failedCount = phases.filter(item => item.status === 'failed').length;
|
|
748
|
+
const runnableCount = phases.filter(item => item.selected_input).length;
|
|
749
|
+
|
|
750
|
+
let status = 'no-op';
|
|
751
|
+
if (failedCount > 0) {
|
|
752
|
+
status = 'failed';
|
|
753
|
+
} else if (options.dryRun && runnableCount > 0) {
|
|
754
|
+
status = 'dry-run';
|
|
755
|
+
} else if (!options.dryRun && completedCount > 0) {
|
|
756
|
+
status = 'completed';
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
const summary = {
|
|
760
|
+
mode: 'moqui-matrix-remediation-phased-runner',
|
|
761
|
+
generated_at: new Date().toISOString(),
|
|
762
|
+
status,
|
|
763
|
+
policy: {
|
|
764
|
+
continue_on_error: options.continueOnError === true,
|
|
765
|
+
fallback_lines_enabled: allowLinesFallback,
|
|
766
|
+
dry_run: options.dryRun === true,
|
|
767
|
+
auto_phase_recovery_enabled: options.phaseRecoveryAttempts > 1,
|
|
768
|
+
cluster_mode_enabled: clusterPrepare.status === 'completed'
|
|
769
|
+
},
|
|
770
|
+
execution_policy: {
|
|
771
|
+
phase_high_parallel: options.phaseHighParallel,
|
|
772
|
+
phase_high_agent_budget: options.phaseHighAgentBudget,
|
|
773
|
+
phase_high_retry_max_rounds: options.highRetryMaxRounds,
|
|
774
|
+
phase_medium_parallel: options.phaseMediumParallel,
|
|
775
|
+
phase_medium_agent_budget: options.phaseMediumAgentBudget,
|
|
776
|
+
phase_medium_retry_max_rounds: options.mediumRetryMaxRounds,
|
|
777
|
+
phase_cooldown_seconds: options.phaseCooldownSeconds,
|
|
778
|
+
phase_recovery_attempts: options.phaseRecoveryAttempts,
|
|
779
|
+
phase_recovery_cooldown_seconds: options.phaseRecoveryCooldownSeconds
|
|
780
|
+
},
|
|
781
|
+
inputs: {
|
|
782
|
+
baseline: baselinePath ? runtime.toRelative(baselinePath) : null,
|
|
783
|
+
high_goals: runtime.toRelative(effectiveHighGoalsPath),
|
|
784
|
+
medium_goals: runtime.toRelative(effectiveMediumGoalsPath),
|
|
785
|
+
high_lines: runtime.toRelative(highLinesPath),
|
|
786
|
+
medium_lines: runtime.toRelative(mediumLinesPath),
|
|
787
|
+
queue_out: runtime.toRelative(queueOutPath),
|
|
788
|
+
queue_lines_out: runtime.toRelative(queueLinesOutPath),
|
|
789
|
+
queue_markdown_out: runtime.toRelative(queueMarkdownOutPath),
|
|
790
|
+
queue_batch_json_out: runtime.toRelative(queueBatchJsonOutPath),
|
|
791
|
+
queue_commands_out: runtime.toRelative(queueCommandsOutPath),
|
|
792
|
+
cluster_goals: clusterGoalsPath ? runtime.toRelative(clusterGoalsPath) : null,
|
|
793
|
+
cluster_high_goals_out: clusterHighGoalsPath ? runtime.toRelative(clusterHighGoalsPath) : null,
|
|
794
|
+
cluster_medium_goals_out: clusterMediumGoalsPath ? runtime.toRelative(clusterMediumGoalsPath) : null
|
|
795
|
+
},
|
|
796
|
+
prepare: {
|
|
797
|
+
status: prepare.status,
|
|
798
|
+
command: prepare.command,
|
|
799
|
+
exit_code: prepare.exit_code,
|
|
800
|
+
queue_summary: prepare.queue_summary
|
|
801
|
+
},
|
|
802
|
+
cluster_prepare: clusterPrepare,
|
|
803
|
+
cooldown: {
|
|
804
|
+
seconds: options.phaseCooldownSeconds,
|
|
805
|
+
planned: shouldCooldown,
|
|
806
|
+
applied: shouldCooldown && !options.dryRun && failedCount === 0
|
|
807
|
+
},
|
|
808
|
+
summary: {
|
|
809
|
+
runnable_phases: runnableCount,
|
|
810
|
+
completed_phases: completedCount,
|
|
811
|
+
planned_phases: plannedCount,
|
|
812
|
+
failed_phases: failedCount
|
|
813
|
+
},
|
|
814
|
+
phases
|
|
815
|
+
};
|
|
816
|
+
|
|
817
|
+
if (options.json) {
|
|
818
|
+
process.stdout.write(`${JSON.stringify(summary, null, 2)}\n`);
|
|
819
|
+
} else {
|
|
820
|
+
process.stdout.write('Moqui matrix remediation phased runner completed.\n');
|
|
821
|
+
process.stdout.write(`- Status: ${summary.status}\n`);
|
|
822
|
+
process.stdout.write(`- Runnable phases: ${summary.summary.runnable_phases}\n`);
|
|
823
|
+
process.stdout.write(`- Failed phases: ${summary.summary.failed_phases}\n`);
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (summary.status === 'failed') {
|
|
827
|
+
process.exitCode = 1;
|
|
828
|
+
}
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
if (require.main === module) {
|
|
832
|
+
main().catch((error) => {
|
|
833
|
+
console.error(`Moqui matrix remediation phased runner failed: ${error.message}`);
|
|
834
|
+
process.exit(1);
|
|
835
|
+
});
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
module.exports = {
|
|
839
|
+
DEFAULT_HIGH_GOALS,
|
|
840
|
+
DEFAULT_MEDIUM_GOALS,
|
|
841
|
+
DEFAULT_HIGH_LINES,
|
|
842
|
+
DEFAULT_MEDIUM_LINES,
|
|
843
|
+
DEFAULT_CLUSTER_GOALS,
|
|
844
|
+
DEFAULT_HIGH_RETRY_MAX_ROUNDS,
|
|
845
|
+
DEFAULT_MEDIUM_RETRY_MAX_ROUNDS,
|
|
846
|
+
DEFAULT_PHASE_RECOVERY_ATTEMPTS,
|
|
847
|
+
DEFAULT_PHASE_RECOVERY_COOLDOWN_SECONDS,
|
|
848
|
+
parseArgs,
|
|
849
|
+
resolvePath,
|
|
850
|
+
derivePhasePath,
|
|
851
|
+
readGoalsPayload,
|
|
852
|
+
readLinesPayload,
|
|
853
|
+
readClusterGoalsPayload,
|
|
854
|
+
writeGoalsPayload,
|
|
855
|
+
prepareClusterGoals,
|
|
856
|
+
buildQueueGenerationArgs,
|
|
857
|
+
runQueueGeneration,
|
|
858
|
+
selectPhaseInput,
|
|
859
|
+
buildCloseLoopArgs,
|
|
860
|
+
quoteCliArg,
|
|
861
|
+
toCommandPreview,
|
|
862
|
+
runPhases,
|
|
863
|
+
halveWithFloor,
|
|
864
|
+
main
|
|
865
|
+
};
|