scene-capability-engine 3.6.32 → 3.6.37

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 (111) hide show
  1. package/CHANGELOG.md +109 -11
  2. package/README.md +119 -122
  3. package/README.zh.md +123 -121
  4. package/bin/scene-capability-engine.js +12 -1
  5. package/docs/331-poc-adaptation-roadmap.md +3 -3
  6. package/docs/README.md +21 -32
  7. package/docs/auto-refactor-index.md +384 -0
  8. package/docs/command-reference.md +99 -7
  9. package/docs/faq.md +1 -1
  10. package/docs/interactive-customization/331-poc-sce-integration-checklist.md +3 -3
  11. package/docs/interactive-customization/moqui-interactive-template-playbook.md +4 -4
  12. package/docs/interactive-customization/phase-acceptance-evidence.md +2 -2
  13. package/docs/magicball-adaptation-task-checklist-v1.md +385 -0
  14. package/docs/magicball-app-bundle-sqlite-and-command-draft.md +539 -0
  15. package/docs/magicball-capability-iteration-api.md +2 -0
  16. package/docs/magicball-capability-iteration-ui.md +2 -0
  17. package/docs/magicball-capability-library.md +2 -0
  18. package/docs/magicball-cli-invocation-examples.md +336 -0
  19. package/docs/magicball-frontend-state-and-command-mapping.md +244 -0
  20. package/docs/magicball-integration-doc-index.md +137 -0
  21. package/docs/magicball-integration-issue-tracker.md +218 -0
  22. package/docs/magicball-mode-home-and-ontology-empty-state-playbook.md +249 -0
  23. package/docs/magicball-sce-adaptation-guide.md +203 -0
  24. package/docs/magicball-three-mode-alignment-plan.md +551 -0
  25. package/docs/magicball-ui-surface-checklist.md +126 -0
  26. package/docs/magicball-write-auth-adaptation-guide.md +328 -0
  27. package/docs/moqui-standard-rebuild-guide.md +6 -6
  28. package/docs/moqui-template-core-library-playbook.md +1 -1
  29. package/docs/refactor-completion-roadmap.md +116 -0
  30. package/docs/release-checklist.md +50 -27
  31. package/docs/releases/README.md +1 -0
  32. package/docs/releases/v3.6.37.md +22 -0
  33. package/docs/steering-strategy-guide.md +7 -7
  34. package/docs/troubleshooting.md +1 -1
  35. package/docs/zh/README.md +27 -30
  36. package/docs/zh/refactor-completion-roadmap.md +116 -0
  37. package/docs/zh/release-checklist.md +40 -17
  38. package/docs/zh/releases/README.md +1 -0
  39. package/docs/zh/releases/v3.6.37.md +22 -0
  40. package/lib/app/registry-config.js +73 -0
  41. package/lib/app/registry-sync-service.js +228 -0
  42. package/lib/auto/archive-schema-service.js +276 -0
  43. package/lib/auto/archive-summary.js +60 -0
  44. package/lib/auto/batch-goal-input-service.js +543 -0
  45. package/lib/auto/batch-output.js +201 -0
  46. package/lib/auto/batch-summary-storage-service.js +110 -0
  47. package/lib/auto/close-loop-batch-service.js +116 -0
  48. package/lib/auto/close-loop-controller-service.js +287 -0
  49. package/lib/auto/close-loop-program-service.js +283 -0
  50. package/lib/auto/close-loop-recovery-service.js +191 -0
  51. package/lib/auto/close-loop-session-storage-service.js +50 -0
  52. package/lib/auto/controller-lock-service.js +55 -0
  53. package/lib/auto/controller-output.js +32 -0
  54. package/lib/auto/controller-queue-service.js +127 -0
  55. package/lib/auto/controller-session-storage-service.js +105 -0
  56. package/lib/auto/governance-advisory-service.js +208 -0
  57. package/lib/auto/governance-close-loop-service.js +411 -0
  58. package/lib/auto/governance-maintenance-presenter.js +162 -0
  59. package/lib/auto/governance-maintenance-service.js +112 -0
  60. package/lib/auto/governance-session-presenter.js +70 -0
  61. package/lib/auto/governance-session-storage-service.js +198 -0
  62. package/lib/auto/governance-signals.js +139 -0
  63. package/lib/auto/governance-stats-presenter.js +337 -0
  64. package/lib/auto/governance-stats-service.js +115 -0
  65. package/lib/auto/governance-summary.js +703 -0
  66. package/lib/auto/handoff-capability-matrix-service.js +281 -0
  67. package/lib/auto/handoff-evidence-review-service.js +251 -0
  68. package/lib/auto/handoff-release-evidence-service.js +190 -0
  69. package/lib/auto/handoff-release-gate-history-loaders-service.js +502 -0
  70. package/lib/auto/handoff-release-gate-history-service.js +257 -0
  71. package/lib/auto/handoff-reporting-service.js +1407 -0
  72. package/lib/auto/handoff-run-service.js +486 -0
  73. package/lib/auto/handoff-snapshots-service.js +645 -0
  74. package/lib/auto/observability-service.js +132 -0
  75. package/lib/auto/output-writer.js +34 -0
  76. package/lib/auto/program-auto-remediation-service.js +130 -0
  77. package/lib/auto/program-diagnostics.js +138 -0
  78. package/lib/auto/program-governance-helpers.js +306 -0
  79. package/lib/auto/program-governance-loop-service.js +413 -0
  80. package/lib/auto/program-output.js +106 -0
  81. package/lib/auto/program-summary.js +183 -0
  82. package/lib/auto/recovery-memory-service.js +684 -0
  83. package/lib/auto/recovery-selection-service.js +52 -0
  84. package/lib/auto/retention-policy.js +98 -0
  85. package/lib/auto/session-persistence-service.js +106 -0
  86. package/lib/auto/session-presenter.js +105 -0
  87. package/lib/auto/session-prune-service.js +190 -0
  88. package/lib/auto/session-query-service.js +249 -0
  89. package/lib/auto/spec-protection.js +141 -0
  90. package/lib/commands/adopt.js +4 -4
  91. package/lib/commands/app.js +911 -0
  92. package/lib/commands/assurance.js +212 -0
  93. package/lib/commands/auto.js +1093 -11065
  94. package/lib/commands/mode.js +321 -0
  95. package/lib/commands/ontology.js +415 -0
  96. package/lib/commands/pm.js +422 -0
  97. package/lib/ontology/seed-profiles.js +160 -0
  98. package/lib/spec/bootstrap/context-collector.js +1 -1
  99. package/lib/state/sce-state-store.js +3369 -1200
  100. package/lib/steering/adoption-config.js +2 -2
  101. package/lib/steering/compliance-cache.js +2 -2
  102. package/lib/steering/steering-manager.js +4 -4
  103. package/lib/task/task-claimer.js +1 -2
  104. package/lib/workspace/multi/workspace-context-resolver.js +3 -3
  105. package/lib/workspace/multi/workspace-state-manager.js +0 -164
  106. package/lib/workspace/sce-tracking-audit.js +1 -1
  107. package/lib/workspace/takeover-baseline.js +1 -1
  108. package/package.json +1 -1
  109. package/template/.sce/README.md +1 -1
  110. package/template/.sce/steering/CORE_PRINCIPLES.md +1 -1
  111. package/bin/kse.js +0 -3
