scene-capability-engine 3.6.44 → 3.6.46
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.
- package/CHANGELOG.md +25 -0
- package/bin/scene-capability-engine.js +36 -2
- package/docs/command-reference.md +5 -0
- package/docs/releases/README.md +2 -0
- package/docs/releases/v3.6.45.md +18 -0
- package/docs/releases/v3.6.46.md +23 -0
- package/docs/zh/releases/README.md +2 -0
- package/docs/zh/releases/v3.6.45.md +18 -0
- package/docs/zh/releases/v3.6.46.md +23 -0
- package/lib/workspace/collab-governance-audit.js +575 -0
- package/package.json +4 -2
- package/scripts/auto-strategy-router.js +231 -0
- package/scripts/capability-mapping-report.js +339 -0
- package/scripts/check-branding-consistency.js +140 -0
- package/scripts/check-sce-tracking.js +54 -0
- package/scripts/check-skip-allowlist.js +94 -0
- package/scripts/errorbook-registry-health-gate.js +172 -0
- package/scripts/errorbook-release-gate.js +132 -0
- package/scripts/failure-attribution-repair.js +317 -0
- package/scripts/git-managed-gate.js +464 -0
- package/scripts/interactive-approval-event-projection.js +400 -0
- package/scripts/interactive-approval-workflow.js +829 -0
- package/scripts/interactive-authorization-tier-evaluate.js +413 -0
- package/scripts/interactive-change-plan-gate.js +225 -0
- package/scripts/interactive-context-bridge.js +617 -0
- package/scripts/interactive-customization-loop.js +1690 -0
- package/scripts/interactive-dialogue-governance.js +842 -0
- package/scripts/interactive-feedback-log.js +253 -0
- package/scripts/interactive-flow-smoke.js +238 -0
- package/scripts/interactive-flow.js +1059 -0
- package/scripts/interactive-governance-report.js +1112 -0
- package/scripts/interactive-intent-build.js +707 -0
- package/scripts/interactive-loop-smoke.js +215 -0
- package/scripts/interactive-moqui-adapter.js +304 -0
- package/scripts/interactive-plan-build.js +426 -0
- package/scripts/interactive-runtime-policy-evaluate.js +495 -0
- package/scripts/interactive-work-order-build.js +552 -0
- package/scripts/matrix-regression-gate.js +167 -0
- package/scripts/moqui-core-regression-suite.js +397 -0
- package/scripts/moqui-lexicon-audit.js +651 -0
- package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
- package/scripts/moqui-matrix-remediation-queue.js +852 -0
- package/scripts/moqui-metadata-extract.js +1340 -0
- package/scripts/moqui-rebuild-gate.js +167 -0
- package/scripts/moqui-release-summary.js +729 -0
- package/scripts/moqui-standard-rebuild.js +1370 -0
- package/scripts/moqui-template-baseline-report.js +682 -0
- package/scripts/npm-package-runtime-asset-check.js +221 -0
- package/scripts/problem-closure-gate.js +441 -0
- package/scripts/release-asset-integrity-check.js +216 -0
- package/scripts/release-asset-nonempty-normalize.js +166 -0
- package/scripts/release-drift-evaluate.js +223 -0
- package/scripts/release-drift-signals.js +255 -0
- package/scripts/release-governance-snapshot-export.js +132 -0
- package/scripts/release-ops-weekly-summary.js +934 -0
- package/scripts/release-risk-remediation-bundle.js +315 -0
- package/scripts/release-weekly-ops-gate.js +423 -0
- package/scripts/state-migration-reconciliation-gate.js +110 -0
- package/scripts/state-storage-tiering-audit.js +337 -0
- package/scripts/steering-content-audit.js +393 -0
- package/scripts/symbol-evidence-locate.js +366 -0
|
@@ -0,0 +1,255 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
function parseNumber(value, fallback) {
|
|
4
|
+
const parsed = Number(value);
|
|
5
|
+
return Number.isFinite(parsed) ? parsed : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function parseBoolean(value, fallback = false) {
|
|
9
|
+
if (value === undefined || value === null) {
|
|
10
|
+
return fallback;
|
|
11
|
+
}
|
|
12
|
+
const normalized = `${value}`.trim().toLowerCase();
|
|
13
|
+
if (!normalized) {
|
|
14
|
+
return fallback;
|
|
15
|
+
}
|
|
16
|
+
if (['1', 'true', 'yes', 'y', 'on'].includes(normalized)) {
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
if (['0', 'false', 'no', 'n', 'off'].includes(normalized)) {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
return fallback;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeRisk(value) {
|
|
26
|
+
const normalized = `${value || 'unknown'}`.trim().toLowerCase();
|
|
27
|
+
if (['low', 'medium', 'high', 'unknown'].includes(normalized)) {
|
|
28
|
+
return normalized;
|
|
29
|
+
}
|
|
30
|
+
return 'unknown';
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function countConsecutive(items, predicate) {
|
|
34
|
+
let count = 0;
|
|
35
|
+
for (const item of items) {
|
|
36
|
+
if (!predicate(item)) {
|
|
37
|
+
break;
|
|
38
|
+
}
|
|
39
|
+
count += 1;
|
|
40
|
+
}
|
|
41
|
+
return count;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function resolveReleaseDriftThresholds(env = process.env) {
|
|
45
|
+
return {
|
|
46
|
+
failStreakMin: Math.max(
|
|
47
|
+
1,
|
|
48
|
+
Math.floor(parseNumber(env.RELEASE_DRIFT_FAIL_STREAK_MIN, 2))
|
|
49
|
+
),
|
|
50
|
+
highRiskShareMinPercent: Math.max(
|
|
51
|
+
0,
|
|
52
|
+
Math.min(100, parseNumber(env.RELEASE_DRIFT_HIGH_RISK_SHARE_MIN_PERCENT, 60))
|
|
53
|
+
),
|
|
54
|
+
highRiskShareDeltaMinPercent: Math.max(
|
|
55
|
+
0,
|
|
56
|
+
Math.min(100, parseNumber(env.RELEASE_DRIFT_HIGH_RISK_SHARE_DELTA_MIN_PERCENT, 25))
|
|
57
|
+
),
|
|
58
|
+
preflightBlockRateMinPercent: Math.max(
|
|
59
|
+
0,
|
|
60
|
+
Math.min(100, parseNumber(env.RELEASE_DRIFT_PREFLIGHT_BLOCK_RATE_MIN_PERCENT, 40))
|
|
61
|
+
),
|
|
62
|
+
hardGateBlockStreakMin: Math.max(
|
|
63
|
+
1,
|
|
64
|
+
Math.floor(parseNumber(env.RELEASE_DRIFT_HARD_GATE_BLOCK_STREAK_MIN, 2))
|
|
65
|
+
),
|
|
66
|
+
preflightUnavailableStreakMin: Math.max(
|
|
67
|
+
1,
|
|
68
|
+
Math.floor(parseNumber(env.RELEASE_DRIFT_PREFLIGHT_UNAVAILABLE_STREAK_MIN, 2))
|
|
69
|
+
),
|
|
70
|
+
capabilityExpectedUnknownRateMinPercent: Math.max(
|
|
71
|
+
0,
|
|
72
|
+
Math.min(100, parseNumber(env.RELEASE_DRIFT_CAPABILITY_EXPECTED_UNKNOWN_RATE_MIN_PERCENT, 40))
|
|
73
|
+
),
|
|
74
|
+
capabilityProvidedUnknownRateMinPercent: Math.max(
|
|
75
|
+
0,
|
|
76
|
+
Math.min(100, parseNumber(env.RELEASE_DRIFT_CAPABILITY_PROVIDED_UNKNOWN_RATE_MIN_PERCENT, 40))
|
|
77
|
+
)
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function buildReleaseDriftSignals(payload = {}, options = {}) {
|
|
82
|
+
const entries = Array.isArray(payload && payload.entries) ? payload.entries : [];
|
|
83
|
+
const windowSize = Math.max(1, Math.floor(parseNumber(options.windowSize, 5)));
|
|
84
|
+
const shortWindowSize = Math.max(1, Math.floor(parseNumber(options.shortWindowSize, 3)));
|
|
85
|
+
const longWindowSize = Math.max(shortWindowSize, Math.floor(parseNumber(options.longWindowSize, 5)));
|
|
86
|
+
const defaultThresholds = resolveReleaseDriftThresholds(options.env || process.env);
|
|
87
|
+
const thresholds = options.thresholds && typeof options.thresholds === 'object'
|
|
88
|
+
? {
|
|
89
|
+
...defaultThresholds,
|
|
90
|
+
...options.thresholds
|
|
91
|
+
}
|
|
92
|
+
: defaultThresholds;
|
|
93
|
+
|
|
94
|
+
const recent = entries.slice(0, Math.min(entries.length, windowSize));
|
|
95
|
+
const shortWindow = entries.slice(0, Math.min(entries.length, shortWindowSize));
|
|
96
|
+
const longWindow = entries.slice(0, Math.min(entries.length, longWindowSize));
|
|
97
|
+
|
|
98
|
+
const riskCounts = recent.reduce((acc, item) => {
|
|
99
|
+
const risk = normalizeRisk(item && item.risk_level);
|
|
100
|
+
acc[risk] += 1;
|
|
101
|
+
return acc;
|
|
102
|
+
}, { low: 0, medium: 0, high: 0, unknown: 0 });
|
|
103
|
+
|
|
104
|
+
const recentPass = recent.filter(item => item && item.gate_passed === true).length;
|
|
105
|
+
const recentKnown = recent.filter(item => item && typeof item.gate_passed === 'boolean').length;
|
|
106
|
+
const failedStreak = countConsecutive(recent, item => item && item.gate_passed === false);
|
|
107
|
+
const highRiskShare = recent.length > 0
|
|
108
|
+
? Number(((riskCounts.high / recent.length) * 100).toFixed(2))
|
|
109
|
+
: 0;
|
|
110
|
+
const shortHighShare = shortWindow.length > 0
|
|
111
|
+
? shortWindow.filter(item => normalizeRisk(item && item.risk_level) === 'high').length / shortWindow.length
|
|
112
|
+
: 0;
|
|
113
|
+
const longHighShare = longWindow.length > 0
|
|
114
|
+
? longWindow.filter(item => normalizeRisk(item && item.risk_level) === 'high').length / longWindow.length
|
|
115
|
+
: 0;
|
|
116
|
+
const highRiskDeltaPercent = Number(((shortHighShare - longHighShare) * 100).toFixed(2));
|
|
117
|
+
|
|
118
|
+
const recentPreflightKnown = recent.filter(
|
|
119
|
+
item => item && typeof item.release_gate_preflight_blocked === 'boolean'
|
|
120
|
+
).length;
|
|
121
|
+
const recentPreflightBlocked = recent.filter(
|
|
122
|
+
item => item && item.release_gate_preflight_blocked === true
|
|
123
|
+
).length;
|
|
124
|
+
const recentPreflightBlockedRate = recentPreflightKnown > 0
|
|
125
|
+
? Number(((recentPreflightBlocked / recentPreflightKnown) * 100).toFixed(2))
|
|
126
|
+
: null;
|
|
127
|
+
const hardGateBlockedStreak = countConsecutive(
|
|
128
|
+
recent,
|
|
129
|
+
item => item && item.require_release_gate_preflight === true && item.release_gate_preflight_blocked === true
|
|
130
|
+
);
|
|
131
|
+
const preflightUnavailableStreak = countConsecutive(
|
|
132
|
+
recent,
|
|
133
|
+
item => item && item.release_gate_preflight_available === false
|
|
134
|
+
);
|
|
135
|
+
const recentCapabilityExpectedUnknownKnown = recent.filter(item => (
|
|
136
|
+
item &&
|
|
137
|
+
Number.isFinite(Number(item.capability_expected_unknown_count))
|
|
138
|
+
)).length;
|
|
139
|
+
const recentCapabilityExpectedUnknownPositive = recent.filter(item => (
|
|
140
|
+
item &&
|
|
141
|
+
Number.isFinite(Number(item.capability_expected_unknown_count)) &&
|
|
142
|
+
Number(item.capability_expected_unknown_count) > 0
|
|
143
|
+
)).length;
|
|
144
|
+
const recentCapabilityExpectedUnknownRate = recentCapabilityExpectedUnknownKnown > 0
|
|
145
|
+
? Number(((recentCapabilityExpectedUnknownPositive / recentCapabilityExpectedUnknownKnown) * 100).toFixed(2))
|
|
146
|
+
: null;
|
|
147
|
+
const recentCapabilityProvidedUnknownKnown = recent.filter(item => (
|
|
148
|
+
item &&
|
|
149
|
+
Number.isFinite(Number(item.capability_provided_unknown_count))
|
|
150
|
+
)).length;
|
|
151
|
+
const recentCapabilityProvidedUnknownPositive = recent.filter(item => (
|
|
152
|
+
item &&
|
|
153
|
+
Number.isFinite(Number(item.capability_provided_unknown_count)) &&
|
|
154
|
+
Number(item.capability_provided_unknown_count) > 0
|
|
155
|
+
)).length;
|
|
156
|
+
const recentCapabilityProvidedUnknownRate = recentCapabilityProvidedUnknownKnown > 0
|
|
157
|
+
? Number(((recentCapabilityProvidedUnknownPositive / recentCapabilityProvidedUnknownKnown) * 100).toFixed(2))
|
|
158
|
+
: null;
|
|
159
|
+
|
|
160
|
+
const alerts = [];
|
|
161
|
+
if (failedStreak >= thresholds.failStreakMin) {
|
|
162
|
+
alerts.push(`consecutive gate failures: ${failedStreak} (threshold=${thresholds.failStreakMin})`);
|
|
163
|
+
}
|
|
164
|
+
if (highRiskShare >= thresholds.highRiskShareMinPercent) {
|
|
165
|
+
alerts.push(
|
|
166
|
+
`high-risk share in latest ${windowSize} is ${highRiskShare}% `
|
|
167
|
+
+ `(threshold=${thresholds.highRiskShareMinPercent}%)`
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
if (
|
|
171
|
+
shortWindow.length >= 2 &&
|
|
172
|
+
longWindow.length >= 3 &&
|
|
173
|
+
highRiskDeltaPercent >= thresholds.highRiskShareDeltaMinPercent
|
|
174
|
+
) {
|
|
175
|
+
alerts.push(
|
|
176
|
+
`high-risk share increased from ${(longHighShare * 100).toFixed(2)}% to ${(shortHighShare * 100).toFixed(2)}% `
|
|
177
|
+
+ `(threshold=${thresholds.highRiskShareDeltaMinPercent}%)`
|
|
178
|
+
);
|
|
179
|
+
}
|
|
180
|
+
if (
|
|
181
|
+
recentPreflightBlockedRate !== null &&
|
|
182
|
+
recentPreflightBlockedRate >= thresholds.preflightBlockRateMinPercent
|
|
183
|
+
) {
|
|
184
|
+
alerts.push(
|
|
185
|
+
`release preflight blocked rate in latest ${windowSize} is ${recentPreflightBlockedRate}% `
|
|
186
|
+
+ `(threshold=${thresholds.preflightBlockRateMinPercent}%, known=${recentPreflightKnown})`
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
if (hardGateBlockedStreak >= thresholds.hardGateBlockStreakMin) {
|
|
190
|
+
alerts.push(
|
|
191
|
+
`hard-gate preflight blocked streak is ${hardGateBlockedStreak} `
|
|
192
|
+
+ `(threshold=${thresholds.hardGateBlockStreakMin})`
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
if (preflightUnavailableStreak >= thresholds.preflightUnavailableStreakMin) {
|
|
196
|
+
alerts.push(
|
|
197
|
+
`release preflight unavailable streak is ${preflightUnavailableStreak} `
|
|
198
|
+
+ `(threshold=${thresholds.preflightUnavailableStreakMin})`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
if (
|
|
202
|
+
recentCapabilityExpectedUnknownRate !== null &&
|
|
203
|
+
recentCapabilityExpectedUnknownRate >= thresholds.capabilityExpectedUnknownRateMinPercent
|
|
204
|
+
) {
|
|
205
|
+
alerts.push(
|
|
206
|
+
`capability expected unknown positive rate in latest ${windowSize} is ${recentCapabilityExpectedUnknownRate}% `
|
|
207
|
+
+ `(threshold=${thresholds.capabilityExpectedUnknownRateMinPercent}%, known=${recentCapabilityExpectedUnknownKnown})`
|
|
208
|
+
);
|
|
209
|
+
}
|
|
210
|
+
if (
|
|
211
|
+
recentCapabilityProvidedUnknownRate !== null &&
|
|
212
|
+
recentCapabilityProvidedUnknownRate >= thresholds.capabilityProvidedUnknownRateMinPercent
|
|
213
|
+
) {
|
|
214
|
+
alerts.push(
|
|
215
|
+
`capability provided unknown positive rate in latest ${windowSize} is ${recentCapabilityProvidedUnknownRate}% `
|
|
216
|
+
+ `(threshold=${thresholds.capabilityProvidedUnknownRateMinPercent}%, known=${recentCapabilityProvidedUnknownKnown})`
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
return {
|
|
221
|
+
entries,
|
|
222
|
+
recent,
|
|
223
|
+
recentPass,
|
|
224
|
+
recentKnown,
|
|
225
|
+
riskCounts,
|
|
226
|
+
failedStreak,
|
|
227
|
+
highRiskShare,
|
|
228
|
+
highRiskDeltaPercent,
|
|
229
|
+
recentPreflightKnown,
|
|
230
|
+
recentPreflightBlocked,
|
|
231
|
+
recentPreflightBlockedRate,
|
|
232
|
+
hardGateBlockedStreak,
|
|
233
|
+
preflightUnavailableStreak,
|
|
234
|
+
recentCapabilityExpectedUnknownKnown,
|
|
235
|
+
recentCapabilityExpectedUnknownPositive,
|
|
236
|
+
recentCapabilityExpectedUnknownRate,
|
|
237
|
+
recentCapabilityProvidedUnknownKnown,
|
|
238
|
+
recentCapabilityProvidedUnknownPositive,
|
|
239
|
+
recentCapabilityProvidedUnknownRate,
|
|
240
|
+
alerts,
|
|
241
|
+
thresholds,
|
|
242
|
+
windows: {
|
|
243
|
+
recent: recent.length,
|
|
244
|
+
short: shortWindow.length,
|
|
245
|
+
long: longWindow.length
|
|
246
|
+
}
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
module.exports = {
|
|
251
|
+
buildReleaseDriftSignals,
|
|
252
|
+
parseBoolean,
|
|
253
|
+
parseNumber,
|
|
254
|
+
resolveReleaseDriftThresholds
|
|
255
|
+
};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
function appendSummary(summaryPath, lines = []) {
|
|
7
|
+
if (!summaryPath) {
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
fs.appendFileSync(summaryPath, `${lines.join('\n')}\n\n`, 'utf8');
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
function safeReadJson(file) {
|
|
14
|
+
try {
|
|
15
|
+
if (!file || !fs.existsSync(file)) {
|
|
16
|
+
return { ok: false, error: `missing file: ${file || 'n/a'}` };
|
|
17
|
+
}
|
|
18
|
+
const payload = JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
19
|
+
return { ok: true, payload };
|
|
20
|
+
} catch (error) {
|
|
21
|
+
return { ok: false, error: `parse error: ${error.message}` };
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function normalizeArray(value) {
|
|
26
|
+
return Array.isArray(value) ? value : [];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function exportReleaseGovernanceSnapshot(options = {}) {
|
|
30
|
+
const env = options.env && typeof options.env === 'object'
|
|
31
|
+
? options.env
|
|
32
|
+
: process.env;
|
|
33
|
+
const now = typeof options.now === 'function'
|
|
34
|
+
? options.now
|
|
35
|
+
: () => new Date().toISOString();
|
|
36
|
+
|
|
37
|
+
const summaryFile = `${env.RELEASE_EVIDENCE_SUMMARY_FILE || ''}`.trim();
|
|
38
|
+
const outputJson = `${env.RELEASE_GOVERNANCE_SNAPSHOT_JSON || '.sce/reports/release-evidence/governance-snapshot.json'}`.trim();
|
|
39
|
+
const outputMarkdown = `${env.RELEASE_GOVERNANCE_SNAPSHOT_MD || '.sce/reports/release-evidence/governance-snapshot.md'}`.trim();
|
|
40
|
+
const releaseTag = `${env.RELEASE_TAG || ''}`.trim();
|
|
41
|
+
const summaryPath = env.GITHUB_STEP_SUMMARY;
|
|
42
|
+
|
|
43
|
+
const summaryResult = safeReadJson(summaryFile);
|
|
44
|
+
const payload = summaryResult.ok && summaryResult.payload && typeof summaryResult.payload === 'object'
|
|
45
|
+
? summaryResult.payload
|
|
46
|
+
: {};
|
|
47
|
+
const governanceSnapshot = payload.governance_snapshot && typeof payload.governance_snapshot === 'object'
|
|
48
|
+
? payload.governance_snapshot
|
|
49
|
+
: null;
|
|
50
|
+
const available = governanceSnapshot !== null;
|
|
51
|
+
const concerns = normalizeArray(governanceSnapshot && governanceSnapshot.health && governanceSnapshot.health.concerns);
|
|
52
|
+
const recommendations = normalizeArray(governanceSnapshot && governanceSnapshot.health && governanceSnapshot.health.recommendations);
|
|
53
|
+
const risk = `${governanceSnapshot && governanceSnapshot.health && governanceSnapshot.health.risk || 'unknown'}`.trim().toLowerCase();
|
|
54
|
+
|
|
55
|
+
const exportPayload = {
|
|
56
|
+
mode: 'release-governance-snapshot',
|
|
57
|
+
generated_at: now(),
|
|
58
|
+
tag: releaseTag || null,
|
|
59
|
+
summary_file: summaryFile || null,
|
|
60
|
+
available,
|
|
61
|
+
warning: summaryResult.ok ? null : summaryResult.error,
|
|
62
|
+
governance_snapshot: governanceSnapshot
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
fs.mkdirSync(path.dirname(outputJson), { recursive: true });
|
|
66
|
+
fs.writeFileSync(outputJson, `${JSON.stringify(exportPayload, null, 2)}\n`, 'utf8');
|
|
67
|
+
|
|
68
|
+
const markdownLines = [
|
|
69
|
+
'# Release Governance Snapshot',
|
|
70
|
+
'',
|
|
71
|
+
`- Tag: ${releaseTag || 'n/a'}`,
|
|
72
|
+
`- Generated At: ${exportPayload.generated_at}`,
|
|
73
|
+
`- Summary File: ${summaryFile || 'n/a'}`,
|
|
74
|
+
`- Available: ${available ? 'yes' : 'no'}`
|
|
75
|
+
];
|
|
76
|
+
|
|
77
|
+
if (!available) {
|
|
78
|
+
markdownLines.push(`- Warning: ${exportPayload.warning || 'governance snapshot unavailable'}`);
|
|
79
|
+
} else {
|
|
80
|
+
markdownLines.push(`- Risk: ${risk}`);
|
|
81
|
+
markdownLines.push(`- Concerns: ${concerns.length}`);
|
|
82
|
+
markdownLines.push(`- Recommendations: ${recommendations.length}`);
|
|
83
|
+
if (concerns.length > 0) {
|
|
84
|
+
markdownLines.push('', '## Concerns');
|
|
85
|
+
concerns.forEach(item => markdownLines.push(`- ${item}`));
|
|
86
|
+
}
|
|
87
|
+
if (recommendations.length > 0) {
|
|
88
|
+
markdownLines.push('', '## Recommendations');
|
|
89
|
+
recommendations.forEach(item => markdownLines.push(`- ${item}`));
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
fs.mkdirSync(path.dirname(outputMarkdown), { recursive: true });
|
|
94
|
+
fs.writeFileSync(outputMarkdown, `${markdownLines.join('\n')}\n`, 'utf8');
|
|
95
|
+
|
|
96
|
+
console.log(`[release-governance-snapshot] summary=${summaryFile || 'n/a'} available=${available}`);
|
|
97
|
+
console.log(`[release-governance-snapshot] json=${outputJson}`);
|
|
98
|
+
console.log(`[release-governance-snapshot] markdown=${outputMarkdown}`);
|
|
99
|
+
|
|
100
|
+
appendSummary(summaryPath, [
|
|
101
|
+
'## Release Governance Snapshot',
|
|
102
|
+
'',
|
|
103
|
+
`- summary: ${summaryFile || 'n/a'}`,
|
|
104
|
+
`- available: ${available}`,
|
|
105
|
+
`- json: ${outputJson}`,
|
|
106
|
+
`- markdown: ${outputMarkdown}`,
|
|
107
|
+
...(available
|
|
108
|
+
? [
|
|
109
|
+
`- risk: ${risk}`,
|
|
110
|
+
`- concerns: ${concerns.length}`,
|
|
111
|
+
`- recommendations: ${recommendations.length}`
|
|
112
|
+
]
|
|
113
|
+
: [`- warning: ${exportPayload.warning || 'governance snapshot unavailable'}`])
|
|
114
|
+
]);
|
|
115
|
+
|
|
116
|
+
return {
|
|
117
|
+
exit_code: 0,
|
|
118
|
+
available,
|
|
119
|
+
warning: exportPayload.warning,
|
|
120
|
+
json_file: outputJson,
|
|
121
|
+
markdown_file: outputMarkdown
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
if (require.main === module) {
|
|
126
|
+
const result = exportReleaseGovernanceSnapshot();
|
|
127
|
+
process.exit(result.exit_code);
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
module.exports = {
|
|
131
|
+
exportReleaseGovernanceSnapshot
|
|
132
|
+
};
|