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,190 @@
1
+ const path = require('path');
2
+
3
+ async function loadAutoHandoffReleaseEvidence(projectPath, fileCandidate = null, dependencies = {}) {
4
+ const { resolveAutoHandoffReleaseEvidenceFile, fs } = dependencies;
5
+ const filePath = resolveAutoHandoffReleaseEvidenceFile(projectPath, fileCandidate);
6
+ if (!(await fs.pathExists(filePath))) {
7
+ throw new Error(`release evidence file not found: ${filePath}`);
8
+ }
9
+
10
+ let payload = null;
11
+ try {
12
+ payload = await fs.readJson(filePath);
13
+ } catch (error) {
14
+ throw new Error(`invalid release evidence JSON: ${filePath} (${error.message})`);
15
+ }
16
+ if (!payload || typeof payload !== 'object') {
17
+ throw new Error(`invalid release evidence payload: ${filePath}`);
18
+ }
19
+
20
+ const sessions = Array.isArray(payload.sessions)
21
+ ? payload.sessions.filter(item => item && typeof item === 'object')
22
+ : [];
23
+ sessions.sort((left, right) => {
24
+ const leftTs = Date.parse(
25
+ left && (left.merged_at || left.generated_at || left.updated_at)
26
+ ? (left.merged_at || left.generated_at || left.updated_at)
27
+ : 0
28
+ );
29
+ const rightTs = Date.parse(
30
+ right && (right.merged_at || right.generated_at || right.updated_at)
31
+ ? (right.merged_at || right.generated_at || right.updated_at)
32
+ : 0
33
+ );
34
+ return (Number.isFinite(rightTs) ? rightTs : 0) - (Number.isFinite(leftTs) ? leftTs : 0);
35
+ });
36
+
37
+ return {
38
+ file: filePath,
39
+ payload,
40
+ sessions
41
+ };
42
+ }
43
+
44
+ async function mergeAutoHandoffRunIntoReleaseEvidence(projectPath, result, reportFile = null, dependencies = {}) {
45
+ const {
46
+ resolveAutoHandoffReleaseEvidenceFile,
47
+ normalizeHandoffReleaseEvidenceWindow,
48
+ loadAutoHandoffReleaseEvidence,
49
+ fs,
50
+ buildAutoHandoffRegressionReport,
51
+ normalizeHandoffText,
52
+ buildAutoHandoffReleaseEvidenceEntry,
53
+ now = () => new Date().toISOString()
54
+ } = dependencies;
55
+
56
+ const evidenceFile = resolveAutoHandoffReleaseEvidenceFile(projectPath);
57
+ if (result && result.dry_run) {
58
+ return {
59
+ mode: 'auto-handoff-release-evidence',
60
+ merged: false,
61
+ skipped: true,
62
+ reason: 'dry-run',
63
+ file: evidenceFile
64
+ };
65
+ }
66
+
67
+ let existing = null;
68
+ try {
69
+ existing = await loadAutoHandoffReleaseEvidence(projectPath, evidenceFile);
70
+ } catch (error) {
71
+ if (!(error && typeof error.message === 'string' && error.message.includes('release evidence file not found'))) {
72
+ throw new Error(`failed to read release evidence JSON: ${evidenceFile} (${error.message})`);
73
+ }
74
+ }
75
+
76
+ const existingSessions = existing && Array.isArray(existing.sessions)
77
+ ? existing.sessions.filter(item => item && typeof item === 'object')
78
+ : [];
79
+ const nowIso = now();
80
+ let trendWindow = null;
81
+ const trendWindowSize = Number(
82
+ result &&
83
+ result.policy &&
84
+ result.policy.release_evidence_window !== undefined &&
85
+ result.policy.release_evidence_window !== null
86
+ ? result.policy.release_evidence_window
87
+ : 5
88
+ );
89
+ if (Number.isInteger(trendWindowSize) && trendWindowSize >= 2 && trendWindowSize <= 50) {
90
+ try {
91
+ const regressionSnapshot = await buildAutoHandoffRegressionReport(projectPath, {
92
+ sessionId: result && result.session_id ? result.session_id : 'latest',
93
+ window: trendWindowSize
94
+ });
95
+ trendWindow = {
96
+ generated_at: nowIso,
97
+ window: regressionSnapshot.window || {
98
+ requested: trendWindowSize,
99
+ actual: null
100
+ },
101
+ trend: normalizeHandoffText(regressionSnapshot.trend),
102
+ window_trend: regressionSnapshot.window_trend || null,
103
+ aggregates: regressionSnapshot.aggregates || null,
104
+ risk_layers: regressionSnapshot.risk_layers || null
105
+ };
106
+ } catch (error) {
107
+ trendWindow = {
108
+ generated_at: nowIso,
109
+ window: {
110
+ requested: trendWindowSize,
111
+ actual: null
112
+ },
113
+ error: error && error.message ? error.message : `${error}`
114
+ };
115
+ }
116
+ }
117
+
118
+ const nextEntry = buildAutoHandoffReleaseEvidenceEntry(projectPath, result, reportFile, trendWindow);
119
+ const sessionId = normalizeHandoffText(nextEntry.session_id);
120
+ let updatedExisting = false;
121
+ const mergedSessions = existingSessions.slice();
122
+
123
+ if (sessionId) {
124
+ const existingIndex = mergedSessions.findIndex(item => normalizeHandoffText(item.session_id) === sessionId);
125
+ if (existingIndex >= 0) {
126
+ mergedSessions[existingIndex] = {
127
+ ...mergedSessions[existingIndex],
128
+ ...nextEntry
129
+ };
130
+ updatedExisting = true;
131
+ } else {
132
+ mergedSessions.push(nextEntry);
133
+ }
134
+ } else {
135
+ mergedSessions.push(nextEntry);
136
+ }
137
+
138
+ mergedSessions.sort((left, right) => {
139
+ const leftTs = Date.parse(left && (left.merged_at || left.generated_at || left.updated_at) ? (left.merged_at || left.generated_at || left.updated_at) : 0);
140
+ const rightTs = Date.parse(right && (right.merged_at || right.generated_at || right.updated_at) ? (right.merged_at || right.generated_at || right.updated_at) : 0);
141
+ return (Number.isFinite(rightTs) ? rightTs : 0) - (Number.isFinite(leftTs) ? leftTs : 0);
142
+ });
143
+
144
+ const generatedAt = existing && existing.payload && typeof existing.payload.generated_at === 'string' && existing.payload.generated_at.trim()
145
+ ? existing.payload.generated_at
146
+ : nowIso;
147
+ const payload = {
148
+ mode: 'auto-handoff-release-evidence',
149
+ generated_at: generatedAt,
150
+ updated_at: nowIso,
151
+ latest_session_id: sessionId || (
152
+ mergedSessions.length > 0 && normalizeHandoffText(mergedSessions[0].session_id)
153
+ ? normalizeHandoffText(mergedSessions[0].session_id)
154
+ : null
155
+ ),
156
+ total_runs: mergedSessions.length,
157
+ latest_trend_window: mergedSessions.length > 0 && mergedSessions[0] && mergedSessions[0].trend_window
158
+ ? mergedSessions[0].trend_window
159
+ : null,
160
+ sessions: mergedSessions
161
+ };
162
+
163
+ await fs.ensureDir(path.dirname(evidenceFile));
164
+ await fs.writeJson(evidenceFile, payload, { spaces: 2 });
165
+ return {
166
+ mode: 'auto-handoff-release-evidence',
167
+ merged: true,
168
+ updated_existing: updatedExisting,
169
+ file: evidenceFile,
170
+ latest_session_id: payload.latest_session_id,
171
+ total_runs: payload.total_runs,
172
+ trend_window: nextEntry.trend_window
173
+ };
174
+ }
175
+
176
+ async function writeAutoHandoffRunReport(projectPath, result, outCandidate = null, dependencies = {}) {
177
+ const { maybeWriteOutput, reportDir } = dependencies;
178
+ if (typeof outCandidate === 'string' && outCandidate.trim().length > 0) {
179
+ await maybeWriteOutput(result, outCandidate.trim(), projectPath);
180
+ return;
181
+ }
182
+ const defaultFile = path.join(reportDir, `${result.session_id}.json`);
183
+ await maybeWriteOutput(result, defaultFile, projectPath);
184
+ }
185
+
186
+ module.exports = {
187
+ loadAutoHandoffReleaseEvidence,
188
+ mergeAutoHandoffRunIntoReleaseEvidence,
189
+ writeAutoHandoffRunReport
190
+ };
@@ -0,0 +1,502 @@
1
+ const path = require('path');
2
+
3
+ function buildAutoHandoffReleaseGateHistoryEntry(entry = {}, options = {}, dependencies = {}) {
4
+ const { parseAutoHandoffGateSignalsMap, normalizeHandoffText, parseAutoHandoffReleaseGateTag, parseAutoHandoffGateBoolean, normalizeAutoHandoffGateRiskLevel, parseAutoHandoffGateNumber, toPortablePath } = dependencies;
5
+ const projectPath = options.projectPath || process.cwd();
6
+ const sourceFile = typeof options.file === 'string' && options.file.trim()
7
+ ? options.file.trim()
8
+ : null;
9
+ const signalMap = parseAutoHandoffGateSignalsMap(entry.signals);
10
+ const derivedTag = normalizeHandoffText(options.tag)
11
+ || (sourceFile ? parseAutoHandoffReleaseGateTag(path.basename(sourceFile)) : null)
12
+ || normalizeHandoffText(entry.tag);
13
+ const gatePassed = parseAutoHandoffGateBoolean(
14
+ entry.gate_passed !== undefined ? entry.gate_passed : signalMap.gate_passed,
15
+ null
16
+ );
17
+ const riskLevel = normalizeAutoHandoffGateRiskLevel(
18
+ normalizeHandoffText(entry.risk_level) || signalMap.risk_level
19
+ );
20
+ const specSuccessRate = parseAutoHandoffGateNumber(
21
+ entry.spec_success_rate_percent !== undefined
22
+ ? entry.spec_success_rate_percent
23
+ : signalMap.spec_success_rate
24
+ );
25
+ const sceneBatchStatus = normalizeHandoffText(
26
+ entry.scene_package_batch_status !== undefined
27
+ ? entry.scene_package_batch_status
28
+ : signalMap.scene_package_batch_status
29
+ );
30
+ let sceneBatchPassed = parseAutoHandoffGateBoolean(
31
+ entry.scene_package_batch_passed !== undefined
32
+ ? entry.scene_package_batch_passed
33
+ : signalMap.scene_package_batch_passed,
34
+ null
35
+ );
36
+ if (sceneBatchPassed === null && sceneBatchStatus && sceneBatchStatus !== 'skipped') {
37
+ sceneBatchPassed = sceneBatchStatus === 'passed';
38
+ }
39
+ const sceneBatchFailureCount = parseAutoHandoffGateNumber(
40
+ entry.scene_package_batch_failure_count !== undefined
41
+ ? entry.scene_package_batch_failure_count
42
+ : signalMap.scene_package_batch_failure_count
43
+ );
44
+ const capabilityExpectedUnknownCount = parseAutoHandoffGateNumber(
45
+ entry.capability_expected_unknown_count !== undefined
46
+ ? entry.capability_expected_unknown_count
47
+ : (
48
+ signalMap.capability_expected_unknown_count !== undefined
49
+ ? signalMap.capability_expected_unknown_count
50
+ : signalMap.capability_lexicon_expected_unknown_count
51
+ )
52
+ );
53
+ const capabilityProvidedUnknownCount = parseAutoHandoffGateNumber(
54
+ entry.capability_provided_unknown_count !== undefined
55
+ ? entry.capability_provided_unknown_count
56
+ : (
57
+ signalMap.capability_provided_unknown_count !== undefined
58
+ ? signalMap.capability_provided_unknown_count
59
+ : signalMap.capability_lexicon_provided_unknown_count
60
+ )
61
+ );
62
+ const releaseGatePreflightAvailable = parseAutoHandoffGateBoolean(
63
+ entry.release_gate_preflight_available !== undefined
64
+ ? entry.release_gate_preflight_available
65
+ : signalMap.release_gate_preflight_available,
66
+ null
67
+ );
68
+ const releaseGatePreflightBlocked = parseAutoHandoffGateBoolean(
69
+ entry.release_gate_preflight_blocked !== undefined
70
+ ? entry.release_gate_preflight_blocked
71
+ : signalMap.release_gate_preflight_blocked,
72
+ null
73
+ );
74
+ const requireReleaseGatePreflight = parseAutoHandoffGateBoolean(
75
+ entry.require_release_gate_preflight !== undefined
76
+ ? entry.require_release_gate_preflight
77
+ : (
78
+ signalMap.require_release_gate_preflight !== undefined
79
+ ? signalMap.require_release_gate_preflight
80
+ : signalMap.release_gate_preflight_hard_gate
81
+ ),
82
+ null
83
+ );
84
+ const drift = entry && typeof entry.drift === 'object' && !Array.isArray(entry.drift)
85
+ ? entry.drift
86
+ : {};
87
+ const driftAlerts = Array.isArray(drift.alerts)
88
+ ? drift.alerts
89
+ .map(item => `${item || ''}`.trim())
90
+ .filter(Boolean)
91
+ : [];
92
+ const hasDriftAlertSource = (
93
+ entry.drift_alert_count !== undefined
94
+ || drift.alert_count !== undefined
95
+ || Array.isArray(drift.alerts)
96
+ );
97
+ const driftAlertCount = hasDriftAlertSource
98
+ ? parseAutoHandoffGateNumber(
99
+ entry.drift_alert_count !== undefined
100
+ ? entry.drift_alert_count
101
+ : (drift.alert_count !== undefined ? drift.alert_count : driftAlerts.length)
102
+ )
103
+ : null;
104
+ const driftBlocked = parseAutoHandoffGateBoolean(
105
+ entry.drift_blocked !== undefined
106
+ ? entry.drift_blocked
107
+ : drift.blocked,
108
+ null
109
+ );
110
+ const driftEnforce = parseAutoHandoffGateBoolean(
111
+ entry.drift_enforce !== undefined
112
+ ? entry.drift_enforce
113
+ : drift.enforce,
114
+ null
115
+ );
116
+ const driftEvaluatedAt = normalizeHandoffText(
117
+ entry.drift_evaluated_at !== undefined
118
+ ? entry.drift_evaluated_at
119
+ : drift.evaluated_at
120
+ );
121
+ const weeklyOps = entry && typeof entry.weekly_ops === 'object' && !Array.isArray(entry.weekly_ops)
122
+ ? entry.weekly_ops
123
+ : {};
124
+ const weeklyOpsSignals = weeklyOps && typeof weeklyOps.signals === 'object' && !Array.isArray(weeklyOps.signals)
125
+ ? weeklyOps.signals
126
+ : {};
127
+ const weeklyOpsViolations = Array.isArray(weeklyOps.violations)
128
+ ? weeklyOps.violations.map(item => `${item}`)
129
+ : [];
130
+ const weeklyOpsWarnings = Array.isArray(weeklyOps.warnings)
131
+ ? weeklyOps.warnings.map(item => `${item}`)
132
+ : [];
133
+ const weeklyOpsConfigWarnings = Array.isArray(weeklyOps.config_warnings)
134
+ ? weeklyOps.config_warnings.map(item => `${item}`)
135
+ : [];
136
+ const weeklyOpsAvailable = parseAutoHandoffGateBoolean(entry.weekly_ops_available, null) === true
137
+ || Object.keys(weeklyOps).length > 0;
138
+ const weeklyOpsBlocked = parseAutoHandoffGateBoolean(
139
+ entry.weekly_ops_blocked !== undefined
140
+ ? entry.weekly_ops_blocked
141
+ : weeklyOps.blocked,
142
+ null
143
+ );
144
+ const weeklyOpsRiskRaw = normalizeHandoffText(
145
+ entry.weekly_ops_risk_level !== undefined
146
+ ? entry.weekly_ops_risk_level
147
+ : weeklyOpsSignals.risk
148
+ );
149
+ const weeklyOpsRiskLevel = weeklyOpsRiskRaw
150
+ ? normalizeAutoHandoffGateRiskLevel(weeklyOpsRiskRaw)
151
+ : null;
152
+ const weeklyOpsGovernanceStatus = normalizeHandoffText(
153
+ entry.weekly_ops_governance_status !== undefined
154
+ ? entry.weekly_ops_governance_status
155
+ : weeklyOpsSignals.governance_status
156
+ ) || null;
157
+ const weeklyOpsAuthorizationTierBlockRatePercentCandidate = (
158
+ entry.weekly_ops_authorization_tier_block_rate_percent !== undefined
159
+ ? entry.weekly_ops_authorization_tier_block_rate_percent
160
+ : weeklyOpsSignals.authorization_tier_block_rate_percent
161
+ );
162
+ const weeklyOpsDialogueAuthorizationBlockRatePercentCandidate = (
163
+ entry.weekly_ops_dialogue_authorization_block_rate_percent !== undefined
164
+ ? entry.weekly_ops_dialogue_authorization_block_rate_percent
165
+ : weeklyOpsSignals.dialogue_authorization_block_rate_percent
166
+ );
167
+ const weeklyOpsMatrixRegressionPositiveRatePercentCandidate = (
168
+ entry.weekly_ops_matrix_regression_positive_rate_percent !== undefined
169
+ ? entry.weekly_ops_matrix_regression_positive_rate_percent
170
+ : weeklyOpsSignals.matrix_regression_positive_rate_percent
171
+ );
172
+ const weeklyOpsRuntimeBlockRatePercentCandidate = (
173
+ entry.weekly_ops_runtime_block_rate_percent !== undefined
174
+ ? entry.weekly_ops_runtime_block_rate_percent
175
+ : weeklyOpsSignals.runtime_block_rate_percent
176
+ );
177
+ const weeklyOpsRuntimeUiModeViolationTotalCandidate = (
178
+ entry.weekly_ops_runtime_ui_mode_violation_total !== undefined
179
+ ? entry.weekly_ops_runtime_ui_mode_violation_total
180
+ : weeklyOpsSignals.runtime_ui_mode_violation_total
181
+ );
182
+ const weeklyOpsRuntimeUiModeViolationRatePercentCandidate = (
183
+ entry.weekly_ops_runtime_ui_mode_violation_rate_percent !== undefined
184
+ ? entry.weekly_ops_runtime_ui_mode_violation_rate_percent
185
+ : weeklyOpsSignals.runtime_ui_mode_violation_rate_percent
186
+ );
187
+ const weeklyOpsViolationsCountCandidate = (
188
+ entry.weekly_ops_violations_count !== undefined
189
+ ? entry.weekly_ops_violations_count
190
+ : (
191
+ weeklyOps.violations_count !== undefined
192
+ ? weeklyOps.violations_count
193
+ : (weeklyOpsAvailable ? weeklyOpsViolations.length : null)
194
+ )
195
+ );
196
+ const weeklyOpsWarningCountCandidate = (
197
+ entry.weekly_ops_warning_count !== undefined
198
+ ? entry.weekly_ops_warning_count
199
+ : (
200
+ weeklyOps.warning_count !== undefined
201
+ ? weeklyOps.warning_count
202
+ : (weeklyOpsAvailable ? weeklyOpsWarnings.length : null)
203
+ )
204
+ );
205
+ const weeklyOpsConfigWarningCountCandidate = (
206
+ entry.weekly_ops_config_warning_count !== undefined
207
+ ? entry.weekly_ops_config_warning_count
208
+ : (
209
+ weeklyOps.config_warning_count !== undefined
210
+ ? weeklyOps.config_warning_count
211
+ : (weeklyOpsAvailable ? weeklyOpsConfigWarnings.length : null)
212
+ )
213
+ );
214
+ const weeklyOpsAuthorizationTierBlockRatePercent = (
215
+ weeklyOpsAuthorizationTierBlockRatePercentCandidate === null
216
+ || weeklyOpsAuthorizationTierBlockRatePercentCandidate === undefined
217
+ || weeklyOpsAuthorizationTierBlockRatePercentCandidate === ''
218
+ )
219
+ ? null
220
+ : parseAutoHandoffGateNumber(weeklyOpsAuthorizationTierBlockRatePercentCandidate);
221
+ const weeklyOpsDialogueAuthorizationBlockRatePercent = (
222
+ weeklyOpsDialogueAuthorizationBlockRatePercentCandidate === null
223
+ || weeklyOpsDialogueAuthorizationBlockRatePercentCandidate === undefined
224
+ || weeklyOpsDialogueAuthorizationBlockRatePercentCandidate === ''
225
+ )
226
+ ? null
227
+ : parseAutoHandoffGateNumber(weeklyOpsDialogueAuthorizationBlockRatePercentCandidate);
228
+ const weeklyOpsMatrixRegressionPositiveRatePercent = (
229
+ weeklyOpsMatrixRegressionPositiveRatePercentCandidate === null
230
+ || weeklyOpsMatrixRegressionPositiveRatePercentCandidate === undefined
231
+ || weeklyOpsMatrixRegressionPositiveRatePercentCandidate === ''
232
+ )
233
+ ? null
234
+ : parseAutoHandoffGateNumber(weeklyOpsMatrixRegressionPositiveRatePercentCandidate);
235
+ const weeklyOpsRuntimeBlockRatePercent = (
236
+ weeklyOpsRuntimeBlockRatePercentCandidate === null
237
+ || weeklyOpsRuntimeBlockRatePercentCandidate === undefined
238
+ || weeklyOpsRuntimeBlockRatePercentCandidate === ''
239
+ )
240
+ ? null
241
+ : parseAutoHandoffGateNumber(weeklyOpsRuntimeBlockRatePercentCandidate);
242
+ const weeklyOpsRuntimeUiModeViolationTotal = (
243
+ weeklyOpsRuntimeUiModeViolationTotalCandidate === null
244
+ || weeklyOpsRuntimeUiModeViolationTotalCandidate === undefined
245
+ || weeklyOpsRuntimeUiModeViolationTotalCandidate === ''
246
+ )
247
+ ? null
248
+ : parseAutoHandoffGateNumber(weeklyOpsRuntimeUiModeViolationTotalCandidate);
249
+ const weeklyOpsRuntimeUiModeViolationRatePercent = (
250
+ weeklyOpsRuntimeUiModeViolationRatePercentCandidate === null
251
+ || weeklyOpsRuntimeUiModeViolationRatePercentCandidate === undefined
252
+ || weeklyOpsRuntimeUiModeViolationRatePercentCandidate === ''
253
+ )
254
+ ? null
255
+ : parseAutoHandoffGateNumber(weeklyOpsRuntimeUiModeViolationRatePercentCandidate);
256
+ const weeklyOpsViolationsCount = (
257
+ weeklyOpsViolationsCountCandidate === null
258
+ || weeklyOpsViolationsCountCandidate === undefined
259
+ || weeklyOpsViolationsCountCandidate === ''
260
+ )
261
+ ? null
262
+ : parseAutoHandoffGateNumber(weeklyOpsViolationsCountCandidate);
263
+ const weeklyOpsWarningCount = (
264
+ weeklyOpsWarningCountCandidate === null
265
+ || weeklyOpsWarningCountCandidate === undefined
266
+ || weeklyOpsWarningCountCandidate === ''
267
+ )
268
+ ? null
269
+ : parseAutoHandoffGateNumber(weeklyOpsWarningCountCandidate);
270
+ const weeklyOpsConfigWarningCount = (
271
+ weeklyOpsConfigWarningCountCandidate === null
272
+ || weeklyOpsConfigWarningCountCandidate === undefined
273
+ || weeklyOpsConfigWarningCountCandidate === ''
274
+ )
275
+ ? null
276
+ : parseAutoHandoffGateNumber(weeklyOpsConfigWarningCountCandidate);
277
+ const violations = Array.isArray(entry.violations)
278
+ ? entry.violations.map(item => `${item}`)
279
+ : [];
280
+ const configWarnings = Array.isArray(entry.config_warnings)
281
+ ? entry.config_warnings.map(item => `${item}`)
282
+ : [];
283
+ const signals = Array.isArray(entry.signals)
284
+ ? entry.signals.map(item => `${item}`)
285
+ : [];
286
+ const thresholds = entry.thresholds && typeof entry.thresholds === 'object' && !Array.isArray(entry.thresholds)
287
+ ? { ...entry.thresholds }
288
+ : {};
289
+ const evaluatedAt = normalizeHandoffText(
290
+ entry.evaluated_at || entry.generated_at || entry.updated_at
291
+ );
292
+ const mode = normalizeHandoffText(entry.mode);
293
+ const enforce = parseAutoHandoffGateBoolean(entry.enforce, false);
294
+ const evidenceUsed = parseAutoHandoffGateBoolean(entry.evidence_used, false);
295
+ const requireEvidence = parseAutoHandoffGateBoolean(entry.require_evidence, false);
296
+ const requireGatePass = parseAutoHandoffGateBoolean(entry.require_gate_pass, true);
297
+ const summaryFile = normalizeHandoffText(entry.summary_file);
298
+ const portableFile = sourceFile
299
+ ? toPortablePath(projectPath, sourceFile)
300
+ : normalizeHandoffText(entry.file);
301
+ const violationsCount = Number.isInteger(entry.violations_count)
302
+ ? entry.violations_count
303
+ : violations.length;
304
+ const configWarningCount = Number.isInteger(entry.config_warning_count)
305
+ ? entry.config_warning_count
306
+ : configWarnings.length;
307
+
308
+ return {
309
+ tag: derivedTag,
310
+ evaluated_at: evaluatedAt,
311
+ gate_passed: gatePassed,
312
+ mode,
313
+ enforce,
314
+ evidence_used: evidenceUsed,
315
+ require_evidence: requireEvidence,
316
+ require_gate_pass: requireGatePass,
317
+ risk_level: riskLevel,
318
+ spec_success_rate_percent: specSuccessRate,
319
+ scene_package_batch_status: sceneBatchStatus || null,
320
+ scene_package_batch_passed: typeof sceneBatchPassed === 'boolean' ? sceneBatchPassed : null,
321
+ scene_package_batch_failure_count: Number.isFinite(sceneBatchFailureCount) ? sceneBatchFailureCount : null,
322
+ capability_expected_unknown_count: Number.isFinite(capabilityExpectedUnknownCount)
323
+ ? Math.max(0, Number(capabilityExpectedUnknownCount))
324
+ : null,
325
+ capability_provided_unknown_count: Number.isFinite(capabilityProvidedUnknownCount)
326
+ ? Math.max(0, Number(capabilityProvidedUnknownCount))
327
+ : null,
328
+ release_gate_preflight_available: typeof releaseGatePreflightAvailable === 'boolean'
329
+ ? releaseGatePreflightAvailable
330
+ : null,
331
+ release_gate_preflight_blocked: typeof releaseGatePreflightBlocked === 'boolean'
332
+ ? releaseGatePreflightBlocked
333
+ : null,
334
+ require_release_gate_preflight: typeof requireReleaseGatePreflight === 'boolean'
335
+ ? requireReleaseGatePreflight
336
+ : null,
337
+ drift_alert_count: Number.isFinite(driftAlertCount) ? Math.max(0, Number(driftAlertCount)) : null,
338
+ drift_blocked: typeof driftBlocked === 'boolean' ? driftBlocked : null,
339
+ drift_enforce: typeof driftEnforce === 'boolean' ? driftEnforce : null,
340
+ drift_evaluated_at: driftEvaluatedAt || null,
341
+ weekly_ops_available: weeklyOpsAvailable,
342
+ weekly_ops_blocked: typeof weeklyOpsBlocked === 'boolean' ? weeklyOpsBlocked : null,
343
+ weekly_ops_risk_level: weeklyOpsRiskLevel,
344
+ weekly_ops_governance_status: weeklyOpsGovernanceStatus,
345
+ weekly_ops_authorization_tier_block_rate_percent: Number.isFinite(weeklyOpsAuthorizationTierBlockRatePercent)
346
+ ? weeklyOpsAuthorizationTierBlockRatePercent
347
+ : null,
348
+ weekly_ops_dialogue_authorization_block_rate_percent: Number.isFinite(weeklyOpsDialogueAuthorizationBlockRatePercent)
349
+ ? weeklyOpsDialogueAuthorizationBlockRatePercent
350
+ : null,
351
+ weekly_ops_matrix_regression_positive_rate_percent: Number.isFinite(weeklyOpsMatrixRegressionPositiveRatePercent)
352
+ ? weeklyOpsMatrixRegressionPositiveRatePercent
353
+ : null,
354
+ weekly_ops_runtime_block_rate_percent: Number.isFinite(weeklyOpsRuntimeBlockRatePercent)
355
+ ? weeklyOpsRuntimeBlockRatePercent
356
+ : null,
357
+ weekly_ops_runtime_ui_mode_violation_total: Number.isFinite(weeklyOpsRuntimeUiModeViolationTotal)
358
+ ? Math.max(0, Number(weeklyOpsRuntimeUiModeViolationTotal))
359
+ : null,
360
+ weekly_ops_runtime_ui_mode_violation_rate_percent: Number.isFinite(weeklyOpsRuntimeUiModeViolationRatePercent)
361
+ ? Math.max(0, Number(weeklyOpsRuntimeUiModeViolationRatePercent))
362
+ : null,
363
+ weekly_ops_violations_count: Number.isFinite(weeklyOpsViolationsCount)
364
+ ? Math.max(0, Number(weeklyOpsViolationsCount))
365
+ : null,
366
+ weekly_ops_warning_count: Number.isFinite(weeklyOpsWarningCount)
367
+ ? Math.max(0, Number(weeklyOpsWarningCount))
368
+ : null,
369
+ weekly_ops_config_warning_count: Number.isFinite(weeklyOpsConfigWarningCount)
370
+ ? Math.max(0, Number(weeklyOpsConfigWarningCount))
371
+ : null,
372
+ violations_count: Math.max(0, Number(violationsCount) || 0),
373
+ config_warning_count: Math.max(0, Number(configWarningCount) || 0),
374
+ thresholds,
375
+ summary_file: summaryFile,
376
+ file: portableFile,
377
+ signals,
378
+ violations,
379
+ config_warnings: configWarnings
380
+ };
381
+ }
382
+
383
+ async function loadAutoHandoffReleaseGateReports(projectPath, dirCandidate = null, dependencies = {}) {
384
+ const { resolveAutoHandoffReleaseEvidenceDir, fs, parseAutoHandoffReleaseGateTag, buildAutoHandoffReleaseGateHistoryEntry } = dependencies;
385
+ const dirPath = resolveAutoHandoffReleaseEvidenceDir(projectPath, dirCandidate);
386
+ const warnings = [];
387
+ if (!(await fs.pathExists(dirPath))) {
388
+ return {
389
+ dir: dirPath,
390
+ report_files: [],
391
+ entries: [],
392
+ warnings
393
+ };
394
+ }
395
+
396
+ const names = await fs.readdir(dirPath);
397
+ const reportFiles = names
398
+ .filter(name => {
399
+ if (typeof name !== 'string') {
400
+ return false;
401
+ }
402
+ const lowered = name.trim().toLowerCase();
403
+ if (!lowered.startsWith('release-gate-') || !lowered.endsWith('.json')) {
404
+ return false;
405
+ }
406
+ if (lowered === 'release-gate-history.json') {
407
+ return false;
408
+ }
409
+ if (lowered.startsWith('release-gate-history-')) {
410
+ return false;
411
+ }
412
+ return parseAutoHandoffReleaseGateTag(name) !== null;
413
+ })
414
+ .map(name => path.join(dirPath, name));
415
+
416
+ const entries = [];
417
+ for (const reportFile of reportFiles) {
418
+ let payload = null;
419
+ try {
420
+ payload = await fs.readJson(reportFile);
421
+ } catch (error) {
422
+ warnings.push(`skip invalid release gate report: ${reportFile} (${error.message})`);
423
+ continue;
424
+ }
425
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
426
+ warnings.push(`skip invalid release gate payload: ${reportFile}`);
427
+ continue;
428
+ }
429
+ entries.push(buildAutoHandoffReleaseGateHistoryEntry(payload, { projectPath, file: reportFile, tag: parseAutoHandoffReleaseGateTag(path.basename(reportFile)) }, dependencies));
430
+ }
431
+
432
+ return {
433
+ dir: dirPath,
434
+ report_files: reportFiles,
435
+ entries,
436
+ warnings
437
+ };
438
+ }
439
+
440
+ async function loadAutoHandoffReleaseGateHistorySeed(projectPath, fileCandidate = null, dependencies = {}) {
441
+ const { resolveAutoHandoffReleaseGateHistoryFile, fs, buildAutoHandoffReleaseGateHistoryEntry } = dependencies;
442
+ const filePath = resolveAutoHandoffReleaseGateHistoryFile(projectPath, fileCandidate);
443
+ if (!(await fs.pathExists(filePath))) {
444
+ return {
445
+ file: filePath,
446
+ entries: [],
447
+ warnings: []
448
+ };
449
+ }
450
+
451
+ let payload = null;
452
+ try {
453
+ payload = await fs.readJson(filePath);
454
+ } catch (error) {
455
+ return {
456
+ file: filePath,
457
+ entries: [],
458
+ warnings: [`skip invalid gate history file: ${filePath} (${error.message})`]
459
+ };
460
+ }
461
+ const list = Array.isArray(payload && payload.entries) ? payload.entries : [];
462
+ const entries = list
463
+ .filter(item => item && typeof item === 'object' && !Array.isArray(item))
464
+ .map(item => buildAutoHandoffReleaseGateHistoryEntry(item, { projectPath }, dependencies));
465
+ return {
466
+ file: filePath,
467
+ entries,
468
+ warnings: []
469
+ };
470
+ }
471
+
472
+ function mergeAutoHandoffReleaseGateHistoryEntries(entries = [], dependencies = {}) {
473
+ const { normalizeHandoffText, toAutoHandoffTimestamp } = dependencies;
474
+ const merged = new Map();
475
+ entries.forEach((entry, index) => {
476
+ if (!entry || typeof entry !== 'object') {
477
+ return;
478
+ }
479
+ const key = normalizeHandoffText(entry.tag)
480
+ || normalizeHandoffText(entry.file)
481
+ || `entry-${index}`;
482
+ const previous = merged.get(key);
483
+ if (!previous) {
484
+ merged.set(key, entry);
485
+ return;
486
+ }
487
+ const prevTs = toAutoHandoffTimestamp(previous.evaluated_at);
488
+ const nextTs = toAutoHandoffTimestamp(entry.evaluated_at);
489
+ if (nextTs >= prevTs) {
490
+ merged.set(key, entry);
491
+ }
492
+ });
493
+ return Array.from(merged.values());
494
+ }
495
+
496
+ module.exports = {
497
+ buildAutoHandoffReleaseGateHistoryEntry,
498
+ loadAutoHandoffReleaseGateReports,
499
+ loadAutoHandoffReleaseGateHistorySeed,
500
+ mergeAutoHandoffReleaseGateHistoryEntries
501
+ };
502
+