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,281 @@
1
+ function buildAutoHandoffCapabilityMatrixPolicy(options = {}, dependencies = {}) {
2
+ const {
3
+ resolveAutoHandoffPolicyPreset,
4
+ normalizeHandoffMinCapabilityCoverage,
5
+ resolveAutoHandoffPolicyOptionNumber,
6
+ normalizeHandoffMinCapabilitySemantic,
7
+ resolveAutoHandoffPolicyOptionBoolean
8
+ } = dependencies;
9
+ const { profile, preset } = resolveAutoHandoffPolicyPreset(options.profile, '--profile');
10
+ return {
11
+ profile,
12
+ min_capability_coverage_percent: normalizeHandoffMinCapabilityCoverage(
13
+ resolveAutoHandoffPolicyOptionNumber(
14
+ options.minCapabilityCoverage,
15
+ preset.min_capability_coverage_percent
16
+ )
17
+ ),
18
+ min_capability_semantic_percent: normalizeHandoffMinCapabilitySemantic(
19
+ resolveAutoHandoffPolicyOptionNumber(
20
+ options.minCapabilitySemantic,
21
+ preset.min_capability_semantic_percent
22
+ )
23
+ ),
24
+ require_capability_coverage: resolveAutoHandoffPolicyOptionBoolean(
25
+ options.requireCapabilityCoverage,
26
+ preset.require_capability_coverage
27
+ ),
28
+ require_capability_semantic: resolveAutoHandoffPolicyOptionBoolean(
29
+ options.requireCapabilitySemantic,
30
+ preset.require_capability_semantic
31
+ ),
32
+ require_capability_lexicon: resolveAutoHandoffPolicyOptionBoolean(
33
+ options.requireCapabilityLexicon,
34
+ preset.require_capability_lexicon
35
+ ),
36
+ require_moqui_baseline: resolveAutoHandoffPolicyOptionBoolean(
37
+ options.requireMoquiBaseline,
38
+ preset.require_moqui_baseline
39
+ )
40
+ };
41
+ }
42
+
43
+ function buildAutoHandoffCapabilityMatrixRecommendations(result = {}, dependencies = {}) {
44
+ const {
45
+ normalizeHandoffText,
46
+ quoteCliArg,
47
+ buildAutoHandoffMoquiCoverageRegressions,
48
+ buildMoquiRegressionRecoverySequenceLines,
49
+ clusterRemediationFile,
50
+ baselineJsonFile
51
+ } = dependencies;
52
+
53
+ const recommendations = [];
54
+ const push = value => {
55
+ const text = `${value || ''}`.trim();
56
+ if (!text || recommendations.includes(text)) {
57
+ return;
58
+ }
59
+ recommendations.push(text);
60
+ };
61
+
62
+ const manifestPath = normalizeHandoffText(result && result.manifest_path);
63
+ const manifestCli = manifestPath ? quoteCliArg(manifestPath) : '<path>';
64
+ const templateDiff = result && result.template_diff && typeof result.template_diff === 'object'
65
+ ? result.template_diff
66
+ : {};
67
+ const capabilityCoverage = result && result.capability_coverage && typeof result.capability_coverage === 'object'
68
+ ? result.capability_coverage
69
+ : {};
70
+ const coverageSummary = capabilityCoverage && capabilityCoverage.summary && typeof capabilityCoverage.summary === 'object'
71
+ ? capabilityCoverage.summary
72
+ : {};
73
+ const coverageNormalization = capabilityCoverage && capabilityCoverage.normalization &&
74
+ typeof capabilityCoverage.normalization === 'object'
75
+ ? capabilityCoverage.normalization
76
+ : {};
77
+ const expectedUnknownCount = Array.isArray(coverageNormalization.expected_unknown)
78
+ ? coverageNormalization.expected_unknown.length
79
+ : 0;
80
+ const providedUnknownCount = Array.isArray(coverageNormalization.provided_unknown)
81
+ ? coverageNormalization.provided_unknown.length
82
+ : 0;
83
+ const baseline = result && result.moqui_baseline && typeof result.moqui_baseline === 'object'
84
+ ? result.moqui_baseline
85
+ : {};
86
+ const baselineCompare = baseline && baseline.compare && typeof baseline.compare === 'object'
87
+ ? baseline.compare
88
+ : {};
89
+ const baselineRegressions = buildAutoHandoffMoquiCoverageRegressions(baselineCompare);
90
+
91
+ if (templateDiff.compatibility === 'needs-sync') {
92
+ push(`Sync template library and rerun: sce auto handoff template-diff --manifest ${manifestCli} --json`);
93
+ }
94
+ if (baseline.status === 'error' || (baseline.summary && baseline.summary.portfolio_passed === false)) {
95
+ push('Rebuild Moqui baseline: sce scene moqui-baseline --json');
96
+ }
97
+ if (baselineRegressions.length > 0) {
98
+ push(
99
+ `Recover Moqui matrix regressions: ` +
100
+ `${baselineRegressions.slice(0, 3).map(item => `${item.label}:${item.delta_rate_percent}%`).join(' | ')}`
101
+ );
102
+ for (const line of buildMoquiRegressionRecoverySequenceLines({
103
+ clusterGoalsArg: quoteCliArg(clusterRemediationFile),
104
+ baselineArg: quoteCliArg(baselineJsonFile),
105
+ wrapCommands: false,
106
+ withPeriod: false
107
+ })) {
108
+ push(line);
109
+ }
110
+ }
111
+ if (capabilityCoverage.status === 'skipped') {
112
+ push('Declare `capabilities` in handoff manifest to enable capability matrix coverage gates.');
113
+ }
114
+ if (coverageSummary && coverageSummary.passed === false) {
115
+ push(
116
+ `Close capability gaps with strict gate: ` +
117
+ `sce auto handoff run --manifest ${manifestCli} --min-capability-coverage ${coverageSummary.min_required_percent} --json`
118
+ );
119
+ }
120
+ if (coverageSummary && coverageSummary.semantic_passed === false) {
121
+ push(
122
+ `Backfill capability ontology semantics and rerun matrix: ` +
123
+ `sce scene package-ontology-backfill-batch --manifest ${manifestCli} --json`
124
+ );
125
+ }
126
+ if (expectedUnknownCount > 0 || providedUnknownCount > 0) {
127
+ push(
128
+ `Normalize capability lexicon gaps with strict audit: ` +
129
+ `node scripts/moqui-lexicon-audit.js --manifest ${manifestCli} ` +
130
+ '--template-dir .sce/templates/scene-packages --fail-on-gap --json'
131
+ );
132
+ }
133
+ if (result.remediation_queue && result.remediation_queue.file) {
134
+ push(
135
+ `Replay remediation queue: sce auto close-loop-batch ${quoteCliArg(result.remediation_queue.file)} --format lines --json`
136
+ );
137
+ }
138
+
139
+ return recommendations;
140
+ }
141
+
142
+ async function buildAutoHandoffCapabilityMatrix(projectPath, options = {}, dependencies = {}) {
143
+ const {
144
+ buildAutoHandoffPlan,
145
+ buildAutoHandoffCapabilityMatrixPolicy,
146
+ buildAutoHandoffTemplateDiff,
147
+ buildAutoHandoffMoquiBaselineSnapshot,
148
+ buildAutoHandoffCapabilityCoverageSnapshot,
149
+ evaluateAutoHandoffMoquiBaselineGateReasons,
150
+ evaluateAutoHandoffCapabilityCoverageGateReasons,
151
+ evaluateAutoHandoffCapabilitySemanticGateReasons,
152
+ evaluateAutoHandoffCapabilityLexiconGateReasons,
153
+ normalizeHandoffText,
154
+ maybeWriteAutoHandoffMoquiRemediationQueue,
155
+ buildAutoHandoffCapabilityMatrixRecommendations,
156
+ now = () => new Date().toISOString()
157
+ } = dependencies;
158
+
159
+ const plan = await buildAutoHandoffPlan(projectPath, {
160
+ manifest: options.manifest,
161
+ strict: options.strict,
162
+ strictWarnings: options.strictWarnings
163
+ });
164
+
165
+ const policy = buildAutoHandoffCapabilityMatrixPolicy(options);
166
+
167
+ const [templateDiff, moquiBaseline, capabilityCoverage] = await Promise.all([
168
+ buildAutoHandoffTemplateDiff(projectPath, { handoff: plan.handoff }),
169
+ buildAutoHandoffMoquiBaselineSnapshot(projectPath),
170
+ buildAutoHandoffCapabilityCoverageSnapshot(projectPath, plan.handoff, policy)
171
+ ]);
172
+
173
+ const templateSyncReasons = templateDiff.compatibility === 'ready'
174
+ ? []
175
+ : [`template-sync:${templateDiff.compatibility}`];
176
+ const baselineGateReasons = evaluateAutoHandoffMoquiBaselineGateReasons(
177
+ { require_moqui_baseline: true },
178
+ moquiBaseline
179
+ );
180
+ const capabilityGateReasons = evaluateAutoHandoffCapabilityCoverageGateReasons(
181
+ policy,
182
+ capabilityCoverage
183
+ );
184
+ const semanticGateReasons = evaluateAutoHandoffCapabilitySemanticGateReasons(
185
+ policy,
186
+ capabilityCoverage
187
+ );
188
+ const lexiconGateReasons = evaluateAutoHandoffCapabilityLexiconGateReasons(
189
+ policy,
190
+ capabilityCoverage
191
+ );
192
+ const reasons = [
193
+ ...templateSyncReasons,
194
+ ...baselineGateReasons.map(item => `moqui-baseline:${item}`),
195
+ ...capabilityGateReasons.map(item => `capability-coverage:${item}`),
196
+ ...semanticGateReasons.map(item => `capability-semantic:${item}`),
197
+ ...lexiconGateReasons.map(item => `capability-lexicon:${item}`)
198
+ ];
199
+
200
+ const result = {
201
+ mode: 'auto-handoff-capability-matrix',
202
+ generated_at: now(),
203
+ status: reasons.length === 0 ? 'ready' : 'needs-remediation',
204
+ manifest_path: plan.manifest_path,
205
+ source_project: plan.source_project || null,
206
+ handoff: {
207
+ spec_count: plan.handoff && Number.isFinite(Number(plan.handoff.spec_count))
208
+ ? Number(plan.handoff.spec_count)
209
+ : 0,
210
+ template_count: plan.handoff && Number.isFinite(Number(plan.handoff.template_count))
211
+ ? Number(plan.handoff.template_count)
212
+ : 0,
213
+ capability_count: Array.isArray(plan.handoff && plan.handoff.capabilities)
214
+ ? plan.handoff.capabilities.length
215
+ : 0,
216
+ capability_source: normalizeHandoffText(plan.handoff && plan.handoff.capability_source) || 'manifest',
217
+ capability_inference: plan.handoff && plan.handoff.capability_inference &&
218
+ typeof plan.handoff.capability_inference === 'object'
219
+ ? plan.handoff.capability_inference
220
+ : {
221
+ applied: false,
222
+ inferred_count: 0,
223
+ inferred_capabilities: [],
224
+ inferred_from_templates: [],
225
+ unresolved_template_count: 0,
226
+ unresolved_templates: []
227
+ },
228
+ capabilities: Array.isArray(plan.handoff && plan.handoff.capabilities)
229
+ ? plan.handoff.capabilities
230
+ : []
231
+ },
232
+ policy,
233
+ template_diff: templateDiff,
234
+ moqui_baseline: moquiBaseline,
235
+ capability_coverage: capabilityCoverage,
236
+ gates: {
237
+ passed: reasons.length === 0,
238
+ reasons,
239
+ template_sync: {
240
+ passed: templateSyncReasons.length === 0,
241
+ reasons: templateSyncReasons
242
+ },
243
+ moqui_baseline: {
244
+ passed: baselineGateReasons.length === 0,
245
+ reasons: baselineGateReasons
246
+ },
247
+ capability_coverage: {
248
+ passed: capabilityGateReasons.length === 0,
249
+ reasons: capabilityGateReasons
250
+ },
251
+ capability_semantic: {
252
+ passed: semanticGateReasons.length === 0,
253
+ reasons: semanticGateReasons
254
+ },
255
+ capability_lexicon: {
256
+ passed: lexiconGateReasons.length === 0,
257
+ reasons: lexiconGateReasons
258
+ }
259
+ },
260
+ remediation_queue: null,
261
+ recommendations: []
262
+ };
263
+
264
+ result.remediation_queue = await maybeWriteAutoHandoffMoquiRemediationQueue(
265
+ projectPath,
266
+ {
267
+ moqui_baseline: moquiBaseline,
268
+ moqui_capability_coverage: capabilityCoverage
269
+ },
270
+ options.remediationQueueOut
271
+ );
272
+ result.recommendations = buildAutoHandoffCapabilityMatrixRecommendations(result);
273
+
274
+ return result;
275
+ }
276
+
277
+ module.exports = {
278
+ buildAutoHandoffCapabilityMatrixPolicy,
279
+ buildAutoHandoffCapabilityMatrixRecommendations,
280
+ buildAutoHandoffCapabilityMatrix
281
+ };
@@ -0,0 +1,251 @@
1
+ function buildAutoHandoffEvidenceSnapshot(entry = {}, dependencies = {}) {
2
+ const {
3
+ normalizeHandoffText,
4
+ buildAutoHandoffMoquiCoverageRegressions,
5
+ normalizeRiskRank
6
+ } = dependencies;
7
+ const toNumber = value => {
8
+ const parsed = Number(value);
9
+ return Number.isFinite(parsed) ? parsed : null;
10
+ };
11
+ const gate = entry && typeof entry.gate === 'object' ? entry.gate : {};
12
+ const gateActual = gate && typeof gate.actual === 'object' ? gate.actual : {};
13
+ const ontology = entry && typeof entry.ontology_validation === 'object'
14
+ ? entry.ontology_validation
15
+ : {};
16
+ const ontologyMetrics = ontology && typeof ontology.metrics === 'object'
17
+ ? ontology.metrics
18
+ : {};
19
+ const moquiBaseline = entry && typeof entry.moqui_baseline === 'object'
20
+ ? entry.moqui_baseline
21
+ : {};
22
+ const moquiCompare = moquiBaseline && typeof moquiBaseline.compare === 'object'
23
+ ? moquiBaseline.compare
24
+ : {};
25
+ const moquiMatrixRegressions = buildAutoHandoffMoquiCoverageRegressions(moquiCompare);
26
+ const scenePackageBatch = entry && typeof entry.scene_package_batch === 'object'
27
+ ? entry.scene_package_batch
28
+ : {};
29
+ const scenePackageBatchSummary = scenePackageBatch && typeof scenePackageBatch.summary === 'object'
30
+ ? scenePackageBatch.summary
31
+ : {};
32
+ const sceneBatchStatus = normalizeHandoffText(scenePackageBatch.status);
33
+ const sceneBatchPassed = sceneBatchStatus
34
+ ? (sceneBatchStatus === 'skipped' ? null : sceneBatchStatus === 'passed')
35
+ : null;
36
+ const riskLevel = normalizeHandoffText(
37
+ gateActual.risk_level
38
+ || (entry && entry.regression ? entry.regression.risk_level : null)
39
+ || 'high'
40
+ ) || 'high';
41
+
42
+ return {
43
+ session_id: normalizeHandoffText(entry.session_id),
44
+ status: normalizeHandoffText(entry.status),
45
+ merged_at: normalizeHandoffText(entry.merged_at),
46
+ manifest_path: normalizeHandoffText(entry.manifest_path),
47
+ gate_passed: gate.passed === true,
48
+ spec_success_rate_percent: toNumber(gateActual.spec_success_rate_percent),
49
+ risk_level: `${riskLevel}`.trim().toLowerCase(),
50
+ risk_level_rank: normalizeRiskRank(riskLevel),
51
+ failed_goals: toNumber(entry && entry.batch_summary ? entry.batch_summary.failed_goals : null),
52
+ elapsed_ms: null,
53
+ ontology_quality_score: toNumber(
54
+ gateActual.ontology_quality_score !== undefined
55
+ ? gateActual.ontology_quality_score
56
+ : ontology.quality_score
57
+ ),
58
+ ontology_unmapped_rules: toNumber(
59
+ gateActual.ontology_business_rule_unmapped !== undefined
60
+ ? gateActual.ontology_business_rule_unmapped
61
+ : ontologyMetrics.business_rule_unmapped
62
+ ),
63
+ ontology_undecided_decisions: toNumber(
64
+ gateActual.ontology_decision_undecided !== undefined
65
+ ? gateActual.ontology_decision_undecided
66
+ : ontologyMetrics.decision_undecided
67
+ ),
68
+ ontology_business_rule_pass_rate_percent: toNumber(ontologyMetrics.business_rule_pass_rate_percent),
69
+ ontology_decision_resolved_rate_percent: toNumber(ontologyMetrics.decision_resolved_rate_percent),
70
+ scene_package_batch_status: sceneBatchStatus,
71
+ scene_package_batch_passed: typeof sceneBatchPassed === 'boolean' ? sceneBatchPassed : null,
72
+ scene_package_batch_failure_count: toNumber(
73
+ scenePackageBatchSummary.batch_gate_failure_count !== undefined
74
+ ? scenePackageBatchSummary.batch_gate_failure_count
75
+ : scenePackageBatchSummary.failed
76
+ ),
77
+ capability_coverage_percent: toNumber(
78
+ entry &&
79
+ entry.capability_coverage &&
80
+ entry.capability_coverage.summary
81
+ ? entry.capability_coverage.summary.coverage_percent
82
+ : null
83
+ ),
84
+ capability_coverage_passed: Boolean(
85
+ entry &&
86
+ entry.capability_coverage &&
87
+ entry.capability_coverage.summary &&
88
+ entry.capability_coverage.summary.passed === true
89
+ ),
90
+ moqui_matrix_regression_count: moquiMatrixRegressions.length,
91
+ generated_at: normalizeHandoffText(entry.merged_at)
92
+ };
93
+ }
94
+
95
+ function buildAutoHandoffEvidenceStatusCounts(entries = []) {
96
+ const counts = {
97
+ completed: 0,
98
+ failed: 0,
99
+ dry_run: 0,
100
+ running: 0,
101
+ other: 0
102
+ };
103
+ entries.forEach(entry => {
104
+ const status = `${entry && entry.status ? entry.status : ''}`.trim().toLowerCase();
105
+ if (status === 'completed') {
106
+ counts.completed += 1;
107
+ } else if (status === 'failed') {
108
+ counts.failed += 1;
109
+ } else if (status === 'dry-run' || status === 'dry_run') {
110
+ counts.dry_run += 1;
111
+ } else if (status === 'running') {
112
+ counts.running += 1;
113
+ } else {
114
+ counts.other += 1;
115
+ }
116
+ });
117
+ return counts;
118
+ }
119
+
120
+ async function resolveAutoHandoffReleaseDraftContext(projectPath, options = {}, dependencies = {}) {
121
+ const { fs, pathModule } = dependencies;
122
+ let packageVersion = null;
123
+ try {
124
+ const packagePayload = await fs.readJson(pathModule.join(projectPath, 'package.json'));
125
+ if (packagePayload && typeof packagePayload.version === 'string' && packagePayload.version.trim()) {
126
+ packageVersion = packagePayload.version.trim();
127
+ }
128
+ } catch (_error) {
129
+ packageVersion = null;
130
+ }
131
+ return {
132
+ version: dependencies.normalizeHandoffReleaseVersion(options.releaseVersion, packageVersion || '0.0.0'),
133
+ releaseDate: dependencies.normalizeHandoffReleaseDate(options.releaseDate)
134
+ };
135
+ }
136
+
137
+ async function buildAutoHandoffEvidenceReviewReport(projectPath, options = {}, dependencies = {}) {
138
+ const {
139
+ loadAutoHandoffReleaseEvidence,
140
+ normalizeHandoffSessionQuery,
141
+ normalizeHandoffEvidenceWindow,
142
+ normalizeHandoffText,
143
+ buildAutoHandoffEvidenceSnapshot,
144
+ buildAutoHandoffRegressionComparison,
145
+ buildAutoHandoffRegressionWindowTrend,
146
+ buildAutoHandoffRegressionAggregates,
147
+ buildAutoHandoffRegressionRiskLayers,
148
+ buildAutoHandoffEvidenceStatusCounts,
149
+ buildAutoGovernanceStats,
150
+ buildAutoHandoffRegressionRecommendations,
151
+ now = () => new Date().toISOString()
152
+ } = dependencies;
153
+
154
+ const releaseEvidence = await loadAutoHandoffReleaseEvidence(projectPath, options.file);
155
+ if (releaseEvidence.sessions.length === 0) {
156
+ throw new Error(`no release evidence sessions found: ${releaseEvidence.file}`);
157
+ }
158
+
159
+ const query = normalizeHandoffSessionQuery(options.sessionId);
160
+ const windowSize = normalizeHandoffEvidenceWindow(options.window);
161
+ let currentIndex = 0;
162
+ if (query !== 'latest') {
163
+ currentIndex = releaseEvidence.sessions.findIndex(item => normalizeHandoffText(item.session_id) === query);
164
+ if (currentIndex < 0) {
165
+ throw new Error(`release evidence session not found: ${query}`);
166
+ }
167
+ }
168
+
169
+ const selectedEntries = releaseEvidence.sessions.slice(currentIndex, currentIndex + windowSize);
170
+ const series = selectedEntries.map(item => buildAutoHandoffEvidenceSnapshot(item));
171
+ const currentSnapshot = series[0];
172
+ const previousSnapshot = series[1] || null;
173
+ const comparison = previousSnapshot
174
+ ? buildAutoHandoffRegressionComparison(currentSnapshot, previousSnapshot)
175
+ : {
176
+ trend: 'baseline',
177
+ delta: {
178
+ spec_success_rate_percent: null,
179
+ risk_level_rank: null,
180
+ failed_goals: null,
181
+ elapsed_ms: null,
182
+ ontology_quality_score: null,
183
+ ontology_unmapped_rules: null,
184
+ ontology_undecided_decisions: null,
185
+ ontology_business_rule_pass_rate_percent: null,
186
+ ontology_decision_resolved_rate_percent: null,
187
+ scene_package_batch_failure_count: null
188
+ }
189
+ };
190
+ const windowTrend = buildAutoHandoffRegressionWindowTrend(series);
191
+ const aggregates = buildAutoHandoffRegressionAggregates(series);
192
+ const riskLayers = buildAutoHandoffRegressionRiskLayers(series);
193
+ const statusCounts = buildAutoHandoffEvidenceStatusCounts(selectedEntries);
194
+ const gatePassCount = selectedEntries.filter(item => item && item.gate && item.gate.passed === true).length;
195
+ const gatePassRate = selectedEntries.length > 0
196
+ ? Number(((gatePassCount / selectedEntries.length) * 100).toFixed(2))
197
+ : null;
198
+ let governanceSnapshot = null;
199
+ try {
200
+ const governanceStats = await buildAutoGovernanceStats(projectPath, {});
201
+ governanceSnapshot = {
202
+ mode: governanceStats && governanceStats.mode ? governanceStats.mode : 'auto-governance-stats',
203
+ generated_at: governanceStats && governanceStats.generated_at ? governanceStats.generated_at : now(),
204
+ criteria: governanceStats && governanceStats.criteria ? governanceStats.criteria : null,
205
+ totals: governanceStats && governanceStats.totals ? governanceStats.totals : null,
206
+ health: governanceStats && governanceStats.health ? governanceStats.health : null
207
+ };
208
+ } catch (error) {
209
+ governanceSnapshot = {
210
+ mode: 'auto-governance-stats',
211
+ generated_at: now(),
212
+ error: error.message
213
+ };
214
+ }
215
+
216
+ const payload = {
217
+ mode: 'auto-handoff-evidence-review',
218
+ generated_at: now(),
219
+ evidence_file: releaseEvidence.file,
220
+ release_evidence_updated_at: normalizeHandoffText(releaseEvidence.payload.updated_at),
221
+ session_query: query,
222
+ current: currentSnapshot,
223
+ current_overview: selectedEntries[0] || null,
224
+ previous: previousSnapshot,
225
+ trend: comparison.trend,
226
+ delta: comparison.delta,
227
+ window: {
228
+ requested: windowSize,
229
+ actual: series.length
230
+ },
231
+ series,
232
+ window_trend: windowTrend,
233
+ aggregates: {
234
+ ...aggregates,
235
+ status_counts: statusCounts,
236
+ gate_pass_rate_percent: gatePassRate
237
+ },
238
+ risk_layers: riskLayers,
239
+ governance_snapshot: governanceSnapshot,
240
+ recommendations: []
241
+ };
242
+ payload.recommendations = buildAutoHandoffRegressionRecommendations(payload);
243
+ return payload;
244
+ }
245
+
246
+ module.exports = {
247
+ buildAutoHandoffEvidenceSnapshot,
248
+ buildAutoHandoffEvidenceStatusCounts,
249
+ resolveAutoHandoffReleaseDraftContext,
250
+ buildAutoHandoffEvidenceReviewReport
251
+ };