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.
Files changed (61) hide show
  1. package/CHANGELOG.md +25 -0
  2. package/bin/scene-capability-engine.js +36 -2
  3. package/docs/command-reference.md +5 -0
  4. package/docs/releases/README.md +2 -0
  5. package/docs/releases/v3.6.45.md +18 -0
  6. package/docs/releases/v3.6.46.md +23 -0
  7. package/docs/zh/releases/README.md +2 -0
  8. package/docs/zh/releases/v3.6.45.md +18 -0
  9. package/docs/zh/releases/v3.6.46.md +23 -0
  10. package/lib/workspace/collab-governance-audit.js +575 -0
  11. package/package.json +4 -2
  12. package/scripts/auto-strategy-router.js +231 -0
  13. package/scripts/capability-mapping-report.js +339 -0
  14. package/scripts/check-branding-consistency.js +140 -0
  15. package/scripts/check-sce-tracking.js +54 -0
  16. package/scripts/check-skip-allowlist.js +94 -0
  17. package/scripts/errorbook-registry-health-gate.js +172 -0
  18. package/scripts/errorbook-release-gate.js +132 -0
  19. package/scripts/failure-attribution-repair.js +317 -0
  20. package/scripts/git-managed-gate.js +464 -0
  21. package/scripts/interactive-approval-event-projection.js +400 -0
  22. package/scripts/interactive-approval-workflow.js +829 -0
  23. package/scripts/interactive-authorization-tier-evaluate.js +413 -0
  24. package/scripts/interactive-change-plan-gate.js +225 -0
  25. package/scripts/interactive-context-bridge.js +617 -0
  26. package/scripts/interactive-customization-loop.js +1690 -0
  27. package/scripts/interactive-dialogue-governance.js +842 -0
  28. package/scripts/interactive-feedback-log.js +253 -0
  29. package/scripts/interactive-flow-smoke.js +238 -0
  30. package/scripts/interactive-flow.js +1059 -0
  31. package/scripts/interactive-governance-report.js +1112 -0
  32. package/scripts/interactive-intent-build.js +707 -0
  33. package/scripts/interactive-loop-smoke.js +215 -0
  34. package/scripts/interactive-moqui-adapter.js +304 -0
  35. package/scripts/interactive-plan-build.js +426 -0
  36. package/scripts/interactive-runtime-policy-evaluate.js +495 -0
  37. package/scripts/interactive-work-order-build.js +552 -0
  38. package/scripts/matrix-regression-gate.js +167 -0
  39. package/scripts/moqui-core-regression-suite.js +397 -0
  40. package/scripts/moqui-lexicon-audit.js +651 -0
  41. package/scripts/moqui-matrix-remediation-phased-runner.js +865 -0
  42. package/scripts/moqui-matrix-remediation-queue.js +852 -0
  43. package/scripts/moqui-metadata-extract.js +1340 -0
  44. package/scripts/moqui-rebuild-gate.js +167 -0
  45. package/scripts/moqui-release-summary.js +729 -0
  46. package/scripts/moqui-standard-rebuild.js +1370 -0
  47. package/scripts/moqui-template-baseline-report.js +682 -0
  48. package/scripts/npm-package-runtime-asset-check.js +221 -0
  49. package/scripts/problem-closure-gate.js +441 -0
  50. package/scripts/release-asset-integrity-check.js +216 -0
  51. package/scripts/release-asset-nonempty-normalize.js +166 -0
  52. package/scripts/release-drift-evaluate.js +223 -0
  53. package/scripts/release-drift-signals.js +255 -0
  54. package/scripts/release-governance-snapshot-export.js +132 -0
  55. package/scripts/release-ops-weekly-summary.js +934 -0
  56. package/scripts/release-risk-remediation-bundle.js +315 -0
  57. package/scripts/release-weekly-ops-gate.js +423 -0
  58. package/scripts/state-migration-reconciliation-gate.js +110 -0
  59. package/scripts/state-storage-tiering-audit.js +337 -0
  60. package/scripts/steering-content-audit.js +393 -0
  61. package/scripts/symbol-evidence-locate.js +366 -0
