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,201 @@
|
|
|
1
|
+
function printCloseLoopBatchSummary(chalk, summary, options = {}, consoleLike = console) {
|
|
2
|
+
if (options.json) {
|
|
3
|
+
consoleLike.log(JSON.stringify(summary, null, 2));
|
|
4
|
+
return;
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
const title = summary.mode === 'auto-close-loop-program'
|
|
8
|
+
? 'Autonomous close-loop program summary'
|
|
9
|
+
: summary.mode === 'auto-close-loop-recover'
|
|
10
|
+
? 'Autonomous close-loop recovery summary'
|
|
11
|
+
: 'Autonomous close-loop batch summary';
|
|
12
|
+
consoleLike.log(chalk.blue(title));
|
|
13
|
+
consoleLike.log(chalk.gray(` Status: ${summary.status}`));
|
|
14
|
+
consoleLike.log(chalk.gray(` Processed: ${summary.processed_goals}/${summary.total_goals}`));
|
|
15
|
+
consoleLike.log(chalk.gray(` Completed: ${summary.completed_goals}`));
|
|
16
|
+
consoleLike.log(chalk.gray(` Failed: ${summary.failed_goals}`));
|
|
17
|
+
consoleLike.log(chalk.gray(` Batch parallel: ${summary.batch_parallel}`));
|
|
18
|
+
if (summary.autonomous_policy && summary.autonomous_policy.enabled) {
|
|
19
|
+
consoleLike.log(chalk.gray(` Autonomous policy: ${summary.autonomous_policy.profile}`));
|
|
20
|
+
}
|
|
21
|
+
if (summary.batch_retry && summary.batch_retry.performed_rounds > 0) {
|
|
22
|
+
consoleLike.log(chalk.gray(
|
|
23
|
+
` Batch retry: ${summary.batch_retry.performed_rounds}/${summary.batch_retry.configured_rounds} extra rounds`
|
|
24
|
+
));
|
|
25
|
+
}
|
|
26
|
+
if (summary.batch_retry && summary.batch_retry.recovery_recommended) {
|
|
27
|
+
consoleLike.log(chalk.yellow(
|
|
28
|
+
` Rate-limit recovery recommended: signals=${summary.batch_retry.total_rate_limit_signals || 0}, ` +
|
|
29
|
+
`backoff=${summary.batch_retry.total_rate_limit_backoff_ms || 0}ms`
|
|
30
|
+
));
|
|
31
|
+
if (summary.batch_retry.recovery_suggested_command) {
|
|
32
|
+
consoleLike.log(chalk.yellow(` Suggested command: ${summary.batch_retry.recovery_suggested_command}`));
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
if (summary.resource_plan.agent_budget !== null) {
|
|
36
|
+
consoleLike.log(chalk.gray(
|
|
37
|
+
` Agent budget: ${summary.resource_plan.agent_budget} ` +
|
|
38
|
+
`(per-goal maxParallel=${summary.resource_plan.per_goal_max_parallel})`
|
|
39
|
+
));
|
|
40
|
+
}
|
|
41
|
+
consoleLike.log(chalk.gray(` Success rate: ${summary.metrics.success_rate_percent}%`));
|
|
42
|
+
if (summary.program_kpi) {
|
|
43
|
+
consoleLike.log(chalk.gray(
|
|
44
|
+
` Program KPI: ${summary.program_kpi.convergence_state}, ` +
|
|
45
|
+
`risk=${summary.program_kpi.risk_level}, ` +
|
|
46
|
+
`retry-recovery=${summary.program_kpi.retry_recovery_rate_percent}%`
|
|
47
|
+
));
|
|
48
|
+
}
|
|
49
|
+
if (summary.program_gate) {
|
|
50
|
+
consoleLike.log(chalk.gray(
|
|
51
|
+
` Program gate: ${summary.program_gate.passed ? 'passed' : 'failed'} ` +
|
|
52
|
+
`(profile=${summary.program_gate.policy.profile || 'default'}, ` +
|
|
53
|
+
`min-success=${summary.program_gate.policy.min_success_rate_percent}%, ` +
|
|
54
|
+
`max-risk=${summary.program_gate.policy.max_risk_level})`
|
|
55
|
+
));
|
|
56
|
+
const gatePolicy = summary.program_gate.policy || {};
|
|
57
|
+
const gateActual = summary.program_gate.actual || {};
|
|
58
|
+
if (
|
|
59
|
+
gatePolicy.max_elapsed_minutes !== null ||
|
|
60
|
+
gatePolicy.max_agent_budget !== null ||
|
|
61
|
+
gatePolicy.max_total_sub_specs !== null
|
|
62
|
+
) {
|
|
63
|
+
consoleLike.log(chalk.gray(
|
|
64
|
+
` Program budget gate: elapsed=${gateActual.elapsed_minutes ?? 'n/a'}/${gatePolicy.max_elapsed_minutes ?? 'n/a'} min, ` +
|
|
65
|
+
`agent=${gateActual.agent_budget ?? 'n/a'}/${gatePolicy.max_agent_budget ?? 'n/a'}, ` +
|
|
66
|
+
`sub-specs=${gateActual.total_sub_specs ?? 'n/a'}/${gatePolicy.max_total_sub_specs ?? 'n/a'}`
|
|
67
|
+
));
|
|
68
|
+
}
|
|
69
|
+
if (
|
|
70
|
+
summary.program_gate_effective &&
|
|
71
|
+
summary.program_gate_effective.source !== 'primary' &&
|
|
72
|
+
summary.program_gate_effective.fallback_profile
|
|
73
|
+
) {
|
|
74
|
+
consoleLike.log(chalk.gray(
|
|
75
|
+
` Program gate fallback accepted: profile=${summary.program_gate_effective.fallback_profile}`
|
|
76
|
+
));
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
if (
|
|
80
|
+
summary.program_diagnostics &&
|
|
81
|
+
Array.isArray(summary.program_diagnostics.remediation_actions) &&
|
|
82
|
+
summary.program_diagnostics.remediation_actions.length > 0
|
|
83
|
+
) {
|
|
84
|
+
const topAction = summary.program_diagnostics.remediation_actions[0];
|
|
85
|
+
consoleLike.log(chalk.gray(` Top remediation: ${topAction.action}`));
|
|
86
|
+
}
|
|
87
|
+
if (summary.recovery_cycle && summary.recovery_cycle.enabled) {
|
|
88
|
+
consoleLike.log(chalk.gray(
|
|
89
|
+
` Recovery rounds: ${summary.recovery_cycle.performed_rounds}/${summary.recovery_cycle.max_rounds}`
|
|
90
|
+
));
|
|
91
|
+
if (summary.recovery_cycle.budget_exhausted) {
|
|
92
|
+
consoleLike.log(chalk.gray(' Recovery time budget exhausted before convergence.'));
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
if (summary.auto_recovery && summary.auto_recovery.triggered) {
|
|
96
|
+
consoleLike.log(chalk.gray(
|
|
97
|
+
` Program auto-recovery: ${summary.auto_recovery.recovery_status} ` +
|
|
98
|
+
`(action ${summary.auto_recovery.selected_action_index || 'n/a'}, ` +
|
|
99
|
+
`source=${summary.auto_recovery.selection_source || 'default'})`
|
|
100
|
+
));
|
|
101
|
+
}
|
|
102
|
+
if (summary.program_governance && summary.program_governance.enabled) {
|
|
103
|
+
consoleLike.log(chalk.gray(
|
|
104
|
+
` Program governance: ${summary.program_governance.performed_rounds}/` +
|
|
105
|
+
`${summary.program_governance.max_rounds} rounds, stop=${summary.program_governance.stop_reason}`
|
|
106
|
+
));
|
|
107
|
+
if (summary.program_governance.action_selection_enabled) {
|
|
108
|
+
consoleLike.log(chalk.gray(
|
|
109
|
+
` Governance action selection: ` +
|
|
110
|
+
`${summary.program_governance.auto_action_enabled ? 'auto' : 'manual-only'}, ` +
|
|
111
|
+
`pinned=${summary.program_governance.pinned_action_index || 'none'}`
|
|
112
|
+
));
|
|
113
|
+
}
|
|
114
|
+
if (Array.isArray(summary.program_governance.history) && summary.program_governance.history.length > 0) {
|
|
115
|
+
const latestRound = summary.program_governance.history[summary.program_governance.history.length - 1];
|
|
116
|
+
if (latestRound && latestRound.selected_action) {
|
|
117
|
+
consoleLike.log(chalk.gray(
|
|
118
|
+
` Governance selected action: #${latestRound.selected_action_index || 'n/a'} ${latestRound.selected_action}`
|
|
119
|
+
));
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
if (summary.program_governance.exhausted) {
|
|
123
|
+
consoleLike.log(chalk.yellow(' Program governance exhausted before reaching stable state.'));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (Array.isArray(summary.program_kpi_anomalies) && summary.program_kpi_anomalies.length > 0) {
|
|
127
|
+
const highCount = summary.program_kpi_anomalies
|
|
128
|
+
.filter(item => `${item && item.severity ? item.severity : ''}`.trim().toLowerCase() === 'high')
|
|
129
|
+
.length;
|
|
130
|
+
consoleLike.log(chalk.gray(
|
|
131
|
+
` Program KPI anomalies: total=${summary.program_kpi_anomalies.length}, high=${highCount}`
|
|
132
|
+
));
|
|
133
|
+
}
|
|
134
|
+
if (summary.program_coordination) {
|
|
135
|
+
consoleLike.log(chalk.gray(
|
|
136
|
+
` Master/Sub sync: masters=${summary.program_coordination.master_spec_count}, ` +
|
|
137
|
+
`sub-specs=${summary.program_coordination.sub_spec_count}, ` +
|
|
138
|
+
`unresolved=${summary.program_coordination.unresolved_goal_count}`
|
|
139
|
+
));
|
|
140
|
+
}
|
|
141
|
+
if (summary.batch_session && summary.batch_session.file) {
|
|
142
|
+
consoleLike.log(chalk.gray(` Batch session: ${summary.batch_session.file}`));
|
|
143
|
+
}
|
|
144
|
+
if (summary.goal_input_guard && summary.goal_input_guard.enabled) {
|
|
145
|
+
consoleLike.log(chalk.gray(
|
|
146
|
+
` Goal duplicate guard: duplicates=${summary.goal_input_guard.duplicate_goals}/` +
|
|
147
|
+
`${summary.goal_input_guard.max_duplicate_goals}`
|
|
148
|
+
));
|
|
149
|
+
if (summary.goal_input_guard.over_limit) {
|
|
150
|
+
consoleLike.log(chalk.yellow(' Goal duplicate guard exceeded.'));
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
if (summary.spec_session_prune && summary.spec_session_prune.enabled) {
|
|
154
|
+
consoleLike.log(chalk.gray(
|
|
155
|
+
` Spec prune: deleted=${summary.spec_session_prune.deleted_count}, ` +
|
|
156
|
+
`protected=${summary.spec_session_prune.protected_count}`
|
|
157
|
+
));
|
|
158
|
+
}
|
|
159
|
+
if (summary.spec_session_budget && summary.spec_session_budget.enabled) {
|
|
160
|
+
consoleLike.log(chalk.gray(
|
|
161
|
+
` Spec budget: ${summary.spec_session_budget.total_after}/${summary.spec_session_budget.max_total} ` +
|
|
162
|
+
`(created~${summary.spec_session_budget.estimated_created}, pruned=${summary.spec_session_budget.pruned_count})`
|
|
163
|
+
));
|
|
164
|
+
if (summary.spec_session_budget.over_limit_after) {
|
|
165
|
+
consoleLike.log(chalk.yellow(
|
|
166
|
+
` Spec budget exceeded (${summary.spec_session_budget.total_after} > ${summary.spec_session_budget.max_total})`
|
|
167
|
+
));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
if (summary.spec_session_growth_guard && summary.spec_session_growth_guard.enabled) {
|
|
171
|
+
consoleLike.log(chalk.gray(
|
|
172
|
+
` Spec growth guard: created~${summary.spec_session_growth_guard.estimated_created}` +
|
|
173
|
+
` (per-goal=${summary.spec_session_growth_guard.estimated_created_per_goal})`
|
|
174
|
+
));
|
|
175
|
+
if (summary.spec_session_growth_guard.over_limit) {
|
|
176
|
+
consoleLike.log(chalk.yellow(` Spec growth guard exceeded: ${summary.spec_session_growth_guard.reasons.join('; ')}`));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
if (summary.program_gate_auto_remediation && summary.program_gate_auto_remediation.enabled) {
|
|
180
|
+
const autoRemediationActions = Array.isArray(summary.program_gate_auto_remediation.actions)
|
|
181
|
+
? summary.program_gate_auto_remediation.actions
|
|
182
|
+
: [];
|
|
183
|
+
consoleLike.log(chalk.gray(
|
|
184
|
+
` Program auto-remediation: actions=${autoRemediationActions.length}, ` +
|
|
185
|
+
`next-patch=${summary.program_gate_auto_remediation.next_run_patch ? 'yes' : 'no'}`
|
|
186
|
+
));
|
|
187
|
+
}
|
|
188
|
+
if (summary.program_kpi_file) {
|
|
189
|
+
consoleLike.log(chalk.gray(` Program KPI file: ${summary.program_kpi_file}`));
|
|
190
|
+
}
|
|
191
|
+
if (summary.program_audit_file) {
|
|
192
|
+
consoleLike.log(chalk.gray(` Program audit file: ${summary.program_audit_file}`));
|
|
193
|
+
}
|
|
194
|
+
if (summary.output_file) {
|
|
195
|
+
consoleLike.log(chalk.gray(` Output: ${summary.output_file}`));
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
module.exports = {
|
|
200
|
+
printCloseLoopBatchSummary
|
|
201
|
+
};
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
|
|
3
|
+
function getCloseLoopBatchSummaryDir(projectPath) {
|
|
4
|
+
return path.join(projectPath, '.sce', 'auto', 'close-loop-batch-summaries');
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
async function readCloseLoopBatchSummaryEntries(projectPath, dependencies = {}) {
|
|
8
|
+
const { fs } = dependencies;
|
|
9
|
+
const summaryDir = getCloseLoopBatchSummaryDir(projectPath);
|
|
10
|
+
if (!(await fs.pathExists(summaryDir))) {
|
|
11
|
+
return [];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
const files = (await fs.readdir(summaryDir)).filter((item) => item.toLowerCase().endsWith('.json'));
|
|
15
|
+
const sessions = [];
|
|
16
|
+
for (const file of files) {
|
|
17
|
+
const filePath = path.join(summaryDir, file);
|
|
18
|
+
const stats = await fs.stat(filePath);
|
|
19
|
+
const fallbackTimestamp = new Date(stats.mtimeMs).toISOString();
|
|
20
|
+
const fallbackId = path.basename(file, '.json');
|
|
21
|
+
let payload = null;
|
|
22
|
+
let parseError = null;
|
|
23
|
+
|
|
24
|
+
try {
|
|
25
|
+
payload = await fs.readJson(filePath);
|
|
26
|
+
} catch (error) {
|
|
27
|
+
parseError = error;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
sessions.push({
|
|
31
|
+
id: payload && typeof payload.batch_session === 'object' && typeof payload.batch_session.id === 'string'
|
|
32
|
+
? payload.batch_session.id
|
|
33
|
+
: fallbackId,
|
|
34
|
+
file: filePath,
|
|
35
|
+
status: payload && typeof payload.status === 'string'
|
|
36
|
+
? payload.status
|
|
37
|
+
: parseError
|
|
38
|
+
? 'invalid'
|
|
39
|
+
: 'unknown',
|
|
40
|
+
goals_file: payload && typeof payload.goals_file === 'string' ? payload.goals_file : null,
|
|
41
|
+
total_goals: payload && Number.isInteger(Number(payload.total_goals)) ? Number(payload.total_goals) : null,
|
|
42
|
+
processed_goals: payload && Number.isInteger(Number(payload.processed_goals)) ? Number(payload.processed_goals) : null,
|
|
43
|
+
updated_at: payload && typeof payload.updated_at === 'string' ? payload.updated_at : fallbackTimestamp,
|
|
44
|
+
parse_error: parseError ? parseError.message : null,
|
|
45
|
+
mtime_ms: stats.mtimeMs
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
sessions.sort((a, b) => b.mtime_ms - a.mtime_ms);
|
|
50
|
+
return sessions;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
async function resolveCloseLoopBatchSummaryFile(projectPath, summaryCandidate, dependencies = {}) {
|
|
54
|
+
const { fs } = dependencies;
|
|
55
|
+
if (typeof summaryCandidate !== 'string' || !summaryCandidate.trim()) {
|
|
56
|
+
throw new Error('--resume-from-summary requires a file path or "latest".');
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const normalizedCandidate = summaryCandidate.trim();
|
|
60
|
+
if (normalizedCandidate.toLowerCase() === 'latest') {
|
|
61
|
+
const summaryDir = getCloseLoopBatchSummaryDir(projectPath);
|
|
62
|
+
if (!(await fs.pathExists(summaryDir))) {
|
|
63
|
+
throw new Error(`No batch summary sessions found in: ${summaryDir}`);
|
|
64
|
+
}
|
|
65
|
+
const candidates = (await fs.readdir(summaryDir)).filter((item) => item.toLowerCase().endsWith('.json'));
|
|
66
|
+
if (candidates.length === 0) {
|
|
67
|
+
throw new Error(`No batch summary sessions found in: ${summaryDir}`);
|
|
68
|
+
}
|
|
69
|
+
const entries = [];
|
|
70
|
+
for (const file of candidates) {
|
|
71
|
+
const filePath = path.join(summaryDir, file);
|
|
72
|
+
const stats = await fs.stat(filePath);
|
|
73
|
+
entries.push({ file: filePath, mtimeMs: stats.mtimeMs });
|
|
74
|
+
}
|
|
75
|
+
entries.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
76
|
+
return entries[0].file;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return path.isAbsolute(normalizedCandidate)
|
|
80
|
+
? normalizedCandidate
|
|
81
|
+
: path.join(projectPath, normalizedCandidate);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function loadCloseLoopBatchSummaryPayload(projectPath, summaryCandidate, dependencies = {}) {
|
|
85
|
+
const { fs } = dependencies;
|
|
86
|
+
const summaryFile = await resolveCloseLoopBatchSummaryFile(projectPath, summaryCandidate, dependencies);
|
|
87
|
+
if (!(await fs.pathExists(summaryFile))) {
|
|
88
|
+
throw new Error(`Batch summary file not found: ${summaryFile}`);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
let payload = null;
|
|
92
|
+
try {
|
|
93
|
+
payload = await fs.readJson(summaryFile);
|
|
94
|
+
} catch (error) {
|
|
95
|
+
throw new Error(`Invalid batch summary JSON: ${summaryFile} (${error.message})`);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (!payload || typeof payload !== 'object') {
|
|
99
|
+
throw new Error(`Invalid batch summary payload: ${summaryFile}`);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return { file: summaryFile, payload };
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
module.exports = {
|
|
106
|
+
getCloseLoopBatchSummaryDir,
|
|
107
|
+
readCloseLoopBatchSummaryEntries,
|
|
108
|
+
resolveCloseLoopBatchSummaryFile,
|
|
109
|
+
loadCloseLoopBatchSummaryPayload
|
|
110
|
+
};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
async function executeCloseLoopBatch(goalsResult, options, projectPath, mode = 'auto-close-loop-batch', dependencies = {}) {
|
|
2
|
+
const {
|
|
3
|
+
buildGoalInputGuard,
|
|
4
|
+
startSpecSessionBudgetEvaluation,
|
|
5
|
+
resolveBatchAutonomousPolicy,
|
|
6
|
+
normalizeBatchParallel,
|
|
7
|
+
runCloseLoopBatchWithRetries,
|
|
8
|
+
buildBatchRunOptions,
|
|
9
|
+
buildBatchMetrics,
|
|
10
|
+
collectSpecNamesFromBatchSummary,
|
|
11
|
+
maybePruneSpecSessionsWithPolicy,
|
|
12
|
+
finalizeSpecSessionBudgetEvaluation,
|
|
13
|
+
buildSpecSessionGrowthGuard,
|
|
14
|
+
buildProgramKpiSnapshot,
|
|
15
|
+
buildProgramDiagnostics,
|
|
16
|
+
buildProgramCoordinationSnapshot,
|
|
17
|
+
maybeWriteProgramKpi,
|
|
18
|
+
maybePersistCloseLoopBatchSummary,
|
|
19
|
+
maybeWriteOutput
|
|
20
|
+
} = dependencies;
|
|
21
|
+
|
|
22
|
+
const goalInputGuard = buildGoalInputGuard(goalsResult && goalsResult.goals, options);
|
|
23
|
+
if (goalInputGuard.over_limit && goalInputGuard.hard_fail_triggered) {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Goal input duplicate guard exceeded: ${goalInputGuard.duplicate_goals} > ${goalInputGuard.max_duplicate_goals}. ` +
|
|
26
|
+
'Reduce duplicated goals or raise --spec-session-max-duplicate-goals.'
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const specSessionBudget = await startSpecSessionBudgetEvaluation(projectPath, options);
|
|
31
|
+
if (specSessionBudget && specSessionBudget.hard_fail && specSessionBudget.over_limit_before) {
|
|
32
|
+
throw new Error(
|
|
33
|
+
`Spec session budget exceeded before run: ${specSessionBudget.total_before} > ${specSessionBudget.max_total}. ` +
|
|
34
|
+
'Run "sce auto spec-session prune ..." or raise --spec-session-max-total.'
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
const batchAutonomousPolicy = resolveBatchAutonomousPolicy(options, goalsResult.goals.length);
|
|
39
|
+
const effectiveBatchOptions = batchAutonomousPolicy.options;
|
|
40
|
+
const batchParallel = normalizeBatchParallel(effectiveBatchOptions.batchParallel);
|
|
41
|
+
const batchRun = await runCloseLoopBatchWithRetries(goalsResult.goals, {
|
|
42
|
+
projectPath,
|
|
43
|
+
continueOnError: Boolean(effectiveBatchOptions.continueOnError),
|
|
44
|
+
batchParallel,
|
|
45
|
+
batchAgentBudget: effectiveBatchOptions.batchAgentBudget,
|
|
46
|
+
batchPriority: effectiveBatchOptions.batchPriority,
|
|
47
|
+
batchAgingFactor: effectiveBatchOptions.batchAgingFactor,
|
|
48
|
+
batchRetryRounds: effectiveBatchOptions.batchRetryRounds,
|
|
49
|
+
batchRetryStrategy: effectiveBatchOptions.batchRetryStrategy,
|
|
50
|
+
batchRetryUntilComplete: effectiveBatchOptions.batchRetryUntilComplete,
|
|
51
|
+
batchRetryMaxRounds: effectiveBatchOptions.batchRetryMaxRounds,
|
|
52
|
+
goalEntries: Array.isArray(goalsResult.goal_entries) ? goalsResult.goal_entries : null,
|
|
53
|
+
runOptions: buildBatchRunOptions(effectiveBatchOptions)
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const results = batchRun.results;
|
|
57
|
+
const stoppedEarly = batchRun.stoppedEarly;
|
|
58
|
+
const failedStatuses = new Set(['failed', 'error', 'unknown', 'stopped']);
|
|
59
|
+
const failedGoals = results.filter(item => failedStatuses.has(item.status)).length;
|
|
60
|
+
const completedGoals = results.length - failedGoals;
|
|
61
|
+
const status = failedGoals === 0
|
|
62
|
+
? 'completed'
|
|
63
|
+
: completedGoals === 0
|
|
64
|
+
? 'failed'
|
|
65
|
+
: 'partial-failed';
|
|
66
|
+
const metrics = buildBatchMetrics(results, goalsResult.goals.length);
|
|
67
|
+
|
|
68
|
+
const summary = {
|
|
69
|
+
mode,
|
|
70
|
+
status,
|
|
71
|
+
goals_file: goalsResult.file,
|
|
72
|
+
resumed_from_summary: goalsResult.resumedFromSummary || null,
|
|
73
|
+
generated_from_goal: goalsResult.generatedFromGoal || null,
|
|
74
|
+
total_goals: goalsResult.goals.length,
|
|
75
|
+
processed_goals: results.length,
|
|
76
|
+
completed_goals: completedGoals,
|
|
77
|
+
failed_goals: failedGoals,
|
|
78
|
+
batch_parallel: batchRun.effectiveParallel,
|
|
79
|
+
autonomous_policy: batchAutonomousPolicy.summary,
|
|
80
|
+
resource_plan: batchRun.resourcePlan,
|
|
81
|
+
batch_retry: batchRun.retry,
|
|
82
|
+
stopped_early: stoppedEarly,
|
|
83
|
+
metrics,
|
|
84
|
+
goal_input_guard: goalInputGuard,
|
|
85
|
+
results
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
const currentRunSpecNames = collectSpecNamesFromBatchSummary(summary);
|
|
89
|
+
summary.spec_session_prune = await maybePruneSpecSessionsWithPolicy(
|
|
90
|
+
projectPath,
|
|
91
|
+
options,
|
|
92
|
+
currentRunSpecNames
|
|
93
|
+
);
|
|
94
|
+
summary.spec_session_budget = await finalizeSpecSessionBudgetEvaluation(
|
|
95
|
+
projectPath,
|
|
96
|
+
specSessionBudget,
|
|
97
|
+
summary.spec_session_prune
|
|
98
|
+
);
|
|
99
|
+
summary.spec_session_growth_guard = buildSpecSessionGrowthGuard(summary, options);
|
|
100
|
+
|
|
101
|
+
if (mode === 'auto-close-loop-program' || mode === 'auto-close-loop-recover') {
|
|
102
|
+
summary.program_kpi = buildProgramKpiSnapshot(summary);
|
|
103
|
+
summary.program_diagnostics = buildProgramDiagnostics(summary);
|
|
104
|
+
summary.program_coordination = buildProgramCoordinationSnapshot(summary);
|
|
105
|
+
await maybeWriteProgramKpi(summary, options.programKpiOut, projectPath);
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
await maybePersistCloseLoopBatchSummary(summary, options, projectPath);
|
|
109
|
+
await maybeWriteOutput(summary, options.out, projectPath);
|
|
110
|
+
|
|
111
|
+
return summary;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
module.exports = {
|
|
115
|
+
executeCloseLoopBatch
|
|
116
|
+
};
|