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.
Files changed (83) hide show
  1. package/CHANGELOG.md +86 -1
  2. package/README.md +119 -122
  3. package/README.zh.md +123 -121
  4. package/bin/scene-capability-engine.js +11 -0
  5. package/docs/README.md +21 -32
  6. package/docs/auto-refactor-index.md +384 -0
  7. package/docs/command-reference.md +94 -2
  8. package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
  9. package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
  10. package/docs/magicball-capability-iteration-api.md +2 -0
  11. package/docs/magicball-capability-iteration-ui.md +2 -0
  12. package/docs/magicball-capability-library.md +2 -0
  13. package/docs/magicball-cli-invocation-examples.md +336 -0
  14. package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
  15. package/docs/magicball-integration-doc-index.md +137 -0
  16. package/docs/magicball-integration-issue-tracker.md +218 -0
  17. package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
  18. package/docs/magicball-sce-adaptation-guide.md +203 -0
  19. package/docs/magicball-three-mode-alignment-plan.md +551 -0
  20. package/docs/magicball-ui-surface-checklist.md +126 -0
  21. package/docs/magicball-write-auth-adaptation-guide.md +328 -0
  22. package/docs/refactor-completion-roadmap.md +116 -0
  23. package/docs/zh/README.md +27 -30
  24. package/docs/zh/refactor-completion-roadmap.md +116 -0
  25. package/lib/app/registry-config.js +73 -0
  26. package/lib/app/registry-sync-service.js +228 -0
  27. package/lib/auto/archive-schema-service.js +276 -0
  28. package/lib/auto/archive-summary.js +60 -0
  29. package/lib/auto/batch-goal-input-service.js +543 -0
  30. package/lib/auto/batch-output.js +201 -0
  31. package/lib/auto/batch-summary-storage-service.js +110 -0
  32. package/lib/auto/close-loop-batch-service.js +116 -0
  33. package/lib/auto/close-loop-controller-service.js +287 -0
  34. package/lib/auto/close-loop-program-service.js +283 -0
  35. package/lib/auto/close-loop-recovery-service.js +191 -0
  36. package/lib/auto/close-loop-session-storage-service.js +50 -0
  37. package/lib/auto/controller-lock-service.js +55 -0
  38. package/lib/auto/controller-output.js +32 -0
  39. package/lib/auto/controller-queue-service.js +127 -0
  40. package/lib/auto/controller-session-storage-service.js +105 -0
  41. package/lib/auto/governance-advisory-service.js +208 -0
  42. package/lib/auto/governance-close-loop-service.js +411 -0
  43. package/lib/auto/governance-maintenance-presenter.js +162 -0
  44. package/lib/auto/governance-maintenance-service.js +112 -0
  45. package/lib/auto/governance-session-presenter.js +70 -0
  46. package/lib/auto/governance-session-storage-service.js +198 -0
  47. package/lib/auto/governance-signals.js +139 -0
  48. package/lib/auto/governance-stats-presenter.js +337 -0
  49. package/lib/auto/governance-stats-service.js +115 -0
  50. package/lib/auto/governance-summary.js +703 -0
  51. package/lib/auto/handoff-capability-matrix-service.js +281 -0
  52. package/lib/auto/handoff-evidence-review-service.js +251 -0
  53. package/lib/auto/handoff-release-evidence-service.js +190 -0
  54. package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
  55. package/lib/auto/handoff-release-gate-history-service.js +257 -0
  56. package/lib/auto/handoff-reporting-service.js +1407 -0
  57. package/lib/auto/handoff-run-service.js +486 -0
  58. package/lib/auto/handoff-snapshots-service.js +645 -0
  59. package/lib/auto/observability-service.js +132 -0
  60. package/lib/auto/output-writer.js +34 -0
  61. package/lib/auto/program-auto-remediation-service.js +130 -0
  62. package/lib/auto/program-diagnostics.js +138 -0
  63. package/lib/auto/program-governance-helpers.js +306 -0
  64. package/lib/auto/program-governance-loop-service.js +413 -0
  65. package/lib/auto/program-output.js +106 -0
  66. package/lib/auto/program-summary.js +183 -0
  67. package/lib/auto/recovery-memory-service.js +684 -0
  68. package/lib/auto/recovery-selection-service.js +52 -0
  69. package/lib/auto/retention-policy.js +98 -0
  70. package/lib/auto/session-persistence-service.js +106 -0
  71. package/lib/auto/session-presenter.js +105 -0
  72. package/lib/auto/session-prune-service.js +190 -0
  73. package/lib/auto/session-query-service.js +249 -0
  74. package/lib/auto/spec-protection.js +141 -0
  75. package/lib/commands/app.js +911 -0
  76. package/lib/commands/assurance.js +212 -0
  77. package/lib/commands/auto.js +1091 -11063
  78. package/lib/commands/mode.js +321 -0
  79. package/lib/commands/ontology.js +415 -0
  80. package/lib/commands/pm.js +422 -0
  81. package/lib/ontology/seed-profiles.js +160 -0
  82. package/lib/state/sce-state-store.js +3369 -1200
  83. 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
+ };