scene-capability-engine 3.6.45 → 3.6.47

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 (72) hide show
  1. package/CHANGELOG.md +22 -0
  2. package/README.md +1 -0
  3. package/README.zh.md +1 -0
  4. package/docs/agent-runtime/symbol-evidence.schema.json +1 -1
  5. package/docs/command-reference.md +8 -0
  6. package/docs/interactive-customization/dialogue-governance-policy-baseline.json +4 -1
  7. package/docs/interactive-customization/embedded-assistant-authorization-dialogue-rules.md +5 -0
  8. package/docs/releases/README.md +2 -0
  9. package/docs/releases/v3.6.46.md +23 -0
  10. package/docs/releases/v3.6.47.md +23 -0
  11. package/docs/sce-business-mode-map.md +2 -1
  12. package/docs/sce-capability-matrix-e2e-example.md +2 -1
  13. package/docs/security-governance-default-baseline.md +2 -0
  14. package/docs/starter-kit/README.md +3 -0
  15. package/docs/zh/releases/README.md +2 -0
  16. package/docs/zh/releases/v3.6.46.md +23 -0
  17. package/docs/zh/releases/v3.6.47.md +23 -0
  18. package/lib/workspace/takeover-baseline.js +293 -1
  19. package/package.json +6 -2
  20. package/scripts/auto-strategy-router.js +231 -0
  21. package/scripts/capability-mapping-report.js +339 -0
  22. package/scripts/check-branding-consistency.js +140 -0
  23. package/scripts/check-sce-tracking.js +54 -0
  24. package/scripts/check-skip-allowlist.js +94 -0
  25. package/scripts/clarification-first-audit.js +322 -0
  26. package/scripts/errorbook-registry-health-gate.js +172 -0
  27. package/scripts/errorbook-release-gate.js +132 -0
  28. package/scripts/failure-attribution-repair.js +317 -0
  29. package/scripts/git-managed-gate.js +464 -0
  30. package/scripts/interactive-approval-event-projection.js +400 -0
  31. package/scripts/interactive-approval-workflow.js +829 -0
  32. package/scripts/interactive-authorization-tier-evaluate.js +413 -0
  33. package/scripts/interactive-change-plan-gate.js +225 -0
  34. package/scripts/interactive-context-bridge.js +617 -0
  35. package/scripts/interactive-customization-loop.js +1690 -0
  36. package/scripts/interactive-dialogue-governance.js +873 -0
  37. package/scripts/interactive-feedback-log.js +253 -0
  38. package/scripts/interactive-flow-smoke.js +238 -0
  39. package/scripts/interactive-flow.js +1059 -0
  40. package/scripts/interactive-governance-report.js +1112 -0
  41. package/scripts/interactive-intent-build.js +707 -0
  42. package/scripts/interactive-loop-smoke.js +215 -0
  43. package/scripts/interactive-moqui-adapter.js +304 -0
  44. package/scripts/interactive-plan-build.js +426 -0
  45. package/scripts/interactive-runtime-policy-evaluate.js +495 -0
  46. package/scripts/interactive-work-order-build.js +552 -0
  47. package/scripts/matrix-regression-gate.js +167 -0
  48. package/scripts/moqui-core-regression-suite.js +397 -0
  49. package/scripts/moqui-lexicon-audit.js +651 -0
  50. package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
  51. package/scripts/moqui-matrix-remediation-queue.js +852 -0
  52. package/scripts/moqui-metadata-extract.js +1340 -0
  53. package/scripts/moqui-rebuild-gate.js +167 -0
  54. package/scripts/moqui-release-summary.js +729 -0
  55. package/scripts/moqui-standard-rebuild.js +1370 -0
  56. package/scripts/moqui-template-baseline-report.js +682 -0
  57. package/scripts/npm-package-runtime-asset-check.js +221 -0
  58. package/scripts/problem-closure-gate.js +441 -0
  59. package/scripts/release-asset-integrity-check.js +216 -0
  60. package/scripts/release-asset-nonempty-normalize.js +166 -0
  61. package/scripts/release-drift-evaluate.js +223 -0
  62. package/scripts/release-drift-signals.js +255 -0
  63. package/scripts/release-governance-snapshot-export.js +132 -0
  64. package/scripts/release-ops-weekly-summary.js +934 -0
  65. package/scripts/release-risk-remediation-bundle.js +315 -0
  66. package/scripts/release-weekly-ops-gate.js +423 -0
  67. package/scripts/state-migration-reconciliation-gate.js +110 -0
  68. package/scripts/state-storage-tiering-audit.js +337 -0
  69. package/scripts/steering-content-audit.js +393 -0
  70. package/scripts/symbol-evidence-locate.js +370 -0
  71. package/template/.sce/README.md +1 -0
  72. package/template/.sce/steering/CORE_PRINCIPLES.md +25 -0
