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,132 @@
1
+ async function buildAutoObservabilitySnapshot(projectPath, options = {}, dependencies = {}) {
2
+ const {
3
+ normalizeStatsWindowDays,
4
+ normalizeStatusFilter,
5
+ statsCloseLoopSessions,
6
+ statsCloseLoopBatchSummarySessions,
7
+ statsCloseLoopControllerSessions,
8
+ statsGovernanceCloseLoopSessions,
9
+ buildAutoGovernanceStats,
10
+ buildAutoKpiTrend,
11
+ calculatePercent,
12
+ schemaVersion,
13
+ now = () => new Date().toISOString()
14
+ } = dependencies;
15
+
16
+ const days = normalizeStatsWindowDays(options.days);
17
+ const statusFilter = normalizeStatusFilter(options.status);
18
+ const normalizedStatsOptions = {
19
+ days,
20
+ status: statusFilter.length > 0 ? statusFilter.join(',') : undefined
21
+ };
22
+ const normalizedTrendOptions = {
23
+ weeks: options.weeks,
24
+ mode: options.trendMode,
25
+ period: options.trendPeriod
26
+ };
27
+
28
+ const [
29
+ sessionStats,
30
+ batchStats,
31
+ controllerStats,
32
+ governanceSessionStats,
33
+ governanceHealth,
34
+ trend
35
+ ] = await Promise.all([
36
+ statsCloseLoopSessions(projectPath, normalizedStatsOptions),
37
+ statsCloseLoopBatchSummarySessions(projectPath, normalizedStatsOptions),
38
+ statsCloseLoopControllerSessions(projectPath, normalizedStatsOptions),
39
+ statsGovernanceCloseLoopSessions(projectPath, normalizedStatsOptions),
40
+ buildAutoGovernanceStats(projectPath, normalizedStatsOptions),
41
+ buildAutoKpiTrend(projectPath, normalizedTrendOptions)
42
+ ]);
43
+
44
+ const totalSessions =
45
+ (Number(sessionStats.total_sessions) || 0) +
46
+ (Number(batchStats.total_sessions) || 0) +
47
+ (Number(controllerStats.total_sessions) || 0) +
48
+ (Number(governanceSessionStats.total_sessions) || 0);
49
+ const completedSessions =
50
+ (Number(sessionStats.completed_sessions) || 0) +
51
+ (Number(batchStats.completed_sessions) || 0) +
52
+ (Number(controllerStats.completed_sessions) || 0) +
53
+ (Number(governanceSessionStats.completed_sessions) || 0);
54
+ const failedSessions =
55
+ (Number(sessionStats.failed_sessions) || 0) +
56
+ (Number(batchStats.failed_sessions) || 0) +
57
+ (Number(controllerStats.failed_sessions) || 0) +
58
+ (Number(governanceSessionStats.failed_sessions) || 0);
59
+ const governanceWeeklyOpsStop = governanceSessionStats &&
60
+ governanceSessionStats.release_gate &&
61
+ governanceSessionStats.release_gate.weekly_ops_stop &&
62
+ typeof governanceSessionStats.release_gate.weekly_ops_stop === 'object'
63
+ ? governanceSessionStats.release_gate.weekly_ops_stop
64
+ : null;
65
+
66
+ return {
67
+ mode: 'auto-observability-snapshot',
68
+ generated_at: now(),
69
+ schema_version: schemaVersion,
70
+ criteria: {
71
+ days,
72
+ status_filter: statusFilter,
73
+ trend_weeks: trend.weeks,
74
+ trend_mode: trend.mode,
75
+ trend_period: trend.period_unit
76
+ },
77
+ highlights: {
78
+ total_sessions: totalSessions,
79
+ completed_sessions: completedSessions,
80
+ failed_sessions: failedSessions,
81
+ completion_rate_percent: calculatePercent(completedSessions, totalSessions),
82
+ failure_rate_percent: calculatePercent(failedSessions, totalSessions),
83
+ governance_risk_level: governanceHealth && governanceHealth.health
84
+ ? governanceHealth.health.risk_level
85
+ : 'unknown',
86
+ governance_weekly_ops_stop_sessions: Number(
87
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.sessions
88
+ ) || 0,
89
+ governance_weekly_ops_stop_session_rate_percent: Number(
90
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.session_rate_percent
91
+ ) || 0,
92
+ governance_weekly_ops_high_pressure_sessions: Number(
93
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.high_pressure_sessions
94
+ ) || 0,
95
+ governance_weekly_ops_high_pressure_rate_percent: Number(
96
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.high_pressure_session_rate_percent
97
+ ) || 0,
98
+ governance_weekly_ops_config_warning_positive_sessions: Number(
99
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.config_warning_positive_sessions
100
+ ) || 0,
101
+ governance_weekly_ops_auth_tier_pressure_sessions: Number(
102
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.auth_tier_pressure_sessions
103
+ ) || 0,
104
+ governance_weekly_ops_dialogue_authorization_pressure_sessions: Number(
105
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.dialogue_authorization_pressure_sessions
106
+ ) || 0,
107
+ governance_weekly_ops_runtime_block_rate_high_sessions: Number(
108
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.runtime_block_rate_high_sessions
109
+ ) || 0,
110
+ governance_weekly_ops_runtime_ui_mode_violation_high_sessions: Number(
111
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.runtime_ui_mode_violation_high_sessions
112
+ ) || 0,
113
+ governance_weekly_ops_runtime_ui_mode_violation_total_sum: Number(
114
+ governanceWeeklyOpsStop && governanceWeeklyOpsStop.runtime_ui_mode_violation_total_sum
115
+ ) || 0,
116
+ kpi_anomaly_count: Array.isArray(trend.anomalies) ? trend.anomalies.length : 0
117
+ },
118
+ snapshots: {
119
+ close_loop_session: sessionStats,
120
+ batch_session: batchStats,
121
+ controller_session: controllerStats,
122
+ governance_session: governanceSessionStats,
123
+ governance_weekly_ops_stop: governanceWeeklyOpsStop,
124
+ governance_health: governanceHealth,
125
+ kpi_trend: trend
126
+ }
127
+ };
128
+ }
129
+
130
+ module.exports = {
131
+ buildAutoObservabilitySnapshot
132
+ };
@@ -0,0 +1,34 @@
1
+ async function maybeWriteOutput(result, outCandidate, projectPath, dependencies = {}) {
2
+ const pathModule = dependencies.pathModule;
3
+ const fs = dependencies.fs;
4
+ if (!outCandidate) {
5
+ return;
6
+ }
7
+
8
+ const outputPath = pathModule.isAbsolute(outCandidate)
9
+ ? outCandidate
10
+ : pathModule.join(projectPath, outCandidate);
11
+ await fs.ensureDir(pathModule.dirname(outputPath));
12
+ await fs.writeJson(outputPath, result, { spaces: 2 });
13
+ result.output_file = outputPath;
14
+ }
15
+
16
+ async function maybeWriteTextOutput(result, content, outCandidate, projectPath, dependencies = {}) {
17
+ const pathModule = dependencies.pathModule;
18
+ const fs = dependencies.fs;
19
+ if (!outCandidate) {
20
+ return;
21
+ }
22
+
23
+ const outputPath = pathModule.isAbsolute(outCandidate)
24
+ ? outCandidate
25
+ : pathModule.join(projectPath, outCandidate);
26
+ await fs.ensureDir(pathModule.dirname(outputPath));
27
+ await fs.writeFile(outputPath, content, 'utf8');
28
+ result.output_file = outputPath;
29
+ }
30
+
31
+ module.exports = {
32
+ maybeWriteOutput,
33
+ maybeWriteTextOutput
34
+ };
@@ -0,0 +1,130 @@
1
+ async function applyProgramGateAutoRemediation(summary, context = {}, dependencies = {}) {
2
+ const {
3
+ collectSpecNamesFromBatchSummary,
4
+ pruneSpecSessions,
5
+ readSpecSessionEntries,
6
+ now = () => new Date().toISOString(),
7
+ cwd = () => process.cwd()
8
+ } = dependencies;
9
+
10
+ const projectPath = context && context.projectPath ? context.projectPath : cwd();
11
+ const options = context && context.options && typeof context.options === 'object'
12
+ ? context.options
13
+ : {};
14
+ const gate = summary && summary.program_gate && typeof summary.program_gate === 'object'
15
+ ? summary.program_gate
16
+ : null;
17
+ const policy = gate && gate.policy && typeof gate.policy === 'object' ? gate.policy : {};
18
+ const reasons = Array.isArray(gate && gate.reasons) ? gate.reasons : [];
19
+ const actions = [];
20
+ const nextRunPatch = {};
21
+
22
+ const maxAgentBudget = Number(policy.max_agent_budget);
23
+ if (
24
+ reasons.some(reason => `${reason || ''}`.includes('agent_budget')) &&
25
+ Number.isFinite(maxAgentBudget) &&
26
+ maxAgentBudget > 0
27
+ ) {
28
+ const currentAgentBudget = Number(options.batchAgentBudget || (summary && summary.batch_parallel) || 0);
29
+ nextRunPatch.batchAgentBudget = maxAgentBudget;
30
+ nextRunPatch.batchParallel = Math.max(1, Math.min(currentAgentBudget || maxAgentBudget, maxAgentBudget));
31
+ actions.push({
32
+ type: 'reduce-agent-budget',
33
+ applied: true,
34
+ details: `Set batchAgentBudget=${nextRunPatch.batchAgentBudget}, batchParallel=${nextRunPatch.batchParallel}.`
35
+ });
36
+ }
37
+
38
+ const maxTotalSubSpecs = Number(policy.max_total_sub_specs);
39
+ if (
40
+ reasons.some(reason => `${reason || ''}`.includes('total_sub_specs')) &&
41
+ Number.isFinite(maxTotalSubSpecs) &&
42
+ maxTotalSubSpecs > 0
43
+ ) {
44
+ const avgSubSpecs = Number(summary && summary.metrics && summary.metrics.average_sub_specs_per_goal) || 1;
45
+ const totalGoals = Number(summary && summary.total_goals) || 2;
46
+ const suggestedProgramGoals = Math.max(2, Math.min(totalGoals, Math.floor(maxTotalSubSpecs / Math.max(1, avgSubSpecs))));
47
+ nextRunPatch.programGoals = suggestedProgramGoals;
48
+ actions.push({
49
+ type: 'shrink-goal-width',
50
+ applied: true,
51
+ details: `Set programGoals=${suggestedProgramGoals} using max_total_sub_specs=${maxTotalSubSpecs}.`
52
+ });
53
+ }
54
+
55
+ const maxElapsedMinutes = Number(policy.max_elapsed_minutes);
56
+ if (
57
+ reasons.some(reason => `${reason || ''}`.includes('program_elapsed_minutes')) &&
58
+ Number.isFinite(maxElapsedMinutes) &&
59
+ maxElapsedMinutes > 0
60
+ ) {
61
+ const totalGoals = Number(summary && summary.total_goals) || 2;
62
+ const reducedProgramGoals = Math.max(2, Math.min(totalGoals, Math.ceil(totalGoals * 0.8)));
63
+ nextRunPatch.programGoals = Math.min(
64
+ Number(nextRunPatch.programGoals) || reducedProgramGoals,
65
+ reducedProgramGoals
66
+ );
67
+ nextRunPatch.batchRetryRounds = 0;
68
+ actions.push({
69
+ type: 'time-budget-constrain',
70
+ applied: true,
71
+ details: `Set programGoals=${nextRunPatch.programGoals}, batchRetryRounds=0 for elapsed budget ${maxElapsedMinutes}m.`
72
+ });
73
+ }
74
+
75
+ let appliedSpecPrune = null;
76
+ const specBudget = summary && summary.spec_session_budget && summary.spec_session_budget.enabled
77
+ ? summary.spec_session_budget
78
+ : null;
79
+ if (specBudget && specBudget.over_limit_after && Number.isFinite(Number(specBudget.max_total))) {
80
+ try {
81
+ const currentRunSpecNames = collectSpecNamesFromBatchSummary(summary || {});
82
+ appliedSpecPrune = await pruneSpecSessions(projectPath, {
83
+ keep: Number(specBudget.max_total),
84
+ olderThanDays: null,
85
+ dryRun: false,
86
+ protectActive: true,
87
+ protectWindowDays: options.specSessionProtectWindowDays,
88
+ additionalProtectedSpecs: currentRunSpecNames
89
+ });
90
+ summary.spec_session_auto_prune = appliedSpecPrune;
91
+ const specsAfter = await readSpecSessionEntries(projectPath);
92
+ const totalAfter = specsAfter.length;
93
+ const prunedCount = Number(appliedSpecPrune && appliedSpecPrune.deleted_count) || 0;
94
+ summary.spec_session_budget = {
95
+ ...specBudget,
96
+ total_after: totalAfter,
97
+ pruned_count: (Number(specBudget.pruned_count) || 0) + prunedCount,
98
+ estimated_created: Math.max(0, totalAfter + ((Number(specBudget.pruned_count) || 0) + prunedCount) - specBudget.total_before),
99
+ over_limit_after: totalAfter > specBudget.max_total,
100
+ hard_fail_triggered: Boolean(specBudget.hard_fail && totalAfter > specBudget.max_total)
101
+ };
102
+ actions.push({
103
+ type: 'trigger-spec-prune',
104
+ applied: true,
105
+ details: `Pruned specs to enforce max_total=${specBudget.max_total}. deleted=${appliedSpecPrune.deleted_count}`
106
+ });
107
+ } catch (error) {
108
+ actions.push({
109
+ type: 'trigger-spec-prune',
110
+ applied: false,
111
+ error: error.message
112
+ });
113
+ }
114
+ }
115
+
116
+ const hasPatch = Object.keys(nextRunPatch).length > 0;
117
+ return {
118
+ enabled: true,
119
+ attempted_at: now(),
120
+ reason_count: reasons.length,
121
+ reasons,
122
+ actions,
123
+ next_run_patch: hasPatch ? nextRunPatch : null,
124
+ applied_spec_prune: appliedSpecPrune
125
+ };
126
+ }
127
+
128
+ module.exports = {
129
+ applyProgramGateAutoRemediation
130
+ };
@@ -0,0 +1,138 @@
1
+ function buildProgramFailureClusters(results = [], normalizeFailureSignatureFromError = (value) => String(value || 'unknown')) {
2
+ const failedStatuses = new Set(['failed', 'error', 'unknown', 'stopped']);
3
+ const source = Array.isArray(results) ? results : [];
4
+ const clusters = new Map();
5
+
6
+ for (const item of source) {
7
+ if (!item || typeof item !== 'object') {
8
+ continue;
9
+ }
10
+ const status = String(item.status || 'unknown').trim().toLowerCase();
11
+ if (!failedStatuses.has(status)) {
12
+ continue;
13
+ }
14
+
15
+ const signatureSeed = normalizeFailureSignatureFromError(item.error);
16
+ const signature = status + ':' + signatureSeed;
17
+ if (!clusters.has(signature)) {
18
+ clusters.set(signature, {
19
+ signature,
20
+ status,
21
+ count: 0,
22
+ goal_indexes: [],
23
+ example_goal: item.goal || null,
24
+ example_error: item.error || null
25
+ });
26
+ }
27
+
28
+ const cluster = clusters.get(signature);
29
+ cluster.count += 1;
30
+ const sourceIndex = Number.isInteger(item.source_index) ? item.source_index + 1 : Number(item.index);
31
+ if (Number.isInteger(sourceIndex) && sourceIndex > 0) {
32
+ cluster.goal_indexes.push(sourceIndex);
33
+ }
34
+ }
35
+
36
+ return [...clusters.values()]
37
+ .sort((left, right) => {
38
+ if (right.count !== left.count) {
39
+ return right.count - left.count;
40
+ }
41
+ return String(left.signature).localeCompare(String(right.signature));
42
+ })
43
+ .map((cluster) => ({
44
+ ...cluster,
45
+ goal_indexes: cluster.goal_indexes.slice(0, 20)
46
+ }));
47
+ }
48
+
49
+ function buildProgramRemediationActions(summary, failureClusters) {
50
+ const failedGoals = Number(summary && summary.failed_goals) || 0;
51
+ const retry = summary && summary.batch_retry ? summary.batch_retry : {};
52
+ const actions = [];
53
+
54
+ if (failedGoals === 0) {
55
+ return [{
56
+ priority: 'monitor',
57
+ action: 'No remediation required. Program converged successfully.',
58
+ reason: 'All goals completed in the current run.',
59
+ suggested_command: null,
60
+ strategy_patch: {}
61
+ }];
62
+ }
63
+
64
+ actions.push({
65
+ priority: 'high',
66
+ action: 'Resume unresolved goals from latest program/batch summary.',
67
+ reason: failedGoals + ' goals are unresolved after the current run.',
68
+ suggested_command: 'sce auto close-loop-recover latest --json',
69
+ strategy_patch: {
70
+ batchAutonomous: true,
71
+ continueOnError: true,
72
+ batchRetryUntilComplete: true
73
+ }
74
+ });
75
+
76
+ if ((Number(retry.max_rounds) || 0) > 0 && (Number(retry.performed_rounds) || 0) >= (Number(retry.max_rounds) || 0)) {
77
+ actions.push({
78
+ priority: 'high',
79
+ action: 'Increase retry ceiling or split the program into smaller sub-goal groups.',
80
+ reason: 'Retry rounds were exhausted before convergence.',
81
+ suggested_command: 'sce auto close-loop-recover latest --batch-retry-max-rounds 15 --json',
82
+ strategy_patch: {
83
+ batchRetryUntilComplete: true,
84
+ batchRetryMaxRounds: 15
85
+ }
86
+ });
87
+ }
88
+
89
+ const failureText = (Array.isArray(failureClusters) ? failureClusters : [])
90
+ .map((cluster) => String(cluster.signature) + ' ' + (cluster.example_error || ''))
91
+ .join(' | ')
92
+ .toLowerCase();
93
+
94
+ if (/timeout|timed out|deadline|terminated|killed/.test(failureText)) {
95
+ actions.push({
96
+ priority: 'medium',
97
+ action: 'Reduce parallel pressure and increase orchestration timeout budget.',
98
+ reason: 'Failure clusters indicate timeout/resource-pressure symptoms.',
99
+ suggested_command: 'sce auto close-loop-recover latest --batch-parallel 2 --batch-agent-budget 2 --json',
100
+ strategy_patch: {
101
+ batchParallel: 2,
102
+ batchAgentBudget: 2,
103
+ batchPriority: 'complex-first',
104
+ batchAgingFactor: 2
105
+ }
106
+ });
107
+ }
108
+
109
+ if (/dod|test|validation|checklist|compliance/.test(failureText)) {
110
+ actions.push({
111
+ priority: 'medium',
112
+ action: 'Run strict quality gates early to surface deterministic failures.',
113
+ reason: 'Failure clusters indicate DoD/test/compliance gate issues.',
114
+ suggested_command: 'sce auto close-loop-recover latest --dod-tests "npm run test:smoke" --dod-tasks-closed --json',
115
+ strategy_patch: {
116
+ dodTests: 'npm run test:smoke',
117
+ dodTasksClosed: true
118
+ }
119
+ });
120
+ }
121
+
122
+ return actions.slice(0, 5);
123
+ }
124
+
125
+ function buildProgramDiagnostics(summary, normalizeFailureSignatureFromError) {
126
+ const failureClusters = buildProgramFailureClusters(summary && summary.results, normalizeFailureSignatureFromError);
127
+ return {
128
+ failed_goal_count: Number(summary && summary.failed_goals) || 0,
129
+ failure_clusters: failureClusters,
130
+ remediation_actions: buildProgramRemediationActions(summary, failureClusters)
131
+ };
132
+ }
133
+
134
+ module.exports = {
135
+ buildProgramFailureClusters,
136
+ buildProgramRemediationActions,
137
+ buildProgramDiagnostics
138
+ };