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,1407 @@
1
+ const { buildMoquiRegressionRecoverySequenceLines } = require('./moqui-recovery-sequence');
2
+
3
+ function quoteCliArg(value) {
4
+ const text = `${value || ''}`;
5
+ if (/^[A-Za-z0-9_./:-]+$/.test(text)) {
6
+ return text;
7
+ }
8
+ return JSON.stringify(text);
9
+ }
10
+
11
+ function buildAutoHandoffRegressionSnapshot(report, dependencies = {}) {
12
+ const { normalizeHandoffText, normalizeRiskRank } = dependencies;
13
+ const payload = report && report.payload ? report.payload : report;
14
+ const specStatus = payload && payload.spec_status ? payload.spec_status : {};
15
+ const gates = payload && payload.gates ? payload.gates : {};
16
+ const gateActual = gates && gates.actual ? gates.actual : {};
17
+ const batchSummary = payload && payload.batch_summary ? payload.batch_summary : {};
18
+ const ontology = payload && payload.ontology_validation ? payload.ontology_validation : {};
19
+ const ontologyMetrics = ontology && ontology.metrics ? ontology.metrics : {};
20
+ const scenePackageBatch = payload && payload.scene_package_batch ? payload.scene_package_batch : {};
21
+ const scenePackageBatchSummary = scenePackageBatch && scenePackageBatch.summary
22
+ ? scenePackageBatch.summary
23
+ : {};
24
+
25
+ const riskLevel = gateActual.risk_level
26
+ || (payload && payload.observability_snapshot && payload.observability_snapshot.highlights
27
+ ? payload.observability_snapshot.highlights.governance_risk_level
28
+ : 'high');
29
+ const successRate = Number(specStatus.success_rate_percent);
30
+ const failedGoals = Number(batchSummary.failed_goals);
31
+ const elapsedMs = Number(payload && payload.elapsed_ms);
32
+ const ontologyQualityScore = Number(
33
+ gateActual.ontology_quality_score !== undefined
34
+ ? gateActual.ontology_quality_score
35
+ : ontology.quality_score
36
+ );
37
+ const ontologyUnmappedRules = Number(
38
+ gateActual.ontology_business_rule_unmapped !== undefined
39
+ ? gateActual.ontology_business_rule_unmapped
40
+ : ontologyMetrics.business_rule_unmapped
41
+ );
42
+ const ontologyUndecidedDecisions = Number(
43
+ gateActual.ontology_decision_undecided !== undefined
44
+ ? gateActual.ontology_decision_undecided
45
+ : ontologyMetrics.decision_undecided
46
+ );
47
+ const businessRulePassRate = Number(
48
+ gateActual.ontology_business_rule_pass_rate_percent !== undefined
49
+ ? gateActual.ontology_business_rule_pass_rate_percent
50
+ : ontologyMetrics.business_rule_pass_rate_percent
51
+ );
52
+ const decisionResolvedRate = Number(
53
+ gateActual.ontology_decision_resolved_rate_percent !== undefined
54
+ ? gateActual.ontology_decision_resolved_rate_percent
55
+ : ontologyMetrics.decision_resolved_rate_percent
56
+ );
57
+ const sceneBatchFailureCount = Number(
58
+ scenePackageBatchSummary.batch_gate_failure_count !== undefined
59
+ ? scenePackageBatchSummary.batch_gate_failure_count
60
+ : scenePackageBatchSummary.failed
61
+ );
62
+ const sceneBatchStatus = normalizeHandoffText(
63
+ scenePackageBatch.status !== undefined
64
+ ? scenePackageBatch.status
65
+ : gateActual.scene_package_batch_status
66
+ );
67
+ const moquiBaseline = payload && payload.moqui_baseline ? payload.moqui_baseline : {};
68
+ const moquiCompare = moquiBaseline && moquiBaseline.compare ? moquiBaseline.compare : {};
69
+ const moquiMatrixRegressionCount = Number(
70
+ gateActual.moqui_matrix_regression_count !== undefined
71
+ ? gateActual.moqui_matrix_regression_count
72
+ : buildAutoHandoffMoquiCoverageRegressions(moquiCompare, { normalizeHandoffText }).length
73
+ );
74
+ let sceneBatchPassed = null;
75
+ if (sceneBatchStatus && sceneBatchStatus !== 'skipped') {
76
+ sceneBatchPassed = sceneBatchStatus === 'passed';
77
+ }
78
+ if (gateActual.scene_package_batch_passed === true) {
79
+ sceneBatchPassed = true;
80
+ } else if (gateActual.scene_package_batch_passed === false) {
81
+ sceneBatchPassed = false;
82
+ }
83
+
84
+ return {
85
+ session_id: payload && payload.session_id ? payload.session_id : null,
86
+ status: payload && payload.status ? payload.status : null,
87
+ spec_success_rate_percent: Number.isFinite(successRate) ? successRate : null,
88
+ risk_level: `${riskLevel || 'high'}`.trim().toLowerCase(),
89
+ risk_level_rank: normalizeRiskRank(riskLevel),
90
+ failed_goals: Number.isFinite(failedGoals) ? failedGoals : null,
91
+ elapsed_ms: Number.isFinite(elapsedMs) ? elapsedMs : null,
92
+ ontology_quality_score: Number.isFinite(ontologyQualityScore) ? ontologyQualityScore : null,
93
+ ontology_unmapped_rules: Number.isFinite(ontologyUnmappedRules) ? ontologyUnmappedRules : null,
94
+ ontology_undecided_decisions: Number.isFinite(ontologyUndecidedDecisions) ? ontologyUndecidedDecisions : null,
95
+ ontology_business_rule_pass_rate_percent: Number.isFinite(businessRulePassRate) ? businessRulePassRate : null,
96
+ ontology_decision_resolved_rate_percent: Number.isFinite(decisionResolvedRate) ? decisionResolvedRate : null,
97
+ moqui_matrix_regression_count: Number.isFinite(moquiMatrixRegressionCount) ? moquiMatrixRegressionCount : null,
98
+ scene_package_batch_status: sceneBatchStatus || null,
99
+ scene_package_batch_passed: typeof sceneBatchPassed === 'boolean' ? sceneBatchPassed : null,
100
+ scene_package_batch_failure_count: Number.isFinite(sceneBatchFailureCount) ? sceneBatchFailureCount : null,
101
+ generated_at: payload && payload.generated_at ? payload.generated_at : null
102
+ };
103
+ }
104
+
105
+ function buildAutoHandoffRegressionComparison(currentSnapshot, previousSnapshot) {
106
+ const deltaSuccess = (
107
+ Number.isFinite(currentSnapshot.spec_success_rate_percent) &&
108
+ Number.isFinite(previousSnapshot.spec_success_rate_percent)
109
+ )
110
+ ? Number((currentSnapshot.spec_success_rate_percent - previousSnapshot.spec_success_rate_percent).toFixed(2))
111
+ : null;
112
+ const deltaRiskRank = (
113
+ Number.isFinite(currentSnapshot.risk_level_rank) &&
114
+ Number.isFinite(previousSnapshot.risk_level_rank)
115
+ )
116
+ ? currentSnapshot.risk_level_rank - previousSnapshot.risk_level_rank
117
+ : null;
118
+ const deltaFailedGoals = (
119
+ Number.isFinite(currentSnapshot.failed_goals) &&
120
+ Number.isFinite(previousSnapshot.failed_goals)
121
+ )
122
+ ? currentSnapshot.failed_goals - previousSnapshot.failed_goals
123
+ : null;
124
+ const deltaElapsedMs = (
125
+ Number.isFinite(currentSnapshot.elapsed_ms) &&
126
+ Number.isFinite(previousSnapshot.elapsed_ms)
127
+ )
128
+ ? currentSnapshot.elapsed_ms - previousSnapshot.elapsed_ms
129
+ : null;
130
+ const deltaOntologyQualityScore = (
131
+ Number.isFinite(currentSnapshot.ontology_quality_score) &&
132
+ Number.isFinite(previousSnapshot.ontology_quality_score)
133
+ )
134
+ ? Number((currentSnapshot.ontology_quality_score - previousSnapshot.ontology_quality_score).toFixed(2))
135
+ : null;
136
+ const deltaOntologyUnmappedRules = (
137
+ Number.isFinite(currentSnapshot.ontology_unmapped_rules) &&
138
+ Number.isFinite(previousSnapshot.ontology_unmapped_rules)
139
+ )
140
+ ? currentSnapshot.ontology_unmapped_rules - previousSnapshot.ontology_unmapped_rules
141
+ : null;
142
+ const deltaOntologyUndecidedDecisions = (
143
+ Number.isFinite(currentSnapshot.ontology_undecided_decisions) &&
144
+ Number.isFinite(previousSnapshot.ontology_undecided_decisions)
145
+ )
146
+ ? currentSnapshot.ontology_undecided_decisions - previousSnapshot.ontology_undecided_decisions
147
+ : null;
148
+ const deltaBusinessRulePassRate = (
149
+ Number.isFinite(currentSnapshot.ontology_business_rule_pass_rate_percent) &&
150
+ Number.isFinite(previousSnapshot.ontology_business_rule_pass_rate_percent)
151
+ )
152
+ ? Number((
153
+ currentSnapshot.ontology_business_rule_pass_rate_percent -
154
+ previousSnapshot.ontology_business_rule_pass_rate_percent
155
+ ).toFixed(2))
156
+ : null;
157
+ const deltaDecisionResolvedRate = (
158
+ Number.isFinite(currentSnapshot.ontology_decision_resolved_rate_percent) &&
159
+ Number.isFinite(previousSnapshot.ontology_decision_resolved_rate_percent)
160
+ )
161
+ ? Number((
162
+ currentSnapshot.ontology_decision_resolved_rate_percent -
163
+ previousSnapshot.ontology_decision_resolved_rate_percent
164
+ ).toFixed(2))
165
+ : null;
166
+ const deltaSceneBatchFailureCount = (
167
+ Number.isFinite(currentSnapshot.scene_package_batch_failure_count) &&
168
+ Number.isFinite(previousSnapshot.scene_package_batch_failure_count)
169
+ )
170
+ ? currentSnapshot.scene_package_batch_failure_count - previousSnapshot.scene_package_batch_failure_count
171
+ : null;
172
+ const deltaMoquiMatrixRegressionCount = (
173
+ Number.isFinite(currentSnapshot.moqui_matrix_regression_count) &&
174
+ Number.isFinite(previousSnapshot.moqui_matrix_regression_count)
175
+ )
176
+ ? currentSnapshot.moqui_matrix_regression_count - previousSnapshot.moqui_matrix_regression_count
177
+ : null;
178
+
179
+ let trend = 'stable';
180
+ if (
181
+ (Number.isFinite(deltaSuccess) && deltaSuccess > 0) &&
182
+ (deltaRiskRank === null || deltaRiskRank <= 0) &&
183
+ (deltaFailedGoals === null || deltaFailedGoals <= 0) &&
184
+ (deltaOntologyQualityScore === null || deltaOntologyQualityScore >= 0) &&
185
+ (deltaOntologyUnmappedRules === null || deltaOntologyUnmappedRules <= 0) &&
186
+ (deltaOntologyUndecidedDecisions === null || deltaOntologyUndecidedDecisions <= 0) &&
187
+ (deltaSceneBatchFailureCount === null || deltaSceneBatchFailureCount <= 0)
188
+ ) {
189
+ trend = 'improved';
190
+ } else if (
191
+ (Number.isFinite(deltaSuccess) && deltaSuccess < 0) ||
192
+ (deltaRiskRank !== null && deltaRiskRank > 0) ||
193
+ (deltaFailedGoals !== null && deltaFailedGoals > 0) ||
194
+ (deltaOntologyQualityScore !== null && deltaOntologyQualityScore < 0) ||
195
+ (deltaOntologyUnmappedRules !== null && deltaOntologyUnmappedRules > 0) ||
196
+ (deltaOntologyUndecidedDecisions !== null && deltaOntologyUndecidedDecisions > 0) ||
197
+ (deltaSceneBatchFailureCount !== null && deltaSceneBatchFailureCount > 0) ||
198
+ (deltaMoquiMatrixRegressionCount !== null && deltaMoquiMatrixRegressionCount > 0)
199
+ ) {
200
+ trend = 'degraded';
201
+ }
202
+
203
+ return {
204
+ trend,
205
+ delta: {
206
+ spec_success_rate_percent: deltaSuccess,
207
+ risk_level_rank: deltaRiskRank,
208
+ failed_goals: deltaFailedGoals,
209
+ elapsed_ms: deltaElapsedMs,
210
+ ontology_quality_score: deltaOntologyQualityScore,
211
+ ontology_unmapped_rules: deltaOntologyUnmappedRules,
212
+ ontology_undecided_decisions: deltaOntologyUndecidedDecisions,
213
+ ontology_business_rule_pass_rate_percent: deltaBusinessRulePassRate,
214
+ ontology_decision_resolved_rate_percent: deltaDecisionResolvedRate,
215
+ moqui_matrix_regression_count: deltaMoquiMatrixRegressionCount,
216
+ scene_package_batch_failure_count: deltaSceneBatchFailureCount
217
+ }
218
+ };
219
+ }
220
+
221
+ function buildAutoHandoffRegressionWindowTrend(series = []) {
222
+ const normalized = Array.isArray(series) ? series.filter(Boolean) : [];
223
+ if (normalized.length < 2) {
224
+ return {
225
+ trend: 'baseline',
226
+ delta: {
227
+ spec_success_rate_percent: null,
228
+ risk_level_rank: null,
229
+ failed_goals: null,
230
+ elapsed_ms: null,
231
+ ontology_quality_score: null,
232
+ ontology_unmapped_rules: null,
233
+ ontology_undecided_decisions: null,
234
+ ontology_business_rule_pass_rate_percent: null,
235
+ ontology_decision_resolved_rate_percent: null,
236
+ moqui_matrix_regression_count: null,
237
+ scene_package_batch_failure_count: null
238
+ },
239
+ has_baseline: false
240
+ };
241
+ }
242
+ const latest = normalized[0];
243
+ const oldest = normalized[normalized.length - 1];
244
+ const comparison = buildAutoHandoffRegressionComparison(latest, oldest);
245
+ return {
246
+ trend: comparison.trend,
247
+ delta: comparison.delta,
248
+ has_baseline: true
249
+ };
250
+ }
251
+
252
+ function buildAutoHandoffRegressionAggregates(series = []) {
253
+ const snapshots = Array.isArray(series) ? series.filter(Boolean) : [];
254
+ const successRates = snapshots
255
+ .map(item => Number(item.spec_success_rate_percent))
256
+ .filter(value => Number.isFinite(value));
257
+ const failedGoals = snapshots
258
+ .map(item => Number(item.failed_goals))
259
+ .filter(value => Number.isFinite(value));
260
+ const ontologyScores = snapshots
261
+ .map(item => Number(item.ontology_quality_score))
262
+ .filter(value => Number.isFinite(value));
263
+ const ontologyUnmappedRules = snapshots
264
+ .map(item => Number(item.ontology_unmapped_rules))
265
+ .filter(value => Number.isFinite(value));
266
+ const ontologyUndecidedDecisions = snapshots
267
+ .map(item => Number(item.ontology_undecided_decisions))
268
+ .filter(value => Number.isFinite(value));
269
+ const rulePassRates = snapshots
270
+ .map(item => Number(item.ontology_business_rule_pass_rate_percent))
271
+ .filter(value => Number.isFinite(value));
272
+ const decisionResolvedRates = snapshots
273
+ .map(item => Number(item.ontology_decision_resolved_rate_percent))
274
+ .filter(value => Number.isFinite(value));
275
+ const sceneBatchFailures = snapshots
276
+ .map(item => Number(item.scene_package_batch_failure_count))
277
+ .filter(value => Number.isFinite(value));
278
+ const moquiMatrixRegressions = snapshots
279
+ .map(item => Number(item.moqui_matrix_regression_count))
280
+ .filter(value => Number.isFinite(value));
281
+ const sceneBatchApplicables = snapshots.filter(item => typeof item.scene_package_batch_passed === 'boolean');
282
+ const sceneBatchPassedCount = sceneBatchApplicables.filter(item => item.scene_package_batch_passed === true).length;
283
+ const sceneBatchFailedCount = sceneBatchApplicables.filter(item => item.scene_package_batch_passed === false).length;
284
+ const riskLevels = {
285
+ low: 0,
286
+ medium: 0,
287
+ high: 0,
288
+ unknown: 0
289
+ };
290
+ snapshots.forEach(item => {
291
+ const risk = `${item && item.risk_level ? item.risk_level : 'unknown'}`.trim().toLowerCase();
292
+ if (Object.prototype.hasOwnProperty.call(riskLevels, risk)) {
293
+ riskLevels[risk] += 1;
294
+ } else {
295
+ riskLevels.unknown += 1;
296
+ }
297
+ });
298
+
299
+ const averageSuccessRate = successRates.length > 0
300
+ ? Number((successRates.reduce((sum, value) => sum + value, 0) / successRates.length).toFixed(2))
301
+ : null;
302
+ const averageFailedGoals = failedGoals.length > 0
303
+ ? Number((failedGoals.reduce((sum, value) => sum + value, 0) / failedGoals.length).toFixed(2))
304
+ : null;
305
+ const averageOntologyScore = ontologyScores.length > 0
306
+ ? Number((ontologyScores.reduce((sum, value) => sum + value, 0) / ontologyScores.length).toFixed(2))
307
+ : null;
308
+ const averageOntologyUnmappedRules = ontologyUnmappedRules.length > 0
309
+ ? Number((ontologyUnmappedRules.reduce((sum, value) => sum + value, 0) / ontologyUnmappedRules.length).toFixed(2))
310
+ : null;
311
+ const averageOntologyUndecidedDecisions = ontologyUndecidedDecisions.length > 0
312
+ ? Number((ontologyUndecidedDecisions.reduce((sum, value) => sum + value, 0) / ontologyUndecidedDecisions.length).toFixed(2))
313
+ : null;
314
+ const averageRulePassRate = rulePassRates.length > 0
315
+ ? Number((rulePassRates.reduce((sum, value) => sum + value, 0) / rulePassRates.length).toFixed(2))
316
+ : null;
317
+ const averageDecisionResolvedRate = decisionResolvedRates.length > 0
318
+ ? Number((decisionResolvedRates.reduce((sum, value) => sum + value, 0) / decisionResolvedRates.length).toFixed(2))
319
+ : null;
320
+ const averageSceneBatchFailures = sceneBatchFailures.length > 0
321
+ ? Number((sceneBatchFailures.reduce((sum, value) => sum + value, 0) / sceneBatchFailures.length).toFixed(2))
322
+ : null;
323
+ const averageMoquiMatrixRegressions = moquiMatrixRegressions.length > 0
324
+ ? Number((moquiMatrixRegressions.reduce((sum, value) => sum + value, 0) / moquiMatrixRegressions.length).toFixed(2))
325
+ : null;
326
+ const sceneBatchPassRate = sceneBatchApplicables.length > 0
327
+ ? Number(((sceneBatchPassedCount / sceneBatchApplicables.length) * 100).toFixed(2))
328
+ : null;
329
+
330
+ return {
331
+ avg_spec_success_rate_percent: averageSuccessRate,
332
+ min_spec_success_rate_percent: successRates.length > 0 ? Math.min(...successRates) : null,
333
+ max_spec_success_rate_percent: successRates.length > 0 ? Math.max(...successRates) : null,
334
+ avg_failed_goals: averageFailedGoals,
335
+ avg_ontology_quality_score: averageOntologyScore,
336
+ min_ontology_quality_score: ontologyScores.length > 0 ? Math.min(...ontologyScores) : null,
337
+ max_ontology_quality_score: ontologyScores.length > 0 ? Math.max(...ontologyScores) : null,
338
+ avg_ontology_unmapped_rules: averageOntologyUnmappedRules,
339
+ max_ontology_unmapped_rules: ontologyUnmappedRules.length > 0 ? Math.max(...ontologyUnmappedRules) : null,
340
+ avg_ontology_undecided_decisions: averageOntologyUndecidedDecisions,
341
+ max_ontology_undecided_decisions: ontologyUndecidedDecisions.length > 0 ? Math.max(...ontologyUndecidedDecisions) : null,
342
+ avg_ontology_business_rule_pass_rate_percent: averageRulePassRate,
343
+ avg_ontology_decision_resolved_rate_percent: averageDecisionResolvedRate,
344
+ scene_package_batch_applicable_count: sceneBatchApplicables.length,
345
+ scene_package_batch_passed_count: sceneBatchPassedCount,
346
+ scene_package_batch_failed_count: sceneBatchFailedCount,
347
+ scene_package_batch_pass_rate_percent: sceneBatchPassRate,
348
+ avg_scene_package_batch_failure_count: averageSceneBatchFailures,
349
+ max_scene_package_batch_failure_count: sceneBatchFailures.length > 0 ? Math.max(...sceneBatchFailures) : null,
350
+ avg_moqui_matrix_regression_count: averageMoquiMatrixRegressions,
351
+ max_moqui_matrix_regression_count: moquiMatrixRegressions.length > 0 ? Math.max(...moquiMatrixRegressions) : null,
352
+ risk_levels: riskLevels
353
+ };
354
+ }
355
+
356
+ function buildAutoHandoffRegressionRiskLayers(series = []) {
357
+ const snapshots = Array.isArray(series) ? series.filter(Boolean) : [];
358
+ const levels = ['low', 'medium', 'high', 'unknown'];
359
+ const result = {};
360
+
361
+ levels.forEach(level => {
362
+ const scoped = snapshots.filter(item => {
363
+ const risk = `${item && item.risk_level ? item.risk_level : 'unknown'}`.trim().toLowerCase();
364
+ return risk === level;
365
+ });
366
+ const successRates = scoped
367
+ .map(item => Number(item.spec_success_rate_percent))
368
+ .filter(value => Number.isFinite(value));
369
+ const failedGoals = scoped
370
+ .map(item => Number(item.failed_goals))
371
+ .filter(value => Number.isFinite(value));
372
+ const ontologyScores = scoped
373
+ .map(item => Number(item.ontology_quality_score))
374
+ .filter(value => Number.isFinite(value));
375
+ const sceneBatchFailures = scoped
376
+ .map(item => Number(item.scene_package_batch_failure_count))
377
+ .filter(value => Number.isFinite(value));
378
+ const moquiMatrixRegressions = scoped
379
+ .map(item => Number(item.moqui_matrix_regression_count))
380
+ .filter(value => Number.isFinite(value));
381
+ const sceneBatchApplicable = scoped.filter(item => typeof item.scene_package_batch_passed === 'boolean');
382
+ const sceneBatchPassed = sceneBatchApplicable.filter(item => item.scene_package_batch_passed === true).length;
383
+
384
+ const avg = values => (
385
+ values.length > 0
386
+ ? Number((values.reduce((sum, value) => sum + value, 0) / values.length).toFixed(2))
387
+ : null
388
+ );
389
+
390
+ result[level] = {
391
+ count: scoped.length,
392
+ sessions: scoped.map(item => item.session_id).filter(Boolean),
393
+ avg_spec_success_rate_percent: avg(successRates),
394
+ max_spec_success_rate_percent: successRates.length > 0 ? Math.max(...successRates) : null,
395
+ min_spec_success_rate_percent: successRates.length > 0 ? Math.min(...successRates) : null,
396
+ avg_failed_goals: avg(failedGoals),
397
+ avg_ontology_quality_score: avg(ontologyScores),
398
+ avg_scene_package_batch_failure_count: avg(sceneBatchFailures),
399
+ avg_moqui_matrix_regression_count: avg(moquiMatrixRegressions),
400
+ max_moqui_matrix_regression_count: moquiMatrixRegressions.length > 0 ? Math.max(...moquiMatrixRegressions) : null,
401
+ scene_package_batch_pass_rate_percent: sceneBatchApplicable.length > 0
402
+ ? Number(((sceneBatchPassed / sceneBatchApplicable.length) * 100).toFixed(2))
403
+ : null
404
+ };
405
+ });
406
+
407
+ return result;
408
+ }
409
+
410
+ function buildAutoHandoffRegressionRecommendations(payload = {}) {
411
+ const recommendations = [];
412
+ const seen = new Set();
413
+ const push = value => {
414
+ const text = `${value || ''}`.trim();
415
+ if (!text || seen.has(text)) {
416
+ return;
417
+ }
418
+ seen.add(text);
419
+ recommendations.push(text);
420
+ };
421
+
422
+ const current = payload.current || {};
423
+ const trend = `${payload.trend || 'stable'}`.trim().toLowerCase();
424
+ const windowTrend = payload.window_trend && payload.window_trend.trend
425
+ ? `${payload.window_trend.trend}`.trim().toLowerCase()
426
+ : trend;
427
+ const currentFailed = Number(current.failed_goals);
428
+ const currentRisk = `${current.risk_level || 'unknown'}`.trim().toLowerCase();
429
+ const ontologyQuality = Number(current.ontology_quality_score);
430
+ const ontologyUnmappedRules = Number(current.ontology_unmapped_rules);
431
+ const ontologyUndecidedDecisions = Number(current.ontology_undecided_decisions);
432
+ const sceneBatchFailureCount = Number(current.scene_package_batch_failure_count);
433
+ const moquiMatrixRegressionCount = Number(current.moqui_matrix_regression_count);
434
+ const sceneBatchPassed = current.scene_package_batch_passed;
435
+
436
+ if (trend === 'degraded' || windowTrend === 'degraded') {
437
+ push(
438
+ `sce auto handoff run --manifest <path> --continue-from ${quoteCliArg(current.session_id || 'latest')} ` +
439
+ '--continue-strategy pending --json'
440
+ );
441
+ } else if (Number.isFinite(currentFailed) && currentFailed > 0) {
442
+ push(
443
+ `sce auto handoff run --manifest <path> --continue-from ${quoteCliArg(current.session_id || 'latest')} ` +
444
+ '--continue-strategy failed-only --json'
445
+ );
446
+ }
447
+
448
+ if (currentRisk === 'high') {
449
+ push('sce auto governance stats --days 14 --json');
450
+ }
451
+
452
+ if (Number.isFinite(ontologyQuality) && ontologyQuality < 80) {
453
+ push('Strengthen ontology quality gate before next run: `--min-ontology-score 80`.');
454
+ }
455
+ if (Number.isFinite(ontologyUnmappedRules) && ontologyUnmappedRules > 0) {
456
+ push('Drive business-rule closure to zero unmapped rules (`--max-unmapped-rules 0`).');
457
+ }
458
+ if (Number.isFinite(ontologyUndecidedDecisions) && ontologyUndecidedDecisions > 0) {
459
+ push('Resolve pending decision logic entries (`--max-undecided-decisions 0`).');
460
+ }
461
+ if (sceneBatchPassed === false || (Number.isFinite(sceneBatchFailureCount) && sceneBatchFailureCount > 0)) {
462
+ push(
463
+ 'Resolve scene package publish-batch gate failures and rerun: ' +
464
+ '`sce scene package-publish-batch --manifest docs/handoffs/handoff-manifest.json --dry-run --json`.'
465
+ );
466
+ }
467
+ if (Number.isFinite(moquiMatrixRegressionCount) && moquiMatrixRegressionCount > 0) {
468
+ push(
469
+ 'Recover Moqui matrix regressions and rerun baseline gate: ' +
470
+ '`sce scene moqui-baseline --include-all --compare-with .sce/reports/release-evidence/moqui-template-baseline.json --json`.'
471
+ );
472
+ for (const line of buildMoquiRegressionRecoverySequenceLines({
473
+ wrapCommands: true,
474
+ withPeriod: true
475
+ })) {
476
+ push(line);
477
+ }
478
+ }
479
+
480
+ if ((payload.window && Number(payload.window.actual) > 0) && (payload.window.requested !== payload.window.actual)) {
481
+ push('Increase regression coverage with `sce auto handoff regression --window 10 --json`.');
482
+ }
483
+
484
+ return recommendations;
485
+ }
486
+
487
+ function formatAutoHandoffRegressionValue(value, fallback = 'n/a') {
488
+ if (value === null || value === undefined) {
489
+ return fallback;
490
+ }
491
+ if (typeof value === 'number' && !Number.isFinite(value)) {
492
+ return fallback;
493
+ }
494
+ return `${value}`;
495
+ }
496
+
497
+ function getAutoHandoffMoquiCoverageMatrix(summary = {}) {
498
+ if (!summary || typeof summary !== 'object') {
499
+ return {};
500
+ }
501
+ return summary.coverage_matrix && typeof summary.coverage_matrix === 'object'
502
+ ? summary.coverage_matrix
503
+ : {};
504
+ }
505
+
506
+ function getAutoHandoffMoquiCoverageMetric(summary = {}, metricName = '', field = 'rate_percent') {
507
+ const matrix = getAutoHandoffMoquiCoverageMatrix(summary);
508
+ const metric = matrix && matrix[metricName] && typeof matrix[metricName] === 'object'
509
+ ? matrix[metricName]
510
+ : {};
511
+ const value = Number(metric[field]);
512
+ return Number.isFinite(value) ? value : null;
513
+ }
514
+
515
+ function formatAutoHandoffMoquiCoverageMetric(summary = {}, metricName = '', field = 'rate_percent', suffix = '') {
516
+ const value = getAutoHandoffMoquiCoverageMetric(summary, metricName, field);
517
+ if (!Number.isFinite(value)) {
518
+ return 'n/a';
519
+ }
520
+ return `${value}${suffix}`;
521
+ }
522
+
523
+ function getAutoHandoffMoquiCoverageDeltaMatrix(compare = {}) {
524
+ if (!compare || typeof compare !== 'object') {
525
+ return {};
526
+ }
527
+ return compare.coverage_matrix_deltas && typeof compare.coverage_matrix_deltas === 'object'
528
+ ? compare.coverage_matrix_deltas
529
+ : {};
530
+ }
531
+
532
+ function getAutoHandoffMoquiCoverageDeltaMetric(compare = {}, metricName = '', field = 'rate_percent') {
533
+ const matrix = getAutoHandoffMoquiCoverageDeltaMatrix(compare);
534
+ const metric = matrix && matrix[metricName] && typeof matrix[metricName] === 'object'
535
+ ? matrix[metricName]
536
+ : {};
537
+ const value = Number(metric[field]);
538
+ return Number.isFinite(value) ? value : null;
539
+ }
540
+
541
+ function formatAutoHandoffMoquiCoverageDeltaMetric(compare = {}, metricName = '', field = 'rate_percent', suffix = '') {
542
+ const value = getAutoHandoffMoquiCoverageDeltaMetric(compare, metricName, field);
543
+ if (!Number.isFinite(value)) {
544
+ return 'n/a';
545
+ }
546
+ return `${value}${suffix}`;
547
+ }
548
+
549
+ function getAutoHandoffMoquiCoverageMetricLabel(metricName = '') {
550
+ const labels = {
551
+ graph_valid: 'graph-valid',
552
+ score_passed: 'score-passed',
553
+ entity_coverage: 'entity-coverage',
554
+ relation_coverage: 'relation-coverage',
555
+ business_rule_coverage: 'business-rule-coverage',
556
+ business_rule_closed: 'business-rule-closed',
557
+ decision_coverage: 'decision-coverage',
558
+ decision_closed: 'decision-closed',
559
+ baseline_passed: 'baseline-passed'
560
+ };
561
+ return labels[metricName] || metricName;
562
+ }
563
+
564
+ function buildAutoHandoffMoquiCoverageRegressions(compare = {}, dependencies = {}) {
565
+ const { normalizeHandoffText } = dependencies;
566
+ const source = compare && typeof compare === 'object' ? compare : {};
567
+ const predefined = Array.isArray(source.coverage_matrix_regressions)
568
+ ? source.coverage_matrix_regressions
569
+ : null;
570
+ if (predefined) {
571
+ const normalized = predefined
572
+ .map(item => {
573
+ const metric = normalizeHandoffText(item && item.metric);
574
+ const deltaRate = Number(item && item.delta_rate_percent);
575
+ if (!metric || !Number.isFinite(deltaRate) || deltaRate >= 0) {
576
+ return null;
577
+ }
578
+ return {
579
+ metric,
580
+ label: normalizeHandoffText(item && item.label) || getAutoHandoffMoquiCoverageMetricLabel(metric),
581
+ delta_rate_percent: Number(deltaRate.toFixed(2))
582
+ };
583
+ })
584
+ .filter(Boolean);
585
+ if (normalized.length > 0) {
586
+ return normalized.sort((a, b) => {
587
+ if (a.delta_rate_percent !== b.delta_rate_percent) {
588
+ return a.delta_rate_percent - b.delta_rate_percent;
589
+ }
590
+ return `${a.metric}`.localeCompare(`${b.metric}`);
591
+ });
592
+ }
593
+ }
594
+
595
+ const deltaMatrix = getAutoHandoffMoquiCoverageDeltaMatrix(source);
596
+ return Object.entries(deltaMatrix)
597
+ .map(([metric, value]) => {
598
+ const deltaRate = Number(value && value.rate_percent);
599
+ if (!Number.isFinite(deltaRate) || deltaRate >= 0) {
600
+ return null;
601
+ }
602
+ return {
603
+ metric,
604
+ label: getAutoHandoffMoquiCoverageMetricLabel(metric),
605
+ delta_rate_percent: Number(deltaRate.toFixed(2))
606
+ };
607
+ })
608
+ .filter(Boolean)
609
+ .sort((a, b) => {
610
+ if (a.delta_rate_percent !== b.delta_rate_percent) {
611
+ return a.delta_rate_percent - b.delta_rate_percent;
612
+ }
613
+ return `${a.metric}`.localeCompare(`${b.metric}`);
614
+ });
615
+ }
616
+
617
+ function formatAutoHandoffMoquiCoverageRegressions(compare = {}, limit = 3) {
618
+ const regressions = buildAutoHandoffMoquiCoverageRegressions(compare);
619
+ if (regressions.length === 0) {
620
+ return 'none';
621
+ }
622
+ const maxItems = Number.isFinite(Number(limit)) && Number(limit) > 0 ? Number(limit) : regressions.length;
623
+ return regressions
624
+ .slice(0, maxItems)
625
+ .map(item => `${item.label}:${item.delta_rate_percent}%`)
626
+ .join(' | ');
627
+ }
628
+
629
+ function renderAutoHandoffRegressionAsciiBar(value, max = 100, width = 20) {
630
+ const parsed = Number(value);
631
+ if (!Number.isFinite(parsed)) {
632
+ return `${'.'.repeat(width)} n/a`;
633
+ }
634
+ const bounded = Math.max(0, Math.min(max, parsed));
635
+ const ratio = max > 0 ? bounded / max : 0;
636
+ const filled = Math.max(0, Math.min(width, Math.round(ratio * width)));
637
+ return `${'#'.repeat(filled)}${'.'.repeat(Math.max(0, width - filled))} ${Number(bounded.toFixed(2))}`;
638
+ }
639
+
640
+ function renderAutoHandoffRegressionMarkdown(payload = {}) {
641
+ const current = payload.current || {};
642
+ const previous = payload.previous || null;
643
+ const window = payload.window || { requested: 2, actual: 0 };
644
+ const delta = payload.delta || {};
645
+ const windowTrend = payload.window_trend || { trend: 'baseline', delta: {} };
646
+ const aggregates = payload.aggregates || {};
647
+ const riskLevels = aggregates.risk_levels || {};
648
+ const recommendations = Array.isArray(payload.recommendations) ? payload.recommendations : [];
649
+ const series = Array.isArray(payload.series) ? payload.series : [];
650
+ const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
651
+ ? payload.risk_layers
652
+ : {};
653
+ const trendSeriesLines = series.length > 0
654
+ ? series.map(item => {
655
+ const sessionId = formatAutoHandoffRegressionValue(item.session_id);
656
+ const generatedAt = formatAutoHandoffRegressionValue(item.generated_at);
657
+ const riskLevel = formatAutoHandoffRegressionValue(item.risk_level);
658
+ const failedGoals = formatAutoHandoffRegressionValue(item.failed_goals);
659
+ const sceneBatch = item.scene_package_batch_passed === null || item.scene_package_batch_passed === undefined
660
+ ? 'n/a'
661
+ : (item.scene_package_batch_passed ? 'pass' : 'fail');
662
+ const successBar = renderAutoHandoffRegressionAsciiBar(item.spec_success_rate_percent, 100, 20);
663
+ const ontologyBar = renderAutoHandoffRegressionAsciiBar(item.ontology_quality_score, 100, 20);
664
+ return `- ${sessionId} | ${generatedAt} | risk=${riskLevel} | failed=${failedGoals} | scene-batch=${sceneBatch} | success=${successBar} | ontology=${ontologyBar}`;
665
+ })
666
+ : ['- None'];
667
+ const riskLayerLines = ['low', 'medium', 'high', 'unknown'].map(level => {
668
+ const scoped = riskLayers[level] && typeof riskLayers[level] === 'object'
669
+ ? riskLayers[level]
670
+ : {};
671
+ return (
672
+ `- ${level}: count=${formatAutoHandoffRegressionValue(scoped.count, '0')}, ` +
673
+ `avg_success=${formatAutoHandoffRegressionValue(scoped.avg_spec_success_rate_percent)}, ` +
674
+ `avg_failed_goals=${formatAutoHandoffRegressionValue(scoped.avg_failed_goals)}, ` +
675
+ `avg_ontology_quality=${formatAutoHandoffRegressionValue(scoped.avg_ontology_quality_score)}, ` +
676
+ `scene_batch_pass_rate=${formatAutoHandoffRegressionValue(scoped.scene_package_batch_pass_rate_percent)}%, ` +
677
+ `avg_moqui_matrix_regressions=${formatAutoHandoffRegressionValue(scoped.avg_moqui_matrix_regression_count, '0')}`
678
+ );
679
+ });
680
+
681
+ const lines = [
682
+ '# Auto Handoff Regression Report',
683
+ '',
684
+ `- Session: ${formatAutoHandoffRegressionValue(current.session_id)}`,
685
+ `- Compared to: ${previous ? formatAutoHandoffRegressionValue(previous.session_id) : 'none'}`,
686
+ `- Trend: ${formatAutoHandoffRegressionValue(payload.trend)}`,
687
+ `- Window: ${formatAutoHandoffRegressionValue(window.actual)}/${formatAutoHandoffRegressionValue(window.requested)}`,
688
+ '',
689
+ '## Point Delta',
690
+ '',
691
+ `- Spec success rate delta: ${formatAutoHandoffRegressionValue(delta.spec_success_rate_percent)}`,
692
+ `- Risk level rank delta: ${formatAutoHandoffRegressionValue(delta.risk_level_rank)}`,
693
+ `- Failed goals delta: ${formatAutoHandoffRegressionValue(delta.failed_goals)}`,
694
+ `- Elapsed ms delta: ${formatAutoHandoffRegressionValue(delta.elapsed_ms)}`,
695
+ `- Ontology quality delta: ${formatAutoHandoffRegressionValue(delta.ontology_quality_score)}`,
696
+ `- Ontology unmapped rules delta: ${formatAutoHandoffRegressionValue(delta.ontology_unmapped_rules)}`,
697
+ `- Ontology undecided decisions delta: ${formatAutoHandoffRegressionValue(delta.ontology_undecided_decisions)}`,
698
+ `- Moqui matrix regression count delta: ${formatAutoHandoffRegressionValue(delta.moqui_matrix_regression_count)}`,
699
+ `- Scene package batch failure count delta: ${formatAutoHandoffRegressionValue(delta.scene_package_batch_failure_count)}`,
700
+ '',
701
+ '## Window Trend',
702
+ '',
703
+ `- Trend: ${formatAutoHandoffRegressionValue(windowTrend.trend)}`,
704
+ `- Success rate delta: ${formatAutoHandoffRegressionValue(windowTrend.delta && windowTrend.delta.spec_success_rate_percent)}`,
705
+ `- Risk level rank delta: ${formatAutoHandoffRegressionValue(windowTrend.delta && windowTrend.delta.risk_level_rank)}`,
706
+ `- Failed goals delta: ${formatAutoHandoffRegressionValue(windowTrend.delta && windowTrend.delta.failed_goals)}`,
707
+ `- Moqui matrix regression count delta: ${formatAutoHandoffRegressionValue(windowTrend.delta && windowTrend.delta.moqui_matrix_regression_count)}`,
708
+ '',
709
+ '## Aggregates',
710
+ '',
711
+ `- Avg spec success rate: ${formatAutoHandoffRegressionValue(aggregates.avg_spec_success_rate_percent)}`,
712
+ `- Min spec success rate: ${formatAutoHandoffRegressionValue(aggregates.min_spec_success_rate_percent)}`,
713
+ `- Max spec success rate: ${formatAutoHandoffRegressionValue(aggregates.max_spec_success_rate_percent)}`,
714
+ `- Avg failed goals: ${formatAutoHandoffRegressionValue(aggregates.avg_failed_goals)}`,
715
+ `- Avg ontology quality score: ${formatAutoHandoffRegressionValue(aggregates.avg_ontology_quality_score)}`,
716
+ `- Min ontology quality score: ${formatAutoHandoffRegressionValue(aggregates.min_ontology_quality_score)}`,
717
+ `- Max ontology quality score: ${formatAutoHandoffRegressionValue(aggregates.max_ontology_quality_score)}`,
718
+ `- Avg ontology unmapped rules: ${formatAutoHandoffRegressionValue(aggregates.avg_ontology_unmapped_rules)}`,
719
+ `- Max ontology unmapped rules: ${formatAutoHandoffRegressionValue(aggregates.max_ontology_unmapped_rules)}`,
720
+ `- Avg ontology undecided decisions: ${formatAutoHandoffRegressionValue(aggregates.avg_ontology_undecided_decisions)}`,
721
+ `- Max ontology undecided decisions: ${formatAutoHandoffRegressionValue(aggregates.max_ontology_undecided_decisions)}`,
722
+ `- Avg business rule pass rate: ${formatAutoHandoffRegressionValue(aggregates.avg_ontology_business_rule_pass_rate_percent)}`,
723
+ `- Avg decision resolved rate: ${formatAutoHandoffRegressionValue(aggregates.avg_ontology_decision_resolved_rate_percent)}`,
724
+ `- Scene package batch pass rate: ${formatAutoHandoffRegressionValue(aggregates.scene_package_batch_pass_rate_percent)}%`,
725
+ `- Scene package batch failed sessions: ${formatAutoHandoffRegressionValue(aggregates.scene_package_batch_failed_count, '0')}`,
726
+ `- Avg scene package batch failure count: ${formatAutoHandoffRegressionValue(aggregates.avg_scene_package_batch_failure_count)}`,
727
+ `- Avg Moqui matrix regression count: ${formatAutoHandoffRegressionValue(aggregates.avg_moqui_matrix_regression_count)}`,
728
+ `- Max Moqui matrix regression count: ${formatAutoHandoffRegressionValue(aggregates.max_moqui_matrix_regression_count)}`,
729
+ `- Risk levels: low=${formatAutoHandoffRegressionValue(riskLevels.low, '0')}, medium=${formatAutoHandoffRegressionValue(riskLevels.medium, '0')}, high=${formatAutoHandoffRegressionValue(riskLevels.high, '0')}, unknown=${formatAutoHandoffRegressionValue(riskLevels.unknown, '0')}`,
730
+ '',
731
+ '## Trend Series',
732
+ '',
733
+ ...trendSeriesLines,
734
+ '',
735
+ '## Risk Layer View',
736
+ '',
737
+ ...riskLayerLines,
738
+ '',
739
+ '## Recommendations'
740
+ ];
741
+
742
+ if (recommendations.length === 0) {
743
+ lines.push('', '- None');
744
+ } else {
745
+ recommendations.forEach(item => {
746
+ lines.push('', `- ${item}`);
747
+ });
748
+ }
749
+
750
+ return `${lines.join('\n')}\n`;
751
+ }
752
+
753
+
754
+ function renderAutoHandoffEvidenceReviewMarkdown(payload = {}) {
755
+ const current = payload.current || {};
756
+ const currentOverview = payload.current_overview || {};
757
+ const gate = currentOverview.gate && typeof currentOverview.gate === 'object'
758
+ ? currentOverview.gate
759
+ : {};
760
+ const gateActual = gate && gate.actual && typeof gate.actual === 'object'
761
+ ? gate.actual
762
+ : {};
763
+ const releaseGatePreflight = currentOverview.release_gate_preflight && typeof currentOverview.release_gate_preflight === 'object'
764
+ ? currentOverview.release_gate_preflight
765
+ : {};
766
+ const failureSummary = currentOverview.failure_summary && typeof currentOverview.failure_summary === 'object'
767
+ ? currentOverview.failure_summary
768
+ : {};
769
+ const currentPolicy = currentOverview.policy && typeof currentOverview.policy === 'object'
770
+ ? currentOverview.policy
771
+ : {};
772
+ const ontology = currentOverview.ontology_validation && typeof currentOverview.ontology_validation === 'object'
773
+ ? currentOverview.ontology_validation
774
+ : {};
775
+ const ontologyMetrics = ontology && ontology.metrics && typeof ontology.metrics === 'object'
776
+ ? ontology.metrics
777
+ : {};
778
+ const regression = currentOverview.regression && typeof currentOverview.regression === 'object'
779
+ ? currentOverview.regression
780
+ : {};
781
+ const moquiBaseline = currentOverview.moqui_baseline && typeof currentOverview.moqui_baseline === 'object'
782
+ ? currentOverview.moqui_baseline
783
+ : {};
784
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
785
+ ? moquiBaseline.summary
786
+ : {};
787
+ const moquiScopeBreakdown = moquiSummary && moquiSummary.scope_breakdown && typeof moquiSummary.scope_breakdown === 'object'
788
+ ? moquiSummary.scope_breakdown
789
+ : {};
790
+ const moquiGapFrequency = Array.isArray(moquiSummary && moquiSummary.gap_frequency)
791
+ ? moquiSummary.gap_frequency
792
+ : [];
793
+ const moquiCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
794
+ ? moquiBaseline.compare
795
+ : {};
796
+ const moquiMatrixRegressions = buildAutoHandoffMoquiCoverageRegressions(moquiCompare);
797
+ const moquiDeltas = moquiCompare && moquiCompare.deltas && typeof moquiCompare.deltas === 'object'
798
+ ? moquiCompare.deltas
799
+ : {};
800
+ const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
801
+ ? moquiCompare.failed_templates
802
+ : {};
803
+ const scenePackageBatch = currentOverview.scene_package_batch && typeof currentOverview.scene_package_batch === 'object'
804
+ ? currentOverview.scene_package_batch
805
+ : {};
806
+ const scenePackageBatchSummary = scenePackageBatch && scenePackageBatch.summary && typeof scenePackageBatch.summary === 'object'
807
+ ? scenePackageBatch.summary
808
+ : {};
809
+ const scenePackageBatchGate = scenePackageBatch && scenePackageBatch.batch_ontology_gate && typeof scenePackageBatch.batch_ontology_gate === 'object'
810
+ ? scenePackageBatch.batch_ontology_gate
811
+ : {};
812
+ const scenePackageBatchFailures = Array.isArray(scenePackageBatchGate.failures)
813
+ ? scenePackageBatchGate.failures
814
+ : [];
815
+ const capabilityCoverage = currentOverview.capability_coverage && typeof currentOverview.capability_coverage === 'object'
816
+ ? currentOverview.capability_coverage
817
+ : {};
818
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
819
+ ? capabilityCoverage.summary
820
+ : {};
821
+ const capabilityCompare = capabilityCoverage && capabilityCoverage.compare && typeof capabilityCoverage.compare === 'object'
822
+ ? capabilityCoverage.compare
823
+ : {};
824
+ const capabilityGaps = Array.isArray(capabilityCoverage && capabilityCoverage.gaps)
825
+ ? capabilityCoverage.gaps
826
+ : [];
827
+ const capabilityNormalization = capabilityCoverage && capabilityCoverage.normalization && typeof capabilityCoverage.normalization === 'object'
828
+ ? capabilityCoverage.normalization
829
+ : {};
830
+ const capabilityWarnings = Array.isArray(capabilityCoverage && capabilityCoverage.warnings)
831
+ ? capabilityCoverage.warnings
832
+ : [];
833
+ const window = payload.window || { requested: 5, actual: 0 };
834
+ const series = Array.isArray(payload.series) ? payload.series : [];
835
+ const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
836
+ ? payload.risk_layers
837
+ : {};
838
+ const recommendations = Array.isArray(payload.recommendations) ? payload.recommendations : [];
839
+ const governanceSnapshot = payload.governance_snapshot && typeof payload.governance_snapshot === 'object'
840
+ ? payload.governance_snapshot
841
+ : null;
842
+ const governanceHealth = governanceSnapshot && governanceSnapshot.health &&
843
+ typeof governanceSnapshot.health === 'object'
844
+ ? governanceSnapshot.health
845
+ : {};
846
+ const governanceReleaseGate = governanceHealth.release_gate && typeof governanceHealth.release_gate === 'object'
847
+ ? governanceHealth.release_gate
848
+ : {};
849
+ const governanceHandoffQuality = governanceHealth.handoff_quality && typeof governanceHealth.handoff_quality === 'object'
850
+ ? governanceHealth.handoff_quality
851
+ : {};
852
+ const trendSeriesLines = series.length > 0
853
+ ? series.map(item => {
854
+ const sessionId = formatAutoHandoffRegressionValue(item.session_id);
855
+ const mergedAt = formatAutoHandoffRegressionValue(item.merged_at || item.generated_at);
856
+ const riskLevel = formatAutoHandoffRegressionValue(item.risk_level);
857
+ const failedGoals = formatAutoHandoffRegressionValue(item.failed_goals);
858
+ const sceneBatch = item.scene_package_batch_passed === null || item.scene_package_batch_passed === undefined
859
+ ? 'n/a'
860
+ : (item.scene_package_batch_passed ? 'pass' : 'fail');
861
+ const successBar = renderAutoHandoffRegressionAsciiBar(item.spec_success_rate_percent, 100, 20);
862
+ const ontologyBar = renderAutoHandoffRegressionAsciiBar(item.ontology_quality_score, 100, 20);
863
+ const capabilityBar = renderAutoHandoffRegressionAsciiBar(item.capability_coverage_percent, 100, 20);
864
+ return (
865
+ `- ${sessionId} | ${mergedAt} | risk=${riskLevel} | failed=${failedGoals} | scene-batch=${sceneBatch} | ` +
866
+ `success=${successBar} | ontology=${ontologyBar} | capability=${capabilityBar}`
867
+ );
868
+ })
869
+ : ['- None'];
870
+ const riskLayerLines = ['low', 'medium', 'high', 'unknown'].map(level => {
871
+ const scoped = riskLayers[level] && typeof riskLayers[level] === 'object'
872
+ ? riskLayers[level]
873
+ : {};
874
+ return (
875
+ `- ${level}: count=${formatAutoHandoffRegressionValue(scoped.count, '0')}, ` +
876
+ `avg_success=${formatAutoHandoffRegressionValue(scoped.avg_spec_success_rate_percent)}, ` +
877
+ `avg_failed_goals=${formatAutoHandoffRegressionValue(scoped.avg_failed_goals)}, ` +
878
+ `avg_ontology_quality=${formatAutoHandoffRegressionValue(scoped.avg_ontology_quality_score)}, ` +
879
+ `scene_batch_pass_rate=${formatAutoHandoffRegressionValue(scoped.scene_package_batch_pass_rate_percent)}%, ` +
880
+ `avg_moqui_matrix_regressions=${formatAutoHandoffRegressionValue(scoped.avg_moqui_matrix_regression_count, '0')}`
881
+ );
882
+ });
883
+
884
+ const lines = [
885
+ '# Auto Handoff Release Evidence Review',
886
+ '',
887
+ `- Evidence file: ${formatAutoHandoffRegressionValue(payload.evidence_file)}`,
888
+ `- Session: ${formatAutoHandoffRegressionValue(current.session_id)}`,
889
+ `- Status: ${formatAutoHandoffRegressionValue(current.status)}`,
890
+ `- Trend: ${formatAutoHandoffRegressionValue(payload.trend)}`,
891
+ `- Window: ${formatAutoHandoffRegressionValue(window.actual)}/${formatAutoHandoffRegressionValue(window.requested)}`,
892
+ '',
893
+ '## Current Gate',
894
+ '',
895
+ `- Passed: ${gate.passed === true ? 'yes' : 'no'}`,
896
+ `- Spec success rate: ${formatAutoHandoffRegressionValue(gateActual.spec_success_rate_percent)}`,
897
+ `- Risk level: ${formatAutoHandoffRegressionValue(gateActual.risk_level)}`,
898
+ `- Ontology quality score: ${formatAutoHandoffRegressionValue(gateActual.ontology_quality_score)}`,
899
+ `- Unmapped business rules: ${formatAutoHandoffRegressionValue(gateActual.ontology_business_rule_unmapped)}`,
900
+ `- Undecided decisions: ${formatAutoHandoffRegressionValue(gateActual.ontology_decision_undecided)}`,
901
+ '',
902
+ '## Current Release Gate Preflight',
903
+ '',
904
+ `- Available: ${releaseGatePreflight.available === true ? 'yes' : 'no'}`,
905
+ `- Blocked: ${releaseGatePreflight.blocked === true ? 'yes' : 'no'}`,
906
+ `- Latest tag: ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_tag)}`,
907
+ `- Latest gate passed: ${releaseGatePreflight.latest_gate_passed === true ? 'yes' : (releaseGatePreflight.latest_gate_passed === false ? 'no' : 'n/a')}`,
908
+ `- Pass rate: ${formatAutoHandoffRegressionValue(releaseGatePreflight.pass_rate_percent)}%`,
909
+ `- Scene batch pass rate: ${formatAutoHandoffRegressionValue(releaseGatePreflight.scene_package_batch_pass_rate_percent)}%`,
910
+ `- Drift alert rate: ${formatAutoHandoffRegressionValue(releaseGatePreflight.drift_alert_rate_percent)}%`,
911
+ `- Drift blocked runs: ${formatAutoHandoffRegressionValue(releaseGatePreflight.drift_blocked_runs)}`,
912
+ `- Runtime block rate (latest/max): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_block_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_block_rate_max_percent)}%`,
913
+ `- Runtime ui-mode violations (latest/total): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_ui_mode_violation_total, '0')}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_total, '0')}`,
914
+ `- Runtime ui-mode violation rate (latest/run-rate/max): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_ui_mode_violation_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_run_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_rate_max_percent)}%`,
915
+ `- Reasons: ${Array.isArray(releaseGatePreflight.reasons) && releaseGatePreflight.reasons.length > 0 ? releaseGatePreflight.reasons.join(' | ') : 'none'}`,
916
+ `- Parse error: ${formatAutoHandoffRegressionValue(releaseGatePreflight.parse_error)}`,
917
+ '',
918
+ '## Current Failure Summary',
919
+ '',
920
+ `- Failed phase: ${formatAutoHandoffRegressionValue(failureSummary.failed_phase && failureSummary.failed_phase.id)}`,
921
+ `- Gate failed: ${failureSummary.gate_failed === true ? 'yes' : 'no'}`,
922
+ `- Release gate preflight blocked: ${failureSummary.release_gate_preflight_blocked === true ? 'yes' : 'no'}`,
923
+ `- Highlights: ${Array.isArray(failureSummary.highlights) && failureSummary.highlights.length > 0 ? failureSummary.highlights.join(' | ') : 'none'}`,
924
+ '',
925
+ '## Current Ontology',
926
+ '',
927
+ `- Status: ${formatAutoHandoffRegressionValue(ontology.status)}`,
928
+ `- Passed: ${ontology.passed === true ? 'yes' : 'no'}`,
929
+ `- Quality score: ${formatAutoHandoffRegressionValue(ontology.quality_score)}`,
930
+ `- Entity total: ${formatAutoHandoffRegressionValue(ontologyMetrics.entity_total)}`,
931
+ `- Relation total: ${formatAutoHandoffRegressionValue(ontologyMetrics.relation_total)}`,
932
+ `- Business rule unmapped: ${formatAutoHandoffRegressionValue(ontologyMetrics.business_rule_unmapped)}`,
933
+ `- Decision undecided: ${formatAutoHandoffRegressionValue(ontologyMetrics.decision_undecided)}`,
934
+ '',
935
+ '## Current Regression',
936
+ '',
937
+ `- Trend: ${formatAutoHandoffRegressionValue(regression.trend)}`,
938
+ `- Delta success rate: ${formatAutoHandoffRegressionValue(regression.delta && regression.delta.spec_success_rate_percent)}`,
939
+ `- Delta risk rank: ${formatAutoHandoffRegressionValue(regression.delta && regression.delta.risk_level_rank)}`,
940
+ `- Delta failed goals: ${formatAutoHandoffRegressionValue(regression.delta && regression.delta.failed_goals)}`,
941
+ '',
942
+ '## Current Moqui Baseline',
943
+ '',
944
+ `- Status: ${formatAutoHandoffRegressionValue(moquiBaseline.status)}`,
945
+ `- Portfolio passed: ${moquiSummary.portfolio_passed === true ? 'yes' : (moquiSummary.portfolio_passed === false ? 'no' : 'n/a')}`,
946
+ `- Avg score: ${formatAutoHandoffRegressionValue(moquiSummary.avg_score)}`,
947
+ `- Valid-rate: ${formatAutoHandoffRegressionValue(moquiSummary.valid_rate_percent)}%`,
948
+ `- Baseline failed templates: ${formatAutoHandoffRegressionValue(moquiSummary.baseline_failed)}`,
949
+ `- Matrix regression count: ${formatAutoHandoffRegressionValue(moquiMatrixRegressions.length, '0')}`,
950
+ `- Matrix regression gate (max): ${formatAutoHandoffRegressionValue(currentPolicy.max_moqui_matrix_regressions)}`,
951
+ `- Scope mix (moqui/suite/other): ${formatAutoHandoffRegressionValue(moquiScopeBreakdown.moqui_erp, '0')}/${formatAutoHandoffRegressionValue(moquiScopeBreakdown.scene_orchestration, '0')}/${formatAutoHandoffRegressionValue(moquiScopeBreakdown.other, '0')}`,
952
+ `- Entity coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'entity_coverage', 'rate_percent', '%')}`,
953
+ `- Relation coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'relation_coverage', 'rate_percent', '%')}`,
954
+ `- Business-rule coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'business_rule_coverage', 'rate_percent', '%')}`,
955
+ `- Business-rule closed: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'business_rule_closed', 'rate_percent', '%')}`,
956
+ `- Decision coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'decision_coverage', 'rate_percent', '%')}`,
957
+ `- Decision closed: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'decision_closed', 'rate_percent', '%')}`,
958
+ `- Delta avg score: ${formatAutoHandoffRegressionValue(moquiDeltas.avg_score)}`,
959
+ `- Delta valid-rate: ${formatAutoHandoffRegressionValue(moquiDeltas.valid_rate_percent)}%`,
960
+ `- Delta entity coverage: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'entity_coverage', 'rate_percent', '%')}`,
961
+ `- Delta business-rule closed: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'business_rule_closed', 'rate_percent', '%')}`,
962
+ `- Delta decision closed: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'decision_closed', 'rate_percent', '%')}`,
963
+ `- Matrix regressions: ${formatAutoHandoffMoquiCoverageRegressions(moquiCompare, 5)}`,
964
+ `- Newly failed templates: ${Array.isArray(moquiFailedTemplates.newly_failed) && moquiFailedTemplates.newly_failed.length > 0 ? moquiFailedTemplates.newly_failed.join(', ') : 'none'}`,
965
+ `- Recovered templates: ${Array.isArray(moquiFailedTemplates.recovered) && moquiFailedTemplates.recovered.length > 0 ? moquiFailedTemplates.recovered.join(', ') : 'none'}`,
966
+ `- Top baseline gaps: ${moquiGapFrequency.length > 0 ? moquiGapFrequency.slice(0, 3).map(item => `${item.gap}:${item.count}`).join(' | ') : 'none'}`,
967
+ `- Baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
968
+ '',
969
+ '## Current Scene Package Batch',
970
+ '',
971
+ `- Status: ${formatAutoHandoffRegressionValue(scenePackageBatch.status)}`,
972
+ `- Generated: ${scenePackageBatch.generated === true ? 'yes' : 'no'}`,
973
+ `- Selected specs: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.selected)}`,
974
+ `- Failed specs: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.failed)}`,
975
+ `- Batch gate passed: ${scenePackageBatchSummary.batch_gate_passed === true ? 'yes' : (scenePackageBatchSummary.batch_gate_passed === false ? 'no' : 'n/a')}`,
976
+ `- Batch gate failure count: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.batch_gate_failure_count)}`,
977
+ `- Ontology average score: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.ontology_average_score)}`,
978
+ `- Ontology valid-rate: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.ontology_valid_rate_percent)}%`,
979
+ `- Batch gate failures: ${scenePackageBatchFailures.length > 0 ? scenePackageBatchFailures.map(item => item && item.message ? item.message : '').filter(Boolean).join(' | ') : 'none'}`,
980
+ `- Scene batch JSON: ${formatAutoHandoffRegressionValue(scenePackageBatch.output && scenePackageBatch.output.json)}`,
981
+ '',
982
+ '## Current Capability Coverage',
983
+ '',
984
+ `- Status: ${formatAutoHandoffRegressionValue(capabilityCoverage.status)}`,
985
+ `- Passed: ${capabilitySummary.passed === true ? 'yes' : (capabilitySummary.passed === false ? 'no' : 'n/a')}`,
986
+ `- Coverage: ${formatAutoHandoffRegressionValue(capabilitySummary.coverage_percent)}%`,
987
+ `- Min required: ${formatAutoHandoffRegressionValue(capabilitySummary.min_required_percent)}%`,
988
+ `- Covered capabilities: ${formatAutoHandoffRegressionValue(capabilitySummary.covered_capabilities)}`,
989
+ `- Uncovered capabilities: ${formatAutoHandoffRegressionValue(capabilitySummary.uncovered_capabilities)}`,
990
+ `- Delta coverage: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_coverage_percent)}%`,
991
+ `- Delta covered capabilities: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_covered_capabilities)}`,
992
+ `- Newly covered: ${Array.isArray(capabilityCompare.newly_covered) && capabilityCompare.newly_covered.length > 0 ? capabilityCompare.newly_covered.join(', ') : 'none'}`,
993
+ `- Newly uncovered: ${Array.isArray(capabilityCompare.newly_uncovered) && capabilityCompare.newly_uncovered.length > 0 ? capabilityCompare.newly_uncovered.join(', ') : 'none'}`,
994
+ `- Lexicon version: ${formatAutoHandoffRegressionValue(capabilityNormalization.lexicon_version)}`,
995
+ `- Expected alias mapped: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.expected_alias_mapped) ? capabilityNormalization.expected_alias_mapped.length : 0)}`,
996
+ `- Expected deprecated alias: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.expected_deprecated_aliases) ? capabilityNormalization.expected_deprecated_aliases.length : 0)}`,
997
+ `- Expected unknown: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.expected_unknown) ? capabilityNormalization.expected_unknown.length : 0)}`,
998
+ `- Provided alias mapped: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.provided_alias_mapped) ? capabilityNormalization.provided_alias_mapped.length : 0)}`,
999
+ `- Provided deprecated alias: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.provided_deprecated_aliases) ? capabilityNormalization.provided_deprecated_aliases.length : 0)}`,
1000
+ `- Provided unknown: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.provided_unknown) ? capabilityNormalization.provided_unknown.length : 0)}`,
1001
+ `- Capability gaps: ${capabilityGaps.length > 0 ? capabilityGaps.join(', ') : 'none'}`,
1002
+ `- Coverage warnings: ${capabilityWarnings.length > 0 ? capabilityWarnings.join(' | ') : 'none'}`,
1003
+ `- Coverage JSON: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.json)}`,
1004
+ '',
1005
+ '## Trend Series',
1006
+ '',
1007
+ ...trendSeriesLines,
1008
+ '',
1009
+ '## Risk Layer View',
1010
+ '',
1011
+ ...riskLayerLines,
1012
+ '',
1013
+ '## Governance Snapshot',
1014
+ '',
1015
+ `- Risk level: ${formatAutoHandoffRegressionValue(governanceHealth.risk_level)}`,
1016
+ `- Concern count: ${formatAutoHandoffRegressionValue(Array.isArray(governanceHealth.concerns) ? governanceHealth.concerns.length : 0, '0')}`,
1017
+ `- Recommendation count: ${formatAutoHandoffRegressionValue(Array.isArray(governanceHealth.recommendations) ? governanceHealth.recommendations.length : 0, '0')}`,
1018
+ `- Release gate available: ${governanceReleaseGate.available === true ? 'yes' : 'no'}`,
1019
+ `- Release gate latest passed: ${governanceReleaseGate.latest_gate_passed === true ? 'yes' : (governanceReleaseGate.latest_gate_passed === false ? 'no' : 'n/a')}`,
1020
+ `- Handoff quality available: ${governanceHandoffQuality.available === true ? 'yes' : 'no'}`,
1021
+ `- Handoff latest status: ${formatAutoHandoffRegressionValue(governanceHandoffQuality.latest_status)}`,
1022
+ `- Handoff latest gate passed: ${governanceHandoffQuality.latest_gate_passed === true ? 'yes' : (governanceHandoffQuality.latest_gate_passed === false ? 'no' : 'n/a')}`,
1023
+ `- Handoff latest ontology score: ${formatAutoHandoffRegressionValue(governanceHandoffQuality.latest_ontology_quality_score)}`,
1024
+ '',
1025
+ '## Recommendations'
1026
+ ];
1027
+
1028
+ if (recommendations.length === 0) {
1029
+ lines.push('', '- None');
1030
+ } else {
1031
+ recommendations.forEach(item => {
1032
+ lines.push('', `- ${item}`);
1033
+ });
1034
+ }
1035
+
1036
+ return `${lines.join('\n')}\n`;
1037
+ }
1038
+
1039
+
1040
+ function renderAutoHandoffReleaseNotesDraft(payload = {}, context = {}, dependencies = {}) {
1041
+ const { normalizeHandoffReleaseVersion, normalizeHandoffReleaseDate } = dependencies;
1042
+ const current = payload.current || {};
1043
+ const currentOverview = payload.current_overview || {};
1044
+ const gate = currentOverview.gate && typeof currentOverview.gate === 'object'
1045
+ ? currentOverview.gate
1046
+ : {};
1047
+ const gateActual = gate && gate.actual && typeof gate.actual === 'object'
1048
+ ? gate.actual
1049
+ : {};
1050
+ const releaseGatePreflight = currentOverview.release_gate_preflight && typeof currentOverview.release_gate_preflight === 'object'
1051
+ ? currentOverview.release_gate_preflight
1052
+ : {};
1053
+ const failureSummary = currentOverview.failure_summary && typeof currentOverview.failure_summary === 'object'
1054
+ ? currentOverview.failure_summary
1055
+ : {};
1056
+ const currentPolicy = currentOverview.policy && typeof currentOverview.policy === 'object'
1057
+ ? currentOverview.policy
1058
+ : {};
1059
+ const ontology = currentOverview.ontology_validation && typeof currentOverview.ontology_validation === 'object'
1060
+ ? currentOverview.ontology_validation
1061
+ : {};
1062
+ const ontologyMetrics = ontology && ontology.metrics && typeof ontology.metrics === 'object'
1063
+ ? ontology.metrics
1064
+ : {};
1065
+ const regression = currentOverview.regression && typeof currentOverview.regression === 'object'
1066
+ ? currentOverview.regression
1067
+ : {};
1068
+ const moquiBaseline = currentOverview.moqui_baseline && typeof currentOverview.moqui_baseline === 'object'
1069
+ ? currentOverview.moqui_baseline
1070
+ : {};
1071
+ const moquiSummary = moquiBaseline && moquiBaseline.summary && typeof moquiBaseline.summary === 'object'
1072
+ ? moquiBaseline.summary
1073
+ : {};
1074
+ const moquiScopeBreakdown = moquiSummary && moquiSummary.scope_breakdown && typeof moquiSummary.scope_breakdown === 'object'
1075
+ ? moquiSummary.scope_breakdown
1076
+ : {};
1077
+ const moquiGapFrequency = Array.isArray(moquiSummary && moquiSummary.gap_frequency)
1078
+ ? moquiSummary.gap_frequency
1079
+ : [];
1080
+ const moquiCompare = moquiBaseline && moquiBaseline.compare && typeof moquiBaseline.compare === 'object'
1081
+ ? moquiBaseline.compare
1082
+ : {};
1083
+ const moquiMatrixRegressions = buildAutoHandoffMoquiCoverageRegressions(moquiCompare);
1084
+ const moquiDeltas = moquiCompare && moquiCompare.deltas && typeof moquiCompare.deltas === 'object'
1085
+ ? moquiCompare.deltas
1086
+ : {};
1087
+ const moquiFailedTemplates = moquiCompare && moquiCompare.failed_templates && typeof moquiCompare.failed_templates === 'object'
1088
+ ? moquiCompare.failed_templates
1089
+ : {};
1090
+ const scenePackageBatch = currentOverview.scene_package_batch && typeof currentOverview.scene_package_batch === 'object'
1091
+ ? currentOverview.scene_package_batch
1092
+ : {};
1093
+ const scenePackageBatchSummary = scenePackageBatch && scenePackageBatch.summary && typeof scenePackageBatch.summary === 'object'
1094
+ ? scenePackageBatch.summary
1095
+ : {};
1096
+ const scenePackageBatchGate = scenePackageBatch && scenePackageBatch.batch_ontology_gate && typeof scenePackageBatch.batch_ontology_gate === 'object'
1097
+ ? scenePackageBatch.batch_ontology_gate
1098
+ : {};
1099
+ const scenePackageBatchFailures = Array.isArray(scenePackageBatchGate.failures)
1100
+ ? scenePackageBatchGate.failures
1101
+ : [];
1102
+ const capabilityCoverage = currentOverview.capability_coverage && typeof currentOverview.capability_coverage === 'object'
1103
+ ? currentOverview.capability_coverage
1104
+ : {};
1105
+ const capabilitySummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
1106
+ ? capabilityCoverage.summary
1107
+ : {};
1108
+ const capabilityCompare = capabilityCoverage && capabilityCoverage.compare && typeof capabilityCoverage.compare === 'object'
1109
+ ? capabilityCoverage.compare
1110
+ : {};
1111
+ const capabilityGaps = Array.isArray(capabilityCoverage && capabilityCoverage.gaps)
1112
+ ? capabilityCoverage.gaps
1113
+ : [];
1114
+ const capabilityNormalization = capabilityCoverage && capabilityCoverage.normalization && typeof capabilityCoverage.normalization === 'object'
1115
+ ? capabilityCoverage.normalization
1116
+ : {};
1117
+ const capabilityWarnings = Array.isArray(capabilityCoverage && capabilityCoverage.warnings)
1118
+ ? capabilityCoverage.warnings
1119
+ : [];
1120
+ const riskLayers = payload.risk_layers && typeof payload.risk_layers === 'object'
1121
+ ? payload.risk_layers
1122
+ : {};
1123
+ const statusCounts = payload.aggregates && payload.aggregates.status_counts
1124
+ ? payload.aggregates.status_counts
1125
+ : {};
1126
+ const recommendations = Array.isArray(payload.recommendations) ? payload.recommendations : [];
1127
+ const governanceSnapshot = payload.governance_snapshot && typeof payload.governance_snapshot === 'object'
1128
+ ? payload.governance_snapshot
1129
+ : null;
1130
+ const governanceHealth = governanceSnapshot && governanceSnapshot.health &&
1131
+ typeof governanceSnapshot.health === 'object'
1132
+ ? governanceSnapshot.health
1133
+ : {};
1134
+ const governanceReleaseGate = governanceHealth.release_gate && typeof governanceHealth.release_gate === 'object'
1135
+ ? governanceHealth.release_gate
1136
+ : {};
1137
+ const governanceHandoffQuality = governanceHealth.handoff_quality && typeof governanceHealth.handoff_quality === 'object'
1138
+ ? governanceHealth.handoff_quality
1139
+ : {};
1140
+ const reviewFile = typeof context.reviewFile === 'string' && context.reviewFile.trim()
1141
+ ? context.reviewFile.trim()
1142
+ : null;
1143
+ const version = normalizeHandoffReleaseVersion(context.version, '0.0.0');
1144
+ const releaseDate = normalizeHandoffReleaseDate(context.releaseDate);
1145
+
1146
+ const riskLines = ['low', 'medium', 'high', 'unknown'].map(level => {
1147
+ const scoped = riskLayers[level] && typeof riskLayers[level] === 'object'
1148
+ ? riskLayers[level]
1149
+ : {};
1150
+ return (
1151
+ `- ${level}: count=${formatAutoHandoffRegressionValue(scoped.count, '0')}, ` +
1152
+ `avg_success=${formatAutoHandoffRegressionValue(scoped.avg_spec_success_rate_percent)}, ` +
1153
+ `avg_failed_goals=${formatAutoHandoffRegressionValue(scoped.avg_failed_goals)}, ` +
1154
+ `avg_ontology_quality=${formatAutoHandoffRegressionValue(scoped.avg_ontology_quality_score)}, ` +
1155
+ `avg_moqui_matrix_regressions=${formatAutoHandoffRegressionValue(scoped.avg_moqui_matrix_regression_count, '0')}`
1156
+ );
1157
+ });
1158
+
1159
+ const lines = [
1160
+ `# Release Notes Draft: ${version}`,
1161
+ '',
1162
+ `Release date: ${releaseDate}`,
1163
+ '',
1164
+ '## Handoff Evidence Summary',
1165
+ '',
1166
+ `- Evidence file: ${formatAutoHandoffRegressionValue(payload.evidence_file)}`,
1167
+ `- Current session: ${formatAutoHandoffRegressionValue(current.session_id)}`,
1168
+ `- Current status: ${formatAutoHandoffRegressionValue(current.status)}`,
1169
+ `- Gate passed: ${gate.passed === true ? 'yes' : 'no'}`,
1170
+ `- Release gate preflight available: ${releaseGatePreflight.available === true ? 'yes' : 'no'}`,
1171
+ `- Release gate preflight blocked: ${releaseGatePreflight.blocked === true ? 'yes' : 'no'}`,
1172
+ `- Release gate preflight hard-gate: ${currentPolicy.require_release_gate_preflight === true ? 'enabled' : 'advisory'}`,
1173
+ `- Release gate preflight reasons: ${Array.isArray(releaseGatePreflight.reasons) && releaseGatePreflight.reasons.length > 0 ? releaseGatePreflight.reasons.join(' | ') : 'none'}`,
1174
+ `- Release gate runtime block rate (latest/max): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_block_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_block_rate_max_percent)}%`,
1175
+ `- Release gate runtime ui-mode violations (latest/total): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_ui_mode_violation_total, '0')}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_total, '0')}`,
1176
+ `- Release gate runtime ui-mode violation rate (latest/run-rate/max): ${formatAutoHandoffRegressionValue(releaseGatePreflight.latest_weekly_ops_runtime_ui_mode_violation_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_run_rate_percent)}/${formatAutoHandoffRegressionValue(releaseGatePreflight.weekly_ops_runtime_ui_mode_violation_rate_max_percent)}%`,
1177
+ `- Failure summary highlights: ${Array.isArray(failureSummary.highlights) && failureSummary.highlights.length > 0 ? failureSummary.highlights.join(' | ') : 'none'}`,
1178
+ `- Spec success rate: ${formatAutoHandoffRegressionValue(gateActual.spec_success_rate_percent)}`,
1179
+ `- Risk level: ${formatAutoHandoffRegressionValue(gateActual.risk_level)}`,
1180
+ `- Ontology quality score: ${formatAutoHandoffRegressionValue(gateActual.ontology_quality_score)}`,
1181
+ `- Ontology unmapped rules: ${formatAutoHandoffRegressionValue(gateActual.ontology_business_rule_unmapped, formatAutoHandoffRegressionValue(ontologyMetrics.business_rule_unmapped))}`,
1182
+ `- Ontology undecided decisions: ${formatAutoHandoffRegressionValue(gateActual.ontology_decision_undecided, formatAutoHandoffRegressionValue(ontologyMetrics.decision_undecided))}`,
1183
+ `- Regression trend: ${formatAutoHandoffRegressionValue(regression.trend, formatAutoHandoffRegressionValue(payload.trend))}`,
1184
+ `- Window trend: ${formatAutoHandoffRegressionValue(payload.window_trend && payload.window_trend.trend)}`,
1185
+ `- Gate pass rate (window): ${formatAutoHandoffRegressionValue(payload.aggregates && payload.aggregates.gate_pass_rate_percent)}%`,
1186
+ `- Moqui baseline portfolio passed: ${moquiSummary.portfolio_passed === true ? 'yes' : (moquiSummary.portfolio_passed === false ? 'no' : 'n/a')}`,
1187
+ `- Moqui baseline avg score: ${formatAutoHandoffRegressionValue(moquiSummary.avg_score)}`,
1188
+ `- Moqui baseline valid-rate: ${formatAutoHandoffRegressionValue(moquiSummary.valid_rate_percent)}%`,
1189
+ `- Moqui baseline failed templates: ${formatAutoHandoffRegressionValue(moquiSummary.baseline_failed)}`,
1190
+ `- Moqui matrix regression count: ${formatAutoHandoffRegressionValue(moquiMatrixRegressions.length, '0')}`,
1191
+ `- Moqui matrix regression gate (max): ${formatAutoHandoffRegressionValue(currentPolicy.max_moqui_matrix_regressions)}`,
1192
+ `- Moqui scope mix (moqui/suite/other): ${formatAutoHandoffRegressionValue(moquiScopeBreakdown.moqui_erp, '0')}/${formatAutoHandoffRegressionValue(moquiScopeBreakdown.scene_orchestration, '0')}/${formatAutoHandoffRegressionValue(moquiScopeBreakdown.other, '0')}`,
1193
+ `- Moqui entity coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'entity_coverage', 'rate_percent', '%')}`,
1194
+ `- Moqui relation coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'relation_coverage', 'rate_percent', '%')}`,
1195
+ `- Moqui business-rule coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'business_rule_coverage', 'rate_percent', '%')}`,
1196
+ `- Moqui business-rule closed: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'business_rule_closed', 'rate_percent', '%')}`,
1197
+ `- Moqui decision coverage: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'decision_coverage', 'rate_percent', '%')}`,
1198
+ `- Moqui decision closed: ${formatAutoHandoffMoquiCoverageMetric(moquiSummary, 'decision_closed', 'rate_percent', '%')}`,
1199
+ `- Moqui baseline avg score delta: ${formatAutoHandoffRegressionValue(moquiDeltas.avg_score)}`,
1200
+ `- Moqui baseline valid-rate delta: ${formatAutoHandoffRegressionValue(moquiDeltas.valid_rate_percent)}%`,
1201
+ `- Moqui entity coverage delta: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'entity_coverage', 'rate_percent', '%')}`,
1202
+ `- Moqui business-rule closed delta: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'business_rule_closed', 'rate_percent', '%')}`,
1203
+ `- Moqui decision closed delta: ${formatAutoHandoffMoquiCoverageDeltaMetric(moquiCompare, 'decision_closed', 'rate_percent', '%')}`,
1204
+ `- Moqui matrix regressions: ${formatAutoHandoffMoquiCoverageRegressions(moquiCompare, 5)}`,
1205
+ `- Moqui newly failed templates: ${Array.isArray(moquiFailedTemplates.newly_failed) && moquiFailedTemplates.newly_failed.length > 0 ? moquiFailedTemplates.newly_failed.join(', ') : 'none'}`,
1206
+ `- Moqui top baseline gaps: ${moquiGapFrequency.length > 0 ? moquiGapFrequency.slice(0, 3).map(item => `${item.gap}:${item.count}`).join(' | ') : 'none'}`,
1207
+ `- Scene package batch status: ${formatAutoHandoffRegressionValue(scenePackageBatch.status)}`,
1208
+ `- Scene package batch selected: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.selected)}`,
1209
+ `- Scene package batch failed: ${formatAutoHandoffRegressionValue(scenePackageBatchSummary.failed)}`,
1210
+ `- Scene package batch gate passed: ${scenePackageBatchSummary.batch_gate_passed === true ? 'yes' : (scenePackageBatchSummary.batch_gate_passed === false ? 'no' : 'n/a')}`,
1211
+ `- Scene package batch gate failures: ${scenePackageBatchFailures.length > 0 ? scenePackageBatchFailures.map(item => item && item.message ? item.message : '').filter(Boolean).join(' | ') : 'none'}`,
1212
+ `- Capability coverage status: ${formatAutoHandoffRegressionValue(capabilityCoverage.status)}`,
1213
+ `- Capability coverage passed: ${capabilitySummary.passed === true ? 'yes' : (capabilitySummary.passed === false ? 'no' : 'n/a')}`,
1214
+ `- Capability coverage: ${formatAutoHandoffRegressionValue(capabilitySummary.coverage_percent)}%`,
1215
+ `- Capability min required: ${formatAutoHandoffRegressionValue(capabilitySummary.min_required_percent)}%`,
1216
+ `- Capability coverage delta: ${formatAutoHandoffRegressionValue(capabilityCompare.delta_coverage_percent)}%`,
1217
+ `- Capability newly uncovered: ${Array.isArray(capabilityCompare.newly_uncovered) && capabilityCompare.newly_uncovered.length > 0 ? capabilityCompare.newly_uncovered.join(', ') : 'none'}`,
1218
+ `- Capability lexicon version: ${formatAutoHandoffRegressionValue(capabilityNormalization.lexicon_version)}`,
1219
+ `- Capability expected alias mapped: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.expected_alias_mapped) ? capabilityNormalization.expected_alias_mapped.length : 0)}`,
1220
+ `- Capability expected deprecated alias: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.expected_deprecated_aliases) ? capabilityNormalization.expected_deprecated_aliases.length : 0)}`,
1221
+ `- Capability provided deprecated alias: ${formatAutoHandoffRegressionValue(Array.isArray(capabilityNormalization.provided_deprecated_aliases) ? capabilityNormalization.provided_deprecated_aliases.length : 0)}`,
1222
+ `- Capability gaps: ${capabilityGaps.length > 0 ? capabilityGaps.join(', ') : 'none'}`,
1223
+ `- Capability warnings: ${capabilityWarnings.length > 0 ? capabilityWarnings.join(' | ') : 'none'}`,
1224
+ '',
1225
+ '## Status Breakdown',
1226
+ '',
1227
+ `- completed: ${formatAutoHandoffRegressionValue(statusCounts.completed, '0')}`,
1228
+ `- failed: ${formatAutoHandoffRegressionValue(statusCounts.failed, '0')}`,
1229
+ `- dry_run: ${formatAutoHandoffRegressionValue(statusCounts.dry_run, '0')}`,
1230
+ `- running: ${formatAutoHandoffRegressionValue(statusCounts.running, '0')}`,
1231
+ `- other: ${formatAutoHandoffRegressionValue(statusCounts.other, '0')}`,
1232
+ '',
1233
+ '## Risk Layer Snapshot',
1234
+ '',
1235
+ ...riskLines,
1236
+ '',
1237
+ '## Governance Snapshot',
1238
+ '',
1239
+ `- Risk level: ${formatAutoHandoffRegressionValue(governanceHealth.risk_level)}`,
1240
+ `- Concern count: ${formatAutoHandoffRegressionValue(Array.isArray(governanceHealth.concerns) ? governanceHealth.concerns.length : 0, '0')}`,
1241
+ `- Recommendation count: ${formatAutoHandoffRegressionValue(Array.isArray(governanceHealth.recommendations) ? governanceHealth.recommendations.length : 0, '0')}`,
1242
+ `- Release gate available: ${governanceReleaseGate.available === true ? 'yes' : 'no'}`,
1243
+ `- Release gate latest passed: ${governanceReleaseGate.latest_gate_passed === true ? 'yes' : (governanceReleaseGate.latest_gate_passed === false ? 'no' : 'n/a')}`,
1244
+ `- Handoff quality available: ${governanceHandoffQuality.available === true ? 'yes' : 'no'}`,
1245
+ `- Handoff latest status: ${formatAutoHandoffRegressionValue(governanceHandoffQuality.latest_status)}`,
1246
+ `- Handoff latest gate passed: ${governanceHandoffQuality.latest_gate_passed === true ? 'yes' : (governanceHandoffQuality.latest_gate_passed === false ? 'no' : 'n/a')}`,
1247
+ `- Handoff latest ontology score: ${formatAutoHandoffRegressionValue(governanceHandoffQuality.latest_ontology_quality_score)}`,
1248
+ '',
1249
+ '## Release Evidence Artifacts',
1250
+ '',
1251
+ `- Evidence review report: ${reviewFile || 'n/a'}`,
1252
+ `- Handoff report: ${formatAutoHandoffRegressionValue(currentOverview.handoff_report_file)}`,
1253
+ `- Release evidence JSON: ${formatAutoHandoffRegressionValue(payload.evidence_file)}`,
1254
+ `- Moqui baseline JSON: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.json)}`,
1255
+ `- Moqui baseline markdown: ${formatAutoHandoffRegressionValue(moquiBaseline.output && moquiBaseline.output.markdown)}`,
1256
+ `- Scene package batch JSON: ${formatAutoHandoffRegressionValue(scenePackageBatch.output && scenePackageBatch.output.json)}`,
1257
+ `- Capability coverage JSON: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.json)}`,
1258
+ `- Capability coverage markdown: ${formatAutoHandoffRegressionValue(capabilityCoverage.output && capabilityCoverage.output.markdown)}`,
1259
+ `- Governance snapshot generated at: ${formatAutoHandoffRegressionValue(governanceSnapshot && governanceSnapshot.generated_at)}`,
1260
+ '',
1261
+ '## Recommendations'
1262
+ ];
1263
+
1264
+ if (recommendations.length === 0) {
1265
+ lines.push('', '- None');
1266
+ } else {
1267
+ recommendations.forEach(item => {
1268
+ lines.push('', `- ${item}`);
1269
+ });
1270
+ }
1271
+
1272
+ return `${lines.join('\n')}\n`;
1273
+ }
1274
+
1275
+
1276
+ async function buildAutoHandoffRegression(projectPath, currentResult, dependencies = {}) {
1277
+ const { listAutoHandoffRunReports, buildAutoHandoffRegressionSnapshot, buildAutoHandoffRegressionComparison } = dependencies;
1278
+ const reports = await listAutoHandoffRunReports(projectPath);
1279
+ const previous = reports.find(item => item.session_id !== currentResult.session_id) || null;
1280
+ const currentSnapshot = buildAutoHandoffRegressionSnapshot(currentResult, dependencies);
1281
+ if (!previous) {
1282
+ return {
1283
+ mode: 'auto-handoff-regression',
1284
+ current: currentSnapshot,
1285
+ previous: null,
1286
+ trend: 'baseline',
1287
+ delta: {
1288
+ spec_success_rate_percent: null,
1289
+ risk_level_rank: null,
1290
+ failed_goals: null,
1291
+ elapsed_ms: null,
1292
+ ontology_quality_score: null,
1293
+ ontology_unmapped_rules: null,
1294
+ ontology_undecided_decisions: null,
1295
+ ontology_business_rule_pass_rate_percent: null,
1296
+ ontology_decision_resolved_rate_percent: null,
1297
+ scene_package_batch_failure_count: null
1298
+ }
1299
+ };
1300
+ }
1301
+
1302
+ const previousSnapshot = buildAutoHandoffRegressionSnapshot(previous, dependencies);
1303
+ const comparison = buildAutoHandoffRegressionComparison(currentSnapshot, previousSnapshot);
1304
+ return {
1305
+ mode: 'auto-handoff-regression',
1306
+ current: currentSnapshot,
1307
+ previous: previousSnapshot,
1308
+ trend: comparison.trend,
1309
+ delta: comparison.delta
1310
+ };
1311
+ }
1312
+
1313
+ async function buildAutoHandoffRegressionReport(projectPath, options = {}, dependencies = {}) {
1314
+ const {
1315
+ listAutoHandoffRunReports,
1316
+ normalizeHandoffSessionQuery,
1317
+ normalizeHandoffRegressionWindow,
1318
+ buildAutoHandoffRegressionSnapshot,
1319
+ buildAutoHandoffRegressionComparison,
1320
+ buildAutoHandoffRegressionWindowTrend,
1321
+ buildAutoHandoffRegressionAggregates,
1322
+ buildAutoHandoffRegressionRiskLayers,
1323
+ buildAutoHandoffRegressionRecommendations
1324
+ } = dependencies;
1325
+ const reports = await listAutoHandoffRunReports(projectPath);
1326
+ if (reports.length === 0) {
1327
+ throw new Error('no handoff run reports found');
1328
+ }
1329
+ const query = normalizeHandoffSessionQuery(options.sessionId);
1330
+ const windowSize = normalizeHandoffRegressionWindow(options.window);
1331
+ let currentIndex = 0;
1332
+ if (query !== 'latest') {
1333
+ currentIndex = reports.findIndex(item => item.session_id === query);
1334
+ if (currentIndex < 0) {
1335
+ throw new Error(`handoff run session not found: ${query}`);
1336
+ }
1337
+ }
1338
+
1339
+ const chainReports = reports.slice(currentIndex, currentIndex + windowSize);
1340
+ const series = chainReports.map(item => buildAutoHandoffRegressionSnapshot(item, dependencies));
1341
+ const currentSnapshot = series[0];
1342
+ const previousSnapshot = series[1] || null;
1343
+ const comparison = previousSnapshot
1344
+ ? buildAutoHandoffRegressionComparison(currentSnapshot, previousSnapshot)
1345
+ : {
1346
+ trend: 'baseline',
1347
+ delta: {
1348
+ spec_success_rate_percent: null,
1349
+ risk_level_rank: null,
1350
+ failed_goals: null,
1351
+ elapsed_ms: null,
1352
+ ontology_quality_score: null,
1353
+ ontology_unmapped_rules: null,
1354
+ ontology_undecided_decisions: null,
1355
+ ontology_business_rule_pass_rate_percent: null,
1356
+ ontology_decision_resolved_rate_percent: null,
1357
+ scene_package_batch_failure_count: null
1358
+ }
1359
+ };
1360
+ const windowTrend = buildAutoHandoffRegressionWindowTrend(series);
1361
+ const aggregates = buildAutoHandoffRegressionAggregates(series);
1362
+ const riskLayers = buildAutoHandoffRegressionRiskLayers(series);
1363
+
1364
+ const payload = {
1365
+ mode: 'auto-handoff-regression',
1366
+ current: currentSnapshot,
1367
+ previous: previousSnapshot,
1368
+ trend: comparison.trend,
1369
+ delta: comparison.delta,
1370
+ window: {
1371
+ requested: windowSize,
1372
+ actual: series.length
1373
+ },
1374
+ series,
1375
+ window_trend: windowTrend,
1376
+ aggregates,
1377
+ risk_layers: riskLayers,
1378
+ recommendations: []
1379
+ };
1380
+ payload.recommendations = buildAutoHandoffRegressionRecommendations(payload);
1381
+ return payload;
1382
+ }
1383
+
1384
+
1385
+ module.exports = {
1386
+ buildAutoHandoffRegressionSnapshot,
1387
+ buildAutoHandoffRegressionComparison,
1388
+ buildAutoHandoffRegressionWindowTrend,
1389
+ buildAutoHandoffRegressionAggregates,
1390
+ buildAutoHandoffRegressionRiskLayers,
1391
+ buildAutoHandoffRegressionRecommendations,
1392
+ formatAutoHandoffRegressionValue,
1393
+ getAutoHandoffMoquiCoverageMatrix,
1394
+ getAutoHandoffMoquiCoverageMetric,
1395
+ formatAutoHandoffMoquiCoverageMetric,
1396
+ getAutoHandoffMoquiCoverageDeltaMatrix,
1397
+ getAutoHandoffMoquiCoverageDeltaMetric,
1398
+ formatAutoHandoffMoquiCoverageDeltaMetric,
1399
+ getAutoHandoffMoquiCoverageMetricLabel,
1400
+ buildAutoHandoffMoquiCoverageRegressions,
1401
+ formatAutoHandoffMoquiCoverageRegressions,
1402
+ renderAutoHandoffRegressionMarkdown,
1403
+ renderAutoHandoffEvidenceReviewMarkdown,
1404
+ renderAutoHandoffReleaseNotesDraft,
1405
+ buildAutoHandoffRegression,
1406
+ buildAutoHandoffRegressionReport
1407
+ };