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,162 @@
1
+ function defaultEvaluateGovernanceReleaseGateBlockState(assessment) {
2
+ const releaseGate = assessment && assessment.release_gate && typeof assessment.release_gate === 'object'
3
+ ? assessment.release_gate
4
+ : (assessment && assessment.health && assessment.health.release_gate && typeof assessment.health.release_gate === 'object'
5
+ ? assessment.health.release_gate
6
+ : {});
7
+ const blockedReasons = Array.isArray(releaseGate.blocked_reasons) ? releaseGate.blocked_reasons : [];
8
+ return {
9
+ blocked: releaseGate.available === true && releaseGate.latest_gate_passed === false,
10
+ reasons: blockedReasons
11
+ };
12
+ }
13
+
14
+ function buildAutoGovernanceMaintenancePlan(assessment = {}, policy = {}, dryRun = false, dependencies = {}) {
15
+ const sessionTotal = Number(assessment && assessment.archives && assessment.archives.session && assessment.archives.session.total_sessions) || 0;
16
+ const batchTotal = Number(assessment && assessment.archives && assessment.archives.batch_session && assessment.archives.batch_session.total_sessions) || 0;
17
+ const controllerTotal = Number(assessment && assessment.archives && assessment.archives.controller_session && assessment.archives.controller_session.total_sessions) || 0;
18
+ const recoverySignatureCount = Number(assessment && assessment.recovery_memory && assessment.recovery_memory.signature_count) || 0;
19
+ const failedSessions = Number(assessment && assessment.totals && assessment.totals.failed_sessions) || 0;
20
+ const pendingGoals = Number(assessment && assessment.throughput && assessment.throughput.controller_pending_goals_sum) || Number(assessment && assessment.totals && assessment.totals.pending_goals_sum) || 0;
21
+
22
+ const sessionKeep = Number(policy.session_keep || 0);
23
+ const batchSessionKeep = Number(policy.batch_session_keep || 0);
24
+ const controllerSessionKeep = Number(policy.controller_session_keep || 0);
25
+ const recoveryOlderThanDays = policy.recovery_memory_older_than_days;
26
+
27
+ const sessionCommand = `sce auto session prune --keep ${sessionKeep}${dryRun ? ' --dry-run' : ''} --json`;
28
+ const batchCommand = `sce auto batch-session prune --keep ${batchSessionKeep}${dryRun ? ' --dry-run' : ''} --json`;
29
+ const controllerCommand = `sce auto controller-session prune --keep ${controllerSessionKeep}${dryRun ? ' --dry-run' : ''} --json`;
30
+ const recoveryCommand = `sce auto recovery-memory prune --older-than-days ${recoveryOlderThanDays}${dryRun ? ' --dry-run' : ''} --json`;
31
+ const evaluateGovernanceReleaseGateBlockState = dependencies.evaluateGovernanceReleaseGateBlockState || defaultEvaluateGovernanceReleaseGateBlockState;
32
+ const releaseGateBlockState = evaluateGovernanceReleaseGateBlockState(assessment);
33
+ const releaseGateReasons = Array.isArray(releaseGateBlockState.reasons) ? releaseGateBlockState.reasons : [];
34
+ const releaseGateAdvisoryPlan = [];
35
+ if (releaseGateBlockState.blocked) {
36
+ releaseGateAdvisoryPlan.push({
37
+ id: 'release-gate-evidence-review',
38
+ type: 'advisory',
39
+ apply_supported: false,
40
+ enabled: true,
41
+ reason: `release gate quality is blocking governance loop: ${releaseGateReasons.join(', ')}`,
42
+ command: 'sce auto handoff evidence --window 5 --json',
43
+ blocked_reasons: releaseGateReasons
44
+ });
45
+ if (releaseGateReasons.some((item) => `${item}`.includes('scene-batch') || `${item}`.includes('drift'))) {
46
+ releaseGateAdvisoryPlan.push({
47
+ id: 'release-gate-scene-batch-remediate',
48
+ type: 'advisory',
49
+ apply_supported: false,
50
+ enabled: true,
51
+ reason: 'release gate drift/scene signals need package-publish-batch remediation',
52
+ command: 'sce scene package-publish-batch --manifest docs/handoffs/handoff-manifest.json --dry-run --json',
53
+ blocked_reasons: releaseGateReasons
54
+ });
55
+ }
56
+ if (releaseGateReasons.some((item) => `${item}`.startsWith('handoff-'))) {
57
+ releaseGateAdvisoryPlan.push({
58
+ id: 'release-gate-handoff-remediate',
59
+ type: 'advisory',
60
+ apply_supported: false,
61
+ enabled: true,
62
+ reason: 'handoff quality signals are blocking governance convergence',
63
+ command: 'sce auto handoff run --manifest docs/handoffs/handoff-manifest.json --dry-run --json',
64
+ blocked_reasons: releaseGateReasons
65
+ });
66
+ }
67
+ }
68
+
69
+ return [
70
+ ...releaseGateAdvisoryPlan,
71
+ {
72
+ id: 'session-prune',
73
+ type: 'maintenance',
74
+ apply_supported: true,
75
+ enabled: sessionTotal > sessionKeep,
76
+ reason: sessionTotal > sessionKeep
77
+ ? `close-loop session archive ${sessionTotal} exceeds keep policy ${sessionKeep}`
78
+ : 'close-loop session archive is within keep policy',
79
+ command: sessionCommand,
80
+ target_total: sessionTotal,
81
+ keep: sessionKeep
82
+ },
83
+ {
84
+ id: 'batch-session-prune',
85
+ type: 'maintenance',
86
+ apply_supported: true,
87
+ enabled: batchTotal > batchSessionKeep,
88
+ reason: batchTotal > batchSessionKeep
89
+ ? `batch session archive ${batchTotal} exceeds keep policy ${batchSessionKeep}`
90
+ : 'batch session archive is within keep policy',
91
+ command: batchCommand,
92
+ target_total: batchTotal,
93
+ keep: batchSessionKeep
94
+ },
95
+ {
96
+ id: 'controller-session-prune',
97
+ type: 'maintenance',
98
+ apply_supported: true,
99
+ enabled: controllerTotal > controllerSessionKeep,
100
+ reason: controllerTotal > controllerSessionKeep
101
+ ? `controller session archive ${controllerTotal} exceeds keep policy ${controllerSessionKeep}`
102
+ : 'controller session archive is within keep policy',
103
+ command: controllerCommand,
104
+ target_total: controllerTotal,
105
+ keep: controllerSessionKeep
106
+ },
107
+ {
108
+ id: 'recovery-memory-prune',
109
+ type: 'maintenance',
110
+ apply_supported: true,
111
+ enabled: recoverySignatureCount > 0,
112
+ reason: recoverySignatureCount > 0
113
+ ? `recovery memory contains ${recoverySignatureCount} signature(s), prune stale entries`
114
+ : 'recovery memory is empty',
115
+ command: recoveryCommand,
116
+ target_total: recoverySignatureCount,
117
+ older_than_days: recoveryOlderThanDays
118
+ },
119
+ {
120
+ id: 'recover-latest',
121
+ type: 'advisory',
122
+ apply_supported: false,
123
+ enabled: failedSessions > 0,
124
+ reason: failedSessions > 0
125
+ ? `${failedSessions} failed session(s) detected, run recovery drain`
126
+ : 'no failed sessions detected',
127
+ command: 'sce auto close-loop-recover latest --recover-until-complete --json'
128
+ },
129
+ {
130
+ id: 'controller-resume-latest',
131
+ type: 'advisory',
132
+ apply_supported: false,
133
+ enabled: pendingGoals > 0,
134
+ reason: pendingGoals > 0
135
+ ? `${pendingGoals} pending controller goal(s) detected, resume controller queue`
136
+ : 'no pending controller goals detected',
137
+ command: 'sce auto close-loop-controller --controller-resume latest --json'
138
+ }
139
+ ];
140
+ }
141
+
142
+ function summarizeGovernanceMaintenanceExecution(plan = [], executedActions = []) {
143
+ const safePlan = Array.isArray(plan) ? plan : [];
144
+ const safeExecuted = Array.isArray(executedActions) ? executedActions : [];
145
+ const plannedActions = safePlan.filter((item) => item && item.enabled).length;
146
+ const applicableActions = safePlan.filter((item) => item && item.enabled && item.apply_supported).length;
147
+ const advisoryActions = safePlan.filter((item) => item && item.enabled && !item.apply_supported).length;
148
+ const appliedActions = safeExecuted.filter((item) => item && item.status === 'applied').length;
149
+ const failedActions = safeExecuted.filter((item) => item && item.status === 'failed').length;
150
+ return {
151
+ planned_actions: plannedActions,
152
+ applicable_actions: applicableActions,
153
+ advisory_actions: advisoryActions,
154
+ applied_actions: appliedActions,
155
+ failed_actions: failedActions
156
+ };
157
+ }
158
+
159
+ module.exports = {
160
+ buildAutoGovernanceMaintenancePlan,
161
+ summarizeGovernanceMaintenanceExecution
162
+ };
@@ -0,0 +1,112 @@
1
+ async function runAutoGovernanceMaintenance(projectPath, options = {}, dependencies = {}) {
2
+ const {
3
+ normalizeStatsWindowDays,
4
+ normalizeStatusFilter,
5
+ normalizeGovernanceKeepOption,
6
+ normalizeGovernanceRecoveryOlderThanDays,
7
+ buildAutoGovernanceStats,
8
+ buildAutoGovernanceMaintenancePlan,
9
+ evaluateGovernanceReleaseGateBlockState,
10
+ pruneCloseLoopSessions,
11
+ pruneCloseLoopBatchSummarySessionsCli,
12
+ pruneCloseLoopControllerSessionsCli,
13
+ pruneCloseLoopRecoveryMemory,
14
+ summarizeGovernanceMaintenanceExecution,
15
+ now = () => new Date()
16
+ } = dependencies;
17
+
18
+ const days = normalizeStatsWindowDays(options.days);
19
+ const statusFilter = normalizeStatusFilter(options.status);
20
+ const assessmentOptions = {
21
+ days,
22
+ status: statusFilter.length > 0 ? statusFilter.join(',') : undefined
23
+ };
24
+ const policy = {
25
+ session_keep: normalizeGovernanceKeepOption(options.sessionKeep, '--session-keep', 50),
26
+ batch_session_keep: normalizeGovernanceKeepOption(options.batchSessionKeep, '--batch-session-keep', 50),
27
+ controller_session_keep: normalizeGovernanceKeepOption(options.controllerSessionKeep, '--controller-session-keep', 50),
28
+ recovery_memory_older_than_days: normalizeGovernanceRecoveryOlderThanDays(options.recoveryMemoryOlderThanDays, 90)
29
+ };
30
+ const apply = Boolean(options.apply);
31
+ const dryRun = Boolean(options.dryRun);
32
+ const assessment = await buildAutoGovernanceStats(projectPath, assessmentOptions);
33
+ const plan = buildAutoGovernanceMaintenancePlan(assessment, policy, dryRun, {
34
+ evaluateGovernanceReleaseGateBlockState
35
+ });
36
+ const executedActions = [];
37
+
38
+ if (apply) {
39
+ for (const action of plan) {
40
+ if (!action.enabled || !action.apply_supported) {
41
+ continue;
42
+ }
43
+ try {
44
+ let result = null;
45
+ if (action.id === 'session-prune') {
46
+ result = await pruneCloseLoopSessions(projectPath, {
47
+ keep: policy.session_keep,
48
+ olderThanDays: null,
49
+ dryRun
50
+ });
51
+ } else if (action.id === 'batch-session-prune') {
52
+ result = await pruneCloseLoopBatchSummarySessionsCli(projectPath, {
53
+ keep: policy.batch_session_keep,
54
+ olderThanDays: null,
55
+ dryRun
56
+ });
57
+ } else if (action.id === 'controller-session-prune') {
58
+ result = await pruneCloseLoopControllerSessionsCli(projectPath, {
59
+ keep: policy.controller_session_keep,
60
+ olderThanDays: null,
61
+ dryRun
62
+ });
63
+ } else if (action.id === 'recovery-memory-prune') {
64
+ result = await pruneCloseLoopRecoveryMemory(projectPath, {
65
+ olderThanDays: policy.recovery_memory_older_than_days,
66
+ dryRun
67
+ });
68
+ }
69
+ executedActions.push({
70
+ id: action.id,
71
+ status: 'applied',
72
+ result
73
+ });
74
+ } catch (error) {
75
+ executedActions.push({
76
+ id: action.id,
77
+ status: 'failed',
78
+ error: error.message
79
+ });
80
+ }
81
+ }
82
+ }
83
+
84
+ const afterAssessment = apply && !dryRun
85
+ ? await buildAutoGovernanceStats(projectPath, assessmentOptions)
86
+ : null;
87
+
88
+ const nowValue = now();
89
+ return {
90
+ mode: 'auto-governance-maintain',
91
+ generated_at: nowValue instanceof Date ? nowValue.toISOString() : new Date(nowValue).toISOString(),
92
+ apply,
93
+ dry_run: dryRun,
94
+ criteria: {
95
+ days,
96
+ status_filter: statusFilter,
97
+ since: days === null
98
+ ? null
99
+ : new Date((nowValue instanceof Date ? nowValue.getTime() : new Date(nowValue).getTime()) - (days * 24 * 60 * 60 * 1000)).toISOString()
100
+ },
101
+ policy,
102
+ assessment,
103
+ plan,
104
+ executed_actions: executedActions,
105
+ summary: summarizeGovernanceMaintenanceExecution(plan, executedActions),
106
+ after_assessment: afterAssessment
107
+ };
108
+ }
109
+
110
+ module.exports = {
111
+ runAutoGovernanceMaintenance
112
+ };
@@ -0,0 +1,70 @@
1
+ function presentGovernanceSessionList(
2
+ projectPath,
3
+ filteredSessions,
4
+ statusFilter,
5
+ resumeOnly,
6
+ buildStatusCounts,
7
+ getGovernanceCloseLoopSessionDir,
8
+ limit = null
9
+ ) {
10
+ const sessions = Array.isArray(filteredSessions) ? filteredSessions : [];
11
+ const resumedSessions = sessions.filter((session) => session && session.resumed_from_governance_session_id).length;
12
+ const maxItems = Number.isInteger(limit) && limit > 0 ? limit : sessions.length;
13
+ return {
14
+ mode: 'auto-governance-session-list',
15
+ session_dir: getGovernanceCloseLoopSessionDir(projectPath),
16
+ total: sessions.length,
17
+ status_filter: statusFilter,
18
+ resume_only: resumeOnly,
19
+ resumed_sessions: resumedSessions,
20
+ fresh_sessions: sessions.length - resumedSessions,
21
+ status_counts: buildStatusCounts(sessions),
22
+ sessions: sessions.slice(0, maxItems).map((item) => ({
23
+ id: item.id,
24
+ status: item.status,
25
+ target_risk: item.target_risk,
26
+ final_risk: item.final_risk,
27
+ performed_rounds: item.performed_rounds,
28
+ max_rounds: item.max_rounds,
29
+ converged: item.converged,
30
+ execute_advisory: item.execute_advisory,
31
+ advisory_failed_actions: item.advisory_failed_actions,
32
+ release_gate_available: item.release_gate_available,
33
+ release_gate_latest_gate_passed: item.release_gate_latest_gate_passed,
34
+ release_gate_pass_rate_percent: item.release_gate_pass_rate_percent,
35
+ release_gate_drift_alert_rate_percent: item.release_gate_drift_alert_rate_percent,
36
+ round_release_gate_observed: item.round_release_gate_observed,
37
+ round_release_gate_changed: item.round_release_gate_changed,
38
+ stop_detail_weekly_ops_available: item.stop_detail_weekly_ops_available,
39
+ stop_detail_weekly_ops_blocked: item.stop_detail_weekly_ops_blocked,
40
+ stop_detail_weekly_ops_high_pressure: item.stop_detail_weekly_ops_high_pressure,
41
+ stop_detail_weekly_ops_config_warning_positive: item.stop_detail_weekly_ops_config_warning_positive,
42
+ stop_detail_weekly_ops_auth_tier_block_rate_high: item.stop_detail_weekly_ops_auth_tier_block_rate_high,
43
+ stop_detail_weekly_ops_dialogue_authorization_block_rate_high:
44
+ item.stop_detail_weekly_ops_dialogue_authorization_block_rate_high,
45
+ stop_detail_weekly_ops_runtime_block_rate_high: item.stop_detail_weekly_ops_runtime_block_rate_high,
46
+ stop_detail_weekly_ops_runtime_ui_mode_violation_high:
47
+ item.stop_detail_weekly_ops_runtime_ui_mode_violation_high,
48
+ stop_detail_weekly_ops_blocked_runs: item.stop_detail_weekly_ops_blocked_runs,
49
+ stop_detail_weekly_ops_block_rate_percent: item.stop_detail_weekly_ops_block_rate_percent,
50
+ stop_detail_weekly_ops_config_warnings_total: item.stop_detail_weekly_ops_config_warnings_total,
51
+ stop_detail_weekly_ops_auth_tier_block_rate_percent: item.stop_detail_weekly_ops_auth_tier_block_rate_percent,
52
+ stop_detail_weekly_ops_dialogue_authorization_block_rate_percent:
53
+ item.stop_detail_weekly_ops_dialogue_authorization_block_rate_percent,
54
+ stop_detail_weekly_ops_runtime_block_rate_percent: item.stop_detail_weekly_ops_runtime_block_rate_percent,
55
+ stop_detail_weekly_ops_runtime_ui_mode_violation_total:
56
+ item.stop_detail_weekly_ops_runtime_ui_mode_violation_total,
57
+ stop_detail_weekly_ops_runtime_ui_mode_violation_rate_percent:
58
+ item.stop_detail_weekly_ops_runtime_ui_mode_violation_rate_percent,
59
+ stop_reason: item.stop_reason,
60
+ resumed_from_governance_session_id: item.resumed_from_governance_session_id,
61
+ updated_at: item.updated_at,
62
+ parse_error: item.parse_error,
63
+ file: item.file
64
+ }))
65
+ };
66
+ }
67
+
68
+ module.exports = {
69
+ presentGovernanceSessionList
70
+ };
@@ -0,0 +1,198 @@
1
+ const path = require('path');
2
+
3
+ async function readGovernanceCloseLoopSessionEntries(projectPath, dependencies = {}) {
4
+ const {
5
+ getGovernanceCloseLoopSessionDir,
6
+ fs,
7
+ normalizeGovernanceReleaseGateSnapshot,
8
+ summarizeGovernanceRoundReleaseGateTelemetry,
9
+ normalizeGovernanceWeeklyOpsStopDetail,
10
+ deriveGovernanceWeeklyOpsReasonFlags
11
+ } = dependencies;
12
+ const sessionDir = getGovernanceCloseLoopSessionDir(projectPath);
13
+ if (!(await fs.pathExists(sessionDir))) {
14
+ return [];
15
+ }
16
+
17
+ const files = (await fs.readdir(sessionDir)).filter((item) => item.toLowerCase().endsWith('.json'));
18
+ const sessions = [];
19
+
20
+ for (const file of files) {
21
+ const filePath = path.join(sessionDir, file);
22
+ const stats = await fs.stat(filePath);
23
+ const fallbackTimestamp = new Date(stats.mtimeMs).toISOString();
24
+ const fallbackId = path.basename(file, '.json');
25
+ let payload = null;
26
+ let parseError = null;
27
+
28
+ try {
29
+ payload = await fs.readJson(filePath);
30
+ } catch (error) {
31
+ parseError = error;
32
+ }
33
+
34
+ const finalReleaseGate = normalizeGovernanceReleaseGateSnapshot(
35
+ payload && payload.final_assessment && payload.final_assessment.health
36
+ ? payload.final_assessment.health.release_gate
37
+ : null
38
+ );
39
+ const roundReleaseGateTelemetry = summarizeGovernanceRoundReleaseGateTelemetry(payload && payload.rounds);
40
+ const stopDetail = payload && payload.stop_detail && typeof payload.stop_detail === 'object' ? payload.stop_detail : null;
41
+ const stopDetailReasons = Array.isArray(stopDetail && stopDetail.reasons) ? stopDetail.reasons : [];
42
+ const stopDetailWeeklyOps = normalizeGovernanceWeeklyOpsStopDetail(stopDetail && stopDetail.weekly_ops);
43
+ const stopDetailWeeklyOpsReasonFlags = deriveGovernanceWeeklyOpsReasonFlags(stopDetailReasons);
44
+
45
+ const latestAuthTier = stopDetailWeeklyOps && stopDetailWeeklyOps.latest && Number.isFinite(stopDetailWeeklyOps.latest.authorization_tier_block_rate_percent)
46
+ ? stopDetailWeeklyOps.latest.authorization_tier_block_rate_percent
47
+ : (stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates && Number.isFinite(stopDetailWeeklyOps.aggregates.authorization_tier_block_rate_max_percent)
48
+ ? stopDetailWeeklyOps.aggregates.authorization_tier_block_rate_max_percent
49
+ : null);
50
+ const latestDialogue = stopDetailWeeklyOps && stopDetailWeeklyOps.latest && Number.isFinite(stopDetailWeeklyOps.latest.dialogue_authorization_block_rate_percent)
51
+ ? stopDetailWeeklyOps.latest.dialogue_authorization_block_rate_percent
52
+ : (stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates && Number.isFinite(stopDetailWeeklyOps.aggregates.dialogue_authorization_block_rate_max_percent)
53
+ ? stopDetailWeeklyOps.aggregates.dialogue_authorization_block_rate_max_percent
54
+ : null);
55
+ const latestRuntimeBlock = stopDetailWeeklyOps && stopDetailWeeklyOps.latest && Number.isFinite(stopDetailWeeklyOps.latest.runtime_block_rate_percent)
56
+ ? stopDetailWeeklyOps.latest.runtime_block_rate_percent
57
+ : (stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates && Number.isFinite(stopDetailWeeklyOps.aggregates.runtime_block_rate_max_percent)
58
+ ? stopDetailWeeklyOps.aggregates.runtime_block_rate_max_percent
59
+ : null);
60
+ const latestUiTotal = stopDetailWeeklyOps && stopDetailWeeklyOps.latest && Number.isFinite(stopDetailWeeklyOps.latest.runtime_ui_mode_violation_total)
61
+ ? stopDetailWeeklyOps.latest.runtime_ui_mode_violation_total
62
+ : (stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates && Number.isFinite(stopDetailWeeklyOps.aggregates.runtime_ui_mode_violation_total)
63
+ ? stopDetailWeeklyOps.aggregates.runtime_ui_mode_violation_total
64
+ : null);
65
+ const latestUiRate = stopDetailWeeklyOps && stopDetailWeeklyOps.latest && Number.isFinite(stopDetailWeeklyOps.latest.runtime_ui_mode_violation_rate_percent)
66
+ ? stopDetailWeeklyOps.latest.runtime_ui_mode_violation_rate_percent
67
+ : (stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates && Number.isFinite(stopDetailWeeklyOps.aggregates.runtime_ui_mode_violation_rate_max_percent)
68
+ ? stopDetailWeeklyOps.aggregates.runtime_ui_mode_violation_rate_max_percent
69
+ : null);
70
+
71
+ sessions.push({
72
+ id: payload && payload.governance_session && typeof payload.governance_session.id === 'string' ? payload.governance_session.id : fallbackId,
73
+ file: filePath,
74
+ status: payload && typeof payload.status === 'string' ? payload.status : (parseError ? 'invalid' : 'unknown'),
75
+ target_risk: payload && typeof payload.target_risk === 'string' ? payload.target_risk : null,
76
+ final_risk: payload && payload.final_assessment && payload.final_assessment.health && typeof payload.final_assessment.health.risk_level === 'string'
77
+ ? payload.final_assessment.health.risk_level
78
+ : null,
79
+ performed_rounds: Number(payload && payload.performed_rounds) || 0,
80
+ max_rounds: Number(payload && payload.max_rounds) || null,
81
+ converged: Boolean(payload && payload.converged),
82
+ execute_advisory: payload && payload.execute_advisory === true,
83
+ advisory_failed_actions: payload && payload.advisory_summary ? Number(payload.advisory_summary.failed_actions) || 0 : null,
84
+ release_gate_available: finalReleaseGate ? finalReleaseGate.available : null,
85
+ release_gate_latest_gate_passed: finalReleaseGate ? finalReleaseGate.latest_gate_passed : null,
86
+ release_gate_pass_rate_percent: finalReleaseGate ? finalReleaseGate.pass_rate_percent : null,
87
+ release_gate_scene_package_batch_pass_rate_percent: finalReleaseGate ? finalReleaseGate.scene_package_batch_pass_rate_percent : null,
88
+ release_gate_drift_alert_rate_percent: finalReleaseGate ? finalReleaseGate.drift_alert_rate_percent : null,
89
+ release_gate_drift_blocked_runs: finalReleaseGate ? finalReleaseGate.drift_blocked_runs : null,
90
+ round_release_gate_observed: roundReleaseGateTelemetry.observed_rounds,
91
+ round_release_gate_changed: roundReleaseGateTelemetry.changed_rounds,
92
+ stop_detail_weekly_ops_available: stopDetailWeeklyOps !== null || stopDetailWeeklyOpsReasonFlags.has_weekly_ops_reason,
93
+ stop_detail_weekly_ops_blocked: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.blocked === 'boolean') ? stopDetailWeeklyOps.pressure.blocked : stopDetailWeeklyOpsReasonFlags.blocked,
94
+ stop_detail_weekly_ops_high_pressure: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.high === 'boolean') ? stopDetailWeeklyOps.pressure.high : stopDetailWeeklyOpsReasonFlags.high,
95
+ stop_detail_weekly_ops_config_warning_positive: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.config_warning_positive === 'boolean') ? stopDetailWeeklyOps.pressure.config_warning_positive : stopDetailWeeklyOpsReasonFlags.config_warning_positive,
96
+ stop_detail_weekly_ops_auth_tier_block_rate_high: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.auth_tier_block_rate_high === 'boolean') ? stopDetailWeeklyOps.pressure.auth_tier_block_rate_high : stopDetailWeeklyOpsReasonFlags.auth_tier_block_rate_high,
97
+ stop_detail_weekly_ops_dialogue_authorization_block_rate_high: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.dialogue_authorization_block_rate_high === 'boolean') ? stopDetailWeeklyOps.pressure.dialogue_authorization_block_rate_high : stopDetailWeeklyOpsReasonFlags.dialogue_authorization_block_rate_high,
98
+ stop_detail_weekly_ops_runtime_block_rate_high: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.runtime_block_rate_high === 'boolean') ? stopDetailWeeklyOps.pressure.runtime_block_rate_high : stopDetailWeeklyOpsReasonFlags.runtime_block_rate_high,
99
+ stop_detail_weekly_ops_runtime_ui_mode_violation_high: (stopDetailWeeklyOps && stopDetailWeeklyOps.pressure && typeof stopDetailWeeklyOps.pressure.runtime_ui_mode_violation_high === 'boolean') ? stopDetailWeeklyOps.pressure.runtime_ui_mode_violation_high : stopDetailWeeklyOpsReasonFlags.runtime_ui_mode_violation_high,
100
+ stop_detail_weekly_ops_blocked_runs: stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates ? stopDetailWeeklyOps.aggregates.blocked_runs : null,
101
+ stop_detail_weekly_ops_block_rate_percent: stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates ? stopDetailWeeklyOps.aggregates.block_rate_percent : null,
102
+ stop_detail_weekly_ops_config_warnings_total: stopDetailWeeklyOps && stopDetailWeeklyOps.aggregates ? stopDetailWeeklyOps.aggregates.config_warnings_total : null,
103
+ stop_detail_weekly_ops_auth_tier_block_rate_percent: latestAuthTier,
104
+ stop_detail_weekly_ops_dialogue_authorization_block_rate_percent: latestDialogue,
105
+ stop_detail_weekly_ops_runtime_block_rate_percent: latestRuntimeBlock,
106
+ stop_detail_weekly_ops_runtime_ui_mode_violation_total: latestUiTotal,
107
+ stop_detail_weekly_ops_runtime_ui_mode_violation_rate_percent: latestUiRate,
108
+ stop_reason: payload && typeof payload.stop_reason === 'string' ? payload.stop_reason : null,
109
+ resumed_from_governance_session_id: payload && payload.resumed_from_governance_session && typeof payload.resumed_from_governance_session.id === 'string'
110
+ ? payload.resumed_from_governance_session.id
111
+ : null,
112
+ updated_at: payload && typeof payload.updated_at === 'string' ? payload.updated_at : fallbackTimestamp,
113
+ parse_error: parseError ? parseError.message : null,
114
+ mtime_ms: stats.mtimeMs
115
+ });
116
+ }
117
+
118
+ sessions.sort((a, b) => b.mtime_ms - a.mtime_ms);
119
+ return sessions;
120
+ }
121
+
122
+ async function resolveGovernanceCloseLoopSessionFile(projectPath, sessionCandidate, dependencies = {}) {
123
+ const { readGovernanceCloseLoopSessionEntries, getGovernanceCloseLoopSessionDir, sanitizeBatchSessionId, fs } = dependencies;
124
+ if (typeof sessionCandidate !== 'string' || !sessionCandidate.trim()) {
125
+ throw new Error('--governance-resume requires a session id/file or "latest".');
126
+ }
127
+ const normalizedCandidate = sessionCandidate.trim();
128
+ if (normalizedCandidate.toLowerCase() === 'latest') {
129
+ const sessions = await readGovernanceCloseLoopSessionEntries(projectPath, dependencies);
130
+ if (sessions.length === 0) {
131
+ throw new Error(`No governance close-loop sessions found in: ${getGovernanceCloseLoopSessionDir(projectPath)}`);
132
+ }
133
+ return sessions[0].file;
134
+ }
135
+ if (path.isAbsolute(normalizedCandidate)) {
136
+ return normalizedCandidate;
137
+ }
138
+ if (normalizedCandidate.includes('/') || normalizedCandidate.includes('\\') || normalizedCandidate.toLowerCase().endsWith('.json')) {
139
+ return path.join(projectPath, normalizedCandidate);
140
+ }
141
+ const byId = path.join(getGovernanceCloseLoopSessionDir(projectPath), `${sanitizeBatchSessionId(normalizedCandidate)}.json`);
142
+ if (await fs.pathExists(byId)) {
143
+ return byId;
144
+ }
145
+ return path.join(projectPath, normalizedCandidate);
146
+ }
147
+
148
+ async function loadGovernanceCloseLoopSessionPayload(projectPath, sessionCandidate, dependencies = {}) {
149
+ const { fs } = dependencies;
150
+ const sessionFile = await resolveGovernanceCloseLoopSessionFile(projectPath, sessionCandidate, dependencies);
151
+ if (!(await fs.pathExists(sessionFile))) {
152
+ throw new Error(`Governance close-loop session file not found: ${sessionFile}`);
153
+ }
154
+ let payload = null;
155
+ try {
156
+ payload = await fs.readJson(sessionFile);
157
+ } catch (error) {
158
+ throw new Error(`Invalid governance close-loop session JSON: ${sessionFile} (${error.message})`);
159
+ }
160
+ if (!payload || typeof payload !== 'object') {
161
+ throw new Error(`Invalid governance close-loop session payload: ${sessionFile}`);
162
+ }
163
+ const sessionId = payload && payload.governance_session && payload.governance_session.id
164
+ ? payload.governance_session.id
165
+ : path.basename(sessionFile, '.json');
166
+ return { id: sessionId, file: sessionFile, payload };
167
+ }
168
+
169
+ async function persistGovernanceCloseLoopSession(projectPath, sessionId, payload, status = 'running', dependencies = {}) {
170
+ const { sanitizeBatchSessionId, getGovernanceCloseLoopSessionDir, schemaVersion, fs, now = () => new Date() } = dependencies;
171
+ const safeSessionId = sanitizeBatchSessionId(sessionId);
172
+ if (!safeSessionId) {
173
+ return null;
174
+ }
175
+ const sessionDir = getGovernanceCloseLoopSessionDir(projectPath);
176
+ const sessionFile = path.join(sessionDir, `${safeSessionId}.json`);
177
+ const nowValue = now();
178
+ const updatedAt = nowValue instanceof Date ? nowValue.toISOString() : new Date(nowValue).toISOString();
179
+ const persisted = {
180
+ ...payload,
181
+ schema_version: schemaVersion,
182
+ status,
183
+ governance_session: { id: safeSessionId, file: sessionFile },
184
+ updated_at: updatedAt
185
+ };
186
+ await fs.ensureDir(sessionDir);
187
+ await fs.writeJson(sessionFile, persisted, { spaces: 2 });
188
+ return { id: safeSessionId, file: sessionFile };
189
+ }
190
+
191
+ module.exports = {
192
+ readGovernanceCloseLoopSessionEntries,
193
+ resolveGovernanceCloseLoopSessionFile,
194
+ loadGovernanceCloseLoopSessionPayload,
195
+ persistGovernanceCloseLoopSession
196
+ };
197
+
198
+