@@ -0,0 +1,934 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+
7
+ const DEFAULT_EVIDENCE = '.sce/reports/release-evidence/handoff-runs.json';
8
+ const DEFAULT_GATE_HISTORY = '.sce/reports/release-evidence/release-gate-history.json';
9
+ const DEFAULT_INTERACTIVE_GOVERNANCE = '.sce/reports/interactive-governance-report.json';
10
+ const DEFAULT_MATRIX_SIGNALS = '.sce/reports/interactive-matrix-signals.jsonl';
11
+ const DEFAULT_OUT = '.sce/reports/release-evidence/weekly-ops-summary.json';
12
+ const DEFAULT_MARKDOWN_OUT = '.sce/reports/release-evidence/weekly-ops-summary.md';
13
+
14
+ function parseArgs(argv) {
15
+ const options = {
16
+ evidence: DEFAULT_EVIDENCE,
17
+ gateHistory: DEFAULT_GATE_HISTORY,
18
+ interactiveGovernance: DEFAULT_INTERACTIVE_GOVERNANCE,
19
+ matrixSignals: DEFAULT_MATRIX_SIGNALS,
20
+ out: DEFAULT_OUT,
21
+ markdownOut: DEFAULT_MARKDOWN_OUT,
22
+ from: null,
23
+ to: null,
24
+ windowDays: 7,
25
+ json: false
26
+ };
27
+
28
+ for (let i = 0; i < argv.length; i += 1) {
29
+ const token = argv[i];
30
+ const next = argv[i + 1];
31
+ if (token === '--evidence' && next) {
32
+ options.evidence = next;
33
+ i += 1;
34
+ } else if (token === '--gate-history' && next) {
35
+ options.gateHistory = next;
36
+ i += 1;
37
+ } else if (token === '--interactive-governance' && next) {
38
+ options.interactiveGovernance = next;
39
+ i += 1;
40
+ } else if (token === '--matrix-signals' && next) {
41
+ options.matrixSignals = next;
42
+ i += 1;
43
+ } else if (token === '--out' && next) {
44
+ options.out = next;
45
+ i += 1;
46
+ } else if (token === '--markdown-out' && next) {
47
+ options.markdownOut = next;
48
+ i += 1;
49
+ } else if (token === '--from' && next) {
50
+ options.from = next;
51
+ i += 1;
52
+ } else if (token === '--to' && next) {
53
+ options.to = next;
54
+ i += 1;
55
+ } else if (token === '--window-days' && next) {
56
+ options.windowDays = Number(next);
57
+ i += 1;
58
+ } else if (token === '--json') {
59
+ options.json = true;
60
+ } else if (token === '--help' || token === '-h') {
61
+ printHelpAndExit(0);
62
+ }
63
+ }
64
+
65
+ const hasFrom = Boolean(typeof options.from === 'string' && options.from.trim());
66
+ const hasTo = Boolean(typeof options.to === 'string' && options.to.trim());
67
+ if (hasFrom !== hasTo) {
68
+ throw new Error('--from and --to must be provided together.');
69
+ }
70
+
71
+ if (!Number.isFinite(options.windowDays) || options.windowDays < 1 || options.windowDays > 90) {
72
+ throw new Error('--window-days must be an integer between 1 and 90.');
73
+ }
74
+
75
+ return options;
76
+ }
77
+
78
+ function printHelpAndExit(code) {
79
+ const lines = [
80
+ 'Usage: node scripts/release-ops-weekly-summary.js [options]',
81
+ '',
82
+ 'Options:',
83
+ ` --evidence <path> Handoff evidence JSON (default: ${DEFAULT_EVIDENCE})`,
84
+ ` --gate-history <path> Release gate history JSON (default: ${DEFAULT_GATE_HISTORY})`,
85
+ ` --interactive-governance <path> Interactive governance JSON (default: ${DEFAULT_INTERACTIVE_GOVERNANCE})`,
86
+ ` --matrix-signals <path> Interactive matrix signals JSONL (default: ${DEFAULT_MATRIX_SIGNALS})`,
87
+ ` --out <path> JSON output path (default: ${DEFAULT_OUT})`,
88
+ ` --markdown-out <path> Markdown output path (default: ${DEFAULT_MARKDOWN_OUT})`,
89
+ ' --from <ISO datetime> Window start (requires --to)',
90
+ ' --to <ISO datetime> Window end (requires --from)',
91
+ ' --window-days <n> Default rolling window in days (1-90, default: 7)',
92
+ ' --json Print report JSON payload',
93
+ ' -h, --help Show this help'
94
+ ];
95
+ console.log(lines.join('\n'));
96
+ process.exit(code);
97
+ }
98
+
99
+ function resolvePath(cwd, value) {
100
+ return path.isAbsolute(value) ? value : path.resolve(cwd, value);
101
+ }
102
+
103
+ function parseIsoDate(value, label) {
104
+ const date = new Date(value);
105
+ if (Number.isNaN(date.getTime())) {
106
+ throw new Error(`invalid date for ${label}: ${value}`);
107
+ }
108
+ return date;
109
+ }
110
+
111
+ function buildWindow(options, now = new Date()) {
112
+ if (options.from && options.to) {
113
+ const from = parseIsoDate(options.from, '--from');
114
+ const to = parseIsoDate(options.to, '--to');
115
+ if (from.getTime() > to.getTime()) {
116
+ throw new Error('--from must be <= --to.');
117
+ }
118
+ return { from, to, mode: 'custom' };
119
+ }
120
+ const to = new Date(now.getTime());
121
+ const from = new Date(now.getTime());
122
+ from.setUTCDate(from.getUTCDate() - options.windowDays);
123
+ return { from, to, mode: 'rolling' };
124
+ }
125
+
126
+ async function safeReadJson(cwd, candidatePath) {
127
+ const absolutePath = resolvePath(cwd, candidatePath);
128
+ const relativePath = path.relative(cwd, absolutePath) || '.';
129
+ const exists = await fs.pathExists(absolutePath);
130
+ if (!exists) {
131
+ return {
132
+ path: relativePath,
133
+ exists: false,
134
+ parse_error: null,
135
+ payload: null
136
+ };
137
+ }
138
+
139
+ try {
140
+ const payload = await fs.readJson(absolutePath);
141
+ return {
142
+ path: relativePath,
143
+ exists: true,
144
+ parse_error: null,
145
+ payload
146
+ };
147
+ } catch (error) {
148
+ return {
149
+ path: relativePath,
150
+ exists: true,
151
+ parse_error: error.message,
152
+ payload: null
153
+ };
154
+ }
155
+ }
156
+
157
+ async function safeReadJsonLines(cwd, candidatePath) {
158
+ const absolutePath = resolvePath(cwd, candidatePath);
159
+ const relativePath = path.relative(cwd, absolutePath) || '.';
160
+ const exists = await fs.pathExists(absolutePath);
161
+ if (!exists) {
162
+ return {
163
+ path: relativePath,
164
+ exists: false,
165
+ parse_error: null,
166
+ records: []
167
+ };
168
+ }
169
+
170
+ try {
171
+ const content = await fs.readFile(absolutePath, 'utf8');
172
+ const records = `${content || ''}`
173
+ .split(/\r?\n/)
174
+ .map(line => line.trim())
175
+ .filter(Boolean)
176
+ .map((line) => {
177
+ try {
178
+ return JSON.parse(line);
179
+ } catch (_error) {
180
+ return null;
181
+ }
182
+ })
183
+ .filter(Boolean);
184
+ return {
185
+ path: relativePath,
186
+ exists: true,
187
+ parse_error: null,
188
+ records
189
+ };
190
+ } catch (error) {
191
+ return {
192
+ path: relativePath,
193
+ exists: true,
194
+ parse_error: error.message,
195
+ records: []
196
+ };
197
+ }
198
+ }
199
+
200
+ function parseTimestamp(entry, fields = []) {
201
+ for (const key of fields) {
202
+ const value = entry && entry[key];
203
+ if (!value) {
204
+ continue;
205
+ }
206
+ const parsed = Date.parse(value);
207
+ if (Number.isFinite(parsed)) {
208
+ return parsed;
209
+ }
210
+ }
211
+ return null;
212
+ }
213
+
214
+ function filterByWindow(entries, window, fields, includeUnknown = false) {
215
+ const fromMs = window.from.getTime();
216
+ const toMs = window.to.getTime();
217
+ return entries.filter((entry) => {
218
+ const ts = parseTimestamp(entry, fields);
219
+ if (ts === null) {
220
+ return includeUnknown;
221
+ }
222
+ return ts >= fromMs && ts <= toMs;
223
+ });
224
+ }
225
+
226
+ function toPercent(numerator, denominator) {
227
+ const total = Number(denominator);
228
+ if (!Number.isFinite(total) || total <= 0) {
229
+ return null;
230
+ }
231
+ return Number(((Number(numerator) / total) * 100).toFixed(2));
232
+ }
233
+
234
+ function toAverage(values = []) {
235
+ const nums = values
236
+ .map(value => Number(value))
237
+ .filter(value => Number.isFinite(value));
238
+ if (nums.length === 0) {
239
+ return null;
240
+ }
241
+ return Number((nums.reduce((sum, value) => sum + value, 0) / nums.length).toFixed(2));
242
+ }
243
+
244
+ function buildHandoffSnapshot(payload, window) {
245
+ const sessions = Array.isArray(payload && payload.sessions) ? payload.sessions : [];
246
+ const scoped = filterByWindow(
247
+ sessions,
248
+ window,
249
+ ['merged_at', 'generated_at', 'updated_at'],
250
+ false
251
+ );
252
+ const statusCounts = {
253
+ completed: 0,
254
+ failed: 0,
255
+ dry_run: 0,
256
+ running: 0,
257
+ other: 0
258
+ };
259
+ const risk = {
260
+ low: 0,
261
+ medium: 0,
262
+ high: 0,
263
+ unknown: 0
264
+ };
265
+ let gateKnownRuns = 0;
266
+ let gatePassedRuns = 0;
267
+ let sceneKnownRuns = 0;
268
+ let scenePassedRuns = 0;
269
+ let preflightKnownRuns = 0;
270
+ let preflightBlockedRuns = 0;
271
+ const specRateValues = [];
272
+ const expectedUnknownKnown = [];
273
+ const providedUnknownKnown = [];
274
+
275
+ for (const session of scoped) {
276
+ const status = `${session && session.status ? session.status : ''}`.trim().toLowerCase();
277
+ if (statusCounts[status] !== undefined) {
278
+ statusCounts[status] += 1;
279
+ } else {
280
+ statusCounts.other += 1;
281
+ }
282
+
283
+ const gate = session && session.gate && typeof session.gate === 'object' ? session.gate : {};
284
+ const gateActual = gate && gate.actual && typeof gate.actual === 'object' ? gate.actual : {};
285
+ if (gate.passed === true || gate.passed === false) {
286
+ gateKnownRuns += 1;
287
+ if (gate.passed === true) {
288
+ gatePassedRuns += 1;
289
+ }
290
+ }
291
+
292
+ const specRate = Number(gateActual.spec_success_rate_percent);
293
+ if (Number.isFinite(specRate)) {
294
+ specRateValues.push(specRate);
295
+ }
296
+
297
+ const riskLevel = `${gateActual.risk_level || 'unknown'}`.trim().toLowerCase();
298
+ if (risk[riskLevel] !== undefined) {
299
+ risk[riskLevel] += 1;
300
+ } else {
301
+ risk.unknown += 1;
302
+ }
303
+
304
+ const scenePackage = session && session.scene_package_batch && typeof session.scene_package_batch === 'object'
305
+ ? session.scene_package_batch
306
+ : {};
307
+ const sceneSummary = scenePackage && scenePackage.summary && typeof scenePackage.summary === 'object'
308
+ ? scenePackage.summary
309
+ : {};
310
+ const scenePassedCandidate = sceneSummary.batch_gate_passed;
311
+ if (scenePassedCandidate === true || scenePassedCandidate === false) {
312
+ sceneKnownRuns += 1;
313
+ if (scenePassedCandidate === true) {
314
+ scenePassedRuns += 1;
315
+ }
316
+ }
317
+
318
+ const preflight = session && session.release_gate_preflight && typeof session.release_gate_preflight === 'object'
319
+ ? session.release_gate_preflight
320
+ : {};
321
+ if (preflight.blocked === true || preflight.blocked === false) {
322
+ preflightKnownRuns += 1;
323
+ if (preflight.blocked === true) {
324
+ preflightBlockedRuns += 1;
325
+ }
326
+ }
327
+
328
+ const expectedUnknown = Number(
329
+ gateActual.capability_expected_unknown_count !== undefined
330
+ ? gateActual.capability_expected_unknown_count
331
+ : null
332
+ );
333
+ if (Number.isFinite(expectedUnknown)) {
334
+ expectedUnknownKnown.push(Math.max(0, expectedUnknown));
335
+ }
336
+ const providedUnknown = Number(
337
+ gateActual.capability_provided_unknown_count !== undefined
338
+ ? gateActual.capability_provided_unknown_count
339
+ : null
340
+ );
341
+ if (Number.isFinite(providedUnknown)) {
342
+ providedUnknownKnown.push(Math.max(0, providedUnknown));
343
+ }
344
+ }
345
+
346
+ const expectedUnknownPositive = expectedUnknownKnown.filter(value => value > 0).length;
347
+ const providedUnknownPositive = providedUnknownKnown.filter(value => value > 0).length;
348
+
349
+ return {
350
+ total_runs: scoped.length,
351
+ status_counts: statusCounts,
352
+ gate_passed_runs: gatePassedRuns,
353
+ gate_known_runs: gateKnownRuns,
354
+ gate_pass_rate_percent: toPercent(gatePassedRuns, gateKnownRuns),
355
+ avg_spec_success_rate_percent: toAverage(specRateValues),
356
+ risk_levels: risk,
357
+ scene_batch_passed_runs: scenePassedRuns,
358
+ scene_batch_known_runs: sceneKnownRuns,
359
+ scene_batch_pass_rate_percent: toPercent(scenePassedRuns, sceneKnownRuns),
360
+ release_preflight_blocked_runs: preflightBlockedRuns,
361
+ release_preflight_known_runs: preflightKnownRuns,
362
+ release_preflight_block_rate_percent: toPercent(preflightBlockedRuns, preflightKnownRuns),
363
+ capability_expected_unknown_positive_runs: expectedUnknownPositive,
364
+ capability_expected_unknown_known_runs: expectedUnknownKnown.length,
365
+ capability_expected_unknown_positive_rate_percent: toPercent(expectedUnknownPositive, expectedUnknownKnown.length),
366
+ capability_provided_unknown_positive_runs: providedUnknownPositive,
367
+ capability_provided_unknown_known_runs: providedUnknownKnown.length,
368
+ capability_provided_unknown_positive_rate_percent: toPercent(providedUnknownPositive, providedUnknownKnown.length)
369
+ };
370
+ }
371
+
372
+ function buildReleaseGateHistorySnapshot(payload, window) {
373
+ const entries = Array.isArray(payload && payload.entries) ? payload.entries : [];
374
+ const scoped = filterByWindow(
375
+ entries,
376
+ window,
377
+ ['evaluated_at'],
378
+ true
379
+ );
380
+
381
+ let gateKnown = 0;
382
+ let gatePassed = 0;
383
+ let preflightKnown = 0;
384
+ let preflightBlocked = 0;
385
+ let driftKnown = 0;
386
+ let driftPositive = 0;
387
+ const risk = {
388
+ low: 0,
389
+ medium: 0,
390
+ high: 0,
391
+ unknown: 0
392
+ };
393
+ const expectedUnknownKnown = [];
394
+ const providedUnknownKnown = [];
395
+
396
+ for (const entry of scoped) {
397
+ if (entry && (entry.gate_passed === true || entry.gate_passed === false)) {
398
+ gateKnown += 1;
399
+ if (entry.gate_passed === true) {
400
+ gatePassed += 1;
401
+ }
402
+ }
403
+
404
+ const riskLevel = `${entry && entry.risk_level ? entry.risk_level : 'unknown'}`.trim().toLowerCase();
405
+ if (risk[riskLevel] !== undefined) {
406
+ risk[riskLevel] += 1;
407
+ } else {
408
+ risk.unknown += 1;
409
+ }
410
+
411
+ if (entry && (entry.release_gate_preflight_blocked === true || entry.release_gate_preflight_blocked === false)) {
412
+ preflightKnown += 1;
413
+ if (entry.release_gate_preflight_blocked === true) {
414
+ preflightBlocked += 1;
415
+ }
416
+ }
417
+
418
+ const driftAlertCount = Number(entry && entry.drift_alert_count);
419
+ if (Number.isFinite(driftAlertCount)) {
420
+ driftKnown += 1;
421
+ if (driftAlertCount > 0) {
422
+ driftPositive += 1;
423
+ }
424
+ }
425
+
426
+ const expectedUnknown = Number(entry && entry.capability_expected_unknown_count);
427
+ if (Number.isFinite(expectedUnknown)) {
428
+ expectedUnknownKnown.push(Math.max(0, expectedUnknown));
429
+ }
430
+ const providedUnknown = Number(entry && entry.capability_provided_unknown_count);
431
+ if (Number.isFinite(providedUnknown)) {
432
+ providedUnknownKnown.push(Math.max(0, providedUnknown));
433
+ }
434
+ }
435
+
436
+ const expectedUnknownPositive = expectedUnknownKnown.filter(value => value > 0).length;
437
+ const providedUnknownPositive = providedUnknownKnown.filter(value => value > 0).length;
438
+
439
+ return {
440
+ total_entries: scoped.length,
441
+ gate_passed_runs: gatePassed,
442
+ gate_known_runs: gateKnown,
443
+ gate_pass_rate_percent: toPercent(gatePassed, gateKnown),
444
+ risk_levels: risk,
445
+ release_preflight_blocked_runs: preflightBlocked,
446
+ release_preflight_known_runs: preflightKnown,
447
+ release_preflight_block_rate_percent: toPercent(preflightBlocked, preflightKnown),
448
+ drift_alert_positive_runs: driftPositive,
449
+ drift_alert_known_runs: driftKnown,
450
+ drift_alert_positive_rate_percent: toPercent(driftPositive, driftKnown),
451
+ capability_expected_unknown_positive_runs: expectedUnknownPositive,
452
+ capability_expected_unknown_known_runs: expectedUnknownKnown.length,
453
+ capability_expected_unknown_positive_rate_percent: toPercent(expectedUnknownPositive, expectedUnknownKnown.length),
454
+ capability_provided_unknown_positive_runs: providedUnknownPositive,
455
+ capability_provided_unknown_known_runs: providedUnknownKnown.length,
456
+ capability_provided_unknown_positive_rate_percent: toPercent(providedUnknownPositive, providedUnknownKnown.length)
457
+ };
458
+ }
459
+
460
+ function buildInteractiveGovernanceSnapshot(payload, window) {
461
+ const generatedTs = parseTimestamp(payload, ['generated_at']);
462
+ const generatedAt = payload && payload.generated_at ? payload.generated_at : null;
463
+ const inWindow = generatedTs === null
464
+ ? null
465
+ : (generatedTs >= window.from.getTime() && generatedTs <= window.to.getTime());
466
+ const summary = payload && payload.summary && typeof payload.summary === 'object'
467
+ ? payload.summary
468
+ : {};
469
+ const metrics = payload && payload.metrics && typeof payload.metrics === 'object'
470
+ ? payload.metrics
471
+ : {};
472
+ return {
473
+ generated_at: generatedAt,
474
+ in_window: inWindow,
475
+ status: typeof summary.status === 'string' ? summary.status : null,
476
+ breaches: Number.isFinite(Number(summary.breaches)) ? Number(summary.breaches) : null,
477
+ warnings: Number.isFinite(Number(summary.warnings)) ? Number(summary.warnings) : null,
478
+ authorization_tier_total: Number.isFinite(Number(metrics.authorization_tier_total))
479
+ ? Number(metrics.authorization_tier_total)
480
+ : null,
481
+ authorization_tier_deny_total: Number.isFinite(Number(metrics.authorization_tier_deny_total))
482
+ ? Number(metrics.authorization_tier_deny_total)
483
+ : null,
484
+ authorization_tier_review_required_total: Number.isFinite(Number(metrics.authorization_tier_review_required_total))
485
+ ? Number(metrics.authorization_tier_review_required_total)
486
+ : null,
487
+ authorization_tier_block_rate_percent: Number.isFinite(Number(metrics.authorization_tier_block_rate_percent))
488
+ ? Number(metrics.authorization_tier_block_rate_percent)
489
+ : null,
490
+ dialogue_authorization_total: Number.isFinite(Number(metrics.dialogue_authorization_total))
491
+ ? Number(metrics.dialogue_authorization_total)
492
+ : null,
493
+ dialogue_authorization_block_total: Number.isFinite(Number(metrics.dialogue_authorization_block_total))
494
+ ? Number(metrics.dialogue_authorization_block_total)
495
+ : null,
496
+ dialogue_authorization_block_rate_percent: Number.isFinite(Number(metrics.dialogue_authorization_block_rate_percent))
497
+ ? Number(metrics.dialogue_authorization_block_rate_percent)
498
+ : null,
499
+ dialogue_authorization_user_app_apply_attempt_total: Number.isFinite(
500
+ Number(metrics.dialogue_authorization_user_app_apply_attempt_total)
501
+ )
502
+ ? Number(metrics.dialogue_authorization_user_app_apply_attempt_total)
503
+ : null,
504
+ dialogue_authorization_unknown_business_mode_total: Number.isFinite(
505
+ Number(metrics.dialogue_authorization_unknown_business_mode_total)
506
+ )
507
+ ? Number(metrics.dialogue_authorization_unknown_business_mode_total)
508
+ : null,
509
+ runtime_total: Number.isFinite(Number(metrics.runtime_total))
510
+ ? Number(metrics.runtime_total)
511
+ : null,
512
+ runtime_deny_total: Number.isFinite(Number(metrics.runtime_deny_total))
513
+ ? Number(metrics.runtime_deny_total)
514
+ : null,
515
+ runtime_review_required_total: Number.isFinite(Number(metrics.runtime_review_required_total))
516
+ ? Number(metrics.runtime_review_required_total)
517
+ : null,
518
+ runtime_block_rate_percent: Number.isFinite(Number(metrics.runtime_block_rate_percent))
519
+ ? Number(metrics.runtime_block_rate_percent)
520
+ : null,
521
+ runtime_ui_mode_violation_total: Number.isFinite(Number(metrics.runtime_ui_mode_violation_total))
522
+ ? Number(metrics.runtime_ui_mode_violation_total)
523
+ : null,
524
+ runtime_ui_mode_violation_rate_percent: Number.isFinite(Number(metrics.runtime_ui_mode_violation_rate_percent))
525
+ ? Number(metrics.runtime_ui_mode_violation_rate_percent)
526
+ : null,
527
+ runtime_unknown_business_mode_total: Number.isFinite(Number(metrics.runtime_unknown_business_mode_total))
528
+ ? Number(metrics.runtime_unknown_business_mode_total)
529
+ : null,
530
+ authorization_tier_unknown_business_mode_total: Number.isFinite(
531
+ Number(metrics.authorization_tier_unknown_business_mode_total)
532
+ )
533
+ ? Number(metrics.authorization_tier_unknown_business_mode_total)
534
+ : null,
535
+ business_mode_unknown_signal_total: Number.isFinite(Number(metrics.business_mode_unknown_signal_total))
536
+ ? Number(metrics.business_mode_unknown_signal_total)
537
+ : null,
538
+ matrix_signal_total: Number.isFinite(Number(metrics.matrix_signal_total))
539
+ ? Number(metrics.matrix_signal_total)
540
+ : null,
541
+ matrix_portfolio_pass_rate_percent: Number.isFinite(Number(metrics.matrix_portfolio_pass_rate_percent))
542
+ ? Number(metrics.matrix_portfolio_pass_rate_percent)
543
+ : null,
544
+ matrix_regression_positive_rate_percent: Number.isFinite(Number(metrics.matrix_regression_positive_rate_percent))
545
+ ? Number(metrics.matrix_regression_positive_rate_percent)
546
+ : null,
547
+ matrix_stage_error_rate_percent: Number.isFinite(Number(metrics.matrix_stage_error_rate_percent))
548
+ ? Number(metrics.matrix_stage_error_rate_percent)
549
+ : null
550
+ };
551
+ }
552
+
553
+ function buildMatrixSignalSnapshot(records, window) {
554
+ const scoped = filterByWindow(
555
+ records,
556
+ window,
557
+ ['generated_at', 'timestamp', 'created_at'],
558
+ false
559
+ );
560
+ let portfolioPassed = 0;
561
+ let regressionPositive = 0;
562
+ let stageError = 0;
563
+ const scores = [];
564
+
565
+ for (const entry of scoped) {
566
+ const matrix = entry && entry.matrix && typeof entry.matrix === 'object'
567
+ ? entry.matrix
568
+ : null;
569
+ if (!matrix) {
570
+ continue;
571
+ }
572
+ if (matrix.portfolio_passed === true) {
573
+ portfolioPassed += 1;
574
+ }
575
+ if (Number(matrix.regression_count) > 0) {
576
+ regressionPositive += 1;
577
+ }
578
+ const stageStatus = `${matrix.stage_status || ''}`.trim().toLowerCase();
579
+ if (stageStatus === 'error' || stageStatus === 'non-zero-exit') {
580
+ stageError += 1;
581
+ }
582
+ const score = Number(matrix.avg_score);
583
+ if (Number.isFinite(score)) {
584
+ scores.push(score);
585
+ }
586
+ }
587
+
588
+ return {
589
+ total_signals: scoped.length,
590
+ portfolio_passed_signals: portfolioPassed,
591
+ portfolio_pass_rate_percent: toPercent(portfolioPassed, scoped.length),
592
+ regression_positive_signals: regressionPositive,
593
+ regression_positive_rate_percent: toPercent(regressionPositive, scoped.length),
594
+ stage_error_signals: stageError,
595
+ stage_error_rate_percent: toPercent(stageError, scoped.length),
596
+ avg_score: toAverage(scores)
597
+ };
598
+ }
599
+
600
+ function promoteRisk(current, target) {
601
+ const rank = {
602
+ low: 1,
603
+ medium: 2,
604
+ high: 3
605
+ };
606
+ return rank[target] > rank[current] ? target : current;
607
+ }
608
+
609
+ function buildHealth(snapshots, warnings) {
610
+ let risk = 'low';
611
+ const concerns = [];
612
+ const recommendations = [];
613
+ const seen = new Set();
614
+ const pushConcern = (value) => {
615
+ const text = `${value || ''}`.trim();
616
+ if (!text || seen.has(`c:${text}`)) {
617
+ return;
618
+ }
619
+ seen.add(`c:${text}`);
620
+ concerns.push(text);
621
+ };
622
+ const pushRecommendation = (value) => {
623
+ const text = `${value || ''}`.trim();
624
+ if (!text || seen.has(`r:${text}`)) {
625
+ return;
626
+ }
627
+ seen.add(`r:${text}`);
628
+ recommendations.push(text);
629
+ };
630
+
631
+ if (warnings.length > 0) {
632
+ risk = promoteRisk(risk, 'medium');
633
+ pushConcern(`evidence inputs missing/invalid: ${warnings.length}`);
634
+ pushRecommendation('Repair missing inputs and regenerate: `node scripts/release-ops-weekly-summary.js --json`.');
635
+ }
636
+
637
+ const handoff = snapshots.handoff;
638
+ if (handoff.total_runs === 0) {
639
+ risk = promoteRisk(risk, 'medium');
640
+ pushConcern('handoff evidence has no runs in current window');
641
+ pushRecommendation('Generate handoff evidence: `npx sce auto handoff run --manifest docs/handoffs/handoff-manifest.json --profile moqui --json`.');
642
+ }
643
+ if (Number.isFinite(handoff.gate_pass_rate_percent) && handoff.gate_pass_rate_percent < 95) {
644
+ risk = promoteRisk(risk, handoff.gate_pass_rate_percent < 80 ? 'high' : 'medium');
645
+ pushConcern(`handoff gate pass rate is ${handoff.gate_pass_rate_percent}%`);
646
+ pushRecommendation('Investigate gate regressions: `npx sce auto handoff regression --session-id latest --json`.');
647
+ }
648
+ if (
649
+ Number.isFinite(handoff.capability_expected_unknown_positive_rate_percent)
650
+ && handoff.capability_expected_unknown_positive_rate_percent > 0
651
+ ) {
652
+ risk = promoteRisk(risk, 'medium');
653
+ pushConcern('handoff capability expected-unknown count is positive');
654
+ pushRecommendation('Close capability lexicon gaps: `node scripts/moqui-lexicon-audit.js --manifest docs/handoffs/handoff-manifest.json --fail-on-gap --json`.');
655
+ }
656
+ if (
657
+ Number.isFinite(handoff.capability_provided_unknown_positive_rate_percent)
658
+ && handoff.capability_provided_unknown_positive_rate_percent > 0
659
+ ) {
660
+ risk = promoteRisk(risk, 'medium');
661
+ pushConcern('handoff capability provided-unknown count is positive');
662
+ pushRecommendation('Regenerate capability matrix and enforce semantic closure: `npx sce auto handoff capability-matrix --manifest docs/handoffs/handoff-manifest.json --profile moqui --fail-on-gap --json`.');
663
+ }
664
+
665
+ const history = snapshots.release_gate_history;
666
+ if (history.total_entries === 0) {
667
+ risk = promoteRisk(risk, 'medium');
668
+ pushConcern('release gate history has no entries in current window');
669
+ pushRecommendation('Refresh release gate history index: `npx sce auto handoff gate-index --dir .sce/reports/release-evidence --out .sce/reports/release-evidence/release-gate-history.json --json`.');
670
+ }
671
+ if (Number.isFinite(history.gate_pass_rate_percent) && history.gate_pass_rate_percent < 90) {
672
+ risk = promoteRisk(risk, history.gate_pass_rate_percent < 75 ? 'high' : 'medium');
673
+ pushConcern(`release gate history pass rate is ${history.gate_pass_rate_percent}%`);
674
+ }
675
+ if (
676
+ Number.isFinite(history.release_preflight_block_rate_percent)
677
+ && history.release_preflight_block_rate_percent >= 20
678
+ ) {
679
+ risk = promoteRisk(risk, 'medium');
680
+ pushConcern(`release preflight blocked rate is ${history.release_preflight_block_rate_percent}%`);
681
+ }
682
+
683
+ const governance = snapshots.interactive_governance;
684
+ if (governance.status === 'alert') {
685
+ risk = promoteRisk(risk, governance.breaches >= 3 ? 'high' : 'medium');
686
+ pushConcern('interactive governance report is in alert status');
687
+ pushRecommendation('Resolve governance alerts: `node scripts/interactive-governance-report.js --period weekly --fail-on-alert --json`.');
688
+ }
689
+ if (
690
+ Number.isFinite(governance.authorization_tier_block_rate_percent)
691
+ && governance.authorization_tier_block_rate_percent > 40
692
+ ) {
693
+ risk = promoteRisk(risk, governance.authorization_tier_block_rate_percent >= 60 ? 'high' : 'medium');
694
+ pushConcern(`authorization-tier block rate is ${governance.authorization_tier_block_rate_percent}%`);
695
+ pushRecommendation('Tune dialogue profile + authorization-tier policy to reduce deny/review pressure for actionable requests.');
696
+ }
697
+ if (
698
+ Number.isFinite(governance.dialogue_authorization_block_rate_percent)
699
+ && governance.dialogue_authorization_block_rate_percent > 40
700
+ ) {
701
+ risk = promoteRisk(risk, governance.dialogue_authorization_block_rate_percent >= 60 ? 'high' : 'medium');
702
+ pushConcern(`dialogue-authorization block rate is ${governance.dialogue_authorization_block_rate_percent}%`);
703
+ pushRecommendation('Tune authorization dialogue policy and ui-mode routing to reduce blocked user intent execution.');
704
+ }
705
+ if (
706
+ Number.isFinite(governance.runtime_ui_mode_violation_total)
707
+ && governance.runtime_ui_mode_violation_total > 0
708
+ ) {
709
+ risk = promoteRisk(risk, governance.runtime_ui_mode_violation_total >= 3 ? 'high' : 'medium');
710
+ pushConcern(`runtime ui-mode violations observed: ${governance.runtime_ui_mode_violation_total}`);
711
+ pushRecommendation('Enforce dual-surface routing: user-app suggestion-only, ops-console for apply workflows.');
712
+ }
713
+ if (
714
+ Number.isFinite(governance.business_mode_unknown_signal_total)
715
+ && governance.business_mode_unknown_signal_total > 0
716
+ ) {
717
+ risk = promoteRisk(risk, 'medium');
718
+ pushConcern(`interactive governance signals missing business-mode tags: ${governance.business_mode_unknown_signal_total}`);
719
+ pushRecommendation('Upgrade to latest interactive-flow/loop so governance signals always include business_mode.');
720
+ }
721
+
722
+ const matrix = snapshots.matrix_signals;
723
+ if (matrix.total_signals === 0) {
724
+ risk = promoteRisk(risk, 'medium');
725
+ pushConcern('interactive matrix signals missing in current window');
726
+ pushRecommendation('Generate matrix signals via interactive flow smoke: `npm run test:interactive-flow-smoke`.');
727
+ } else if (
728
+ Number.isFinite(matrix.regression_positive_rate_percent)
729
+ && matrix.regression_positive_rate_percent > 20
730
+ ) {
731
+ risk = promoteRisk(risk, 'medium');
732
+ pushConcern(`matrix regression-positive rate is ${matrix.regression_positive_rate_percent}%`);
733
+ pushRecommendation('Run matrix remediation queue: `npm run report:matrix-remediation-queue`.');
734
+ }
735
+
736
+ if (recommendations.length === 0) {
737
+ recommendations.push('Current weekly ops metrics are stable. Keep default release and governance gates enabled.');
738
+ }
739
+
740
+ return {
741
+ risk,
742
+ concerns,
743
+ recommendations
744
+ };
745
+ }
746
+
747
+ function buildMarkdown(report) {
748
+ const lines = [];
749
+ lines.push('# Release Weekly Ops Summary');
750
+ lines.push('');
751
+ lines.push(`- Generated at: ${report.generated_at}`);
752
+ lines.push(`- Window: ${report.period.from} -> ${report.period.to}`);
753
+ lines.push(`- Risk: ${report.health.risk}`);
754
+ lines.push('');
755
+ lines.push('## Handoff');
756
+ lines.push('');
757
+ lines.push(`- Total runs: ${report.snapshots.handoff.total_runs}`);
758
+ lines.push(`- Gate pass rate: ${report.snapshots.handoff.gate_pass_rate_percent == null ? 'n/a' : `${report.snapshots.handoff.gate_pass_rate_percent}%`}`);
759
+ lines.push(`- Avg spec success: ${report.snapshots.handoff.avg_spec_success_rate_percent == null ? 'n/a' : `${report.snapshots.handoff.avg_spec_success_rate_percent}%`}`);
760
+ lines.push(`- Scene batch pass rate: ${report.snapshots.handoff.scene_batch_pass_rate_percent == null ? 'n/a' : `${report.snapshots.handoff.scene_batch_pass_rate_percent}%`}`);
761
+ lines.push(`- Release preflight blocked rate: ${report.snapshots.handoff.release_preflight_block_rate_percent == null ? 'n/a' : `${report.snapshots.handoff.release_preflight_block_rate_percent}%`}`);
762
+ lines.push('');
763
+ lines.push('## Release Gate History');
764
+ lines.push('');
765
+ lines.push(`- Entries: ${report.snapshots.release_gate_history.total_entries}`);
766
+ lines.push(`- Gate pass rate: ${report.snapshots.release_gate_history.gate_pass_rate_percent == null ? 'n/a' : `${report.snapshots.release_gate_history.gate_pass_rate_percent}%`}`);
767
+ lines.push(`- Preflight blocked rate: ${report.snapshots.release_gate_history.release_preflight_block_rate_percent == null ? 'n/a' : `${report.snapshots.release_gate_history.release_preflight_block_rate_percent}%`}`);
768
+ lines.push(`- Drift alert positive rate: ${report.snapshots.release_gate_history.drift_alert_positive_rate_percent == null ? 'n/a' : `${report.snapshots.release_gate_history.drift_alert_positive_rate_percent}%`}`);
769
+ lines.push('');
770
+ lines.push('## Interactive Governance');
771
+ lines.push('');
772
+ lines.push(`- Status: ${report.snapshots.interactive_governance.status || 'n/a'}`);
773
+ lines.push(`- Breaches: ${report.snapshots.interactive_governance.breaches == null ? 'n/a' : report.snapshots.interactive_governance.breaches}`);
774
+ lines.push(`- Warnings: ${report.snapshots.interactive_governance.warnings == null ? 'n/a' : report.snapshots.interactive_governance.warnings}`);
775
+ lines.push(`- Authorization tier signals: ${report.snapshots.interactive_governance.authorization_tier_total == null ? 'n/a' : report.snapshots.interactive_governance.authorization_tier_total}`);
776
+ lines.push(`- Authorization tier deny total: ${report.snapshots.interactive_governance.authorization_tier_deny_total == null ? 'n/a' : report.snapshots.interactive_governance.authorization_tier_deny_total}`);
777
+ lines.push(`- Authorization tier review-required total: ${report.snapshots.interactive_governance.authorization_tier_review_required_total == null ? 'n/a' : report.snapshots.interactive_governance.authorization_tier_review_required_total}`);
778
+ lines.push(`- Authorization tier block rate: ${report.snapshots.interactive_governance.authorization_tier_block_rate_percent == null ? 'n/a' : `${report.snapshots.interactive_governance.authorization_tier_block_rate_percent}%`}`);
779
+ lines.push(`- Dialogue authorization signals: ${report.snapshots.interactive_governance.dialogue_authorization_total == null ? 'n/a' : report.snapshots.interactive_governance.dialogue_authorization_total}`);
780
+ lines.push(`- Dialogue authorization block total: ${report.snapshots.interactive_governance.dialogue_authorization_block_total == null ? 'n/a' : report.snapshots.interactive_governance.dialogue_authorization_block_total}`);
781
+ lines.push(`- Dialogue authorization block rate: ${report.snapshots.interactive_governance.dialogue_authorization_block_rate_percent == null ? 'n/a' : `${report.snapshots.interactive_governance.dialogue_authorization_block_rate_percent}%`}`);
782
+ lines.push(`- Dialogue user-app apply attempts: ${report.snapshots.interactive_governance.dialogue_authorization_user_app_apply_attempt_total == null ? 'n/a' : report.snapshots.interactive_governance.dialogue_authorization_user_app_apply_attempt_total}`);
783
+ lines.push(`- Dialogue unknown business-mode signals: ${report.snapshots.interactive_governance.dialogue_authorization_unknown_business_mode_total == null ? 'n/a' : report.snapshots.interactive_governance.dialogue_authorization_unknown_business_mode_total}`);
784
+ lines.push(`- Runtime signals: ${report.snapshots.interactive_governance.runtime_total == null ? 'n/a' : report.snapshots.interactive_governance.runtime_total}`);
785
+ lines.push(`- Runtime block rate: ${report.snapshots.interactive_governance.runtime_block_rate_percent == null ? 'n/a' : `${report.snapshots.interactive_governance.runtime_block_rate_percent}%`}`);
786
+ lines.push(`- Runtime ui-mode violations: ${report.snapshots.interactive_governance.runtime_ui_mode_violation_total == null ? 'n/a' : report.snapshots.interactive_governance.runtime_ui_mode_violation_total}`);
787
+ lines.push(`- Runtime unknown business-mode signals: ${report.snapshots.interactive_governance.runtime_unknown_business_mode_total == null ? 'n/a' : report.snapshots.interactive_governance.runtime_unknown_business_mode_total}`);
788
+ lines.push(`- Authorization-tier unknown business-mode signals: ${report.snapshots.interactive_governance.authorization_tier_unknown_business_mode_total == null ? 'n/a' : report.snapshots.interactive_governance.authorization_tier_unknown_business_mode_total}`);
789
+ lines.push(`- Total unknown business-mode signals: ${report.snapshots.interactive_governance.business_mode_unknown_signal_total == null ? 'n/a' : report.snapshots.interactive_governance.business_mode_unknown_signal_total}`);
790
+ lines.push('');
791
+ lines.push('## Matrix Signals');
792
+ lines.push('');
793
+ lines.push(`- Total signals: ${report.snapshots.matrix_signals.total_signals}`);
794
+ lines.push(`- Portfolio pass rate: ${report.snapshots.matrix_signals.portfolio_pass_rate_percent == null ? 'n/a' : `${report.snapshots.matrix_signals.portfolio_pass_rate_percent}%`}`);
795
+ lines.push(`- Regression-positive rate: ${report.snapshots.matrix_signals.regression_positive_rate_percent == null ? 'n/a' : `${report.snapshots.matrix_signals.regression_positive_rate_percent}%`}`);
796
+ lines.push(`- Stage error rate: ${report.snapshots.matrix_signals.stage_error_rate_percent == null ? 'n/a' : `${report.snapshots.matrix_signals.stage_error_rate_percent}%`}`);
797
+ if (report.warnings.length > 0) {
798
+ lines.push('');
799
+ lines.push('## Warnings');
800
+ lines.push('');
801
+ for (const warning of report.warnings) {
802
+ lines.push(`- ${warning}`);
803
+ }
804
+ }
805
+ lines.push('');
806
+ lines.push('## Concerns');
807
+ lines.push('');
808
+ if (report.health.concerns.length === 0) {
809
+ lines.push('- none');
810
+ } else {
811
+ for (const concern of report.health.concerns) {
812
+ lines.push(`- ${concern}`);
813
+ }
814
+ }
815
+ lines.push('');
816
+ lines.push('## Recommendations');
817
+ lines.push('');
818
+ for (const recommendation of report.health.recommendations) {
819
+ lines.push(`- ${recommendation}`);
820
+ }
821
+ return `${lines.join('\n')}\n`;
822
+ }
823
+
824
+ async function buildReleaseOpsWeeklySummary(cwd, options, now = new Date()) {
825
+ const window = buildWindow(options, now);
826
+ const [evidenceInput, gateHistoryInput, governanceInput, matrixSignalsInput] = await Promise.all([
827
+ safeReadJson(cwd, options.evidence),
828
+ safeReadJson(cwd, options.gateHistory),
829
+ safeReadJson(cwd, options.interactiveGovernance),
830
+ safeReadJsonLines(cwd, options.matrixSignals)
831
+ ]);
832
+
833
+ const warnings = [];
834
+ const collectWarning = (label, input) => {
835
+ if (!input.exists) {
836
+ warnings.push(`${label}: missing (${input.path})`);
837
+ return;
838
+ }
839
+ if (input.parse_error) {
840
+ warnings.push(`${label}: parse error (${input.path}) ${input.parse_error}`);
841
+ }
842
+ };
843
+ collectWarning('evidence', evidenceInput);
844
+ collectWarning('gate_history', gateHistoryInput);
845
+ collectWarning('interactive_governance', governanceInput);
846
+ collectWarning('matrix_signals', matrixSignalsInput);
847
+
848
+ const snapshots = {
849
+ handoff: buildHandoffSnapshot(evidenceInput.payload || {}, window),
850
+ release_gate_history: buildReleaseGateHistorySnapshot(gateHistoryInput.payload || {}, window),
851
+ interactive_governance: buildInteractiveGovernanceSnapshot(governanceInput.payload || {}, window),
852
+ matrix_signals: buildMatrixSignalSnapshot(matrixSignalsInput.records || [], window)
853
+ };
854
+
855
+ const health = buildHealth(snapshots, warnings);
856
+ const outPath = resolvePath(cwd, options.out);
857
+ const markdownOutPath = resolvePath(cwd, options.markdownOut);
858
+
859
+ const report = {
860
+ mode: 'release-weekly-ops-summary',
861
+ generated_at: now.toISOString(),
862
+ period: {
863
+ mode: window.mode,
864
+ window_days: options.windowDays,
865
+ from: window.from.toISOString(),
866
+ to: window.to.toISOString()
867
+ },
868
+ inputs: {
869
+ evidence: evidenceInput,
870
+ gate_history: gateHistoryInput,
871
+ interactive_governance: governanceInput,
872
+ matrix_signals: {
873
+ path: matrixSignalsInput.path,
874
+ exists: matrixSignalsInput.exists,
875
+ parse_error: matrixSignalsInput.parse_error
876
+ }
877
+ },
878
+ warnings,
879
+ snapshots,
880
+ health,
881
+ output: {
882
+ json: path.relative(cwd, outPath) || '.',
883
+ markdown: path.relative(cwd, markdownOutPath) || '.'
884
+ }
885
+ };
886
+
887
+ await fs.ensureDir(path.dirname(outPath));
888
+ await fs.writeJson(outPath, report, { spaces: 2 });
889
+ await fs.ensureDir(path.dirname(markdownOutPath));
890
+ await fs.writeFile(markdownOutPath, buildMarkdown(report), 'utf8');
891
+
892
+ return report;
893
+ }
894
+
895
+ async function main() {
896
+ const options = parseArgs(process.argv.slice(2));
897
+ const cwd = process.cwd();
898
+ const report = await buildReleaseOpsWeeklySummary(cwd, options, new Date());
899
+ if (options.json) {
900
+ process.stdout.write(`${JSON.stringify(report, null, 2)}\n`);
901
+ } else {
902
+ process.stdout.write(`Release weekly ops summary generated (${report.health.risk}).\n`);
903
+ process.stdout.write(`- JSON: ${report.output.json}\n`);
904
+ process.stdout.write(`- Markdown: ${report.output.markdown}\n`);
905
+ }
906
+ }
907
+
908
+ if (require.main === module) {
909
+ main().catch((error) => {
910
+ console.error(`Release weekly ops summary failed: ${error.message}`);
911
+ process.exit(1);
912
+ });
913
+ }
914
+
915
+ module.exports = {
916
+ DEFAULT_EVIDENCE,
917
+ DEFAULT_GATE_HISTORY,
918
+ DEFAULT_INTERACTIVE_GOVERNANCE,
919
+ DEFAULT_MATRIX_SIGNALS,
920
+ DEFAULT_OUT,
921
+ DEFAULT_MARKDOWN_OUT,
922
+ parseArgs,
923
+ buildWindow,
924
+ safeReadJson,
925
+ safeReadJsonLines,
926
+ buildHandoffSnapshot,
927
+ buildReleaseGateHistorySnapshot,
928
+ buildInteractiveGovernanceSnapshot,
929
+ buildMatrixSignalSnapshot,
930
+ buildHealth,
931
+ buildMarkdown,
932
+ buildReleaseOpsWeeklySummary,
933
+ main
934
+ };