@@ -0,0 +1,495 @@
1
+ #!/usr/bin/env node
2
+ 'use strict';
3
+
4
+ const path = require('path');
5
+ const fs = require('fs-extra');
6
+
7
+ const DEFAULT_POLICY = 'docs/interactive-customization/runtime-mode-policy-baseline.json';
8
+ const DEFAULT_OUT = '.sce/reports/interactive-runtime-policy.json';
9
+ const RISK_ORDER = ['low', 'medium', 'high', 'critical'];
10
+
11
+ function parseArgs(argv) {
12
+ const options = {
13
+ plan: null,
14
+ uiMode: null,
15
+ runtimeMode: null,
16
+ runtimeEnvironment: null,
17
+ policy: DEFAULT_POLICY,
18
+ out: DEFAULT_OUT,
19
+ failOnNonAllow: false,
20
+ json: false
21
+ };
22
+
23
+ for (let index = 0; index < argv.length; index += 1) {
24
+ const token = argv[index];
25
+ const next = argv[index + 1];
26
+ if (token === '--plan' && next) {
27
+ options.plan = next;
28
+ index += 1;
29
+ } else if (token === '--ui-mode' && next) {
30
+ options.uiMode = `${next}`.trim().toLowerCase();
31
+ index += 1;
32
+ } else if (token === '--runtime-mode' && next) {
33
+ options.runtimeMode = next;
34
+ index += 1;
35
+ } else if (token === '--runtime-environment' && next) {
36
+ options.runtimeEnvironment = next;
37
+ index += 1;
38
+ } else if (token === '--policy' && next) {
39
+ options.policy = next;
40
+ index += 1;
41
+ } else if (token === '--out' && next) {
42
+ options.out = next;
43
+ index += 1;
44
+ } else if (token === '--fail-on-non-allow') {
45
+ options.failOnNonAllow = true;
46
+ } else if (token === '--json') {
47
+ options.json = true;
48
+ } else if (token === '--help' || token === '-h') {
49
+ printHelpAndExit(0);
50
+ }
51
+ }
52
+
53
+ if (!options.plan) {
54
+ throw new Error('--plan is required.');
55
+ }
56
+ return options;
57
+ }
58
+
59
+ function printHelpAndExit(code) {
60
+ const lines = [
61
+ 'Usage: node scripts/interactive-runtime-policy-evaluate.js --plan <path> [options]',
62
+ '',
63
+ 'Options:',
64
+ ' --plan <path> Change plan JSON file (required)',
65
+ ' --ui-mode <name> UI surface mode (optional, evaluated when policy.ui_modes is present)',
66
+ ' --runtime-mode <name> Runtime mode (default from policy defaults.runtime_mode)',
67
+ ' --runtime-environment <name> Runtime environment (default from policy defaults.runtime_environment)',
68
+ ` --policy <path> Runtime policy JSON file (default: ${DEFAULT_POLICY})`,
69
+ ` --out <path> Report JSON output path (default: ${DEFAULT_OUT})`,
70
+ ' --fail-on-non-allow Exit code 2 when decision is deny/review-required',
71
+ ' --json Print report to stdout',
72
+ ' -h, --help Show this help'
73
+ ];
74
+ console.log(lines.join('\n'));
75
+ process.exit(code);
76
+ }
77
+
78
+ function resolvePath(cwd, value) {
79
+ return path.isAbsolute(value) ? value : path.resolve(cwd, value);
80
+ }
81
+
82
+ function normalizeRiskLevel(value, fallback = 'medium') {
83
+ const normalized = `${value || ''}`.trim().toLowerCase();
84
+ return RISK_ORDER.includes(normalized) ? normalized : fallback;
85
+ }
86
+
87
+ function riskRank(value) {
88
+ return RISK_ORDER.indexOf(normalizeRiskLevel(value, 'medium'));
89
+ }
90
+
91
+ function isMutatingAction(action) {
92
+ const actionType = `${action && action.type ? action.type : ''}`.trim().toLowerCase();
93
+ return actionType.length > 0 && actionType !== 'analysis_only';
94
+ }
95
+
96
+ async function readJsonFile(filePath, label) {
97
+ if (!(await fs.pathExists(filePath))) {
98
+ throw new Error(`${label} not found: ${filePath}`);
99
+ }
100
+ const text = await fs.readFile(filePath, 'utf8');
101
+ try {
102
+ return JSON.parse(text);
103
+ } catch (error) {
104
+ throw new Error(`invalid JSON in ${label}: ${error.message}`);
105
+ }
106
+ }
107
+
108
+ function toUniqueList(values = []) {
109
+ return Array.from(new Set(
110
+ values
111
+ .map(item => `${item || ''}`.trim())
112
+ .filter(Boolean)
113
+ ));
114
+ }
115
+
116
+ function addViolation(violations, severity, code, message, details = {}) {
117
+ violations.push({
118
+ severity,
119
+ code,
120
+ message,
121
+ details
122
+ });
123
+ }
124
+
125
+ function decideFromViolations(violations = []) {
126
+ if (violations.some(item => item && item.severity === 'deny')) {
127
+ return 'deny';
128
+ }
129
+ if (violations.some(item => item && item.severity === 'review')) {
130
+ return 'review-required';
131
+ }
132
+ return 'allow';
133
+ }
134
+
135
+ function evaluateRuntimePolicy({
136
+ plan,
137
+ uiMode,
138
+ runtimeMode,
139
+ runtimeEnvironment,
140
+ policy
141
+ }) {
142
+ const normalizedRuntimeMode = `${runtimeMode || ''}`.trim().toLowerCase();
143
+ const normalizedRuntimeEnvironment = `${runtimeEnvironment || ''}`.trim().toLowerCase();
144
+ const normalizedUiMode = `${uiMode || (plan && plan.ui_mode) || ''}`.trim().toLowerCase() || null;
145
+
146
+ const modeConfig = policy && policy.modes ? policy.modes[normalizedRuntimeMode] : null;
147
+ const envConfig = policy && policy.environments ? policy.environments[normalizedRuntimeEnvironment] : null;
148
+ const uiModePolicyConfig = policy && policy.ui_modes && typeof policy.ui_modes === 'object'
149
+ ? policy.ui_modes
150
+ : null;
151
+ const hasUiModePolicy = Boolean(uiModePolicyConfig && Object.keys(uiModePolicyConfig).length > 0);
152
+ const uiModeConfig = normalizedUiMode && hasUiModePolicy
153
+ ? uiModePolicyConfig[normalizedUiMode]
154
+ : null;
155
+
156
+ if (!modeConfig) {
157
+ throw new Error(`runtime mode not defined in policy: ${normalizedRuntimeMode}`);
158
+ }
159
+ if (!envConfig) {
160
+ throw new Error(`runtime environment not defined in policy: ${normalizedRuntimeEnvironment}`);
161
+ }
162
+
163
+ const executionMode = `${plan && plan.execution_mode ? plan.execution_mode : 'suggestion'}`.trim().toLowerCase();
164
+ const riskLevel = normalizeRiskLevel(plan && plan.risk_level, 'medium');
165
+ const actionItems = Array.isArray(plan && plan.actions)
166
+ ? plan.actions.filter(item => item && typeof item === 'object')
167
+ : [];
168
+ const actionTypes = toUniqueList(actionItems.map(item => `${item.type || ''}`.trim().toLowerCase()));
169
+ const mutatingActions = actionItems.filter(item => isMutatingAction(item));
170
+ const mutatingActionTypes = toUniqueList(mutatingActions.map(item => `${item.type || ''}`.trim().toLowerCase()));
171
+ const approvalStatus = `${plan && plan.approval && plan.approval.status ? plan.approval.status : ''}`.trim().toLowerCase();
172
+ const planAuthorization = plan && plan.authorization && typeof plan.authorization === 'object'
173
+ ? plan.authorization
174
+ : {};
175
+
176
+ const allowedExecutionModes = toUniqueList(
177
+ Array.isArray(modeConfig.allow_execution_modes)
178
+ ? modeConfig.allow_execution_modes.map(item => `${item || ''}`.trim().toLowerCase())
179
+ : []
180
+ );
181
+ const denyActionTypes = toUniqueList(
182
+ Array.isArray(modeConfig.deny_action_types)
183
+ ? modeConfig.deny_action_types.map(item => `${item || ''}`.trim().toLowerCase())
184
+ : []
185
+ );
186
+ const reviewRequiredActionTypes = toUniqueList(
187
+ Array.isArray(modeConfig.review_required_action_types)
188
+ ? modeConfig.review_required_action_types.map(item => `${item || ''}`.trim().toLowerCase())
189
+ : []
190
+ );
191
+ const approvalRiskLevels = toUniqueList(
192
+ Array.isArray(envConfig.require_approval_for_risk_levels)
193
+ ? envConfig.require_approval_for_risk_levels.map(item => normalizeRiskLevel(item, 'medium'))
194
+ : []
195
+ );
196
+ const uiModeAllowedRuntimeModes = toUniqueList(
197
+ Array.isArray(uiModeConfig && uiModeConfig.allow_runtime_modes)
198
+ ? uiModeConfig.allow_runtime_modes.map(item => `${item || ''}`.trim().toLowerCase())
199
+ : []
200
+ );
201
+ const uiModeAllowedExecutionModes = toUniqueList(
202
+ Array.isArray(uiModeConfig && uiModeConfig.allow_execution_modes)
203
+ ? uiModeConfig.allow_execution_modes.map(item => `${item || ''}`.trim().toLowerCase())
204
+ : []
205
+ );
206
+ const uiModeDenyExecutionModes = toUniqueList(
207
+ Array.isArray(uiModeConfig && uiModeConfig.deny_execution_modes)
208
+ ? uiModeConfig.deny_execution_modes.map(item => `${item || ''}`.trim().toLowerCase())
209
+ : []
210
+ );
211
+
212
+ const maxRiskLevelForApply = normalizeRiskLevel(envConfig.max_risk_level_for_apply, 'high');
213
+ const maxAutoExecuteRiskLevel = normalizeRiskLevel(envConfig.max_auto_execute_risk_level, 'low');
214
+ const requireWorkOrder = modeConfig.require_work_order === true;
215
+ const allowMutatingApply = modeConfig.allow_mutating_apply === true;
216
+ const allowLiveApply = envConfig.allow_live_apply === true;
217
+ const requireDryRunBeforeLiveApply = envConfig.require_dry_run_before_live_apply === true;
218
+ const manualReviewRequiredForApply = envConfig.manual_review_required_for_apply === true && executionMode === 'apply';
219
+ const requirePasswordForApplyMutations = (
220
+ envConfig.require_password_for_apply_mutations === true &&
221
+ executionMode === 'apply' &&
222
+ mutatingActions.length > 0
223
+ );
224
+ const passwordConfigured = planAuthorization.password_required === true;
225
+ const approvalRequired = executionMode === 'apply' && approvalRiskLevels.includes(riskLevel);
226
+ const approvalSatisfied = !approvalRequired || approvalStatus === 'approved';
227
+ const reviewActionHits = actionTypes.filter(type => reviewRequiredActionTypes.includes(type));
228
+ const denyActionHits = actionTypes.filter(type => denyActionTypes.includes(type));
229
+
230
+ const violations = [];
231
+
232
+ if (!allowedExecutionModes.includes(executionMode)) {
233
+ addViolation(
234
+ violations,
235
+ 'deny',
236
+ 'execution-mode-not-allowed',
237
+ `execution_mode "${executionMode}" is not allowed in runtime mode "${runtimeMode}"`,
238
+ { execution_mode: executionMode, allowed_execution_modes: allowedExecutionModes }
239
+ );
240
+ }
241
+ if (denyActionHits.length > 0) {
242
+ addViolation(
243
+ violations,
244
+ 'deny',
245
+ 'deny-action-type-hit',
246
+ 'plan contains action types denied by runtime mode policy',
247
+ { action_types: denyActionHits }
248
+ );
249
+ }
250
+ if (normalizedUiMode && hasUiModePolicy && !uiModeConfig) {
251
+ addViolation(
252
+ violations,
253
+ 'deny',
254
+ 'ui-mode-not-defined',
255
+ `ui_mode "${normalizedUiMode}" is not defined in runtime policy`,
256
+ { ui_mode: normalizedUiMode }
257
+ );
258
+ }
259
+ if (
260
+ normalizedUiMode &&
261
+ uiModeConfig &&
262
+ uiModeAllowedRuntimeModes.length > 0 &&
263
+ !uiModeAllowedRuntimeModes.includes(normalizedRuntimeMode)
264
+ ) {
265
+ addViolation(
266
+ violations,
267
+ 'deny',
268
+ 'ui-mode-runtime-mode-not-allowed',
269
+ `runtime_mode "${normalizedRuntimeMode}" is not allowed for ui_mode "${normalizedUiMode}"`,
270
+ { ui_mode: normalizedUiMode, runtime_mode: normalizedRuntimeMode, allowed_runtime_modes: uiModeAllowedRuntimeModes }
271
+ );
272
+ }
273
+ if (
274
+ normalizedUiMode &&
275
+ uiModeConfig &&
276
+ uiModeDenyExecutionModes.includes(executionMode)
277
+ ) {
278
+ addViolation(
279
+ violations,
280
+ 'deny',
281
+ 'ui-mode-execution-mode-denied',
282
+ `execution_mode "${executionMode}" is denied for ui_mode "${normalizedUiMode}"`,
283
+ { ui_mode: normalizedUiMode, execution_mode: executionMode, deny_execution_modes: uiModeDenyExecutionModes }
284
+ );
285
+ }
286
+ if (
287
+ normalizedUiMode &&
288
+ uiModeConfig &&
289
+ uiModeAllowedExecutionModes.length > 0 &&
290
+ !uiModeAllowedExecutionModes.includes(executionMode)
291
+ ) {
292
+ addViolation(
293
+ violations,
294
+ 'deny',
295
+ 'ui-mode-execution-mode-not-allowed',
296
+ `execution_mode "${executionMode}" is not allowed for ui_mode "${normalizedUiMode}"`,
297
+ { ui_mode: normalizedUiMode, execution_mode: executionMode, allowed_execution_modes: uiModeAllowedExecutionModes }
298
+ );
299
+ }
300
+ if (executionMode === 'apply' && mutatingActions.length > 0 && !allowMutatingApply) {
301
+ addViolation(
302
+ violations,
303
+ 'deny',
304
+ 'mutating-apply-not-allowed',
305
+ `runtime mode "${runtimeMode}" disallows mutating apply`,
306
+ { mutating_action_types: mutatingActionTypes }
307
+ );
308
+ }
309
+ if (executionMode === 'apply' && riskRank(riskLevel) > riskRank(maxRiskLevelForApply)) {
310
+ addViolation(
311
+ violations,
312
+ 'deny',
313
+ 'risk-exceeds-max-apply',
314
+ `risk_level "${riskLevel}" exceeds max apply risk "${maxRiskLevelForApply}" for environment "${runtimeEnvironment}"`,
315
+ { risk_level: riskLevel, max_risk_level_for_apply: maxRiskLevelForApply }
316
+ );
317
+ }
318
+ if (reviewActionHits.length > 0) {
319
+ addViolation(
320
+ violations,
321
+ 'review',
322
+ 'review-action-type-hit',
323
+ 'plan contains action types that require manual review',
324
+ { action_types: reviewActionHits }
325
+ );
326
+ }
327
+ if (manualReviewRequiredForApply) {
328
+ addViolation(
329
+ violations,
330
+ 'review',
331
+ 'manual-review-required-for-apply',
332
+ `runtime environment "${runtimeEnvironment}" requires manual review before apply`,
333
+ { runtime_environment: runtimeEnvironment }
334
+ );
335
+ }
336
+ if (approvalRequired && !approvalSatisfied) {
337
+ addViolation(
338
+ violations,
339
+ 'review',
340
+ 'approval-required',
341
+ `risk_level "${riskLevel}" requires approval before apply in environment "${runtimeEnvironment}"`,
342
+ { approval_status: approvalStatus || 'unknown', required_levels: approvalRiskLevels }
343
+ );
344
+ }
345
+ if (requirePasswordForApplyMutations && !passwordConfigured) {
346
+ addViolation(
347
+ violations,
348
+ 'review',
349
+ 'password-authorization-required',
350
+ 'mutating apply requires password authorization configuration',
351
+ {
352
+ authorization_required: true,
353
+ plan_password_required: planAuthorization.password_required === true
354
+ }
355
+ );
356
+ }
357
+
358
+ const decision = decideFromViolations(violations);
359
+ const reasons = violations.map(item => item.message);
360
+ const autoExecuteAllowed = (
361
+ executionMode === 'apply' &&
362
+ riskRank(riskLevel) <= riskRank(maxAutoExecuteRiskLevel) &&
363
+ decision === 'allow'
364
+ );
365
+
366
+ return {
367
+ decision,
368
+ reasons,
369
+ violations,
370
+ summary: {
371
+ ui_mode: normalizedUiMode,
372
+ execution_mode: executionMode,
373
+ risk_level: riskLevel,
374
+ action_count: actionItems.length,
375
+ mutating_action_count: mutatingActions.length,
376
+ action_types: actionTypes
377
+ },
378
+ requirements: {
379
+ require_work_order: requireWorkOrder,
380
+ allow_live_apply: allowLiveApply,
381
+ require_dry_run_before_live_apply: requireDryRunBeforeLiveApply,
382
+ manual_review_required_for_apply: manualReviewRequiredForApply,
383
+ allow_mutating_apply: allowMutatingApply,
384
+ require_password_for_apply_mutations: requirePasswordForApplyMutations,
385
+ password_configured: passwordConfigured,
386
+ require_approval: approvalRequired,
387
+ approval_satisfied: approvalSatisfied,
388
+ approval_risk_levels: approvalRiskLevels,
389
+ ui_mode_policy_evaluated: normalizedUiMode !== null && hasUiModePolicy,
390
+ ui_mode_policy_configured: Boolean(uiModeConfig),
391
+ ui_mode_allowed_runtime_modes: uiModeAllowedRuntimeModes,
392
+ ui_mode_allowed_execution_modes: uiModeAllowedExecutionModes,
393
+ ui_mode_deny_execution_modes: uiModeDenyExecutionModes,
394
+ ui_mode_runtime_mode_allowed: uiModeAllowedRuntimeModes.length > 0
395
+ ? uiModeAllowedRuntimeModes.includes(normalizedRuntimeMode)
396
+ : null,
397
+ ui_mode_execution_mode_allowed: uiModeAllowedExecutionModes.length > 0
398
+ ? uiModeAllowedExecutionModes.includes(executionMode)
399
+ : null,
400
+ max_risk_level_for_apply: maxRiskLevelForApply,
401
+ max_auto_execute_risk_level: maxAutoExecuteRiskLevel,
402
+ auto_execute_allowed: autoExecuteAllowed,
403
+ review_action_hits: reviewActionHits,
404
+ deny_action_hits: denyActionHits
405
+ }
406
+ };
407
+ }
408
+
409
+ async function main() {
410
+ const options = parseArgs(process.argv.slice(2));
411
+ const cwd = process.cwd();
412
+ const planPath = resolvePath(cwd, options.plan);
413
+ const policyPath = resolvePath(cwd, options.policy);
414
+ const outPath = resolvePath(cwd, options.out);
415
+
416
+ const [plan, policy] = await Promise.all([
417
+ readJsonFile(planPath, 'plan'),
418
+ readJsonFile(policyPath, 'policy')
419
+ ]);
420
+
421
+ const defaults = policy && policy.defaults && typeof policy.defaults === 'object'
422
+ ? policy.defaults
423
+ : {};
424
+ const uiMode = `${options.uiMode || defaults.ui_mode || ''}`.trim().toLowerCase() || null;
425
+ const runtimeMode = `${options.runtimeMode || defaults.runtime_mode || ''}`.trim().toLowerCase();
426
+ const runtimeEnvironment = `${options.runtimeEnvironment || defaults.runtime_environment || ''}`.trim().toLowerCase();
427
+
428
+ if (!runtimeMode) {
429
+ throw new Error('runtime_mode is required (set --runtime-mode or defaults.runtime_mode in policy).');
430
+ }
431
+ if (!runtimeEnvironment) {
432
+ throw new Error('runtime_environment is required (set --runtime-environment or defaults.runtime_environment in policy).');
433
+ }
434
+
435
+ const evaluation = evaluateRuntimePolicy({
436
+ plan,
437
+ uiMode,
438
+ runtimeMode,
439
+ runtimeEnvironment,
440
+ policy
441
+ });
442
+
443
+ const payload = {
444
+ mode: 'interactive-runtime-policy-evaluate',
445
+ generated_at: new Date().toISOString(),
446
+ ui_mode: uiMode,
447
+ runtime_mode: runtimeMode,
448
+ runtime_environment: runtimeEnvironment,
449
+ inputs: {
450
+ plan: path.relative(cwd, planPath) || '.',
451
+ policy: path.relative(cwd, policyPath) || '.'
452
+ },
453
+ ...evaluation,
454
+ output: {
455
+ json: path.relative(cwd, outPath) || '.'
456
+ }
457
+ };
458
+
459
+ await fs.ensureDir(path.dirname(outPath));
460
+ await fs.writeJson(outPath, payload, { spaces: 2 });
461
+
462
+ if (options.json) {
463
+ process.stdout.write(`${JSON.stringify(payload, null, 2)}\n`);
464
+ } else {
465
+ process.stdout.write(`Interactive runtime policy decision: ${payload.decision}\n`);
466
+ process.stdout.write(`- Runtime: ${runtimeMode}@${runtimeEnvironment}\n`);
467
+ process.stdout.write(`- Output: ${payload.output.json}\n`);
468
+ }
469
+
470
+ if (options.failOnNonAllow && payload.decision !== 'allow') {
471
+ process.exitCode = 2;
472
+ }
473
+ }
474
+
475
+ if (require.main === module) {
476
+ main().catch((error) => {
477
+ console.error(`Interactive runtime policy evaluate failed: ${error.message}`);
478
+ process.exit(1);
479
+ });
480
+ }
481
+
482
+ module.exports = {
483
+ DEFAULT_POLICY,
484
+ DEFAULT_OUT,
485
+ RISK_ORDER,
486
+ parseArgs,
487
+ resolvePath,
488
+ normalizeRiskLevel,
489
+ riskRank,
490
+ isMutatingAction,
491
+ readJsonFile,
492
+ toUniqueList,
493
+ evaluateRuntimePolicy,
494
+ main
495
+ };