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.
- package/CHANGELOG.md +109 -11
- package/README.md +119 -122
- package/README.zh.md +123 -121
- package/bin/scene-capability-engine.js +12 -1
- package/docs/331-poc-adaptation-roadmap.md +3 -3
- package/docs/README.md +21 -32
- package/docs/auto-refactor-index.md +384 -0
- package/docs/command-reference.md +99 -7
- package/docs/faq.md +1 -1
- package/docs/interactive-customization/331-poc-sce-integration-checklist.md +3 -3
- package/docs/interactive-customization/moqui-interactive-template-playbook.md +4 -4
- package/docs/interactive-customization/phase-acceptance-evidence.md +2 -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/moqui-standard-rebuild-guide.md +6 -6
- package/docs/moqui-template-core-library-playbook.md +1 -1
- package/docs/refactor-completion-roadmap.md +116 -0
- package/docs/release-checklist.md +50 -27
- package/docs/releases/README.md +1 -0
- package/docs/releases/v3.6.37.md +22 -0
- package/docs/steering-strategy-guide.md +7 -7
- package/docs/troubleshooting.md +1 -1
- package/docs/zh/README.md +27 -30
- package/docs/zh/refactor-completion-roadmap.md +116 -0
- package/docs/zh/release-checklist.md +40 -17
- package/docs/zh/releases/README.md +1 -0
- package/docs/zh/releases/v3.6.37.md +22 -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/adopt.js +4 -4
- package/lib/commands/app.js +911 -0
- package/lib/commands/assurance.js +212 -0
- package/lib/commands/auto.js +1093 -11065
- 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/spec/bootstrap/context-collector.js +1 -1
- package/lib/state/sce-state-store.js +3369 -1200
- package/lib/steering/adoption-config.js +2 -2
- package/lib/steering/compliance-cache.js +2 -2
- package/lib/steering/steering-manager.js +4 -4
- package/lib/task/task-claimer.js +1 -2
- package/lib/workspace/multi/workspace-context-resolver.js +3 -3
- package/lib/workspace/multi/workspace-state-manager.js +0 -164
- package/lib/workspace/sce-tracking-audit.js +1 -1
- package/lib/workspace/takeover-baseline.js +1 -1
- package/package.json +1 -1
- package/template/.sce/README.md +1 -1
- package/template/.sce/steering/CORE_PRINCIPLES.md +1 -1
- 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
|
+
};
|