@@ -0,0 +1,208 @@
1
+ const { resolveLatestRecoverableBatchSummary, resolveLatestPendingControllerSession } = require('./recovery-selection-service');
2
+
3
+ async function executeGovernanceAdvisoryRecover(projectPath, options = {}, dependencies = {}) {
4
+ const {
5
+ normalizeGovernanceAdvisoryRecoverMaxRounds,
6
+ loadCloseLoopBatchSummaryPayload,
7
+ resolveRecoveryMemoryScope,
8
+ executeCloseLoopRecoveryCycle,
9
+ readCloseLoopBatchSummaryEntries,
10
+ buildCloseLoopBatchGoalsFromSummaryPayload
11
+ } = dependencies;
12
+ const recoverMaxRounds = normalizeGovernanceAdvisoryRecoverMaxRounds(options.recoverMaxRounds, 3);
13
+ const dryRun = Boolean(options.dryRun);
14
+ const summaryCandidate = typeof options.summary === 'string' && options.summary.trim()
15
+ ? options.summary.trim()
16
+ : 'latest';
17
+ const explicitSummary = summaryCandidate.toLowerCase() !== 'latest';
18
+ let sourceSummary = null;
19
+
20
+ if (explicitSummary) {
21
+ try {
22
+ sourceSummary = await loadCloseLoopBatchSummaryPayload(projectPath, summaryCandidate);
23
+ } catch (error) {
24
+ return {
25
+ id: 'recover-latest',
26
+ status: 'failed',
27
+ recover_max_rounds: recoverMaxRounds,
28
+ dry_run: dryRun,
29
+ error: error.message
30
+ };
31
+ }
32
+ } else {
33
+ sourceSummary = await resolveLatestRecoverableBatchSummary(projectPath, 'pending', {
34
+ readCloseLoopBatchSummaryEntries,
35
+ loadCloseLoopBatchSummaryPayload,
36
+ buildCloseLoopBatchGoalsFromSummaryPayload
37
+ });
38
+ }
39
+
40
+ if (!sourceSummary) {
41
+ return {
42
+ id: 'recover-latest',
43
+ status: 'skipped',
44
+ recover_max_rounds: recoverMaxRounds,
45
+ dry_run: dryRun,
46
+ error: 'No recoverable batch summary with pending goals was found.'
47
+ };
48
+ }
49
+
50
+ try {
51
+ const recoveryMemoryScope = await resolveRecoveryMemoryScope(projectPath, options.recoveryMemoryScope);
52
+ const recoveryResult = await executeCloseLoopRecoveryCycle({
53
+ projectPath,
54
+ sourceSummary,
55
+ baseOptions: {
56
+ dryRun,
57
+ run: dryRun ? false : undefined,
58
+ resumeStrategy: 'pending',
59
+ batchAutonomous: true,
60
+ continueOnError: true
61
+ },
62
+ recoverAutonomousEnabled: true,
63
+ resumeStrategy: 'pending',
64
+ recoverUntilComplete: true,
65
+ recoverMaxRounds,
66
+ recoverMaxDurationMs: null,
67
+ recoveryMemoryTtlDays: null,
68
+ recoveryMemoryScope,
69
+ actionCandidate: null
70
+ });
71
+ const recoverySummary = recoveryResult && recoveryResult.summary ? recoveryResult.summary : null;
72
+ const recoveryStatus = `${recoverySummary && recoverySummary.status ? recoverySummary.status : ''}`.trim().toLowerCase();
73
+ const failed = recoveryStatus === 'failed' || recoveryStatus === 'partial-failed';
74
+
75
+ return {
76
+ id: 'recover-latest',
77
+ status: failed ? 'failed' : 'applied',
78
+ recover_max_rounds: recoverMaxRounds,
79
+ dry_run: dryRun,
80
+ source_summary_file: sourceSummary.file,
81
+ result: recoverySummary
82
+ ? {
83
+ status: recoverySummary.status,
84
+ processed_goals: Number(recoverySummary.processed_goals) || 0,
85
+ failed_goals: Number(recoverySummary.failed_goals) || 0,
86
+ recovery_cycle: recoverySummary.recovery_cycle || null,
87
+ batch_session_file: recoverySummary.batch_session && recoverySummary.batch_session.file
88
+ ? recoverySummary.batch_session.file
89
+ : null
90
+ }
91
+ : null,
92
+ error: failed && recoverySummary
93
+ ? `Recovery finished with status: ${recoverySummary.status}`
94
+ : null
95
+ };
96
+ } catch (error) {
97
+ return {
98
+ id: 'recover-latest',
99
+ status: 'failed',
100
+ recover_max_rounds: recoverMaxRounds,
101
+ dry_run: dryRun,
102
+ source_summary_file: sourceSummary.file,
103
+ error: error.message
104
+ };
105
+ }
106
+ }
107
+
108
+ async function executeGovernanceAdvisoryControllerResume(projectPath, options = {}, dependencies = {}) {
109
+ const {
110
+ normalizeGovernanceAdvisoryControllerMaxCycles,
111
+ loadCloseLoopControllerSessionPayload,
112
+ runCloseLoopController,
113
+ readCloseLoopControllerSessionEntries
114
+ } = dependencies;
115
+ const maxCycles = normalizeGovernanceAdvisoryControllerMaxCycles(options.maxCycles, 20);
116
+ const dryRun = Boolean(options.dryRun);
117
+ const sessionCandidate = typeof options.session === 'string' && options.session.trim()
118
+ ? options.session.trim()
119
+ : 'latest';
120
+ const explicitSession = sessionCandidate.toLowerCase() !== 'latest';
121
+ let sourceSession = null;
122
+
123
+ if (explicitSession) {
124
+ try {
125
+ sourceSession = await loadCloseLoopControllerSessionPayload(projectPath, sessionCandidate);
126
+ } catch (error) {
127
+ return {
128
+ id: 'controller-resume-latest',
129
+ status: 'failed',
130
+ max_cycles: maxCycles,
131
+ dry_run: dryRun,
132
+ error: error.message
133
+ };
134
+ }
135
+ } else {
136
+ sourceSession = await resolveLatestPendingControllerSession(projectPath, {
137
+ readCloseLoopControllerSessionEntries,
138
+ loadCloseLoopControllerSessionPayload
139
+ });
140
+ }
141
+
142
+ if (!sourceSession) {
143
+ return {
144
+ id: 'controller-resume-latest',
145
+ status: 'skipped',
146
+ max_cycles: maxCycles,
147
+ dry_run: dryRun,
148
+ error: 'No controller session with pending goals was found.'
149
+ };
150
+ }
151
+
152
+ try {
153
+ const controllerSummary = await runCloseLoopController(null, {
154
+ maxCycles,
155
+ dryRun,
156
+ run: dryRun ? false : undefined,
157
+ waitOnEmpty: false,
158
+ stopOnGoalFailure: false,
159
+ controllerPrintProgramSummary: false,
160
+ controllerOut: null,
161
+ out: null,
162
+ json: false
163
+ }, {
164
+ projectPath,
165
+ resumedSession: sourceSession
166
+ });
167
+ const controllerStatus = `${controllerSummary && controllerSummary.status ? controllerSummary.status : ''}`.trim().toLowerCase();
168
+ const failed = controllerStatus === 'failed' || controllerStatus === 'partial-failed';
169
+
170
+ return {
171
+ id: 'controller-resume-latest',
172
+ status: failed ? 'failed' : 'applied',
173
+ max_cycles: maxCycles,
174
+ dry_run: dryRun,
175
+ source_controller_session_file: sourceSession.file,
176
+ result: controllerSummary
177
+ ? {
178
+ status: controllerSummary.status,
179
+ cycles_performed: Number(controllerSummary.cycles_performed) || 0,
180
+ processed_goals: Number(controllerSummary.processed_goals) || 0,
181
+ failed_goals: Number(controllerSummary.failed_goals) || 0,
182
+ pending_goals: Number(controllerSummary.pending_goals) || 0,
183
+ stop_reason: controllerSummary.stop_reason || null,
184
+ controller_session_file: controllerSummary.controller_session && controllerSummary.controller_session.file
185
+ ? controllerSummary.controller_session.file
186
+ : null
187
+ }
188
+ : null,
189
+ error: failed && controllerSummary
190
+ ? `Controller resume finished with status: ${controllerSummary.status}`
191
+ : null
192
+ };
193
+ } catch (error) {
194
+ return {
195
+ id: 'controller-resume-latest',
196
+ status: 'failed',
197
+ max_cycles: maxCycles,
198
+ dry_run: dryRun,
199
+ source_controller_session_file: sourceSession.file,
200
+ error: error.message
201
+ };
202
+ }
203
+ }
204
+
205
+ module.exports = {
206
+ executeGovernanceAdvisoryRecover,
207
+ executeGovernanceAdvisoryControllerResume
208
+ };
@@ -0,0 +1,411 @@
1
+ const path = require('path');
2
+
3
+ async function runAutoGovernanceCloseLoop(projectPath, options = {}, dependencies = {}) {
4
+ const {
5
+ loadGovernanceCloseLoopSessionPayload,
6
+ isExplicitOptionSource,
7
+ normalizeStatsWindowDays,
8
+ normalizeStatusFilter,
9
+ normalizeGovernanceMaxRounds,
10
+ normalizeGovernanceTargetRiskLevel,
11
+ normalizeGovernanceAdvisoryRecoverMaxRounds,
12
+ normalizeGovernanceAdvisoryControllerMaxCycles,
13
+ normalizeGovernanceSessionKeep,
14
+ normalizeGovernanceSessionOlderThanDays,
15
+ sanitizeBatchSessionId,
16
+ createGovernanceCloseLoopSessionId,
17
+ buildAutoGovernanceStats,
18
+ runAutoGovernanceMaintenance,
19
+ executeGovernanceAdvisoryRecover,
20
+ executeGovernanceAdvisoryControllerResume,
21
+ evaluateGovernanceReleaseGateBlockState,
22
+ extractGovernanceWeeklyOpsStopDetail,
23
+ compareRiskLevel,
24
+ buildGovernanceCloseLoopRecommendations,
25
+ persistGovernanceCloseLoopSession,
26
+ resolveGovernanceCloseLoopRunStatus,
27
+ pruneGovernanceCloseLoopSessions,
28
+ getGovernanceCloseLoopSessionDir,
29
+ extractGovernanceReleaseGateSnapshot
30
+ } = dependencies;
31
+
32
+ let resumedGovernanceSession = null;
33
+ if (options.governanceResume) {
34
+ resumedGovernanceSession = await loadGovernanceCloseLoopSessionPayload(projectPath, options.governanceResume);
35
+ }
36
+
37
+ const resumePayload = resumedGovernanceSession && resumedGovernanceSession.payload && typeof resumedGovernanceSession.payload === 'object'
38
+ ? resumedGovernanceSession.payload
39
+ : null;
40
+ const optionSources = options && options.optionSources && typeof options.optionSources === 'object'
41
+ ? options.optionSources
42
+ : {};
43
+ const isExplicitOption = optionName => isExplicitOptionSource(optionSources[optionName]);
44
+ const allowResumeDrift = Boolean(options.governanceResumeAllowDrift);
45
+
46
+ const maxRounds = normalizeGovernanceMaxRounds(
47
+ options.maxRounds !== undefined && options.maxRounds !== null
48
+ ? options.maxRounds
49
+ : (resumePayload && resumePayload.max_rounds !== undefined && resumePayload.max_rounds !== null
50
+ ? resumePayload.max_rounds
51
+ : undefined),
52
+ 3
53
+ );
54
+ const resumeTargetRisk = resumedGovernanceSession && resumePayload && typeof resumePayload.target_risk === 'string'
55
+ ? normalizeGovernanceTargetRiskLevel(resumePayload.target_risk)
56
+ : null;
57
+ const targetRisk = normalizeGovernanceTargetRiskLevel(
58
+ resumedGovernanceSession && !isExplicitOption('targetRisk')
59
+ ? (resumePayload && resumePayload.target_risk !== undefined && resumePayload.target_risk !== null
60
+ ? resumePayload.target_risk
61
+ : options.targetRisk)
62
+ : options.targetRisk
63
+ );
64
+ const resumeExecuteAdvisory = resumedGovernanceSession && resumePayload && typeof resumePayload.execute_advisory === 'boolean'
65
+ ? resumePayload.execute_advisory
66
+ : false;
67
+ const executeAdvisory = resumedGovernanceSession && !isExplicitOption('executeAdvisory')
68
+ ? resumeExecuteAdvisory
69
+ : Boolean(options.executeAdvisory);
70
+ const resumeAdvisoryPolicy = resumePayload && resumePayload.advisory_policy && typeof resumePayload.advisory_policy === 'object'
71
+ ? resumePayload.advisory_policy
72
+ : null;
73
+ const resumeAdvisoryRecoverMaxRounds = resumeAdvisoryPolicy && resumeAdvisoryPolicy.recover_max_rounds !== undefined &&
74
+ resumeAdvisoryPolicy.recover_max_rounds !== null
75
+ ? normalizeGovernanceAdvisoryRecoverMaxRounds(resumeAdvisoryPolicy.recover_max_rounds, 3)
76
+ : 3;
77
+ const resumeAdvisoryControllerMaxCycles = resumeAdvisoryPolicy && resumeAdvisoryPolicy.controller_max_cycles !== undefined &&
78
+ resumeAdvisoryPolicy.controller_max_cycles !== null
79
+ ? normalizeGovernanceAdvisoryControllerMaxCycles(resumeAdvisoryPolicy.controller_max_cycles, 20)
80
+ : 20;
81
+ const governanceSessionEnabled = options.governanceSession !== false;
82
+ const governanceSessionKeep = normalizeGovernanceSessionKeep(options.governanceSessionKeep);
83
+ const governanceSessionOlderThanDays = normalizeGovernanceSessionOlderThanDays(options.governanceSessionOlderThanDays);
84
+ if (governanceSessionOlderThanDays !== null && governanceSessionKeep === null) {
85
+ throw new Error('--governance-session-older-than-days requires --governance-session-keep.');
86
+ }
87
+ const requestedGovernanceSessionId = options.governanceSessionId !== undefined && options.governanceSessionId !== null
88
+ ? sanitizeBatchSessionId(options.governanceSessionId)
89
+ : null;
90
+ if (options.governanceSessionId && !requestedGovernanceSessionId) {
91
+ throw new Error('--governance-session-id is invalid after sanitization.');
92
+ }
93
+ let governanceSessionId = requestedGovernanceSessionId
94
+ || (resumedGovernanceSession && resumedGovernanceSession.id ? sanitizeBatchSessionId(resumedGovernanceSession.id) : null)
95
+ || (governanceSessionEnabled ? createGovernanceCloseLoopSessionId() : null);
96
+ if (governanceSessionEnabled && !governanceSessionId) {
97
+ throw new Error('Failed to resolve governance close-loop session id.');
98
+ }
99
+ if (
100
+ !executeAdvisory &&
101
+ options.advisoryRecoverMaxRounds !== undefined &&
102
+ options.advisoryRecoverMaxRounds !== null
103
+ ) {
104
+ throw new Error('--advisory-recover-max-rounds requires --execute-advisory.');
105
+ }
106
+ if (
107
+ !executeAdvisory &&
108
+ options.advisoryControllerMaxCycles !== undefined &&
109
+ options.advisoryControllerMaxCycles !== null
110
+ ) {
111
+ throw new Error('--advisory-controller-max-cycles requires --execute-advisory.');
112
+ }
113
+ const advisoryRecoverMaxRounds = normalizeGovernanceAdvisoryRecoverMaxRounds(
114
+ resumedGovernanceSession && !isExplicitOption('advisoryRecoverMaxRounds')
115
+ ? (resumeAdvisoryPolicy && resumeAdvisoryPolicy.recover_max_rounds !== undefined &&
116
+ resumeAdvisoryPolicy.recover_max_rounds !== null
117
+ ? resumeAdvisoryPolicy.recover_max_rounds
118
+ : options.advisoryRecoverMaxRounds)
119
+ : options.advisoryRecoverMaxRounds,
120
+ 3
121
+ );
122
+ const advisoryControllerMaxCycles = normalizeGovernanceAdvisoryControllerMaxCycles(
123
+ resumedGovernanceSession && !isExplicitOption('advisoryControllerMaxCycles')
124
+ ? (resumeAdvisoryPolicy && resumeAdvisoryPolicy.controller_max_cycles !== undefined &&
125
+ resumeAdvisoryPolicy.controller_max_cycles !== null
126
+ ? resumeAdvisoryPolicy.controller_max_cycles
127
+ : options.advisoryControllerMaxCycles)
128
+ : options.advisoryControllerMaxCycles,
129
+ 20
130
+ );
131
+ const planOnly = Boolean(options.planOnly);
132
+ const dryRun = Boolean(options.dryRun);
133
+ const days = normalizeStatsWindowDays(options.days);
134
+ const statusFilter = normalizeStatusFilter(options.status);
135
+ const assessmentOptions = {
136
+ days,
137
+ status: statusFilter.length > 0 ? statusFilter.join(',') : undefined
138
+ };
139
+ const apply = !planOnly;
140
+ const rounds = Array.isArray(resumePayload && resumePayload.rounds)
141
+ ? [...resumePayload.rounds]
142
+ : [];
143
+ if (
144
+ resumedGovernanceSession &&
145
+ !allowResumeDrift &&
146
+ isExplicitOption('maxRounds') &&
147
+ maxRounds < rounds.length
148
+ ) {
149
+ throw new Error(
150
+ `--max-rounds (${maxRounds}) is lower than resumed performed rounds (${rounds.length}). ` +
151
+ 'Use --governance-resume-allow-drift to override.'
152
+ );
153
+ }
154
+ if (resumedGovernanceSession && !allowResumeDrift) {
155
+ const driftIssues = [];
156
+ if (isExplicitOption('targetRisk') && resumeTargetRisk && targetRisk !== resumeTargetRisk) {
157
+ driftIssues.push(`target-risk resumed=${resumeTargetRisk} requested=${targetRisk}`);
158
+ }
159
+ if (isExplicitOption('executeAdvisory') && executeAdvisory !== resumeExecuteAdvisory) {
160
+ driftIssues.push(
161
+ `execute-advisory resumed=${resumeExecuteAdvisory ? 'enabled' : 'disabled'} ` +
162
+ `requested=${executeAdvisory ? 'enabled' : 'disabled'}`
163
+ );
164
+ }
165
+ if (
166
+ executeAdvisory &&
167
+ resumeExecuteAdvisory &&
168
+ isExplicitOption('advisoryRecoverMaxRounds') &&
169
+ advisoryRecoverMaxRounds !== resumeAdvisoryRecoverMaxRounds
170
+ ) {
171
+ driftIssues.push(
172
+ `advisory-recover-max-rounds resumed=${resumeAdvisoryRecoverMaxRounds} requested=${advisoryRecoverMaxRounds}`
173
+ );
174
+ }
175
+ if (
176
+ executeAdvisory &&
177
+ resumeExecuteAdvisory &&
178
+ isExplicitOption('advisoryControllerMaxCycles') &&
179
+ advisoryControllerMaxCycles !== resumeAdvisoryControllerMaxCycles
180
+ ) {
181
+ driftIssues.push(
182
+ `advisory-controller-max-cycles resumed=${resumeAdvisoryControllerMaxCycles} ` +
183
+ `requested=${advisoryControllerMaxCycles}`
184
+ );
185
+ }
186
+ if (driftIssues.length > 0) {
187
+ throw new Error(
188
+ `Governance resume option drift detected: ${driftIssues.join('; ')}. ` +
189
+ 'Use --governance-resume-allow-drift to override.'
190
+ );
191
+ }
192
+ }
193
+ let stopReason = 'max-rounds-exhausted';
194
+ let stopDetail = resumePayload && resumePayload.stop_detail && typeof resumePayload.stop_detail === 'object'
195
+ ? resumePayload.stop_detail
196
+ : null;
197
+ let converged = Boolean(resumePayload && resumePayload.converged);
198
+ let initialAssessment = resumePayload && resumePayload.initial_assessment
199
+ ? resumePayload.initial_assessment
200
+ : null;
201
+ let finalAssessment = resumePayload && resumePayload.final_assessment
202
+ ? resumePayload.final_assessment
203
+ : null;
204
+ const advisorySummary = {
205
+ planned_actions: Number(resumePayload && resumePayload.advisory_summary && resumePayload.advisory_summary.planned_actions) || 0,
206
+ executed_actions: Number(resumePayload && resumePayload.advisory_summary && resumePayload.advisory_summary.executed_actions) || 0,
207
+ failed_actions: Number(resumePayload && resumePayload.advisory_summary && resumePayload.advisory_summary.failed_actions) || 0,
208
+ skipped_actions: Number(resumePayload && resumePayload.advisory_summary && resumePayload.advisory_summary.skipped_actions) || 0
209
+ };
210
+ let governanceSessionPrune = null;
211
+
212
+ const buildGovernanceCloseLoopResult = () => ({
213
+ mode: 'auto-governance-close-loop',
214
+ generated_at: new Date().toISOString(),
215
+ apply,
216
+ plan_only: planOnly,
217
+ dry_run: dryRun,
218
+ target_risk: targetRisk,
219
+ max_rounds: maxRounds,
220
+ performed_rounds: rounds.length,
221
+ converged,
222
+ execute_advisory: executeAdvisory,
223
+ advisory_policy: {
224
+ recover_max_rounds: advisoryRecoverMaxRounds,
225
+ controller_max_cycles: advisoryControllerMaxCycles
226
+ },
227
+ advisory_summary: advisorySummary,
228
+ stop_reason: stopReason,
229
+ stop_detail: stopDetail,
230
+ initial_assessment: initialAssessment,
231
+ final_assessment: finalAssessment,
232
+ recommendations: buildGovernanceCloseLoopRecommendations(finalAssessment, stopReason, stopDetail),
233
+ resumed_from_governance_session: resumedGovernanceSession
234
+ ? {
235
+ id: resumedGovernanceSession.id,
236
+ file: resumedGovernanceSession.file,
237
+ status: resumePayload && resumePayload.status ? resumePayload.status : null
238
+ }
239
+ : null,
240
+ governance_session: governanceSessionEnabled
241
+ ? {
242
+ id: governanceSessionId,
243
+ file: path.join(getGovernanceCloseLoopSessionDir(projectPath), `${governanceSessionId}.json`)
244
+ }
245
+ : null,
246
+ governance_session_prune: governanceSessionPrune,
247
+ rounds
248
+ });
249
+
250
+ const persistGovernanceState = async status => {
251
+ if (!governanceSessionEnabled) {
252
+ return;
253
+ }
254
+ const persisted = await persistGovernanceCloseLoopSession(projectPath, governanceSessionId, buildGovernanceCloseLoopResult(), status);
255
+ if (persisted && persisted.id) {
256
+ governanceSessionId = persisted.id;
257
+ }
258
+ };
259
+
260
+ if (converged) {
261
+ stopReason = 'already-converged';
262
+ } else if (rounds.length >= maxRounds) {
263
+ stopReason = 'max-rounds-already-reached';
264
+ } else {
265
+ stopDetail = null;
266
+ await persistGovernanceState('running');
267
+ }
268
+
269
+ for (let round = rounds.length + 1; round <= maxRounds && stopReason === 'max-rounds-exhausted'; round += 1) {
270
+ const roundResult = await runAutoGovernanceMaintenance(projectPath, {
271
+ ...options,
272
+ apply,
273
+ dryRun
274
+ });
275
+ if (!initialAssessment) {
276
+ initialAssessment = roundResult.assessment;
277
+ }
278
+ const roundPlan = Array.isArray(roundResult.plan) ? roundResult.plan : [];
279
+ const advisoryPlannedActions = roundPlan.filter(item => item && item.enabled && !item.apply_supported).length;
280
+ const advisoryActions = [];
281
+
282
+ if (executeAdvisory && apply) {
283
+ const shouldRecover = roundPlan.some(item => item && item.id === 'recover-latest' && item.enabled);
284
+ if (shouldRecover) {
285
+ advisoryActions.push(await executeGovernanceAdvisoryRecover(projectPath, {
286
+ recoverMaxRounds: advisoryRecoverMaxRounds,
287
+ dryRun,
288
+ recoveryMemoryScope: options.recoveryMemoryScope
289
+ }));
290
+ }
291
+
292
+ const shouldResumeController = roundPlan.some(item => item && item.id === 'controller-resume-latest' && item.enabled);
293
+ if (shouldResumeController) {
294
+ advisoryActions.push(await executeGovernanceAdvisoryControllerResume(projectPath, {
295
+ maxCycles: advisoryControllerMaxCycles,
296
+ dryRun
297
+ }));
298
+ }
299
+ }
300
+
301
+ const advisoryExecutedActions = advisoryActions.filter(item => item && item.status === 'applied').length;
302
+ const advisoryFailedActions = advisoryActions.filter(item => item && item.status === 'failed').length;
303
+ const advisorySkippedActions = advisoryActions.filter(item => item && item.status === 'skipped').length;
304
+ advisorySummary.planned_actions += advisoryPlannedActions;
305
+ advisorySummary.executed_actions += advisoryExecutedActions;
306
+ advisorySummary.failed_actions += advisoryFailedActions;
307
+ advisorySummary.skipped_actions += advisorySkippedActions;
308
+
309
+ let effectiveAfterAssessment = roundResult.after_assessment || roundResult.assessment;
310
+ if (executeAdvisory && advisoryActions.length > 0) {
311
+ effectiveAfterAssessment = await buildAutoGovernanceStats(projectPath, assessmentOptions);
312
+ }
313
+ finalAssessment = effectiveAfterAssessment;
314
+ rounds.push({
315
+ round,
316
+ risk_before: roundResult.assessment && roundResult.assessment.health
317
+ ? roundResult.assessment.health.risk_level
318
+ : null,
319
+ risk_after: effectiveAfterAssessment && effectiveAfterAssessment.health
320
+ ? effectiveAfterAssessment.health.risk_level
321
+ : null,
322
+ release_gate_before: extractGovernanceReleaseGateSnapshot(roundResult.assessment),
323
+ release_gate_after: extractGovernanceReleaseGateSnapshot(effectiveAfterAssessment),
324
+ planned_actions: roundResult.summary.planned_actions,
325
+ applicable_actions: roundResult.summary.applicable_actions,
326
+ applied_actions: roundResult.summary.applied_actions,
327
+ failed_actions: roundResult.summary.failed_actions,
328
+ advisory_planned_actions: advisoryPlannedActions,
329
+ advisory_executed_actions: advisoryExecutedActions,
330
+ advisory_failed_actions: advisoryFailedActions,
331
+ advisory_skipped_actions: advisorySkippedActions,
332
+ advisory_actions: advisoryActions
333
+ });
334
+
335
+ if (roundResult.summary.failed_actions > 0) {
336
+ stopReason = 'maintenance-action-failed';
337
+ break;
338
+ }
339
+ if (executeAdvisory && advisoryFailedActions > 0) {
340
+ stopReason = 'advisory-action-failed';
341
+ break;
342
+ }
343
+ if (planOnly || dryRun) {
344
+ stopReason = 'non-mutating-mode';
345
+ break;
346
+ }
347
+ const releaseGateBlockState = evaluateGovernanceReleaseGateBlockState(effectiveAfterAssessment);
348
+ if (releaseGateBlockState.blocked) {
349
+ const weeklyOpsStopDetail = extractGovernanceWeeklyOpsStopDetail(
350
+ effectiveAfterAssessment &&
351
+ effectiveAfterAssessment.health &&
352
+ effectiveAfterAssessment.health.release_gate
353
+ );
354
+ stopReason = 'release-gate-blocked';
355
+ stopDetail = {
356
+ type: 'release-gate-block',
357
+ reasons: releaseGateBlockState.reasons,
358
+ release_gate: releaseGateBlockState.snapshot,
359
+ weekly_ops: weeklyOpsStopDetail
360
+ };
361
+ break;
362
+ }
363
+ if (compareRiskLevel(effectiveAfterAssessment.health.risk_level, targetRisk) <= 0) {
364
+ converged = true;
365
+ stopReason = 'target-risk-reached';
366
+ break;
367
+ }
368
+ if (
369
+ roundResult.summary.applicable_actions === 0 &&
370
+ !(executeAdvisory && (advisoryExecutedActions > 0 || advisoryFailedActions > 0))
371
+ ) {
372
+ stopReason = 'no-applicable-actions';
373
+ break;
374
+ }
375
+
376
+ await persistGovernanceState('running');
377
+ }
378
+
379
+ if (!initialAssessment || !finalAssessment) {
380
+ throw new Error('Governance close-loop did not produce any assessment.');
381
+ }
382
+
383
+ const result = buildGovernanceCloseLoopResult();
384
+ if (governanceSessionEnabled) {
385
+ const persistedSession = await persistGovernanceCloseLoopSession(
386
+ projectPath,
387
+ governanceSessionId,
388
+ result,
389
+ resolveGovernanceCloseLoopRunStatus(stopReason, converged)
390
+ );
391
+ if (persistedSession && persistedSession.id) {
392
+ governanceSessionId = persistedSession.id;
393
+ }
394
+ }
395
+ if (governanceSessionKeep !== null) {
396
+ governanceSessionPrune = await pruneGovernanceCloseLoopSessions(projectPath, {
397
+ keep: governanceSessionKeep,
398
+ olderThanDays: governanceSessionOlderThanDays,
399
+ dryRun,
400
+ currentFile: governanceSessionEnabled
401
+ ? path.join(getGovernanceCloseLoopSessionDir(projectPath), `${governanceSessionId}.json`)
402
+ : null
403
+ });
404
+ result.governance_session_prune = governanceSessionPrune;
405
+ }
406
+ return result;
407
+ }
408
+
409
+ module.exports = {
410
+ runAutoGovernanceCloseLoop
411
+ };