scene-capability-engine 3.6.32 → 3.6.36
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 +86 -1
- package/README.md +119 -122
- package/README.zh.md +123 -121
- package/bin/scene-capability-engine.js +11 -0
- package/docs/README.md +21 -32
- package/docs/auto-refactor-index.md +384 -0
- package/docs/command-reference.md +94 -2
- package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
- package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
- package/docs/magicball-capability-iteration-api.md +2 -0
- package/docs/magicball-capability-iteration-ui.md +2 -0
- package/docs/magicball-capability-library.md +2 -0
- package/docs/magicball-cli-invocation-examples.md +336 -0
- package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
- package/docs/magicball-integration-doc-index.md +137 -0
- package/docs/magicball-integration-issue-tracker.md +218 -0
- package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
- package/docs/magicball-sce-adaptation-guide.md +203 -0
- package/docs/magicball-three-mode-alignment-plan.md +551 -0
- package/docs/magicball-ui-surface-checklist.md +126 -0
- package/docs/magicball-write-auth-adaptation-guide.md +328 -0
- package/docs/refactor-completion-roadmap.md +116 -0
- package/docs/zh/README.md +27 -30
- package/docs/zh/refactor-completion-roadmap.md +116 -0
- package/lib/app/registry-config.js +73 -0
- package/lib/app/registry-sync-service.js +228 -0
- package/lib/auto/archive-schema-service.js +276 -0
- package/lib/auto/archive-summary.js +60 -0
- package/lib/auto/batch-goal-input-service.js +543 -0
- package/lib/auto/batch-output.js +201 -0
- package/lib/auto/batch-summary-storage-service.js +110 -0
- package/lib/auto/close-loop-batch-service.js +116 -0
- package/lib/auto/close-loop-controller-service.js +287 -0
- package/lib/auto/close-loop-program-service.js +283 -0
- package/lib/auto/close-loop-recovery-service.js +191 -0
- package/lib/auto/close-loop-session-storage-service.js +50 -0
- package/lib/auto/controller-lock-service.js +55 -0
- package/lib/auto/controller-output.js +32 -0
- package/lib/auto/controller-queue-service.js +127 -0
- package/lib/auto/controller-session-storage-service.js +105 -0
- package/lib/auto/governance-advisory-service.js +208 -0
- package/lib/auto/governance-close-loop-service.js +411 -0
- package/lib/auto/governance-maintenance-presenter.js +162 -0
- package/lib/auto/governance-maintenance-service.js +112 -0
- package/lib/auto/governance-session-presenter.js +70 -0
- package/lib/auto/governance-session-storage-service.js +198 -0
- package/lib/auto/governance-signals.js +139 -0
- package/lib/auto/governance-stats-presenter.js +337 -0
- package/lib/auto/governance-stats-service.js +115 -0
- package/lib/auto/governance-summary.js +703 -0
- package/lib/auto/handoff-capability-matrix-service.js +281 -0
- package/lib/auto/handoff-evidence-review-service.js +251 -0
- package/lib/auto/handoff-release-evidence-service.js +190 -0
- package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
- package/lib/auto/handoff-release-gate-history-service.js +257 -0
- package/lib/auto/handoff-reporting-service.js +1407 -0
- package/lib/auto/handoff-run-service.js +486 -0
- package/lib/auto/handoff-snapshots-service.js +645 -0
- package/lib/auto/observability-service.js +132 -0
- package/lib/auto/output-writer.js +34 -0
- package/lib/auto/program-auto-remediation-service.js +130 -0
- package/lib/auto/program-diagnostics.js +138 -0
- package/lib/auto/program-governance-helpers.js +306 -0
- package/lib/auto/program-governance-loop-service.js +413 -0
- package/lib/auto/program-output.js +106 -0
- package/lib/auto/program-summary.js +183 -0
- package/lib/auto/recovery-memory-service.js +684 -0
- package/lib/auto/recovery-selection-service.js +52 -0
- package/lib/auto/retention-policy.js +98 -0
- package/lib/auto/session-persistence-service.js +106 -0
- package/lib/auto/session-presenter.js +105 -0
- package/lib/auto/session-prune-service.js +190 -0
- package/lib/auto/session-query-service.js +249 -0
- package/lib/auto/spec-protection.js +141 -0
- package/lib/commands/app.js +911 -0
- package/lib/commands/assurance.js +212 -0
- package/lib/commands/auto.js +1091 -11063
- package/lib/commands/mode.js +321 -0
- package/lib/commands/ontology.js +415 -0
- package/lib/commands/pm.js +422 -0
- package/lib/ontology/seed-profiles.js +160 -0
- package/lib/state/sce-state-store.js +3369 -1200
- package/package.json +1 -1
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
async function runCloseLoopController(queueFile, options = {}, context = {}, dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
loadControllerGoalQueue,
|
|
4
|
+
normalizeControllerMaxCycles,
|
|
5
|
+
normalizeControllerMaxMinutes,
|
|
6
|
+
normalizeControllerPollSeconds,
|
|
7
|
+
normalizeControllerDequeueLimit,
|
|
8
|
+
writeControllerGoalQueue,
|
|
9
|
+
acquireControllerLock,
|
|
10
|
+
refreshControllerLock,
|
|
11
|
+
releaseControllerLock,
|
|
12
|
+
sleepForMs,
|
|
13
|
+
executeCloseLoopProgramGoal,
|
|
14
|
+
appendControllerGoalArchive,
|
|
15
|
+
maybePersistCloseLoopControllerSummary,
|
|
16
|
+
maybeWriteOutput,
|
|
17
|
+
now = () => Date.now()
|
|
18
|
+
} = dependencies;
|
|
19
|
+
|
|
20
|
+
const clock = typeof now === 'function' ? now : () => Date.now();
|
|
21
|
+
const projectPath = context.projectPath || process.cwd();
|
|
22
|
+
const resumedSession = context.resumedSession || null;
|
|
23
|
+
const queueInput = typeof queueFile === 'string' && queueFile.trim()
|
|
24
|
+
? queueFile.trim()
|
|
25
|
+
: resumedSession && resumedSession.payload && typeof resumedSession.payload.queue_file === 'string'
|
|
26
|
+
? resumedSession.payload.queue_file
|
|
27
|
+
: null;
|
|
28
|
+
const queueFormatCandidate = (
|
|
29
|
+
options.queueFormat === 'auto' &&
|
|
30
|
+
resumedSession &&
|
|
31
|
+
resumedSession.payload &&
|
|
32
|
+
typeof resumedSession.payload.queue_format === 'string' &&
|
|
33
|
+
resumedSession.payload.queue_format.trim()
|
|
34
|
+
)
|
|
35
|
+
? resumedSession.payload.queue_format
|
|
36
|
+
: options.queueFormat;
|
|
37
|
+
const queuePayload = await loadControllerGoalQueue(projectPath, queueInput, queueFormatCandidate, {
|
|
38
|
+
dedupe: options.controllerDedupe !== false
|
|
39
|
+
});
|
|
40
|
+
const maxCycles = normalizeControllerMaxCycles(options.maxCycles);
|
|
41
|
+
const maxMinutes = normalizeControllerMaxMinutes(options.maxMinutes);
|
|
42
|
+
const maxDurationMs = maxMinutes * 60 * 1000;
|
|
43
|
+
const pollSeconds = normalizeControllerPollSeconds(options.pollSeconds);
|
|
44
|
+
const dequeueLimit = normalizeControllerDequeueLimit(options.dequeueLimit);
|
|
45
|
+
const waitOnEmpty = Boolean(options.waitOnEmpty);
|
|
46
|
+
const stopOnGoalFailure = Boolean(options.stopOnGoalFailure);
|
|
47
|
+
const startedAtMs = clock();
|
|
48
|
+
const startedAtIso = new Date(startedAtMs).toISOString();
|
|
49
|
+
const history = [];
|
|
50
|
+
const results = [];
|
|
51
|
+
let performedCycles = 0;
|
|
52
|
+
let stopReason = 'completed';
|
|
53
|
+
let exhausted = false;
|
|
54
|
+
let haltRequested = false;
|
|
55
|
+
let doneArchiveFile = null;
|
|
56
|
+
let failedArchiveFile = null;
|
|
57
|
+
let dedupeDroppedGoals = Number(queuePayload.duplicate_count) || 0;
|
|
58
|
+
let lockState = null;
|
|
59
|
+
|
|
60
|
+
if (options.controllerDedupe !== false && queuePayload.duplicate_count > 0) {
|
|
61
|
+
await writeControllerGoalQueue(queuePayload.file, queuePayload.format, queuePayload.goals);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
lockState = await acquireControllerLock(projectPath, queuePayload.file, options);
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
for (let cycle = 1; cycle <= maxCycles; cycle += 1) {
|
|
68
|
+
if ((clock() - startedAtMs) >= maxDurationMs) {
|
|
69
|
+
exhausted = true;
|
|
70
|
+
stopReason = 'time-budget-exhausted';
|
|
71
|
+
break;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
await refreshControllerLock(lockState);
|
|
75
|
+
|
|
76
|
+
const currentQueue = await loadControllerGoalQueue(projectPath, queuePayload.file, queuePayload.format, {
|
|
77
|
+
dedupe: options.controllerDedupe !== false
|
|
78
|
+
});
|
|
79
|
+
const pendingGoals = currentQueue.goals;
|
|
80
|
+
dedupeDroppedGoals += Number(currentQueue.duplicate_count) || 0;
|
|
81
|
+
|
|
82
|
+
if (options.controllerDedupe !== false && currentQueue.duplicate_count > 0) {
|
|
83
|
+
await writeControllerGoalQueue(currentQueue.file, currentQueue.format, pendingGoals);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
if (pendingGoals.length === 0) {
|
|
87
|
+
history.push({
|
|
88
|
+
cycle,
|
|
89
|
+
queue_before: 0,
|
|
90
|
+
dequeued: 0,
|
|
91
|
+
queue_after: 0,
|
|
92
|
+
status: waitOnEmpty ? 'idle-wait' : 'empty-stop'
|
|
93
|
+
});
|
|
94
|
+
performedCycles += 1;
|
|
95
|
+
if (!waitOnEmpty) {
|
|
96
|
+
stopReason = 'queue-empty';
|
|
97
|
+
break;
|
|
98
|
+
}
|
|
99
|
+
await sleepForMs(pollSeconds * 1000);
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
const effectiveDequeueLimit = dequeueLimit === null ? pendingGoals.length : dequeueLimit;
|
|
104
|
+
const dequeuedGoals = pendingGoals.slice(0, effectiveDequeueLimit);
|
|
105
|
+
const remainingGoals = pendingGoals.slice(dequeuedGoals.length);
|
|
106
|
+
await writeControllerGoalQueue(currentQueue.file, currentQueue.format, remainingGoals);
|
|
107
|
+
|
|
108
|
+
const cycleRecord = {
|
|
109
|
+
cycle,
|
|
110
|
+
queue_before: pendingGoals.length,
|
|
111
|
+
dequeued: dequeuedGoals.length,
|
|
112
|
+
queue_after: remainingGoals.length,
|
|
113
|
+
processed: 0,
|
|
114
|
+
completed: 0,
|
|
115
|
+
failed: 0,
|
|
116
|
+
status: 'processed'
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
for (let index = 0; index < dequeuedGoals.length; index += 1) {
|
|
120
|
+
const goal = dequeuedGoals[index];
|
|
121
|
+
const goalStartedAt = clock();
|
|
122
|
+
let goalResult = {
|
|
123
|
+
cycle,
|
|
124
|
+
queue_index: index + 1,
|
|
125
|
+
goal,
|
|
126
|
+
status: 'failed',
|
|
127
|
+
error: null
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
try {
|
|
131
|
+
const perGoalOptions = {
|
|
132
|
+
...options,
|
|
133
|
+
out: null,
|
|
134
|
+
programKpiOut: null,
|
|
135
|
+
programAuditOut: null,
|
|
136
|
+
json: false
|
|
137
|
+
};
|
|
138
|
+
const programResult = await executeCloseLoopProgramGoal(goal, perGoalOptions, {
|
|
139
|
+
projectPath,
|
|
140
|
+
printSummary: options.controllerPrintProgramSummary === true,
|
|
141
|
+
writeOutputs: false
|
|
142
|
+
});
|
|
143
|
+
const programSummary = programResult.summary || {};
|
|
144
|
+
const failed = programResult.exitCode !== 0;
|
|
145
|
+
goalResult = {
|
|
146
|
+
...goalResult,
|
|
147
|
+
status: failed ? 'failed' : 'completed',
|
|
148
|
+
program_status: programSummary.status || null,
|
|
149
|
+
program_gate_passed: Boolean(
|
|
150
|
+
programSummary.program_gate_effective &&
|
|
151
|
+
programSummary.program_gate_effective.passed
|
|
152
|
+
),
|
|
153
|
+
governance_stop_reason: programSummary.program_governance
|
|
154
|
+
? programSummary.program_governance.stop_reason
|
|
155
|
+
: null,
|
|
156
|
+
batch_session_file: programSummary.batch_session && programSummary.batch_session.file
|
|
157
|
+
? programSummary.batch_session.file
|
|
158
|
+
: null
|
|
159
|
+
};
|
|
160
|
+
} catch (error) {
|
|
161
|
+
goalResult.error = error.message;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
goalResult.elapsed_ms = Math.max(0, clock() - goalStartedAt);
|
|
165
|
+
results.push(goalResult);
|
|
166
|
+
cycleRecord.processed += 1;
|
|
167
|
+
|
|
168
|
+
if (goalResult.status === 'completed') {
|
|
169
|
+
cycleRecord.completed += 1;
|
|
170
|
+
doneArchiveFile = await appendControllerGoalArchive(
|
|
171
|
+
options.controllerDoneFile,
|
|
172
|
+
projectPath,
|
|
173
|
+
goal,
|
|
174
|
+
{
|
|
175
|
+
status: 'completed',
|
|
176
|
+
program_status: goalResult.program_status,
|
|
177
|
+
gate_passed: goalResult.program_gate_passed
|
|
178
|
+
}
|
|
179
|
+
) || doneArchiveFile;
|
|
180
|
+
} else {
|
|
181
|
+
cycleRecord.failed += 1;
|
|
182
|
+
failedArchiveFile = await appendControllerGoalArchive(
|
|
183
|
+
options.controllerFailedFile,
|
|
184
|
+
projectPath,
|
|
185
|
+
goal,
|
|
186
|
+
{
|
|
187
|
+
status: 'failed',
|
|
188
|
+
program_status: goalResult.program_status,
|
|
189
|
+
gate_passed: goalResult.program_gate_passed
|
|
190
|
+
}
|
|
191
|
+
) || failedArchiveFile;
|
|
192
|
+
if (stopOnGoalFailure) {
|
|
193
|
+
haltRequested = true;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (haltRequested) {
|
|
198
|
+
break;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
if (haltRequested) {
|
|
203
|
+
cycleRecord.status = 'stopped-on-goal-failure';
|
|
204
|
+
stopReason = 'goal-failure';
|
|
205
|
+
}
|
|
206
|
+
history.push(cycleRecord);
|
|
207
|
+
performedCycles += 1;
|
|
208
|
+
|
|
209
|
+
if (haltRequested) {
|
|
210
|
+
break;
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} finally {
|
|
214
|
+
await releaseControllerLock(lockState);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const finalQueue = await loadControllerGoalQueue(projectPath, queuePayload.file, queuePayload.format, {
|
|
218
|
+
dedupe: options.controllerDedupe !== false
|
|
219
|
+
});
|
|
220
|
+
const pendingGoals = finalQueue.goals.length;
|
|
221
|
+
dedupeDroppedGoals += Number(finalQueue.duplicate_count) || 0;
|
|
222
|
+
if (options.controllerDedupe !== false && finalQueue.duplicate_count > 0) {
|
|
223
|
+
await writeControllerGoalQueue(finalQueue.file, finalQueue.format, finalQueue.goals);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (!exhausted && stopReason === 'completed') {
|
|
227
|
+
if (performedCycles >= maxCycles && (pendingGoals > 0 || waitOnEmpty)) {
|
|
228
|
+
exhausted = true;
|
|
229
|
+
stopReason = 'cycle-limit-reached';
|
|
230
|
+
} else if (pendingGoals === 0 && results.length === 0) {
|
|
231
|
+
stopReason = 'queue-empty';
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
const completedGoals = results.filter(item => item.status === 'completed').length;
|
|
236
|
+
const failedGoals = results.filter(item => item.status !== 'completed').length;
|
|
237
|
+
const status = failedGoals === 0
|
|
238
|
+
? 'completed'
|
|
239
|
+
: completedGoals === 0
|
|
240
|
+
? 'failed'
|
|
241
|
+
: 'partial-failed';
|
|
242
|
+
const summary = {
|
|
243
|
+
mode: 'auto-close-loop-controller',
|
|
244
|
+
status,
|
|
245
|
+
queue_file: queuePayload.file,
|
|
246
|
+
queue_format: queuePayload.format,
|
|
247
|
+
started_at: startedAtIso,
|
|
248
|
+
completed_at: new Date(clock()).toISOString(),
|
|
249
|
+
elapsed_ms: Math.max(0, clock() - startedAtMs),
|
|
250
|
+
wait_on_empty: waitOnEmpty,
|
|
251
|
+
poll_seconds: pollSeconds,
|
|
252
|
+
dequeue_limit: dequeueLimit === null ? 'all' : dequeueLimit,
|
|
253
|
+
max_cycles: maxCycles,
|
|
254
|
+
max_minutes: maxMinutes,
|
|
255
|
+
cycles_performed: performedCycles,
|
|
256
|
+
exhausted,
|
|
257
|
+
stop_reason: stopReason,
|
|
258
|
+
processed_goals: results.length,
|
|
259
|
+
completed_goals: completedGoals,
|
|
260
|
+
failed_goals: failedGoals,
|
|
261
|
+
pending_goals: pendingGoals,
|
|
262
|
+
dedupe_enabled: options.controllerDedupe !== false,
|
|
263
|
+
dedupe_dropped_goals: dedupeDroppedGoals,
|
|
264
|
+
lock_enabled: options.controllerLock !== false,
|
|
265
|
+
lock_file: lockState && lockState.file ? lockState.file : null,
|
|
266
|
+
lock_ttl_seconds: lockState && Number.isInteger(lockState.ttl_seconds) ? lockState.ttl_seconds : null,
|
|
267
|
+
resumed_from_controller_session: resumedSession
|
|
268
|
+
? {
|
|
269
|
+
id: resumedSession.id,
|
|
270
|
+
file: resumedSession.file || null,
|
|
271
|
+
created_at: resumedSession.created_at || null
|
|
272
|
+
}
|
|
273
|
+
: null,
|
|
274
|
+
done_archive_file: doneArchiveFile,
|
|
275
|
+
failed_archive_file: failedArchiveFile,
|
|
276
|
+
history,
|
|
277
|
+
results
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
await maybePersistCloseLoopControllerSummary(summary, options, projectPath);
|
|
281
|
+
await maybeWriteOutput(summary, options.controllerOut, projectPath);
|
|
282
|
+
return summary;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
module.exports = {
|
|
286
|
+
runCloseLoopController
|
|
287
|
+
};
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
async function executeCloseLoopProgramGoal(goal, options = {}, context = {}, dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
normalizeRecoverMaxRounds,
|
|
4
|
+
normalizeRecoverMaxMinutes,
|
|
5
|
+
normalizeResumeStrategy,
|
|
6
|
+
normalizeProgramGovernMaxRounds,
|
|
7
|
+
normalizeProgramGovernMaxMinutes,
|
|
8
|
+
normalizeProgramGovernAnomalyWeeks,
|
|
9
|
+
normalizeAutoKpiTrendPeriod,
|
|
10
|
+
normalizeProgramGovernUseAction,
|
|
11
|
+
resolveProgramGatePolicy,
|
|
12
|
+
normalizeProgramGateFallbackProfile,
|
|
13
|
+
resolveProgramGateFallbackChain,
|
|
14
|
+
resolveRecoveryMemoryScope,
|
|
15
|
+
normalizeBatchRetryMaxRounds,
|
|
16
|
+
normalizeBatchSessionKeep,
|
|
17
|
+
normalizeBatchSessionOlderThanDays,
|
|
18
|
+
normalizeSpecKeep,
|
|
19
|
+
normalizeOlderThanDays,
|
|
20
|
+
normalizeSpecSessionProtectWindowDays,
|
|
21
|
+
normalizeSpecSessionMaxTotal,
|
|
22
|
+
normalizeSpecSessionMaxCreated,
|
|
23
|
+
normalizeSpecSessionMaxCreatedPerGoal,
|
|
24
|
+
normalizeSpecSessionMaxDuplicateGoals,
|
|
25
|
+
sanitizeBatchSessionId,
|
|
26
|
+
buildCloseLoopBatchGoalsFromGoal,
|
|
27
|
+
executeCloseLoopBatch,
|
|
28
|
+
executeCloseLoopRecoveryCycle,
|
|
29
|
+
mergeProgramRecoveryIntoProgramSummary,
|
|
30
|
+
buildProgramKpiSnapshot,
|
|
31
|
+
buildProgramDiagnostics,
|
|
32
|
+
buildProgramCoordinationSnapshot,
|
|
33
|
+
maybeWriteProgramKpi,
|
|
34
|
+
maybeWriteOutput,
|
|
35
|
+
maybePersistCloseLoopBatchSummary,
|
|
36
|
+
applyProgramGateOutcome,
|
|
37
|
+
runProgramGovernanceLoop,
|
|
38
|
+
isSpecSessionBudgetHardFailure,
|
|
39
|
+
isSpecSessionGrowthGuardHardFailure,
|
|
40
|
+
maybeWriteProgramAudit,
|
|
41
|
+
printCloseLoopBatchSummary,
|
|
42
|
+
now = () => Date.now(),
|
|
43
|
+
cwd = () => process.cwd()
|
|
44
|
+
} = dependencies;
|
|
45
|
+
|
|
46
|
+
const programStartedAt = now();
|
|
47
|
+
const projectPath = context.projectPath || cwd();
|
|
48
|
+
const shouldPrintSummary = context.printSummary !== false;
|
|
49
|
+
const writeOutputs = context.writeOutputs !== false;
|
|
50
|
+
const programAutonomousEnabled = options.batchAutonomous !== false;
|
|
51
|
+
const programAutoRecoverEnabled = options.programAutoRecover !== false;
|
|
52
|
+
const programRecoverMaxRounds = normalizeRecoverMaxRounds(options.programRecoverMaxRounds);
|
|
53
|
+
const programRecoverMaxMinutes = normalizeRecoverMaxMinutes(options.programRecoverMaxMinutes, '--program-recover-max-minutes');
|
|
54
|
+
const programRecoverResumeStrategy = normalizeResumeStrategy(options.programRecoverResumeStrategy);
|
|
55
|
+
const programGovernUntilStable = Boolean(options.programGovernUntilStable);
|
|
56
|
+
const programGovernMaxRounds = normalizeProgramGovernMaxRounds(options.programGovernMaxRounds);
|
|
57
|
+
const programGovernMaxMinutes = normalizeProgramGovernMaxMinutes(options.programGovernMaxMinutes);
|
|
58
|
+
const programGovernAnomalyEnabled = options.programGovernAnomaly !== false;
|
|
59
|
+
const programGovernAnomalyWeeks = normalizeProgramGovernAnomalyWeeks(options.programGovernAnomalyWeeks);
|
|
60
|
+
const programGovernAnomalyPeriod = normalizeAutoKpiTrendPeriod(options.programGovernAnomalyPeriod);
|
|
61
|
+
const programGovernUseAction = normalizeProgramGovernUseAction(options.programGovernUseAction);
|
|
62
|
+
const programGovernAutoActionEnabled = options.programGovernAutoAction !== false;
|
|
63
|
+
const programGatePolicy = resolveProgramGatePolicy({
|
|
64
|
+
profile: options.programGateProfile,
|
|
65
|
+
minSuccessRate: options.programMinSuccessRate,
|
|
66
|
+
maxRiskLevel: options.programMaxRiskLevel,
|
|
67
|
+
maxElapsedMinutes: options.programMaxElapsedMinutes,
|
|
68
|
+
maxAgentBudget: options.programMaxAgentBudget,
|
|
69
|
+
maxTotalSubSpecs: options.programMaxTotalSubSpecs
|
|
70
|
+
});
|
|
71
|
+
const gateFallbackProfile = normalizeProgramGateFallbackProfile(options.programGateFallbackProfile);
|
|
72
|
+
const gateFallbackChain = resolveProgramGateFallbackChain(options.programGateFallbackChain, gateFallbackProfile);
|
|
73
|
+
const recoveryMemoryScope = await resolveRecoveryMemoryScope(projectPath, options.recoveryMemoryScope);
|
|
74
|
+
if (options.resume) {
|
|
75
|
+
throw new Error('--resume is not supported in close-loop-program. Use close-loop --resume or remove --resume.');
|
|
76
|
+
}
|
|
77
|
+
if (options.sessionId) {
|
|
78
|
+
throw new Error('--session-id is not supported in close-loop-program. Session ids are generated per goal.');
|
|
79
|
+
}
|
|
80
|
+
if (
|
|
81
|
+
options.batchRetryMaxRounds !== undefined &&
|
|
82
|
+
options.batchRetryMaxRounds !== null &&
|
|
83
|
+
!options.batchRetryUntilComplete &&
|
|
84
|
+
!programAutonomousEnabled
|
|
85
|
+
) {
|
|
86
|
+
throw new Error('--batch-retry-max-rounds requires --batch-retry-until-complete.');
|
|
87
|
+
}
|
|
88
|
+
if (options.batchRetryMaxRounds !== undefined && options.batchRetryMaxRounds !== null) {
|
|
89
|
+
normalizeBatchRetryMaxRounds(options.batchRetryMaxRounds);
|
|
90
|
+
}
|
|
91
|
+
if (options.batchSessionKeep !== undefined && options.batchSessionKeep !== null) {
|
|
92
|
+
normalizeBatchSessionKeep(options.batchSessionKeep);
|
|
93
|
+
}
|
|
94
|
+
if (options.batchSessionOlderThanDays !== undefined && options.batchSessionOlderThanDays !== null) {
|
|
95
|
+
normalizeBatchSessionOlderThanDays(options.batchSessionOlderThanDays);
|
|
96
|
+
}
|
|
97
|
+
if (options.specSessionKeep !== undefined && options.specSessionKeep !== null) {
|
|
98
|
+
normalizeSpecKeep(options.specSessionKeep);
|
|
99
|
+
}
|
|
100
|
+
if (options.specSessionOlderThanDays !== undefined && options.specSessionOlderThanDays !== null) {
|
|
101
|
+
normalizeOlderThanDays(options.specSessionOlderThanDays);
|
|
102
|
+
}
|
|
103
|
+
if (options.specSessionProtectWindowDays !== undefined && options.specSessionProtectWindowDays !== null) {
|
|
104
|
+
normalizeSpecSessionProtectWindowDays(options.specSessionProtectWindowDays);
|
|
105
|
+
}
|
|
106
|
+
if (options.specSessionMaxTotal !== undefined && options.specSessionMaxTotal !== null) {
|
|
107
|
+
normalizeSpecSessionMaxTotal(options.specSessionMaxTotal);
|
|
108
|
+
}
|
|
109
|
+
if (options.specSessionMaxCreated !== undefined && options.specSessionMaxCreated !== null) {
|
|
110
|
+
normalizeSpecSessionMaxCreated(options.specSessionMaxCreated);
|
|
111
|
+
}
|
|
112
|
+
if (options.specSessionMaxCreatedPerGoal !== undefined && options.specSessionMaxCreatedPerGoal !== null) {
|
|
113
|
+
normalizeSpecSessionMaxCreatedPerGoal(options.specSessionMaxCreatedPerGoal);
|
|
114
|
+
}
|
|
115
|
+
if (options.specSessionMaxDuplicateGoals !== undefined && options.specSessionMaxDuplicateGoals !== null) {
|
|
116
|
+
normalizeSpecSessionMaxDuplicateGoals(options.specSessionMaxDuplicateGoals);
|
|
117
|
+
}
|
|
118
|
+
if (options.batchSessionId !== undefined && options.batchSessionId !== null) {
|
|
119
|
+
const sanitizedBatchSessionId = sanitizeBatchSessionId(options.batchSessionId);
|
|
120
|
+
if (!sanitizedBatchSessionId) {
|
|
121
|
+
throw new Error('--batch-session-id is invalid after sanitization.');
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
const goalsResult = buildCloseLoopBatchGoalsFromGoal(goal, options.programGoals, {
|
|
126
|
+
minQualityScore: options.programMinQualityScore,
|
|
127
|
+
enforceQualityGate: Boolean(options.programQualityGate)
|
|
128
|
+
});
|
|
129
|
+
const programOptions = {
|
|
130
|
+
...options,
|
|
131
|
+
batchAutonomous: programAutonomousEnabled
|
|
132
|
+
};
|
|
133
|
+
const initialSummary = await executeCloseLoopBatch(goalsResult, programOptions, projectPath, 'auto-close-loop-program');
|
|
134
|
+
let summary = {
|
|
135
|
+
...initialSummary,
|
|
136
|
+
auto_recovery: {
|
|
137
|
+
enabled: programAutoRecoverEnabled,
|
|
138
|
+
triggered: false,
|
|
139
|
+
converged: initialSummary.status === 'completed',
|
|
140
|
+
source_status: initialSummary.status
|
|
141
|
+
}
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
if (programAutoRecoverEnabled && initialSummary.status !== 'completed') {
|
|
145
|
+
const recoveryResult = await executeCloseLoopRecoveryCycle({
|
|
146
|
+
projectPath,
|
|
147
|
+
sourceSummary: {
|
|
148
|
+
file: initialSummary.batch_session && initialSummary.batch_session.file
|
|
149
|
+
? initialSummary.batch_session.file
|
|
150
|
+
: '(auto-close-loop-program-in-memory)',
|
|
151
|
+
payload: initialSummary
|
|
152
|
+
},
|
|
153
|
+
baseOptions: {
|
|
154
|
+
...programOptions,
|
|
155
|
+
useAction: options.programRecoverUseAction
|
|
156
|
+
},
|
|
157
|
+
recoverAutonomousEnabled: true,
|
|
158
|
+
resumeStrategy: programRecoverResumeStrategy,
|
|
159
|
+
recoverUntilComplete: true,
|
|
160
|
+
recoverMaxRounds: programRecoverMaxRounds,
|
|
161
|
+
recoverMaxDurationMs: programRecoverMaxMinutes === null ? null : programRecoverMaxMinutes * 60 * 1000,
|
|
162
|
+
recoveryMemoryScope,
|
|
163
|
+
actionCandidate: options.programRecoverUseAction
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
summary = mergeProgramRecoveryIntoProgramSummary(
|
|
167
|
+
initialSummary,
|
|
168
|
+
recoveryResult.summary,
|
|
169
|
+
{
|
|
170
|
+
enabled: true,
|
|
171
|
+
triggered: true,
|
|
172
|
+
recover_until_complete: true,
|
|
173
|
+
recover_max_rounds: programRecoverMaxRounds,
|
|
174
|
+
recover_max_minutes: programRecoverMaxMinutes,
|
|
175
|
+
resume_strategy: programRecoverResumeStrategy
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
summary.program_kpi = buildProgramKpiSnapshot(summary);
|
|
179
|
+
summary.program_diagnostics = buildProgramDiagnostics(summary);
|
|
180
|
+
summary.program_coordination = buildProgramCoordinationSnapshot(summary);
|
|
181
|
+
if (writeOutputs) {
|
|
182
|
+
await maybeWriteProgramKpi(summary, options.programKpiOut, projectPath);
|
|
183
|
+
await maybeWriteOutput(summary, options.out, projectPath);
|
|
184
|
+
}
|
|
185
|
+
if (programOptions.batchSession !== false) {
|
|
186
|
+
await maybePersistCloseLoopBatchSummary(summary, programOptions, projectPath);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
const programCompletedAt = now();
|
|
191
|
+
summary.program_started_at = new Date(programStartedAt).toISOString();
|
|
192
|
+
summary.program_completed_at = new Date(programCompletedAt).toISOString();
|
|
193
|
+
summary.program_elapsed_ms = Math.max(0, programCompletedAt - programStartedAt);
|
|
194
|
+
|
|
195
|
+
await applyProgramGateOutcome(summary, {
|
|
196
|
+
projectPath,
|
|
197
|
+
options: programOptions,
|
|
198
|
+
programGatePolicy,
|
|
199
|
+
gateFallbackChain,
|
|
200
|
+
enableAutoRemediation: options.programGateAutoRemediate !== false
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
if (programGovernUntilStable) {
|
|
204
|
+
const governanceResult = await runProgramGovernanceLoop({
|
|
205
|
+
enabled: true,
|
|
206
|
+
summary,
|
|
207
|
+
projectPath,
|
|
208
|
+
programOptions,
|
|
209
|
+
baseGoalsResult: goalsResult,
|
|
210
|
+
maxRounds: programGovernMaxRounds,
|
|
211
|
+
maxMinutes: programGovernMaxMinutes,
|
|
212
|
+
anomalyEnabled: programGovernAnomalyEnabled,
|
|
213
|
+
anomalyWeeks: programGovernAnomalyWeeks,
|
|
214
|
+
anomalyPeriod: programGovernAnomalyPeriod,
|
|
215
|
+
programGatePolicy,
|
|
216
|
+
gateFallbackChain,
|
|
217
|
+
recoveryMemoryScope,
|
|
218
|
+
recoverResumeStrategy: programRecoverResumeStrategy,
|
|
219
|
+
recoverMaxRounds: programRecoverMaxRounds,
|
|
220
|
+
recoverMaxMinutes: programRecoverMaxMinutes,
|
|
221
|
+
programRecoverUseAction: options.programRecoverUseAction,
|
|
222
|
+
programGateAutoRemediate: options.programGateAutoRemediate !== false,
|
|
223
|
+
governUseAction: programGovernUseAction,
|
|
224
|
+
governAutoActionEnabled: programGovernAutoActionEnabled
|
|
225
|
+
});
|
|
226
|
+
summary = governanceResult.summary;
|
|
227
|
+
summary.program_governance = governanceResult.governance;
|
|
228
|
+
} else {
|
|
229
|
+
summary.program_governance = {
|
|
230
|
+
enabled: false,
|
|
231
|
+
anomaly_enabled: programGovernAnomalyEnabled,
|
|
232
|
+
anomaly_weeks: programGovernAnomalyWeeks,
|
|
233
|
+
anomaly_period: programGovernAnomalyPeriod,
|
|
234
|
+
auto_action_enabled: programGovernAutoActionEnabled,
|
|
235
|
+
action_selection_enabled: false,
|
|
236
|
+
pinned_action_index: programGovernUseAction,
|
|
237
|
+
max_rounds: programGovernMaxRounds,
|
|
238
|
+
max_minutes: programGovernMaxMinutes,
|
|
239
|
+
performed_rounds: 0,
|
|
240
|
+
converged: Boolean(
|
|
241
|
+
summary &&
|
|
242
|
+
summary.program_gate_effective &&
|
|
243
|
+
summary.program_gate_effective.passed &&
|
|
244
|
+
!isSpecSessionBudgetHardFailure(summary) &&
|
|
245
|
+
!isSpecSessionGrowthGuardHardFailure(summary)
|
|
246
|
+
),
|
|
247
|
+
exhausted: false,
|
|
248
|
+
stop_reason: 'disabled',
|
|
249
|
+
history: []
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const finalProgramCompletedAt = now();
|
|
254
|
+
summary.program_completed_at = new Date(finalProgramCompletedAt).toISOString();
|
|
255
|
+
summary.program_elapsed_ms = Math.max(0, finalProgramCompletedAt - programStartedAt);
|
|
256
|
+
|
|
257
|
+
if (writeOutputs) {
|
|
258
|
+
await maybeWriteProgramKpi(summary, options.programKpiOut, projectPath);
|
|
259
|
+
await maybeWriteOutput(summary, options.out, projectPath);
|
|
260
|
+
await maybeWriteProgramAudit(summary, options.programAuditOut, projectPath);
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (shouldPrintSummary) {
|
|
264
|
+
printCloseLoopBatchSummary(summary, programOptions);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const exitCode = (
|
|
268
|
+
summary.status !== 'completed' ||
|
|
269
|
+
!summary.program_gate_effective.passed ||
|
|
270
|
+
isSpecSessionBudgetHardFailure(summary) ||
|
|
271
|
+
isSpecSessionGrowthGuardHardFailure(summary)
|
|
272
|
+
) ? 1 : 0;
|
|
273
|
+
|
|
274
|
+
return {
|
|
275
|
+
summary,
|
|
276
|
+
options: programOptions,
|
|
277
|
+
exitCode
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
module.exports = {
|
|
282
|
+
executeCloseLoopProgramGoal
|
|
283
|
+
};
|