@neurcode-ai/cli 0.9.35 → 0.9.37

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 (62) hide show
  1. package/README.md +4 -2
  2. package/dist/api-client.d.ts +345 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +237 -9
  5. package/dist/api-client.js.map +1 -1
  6. package/dist/commands/audit.d.ts +3 -0
  7. package/dist/commands/audit.d.ts.map +1 -0
  8. package/dist/commands/audit.js +133 -0
  9. package/dist/commands/audit.js.map +1 -0
  10. package/dist/commands/contract.d.ts +3 -0
  11. package/dist/commands/contract.d.ts.map +1 -0
  12. package/dist/commands/contract.js +235 -0
  13. package/dist/commands/contract.js.map +1 -0
  14. package/dist/commands/feedback.d.ts +3 -0
  15. package/dist/commands/feedback.d.ts.map +1 -0
  16. package/dist/commands/feedback.js +208 -0
  17. package/dist/commands/feedback.js.map +1 -0
  18. package/dist/commands/plan.d.ts.map +1 -1
  19. package/dist/commands/plan.js +19 -3
  20. package/dist/commands/plan.js.map +1 -1
  21. package/dist/commands/policy.d.ts.map +1 -1
  22. package/dist/commands/policy.js +611 -26
  23. package/dist/commands/policy.js.map +1 -1
  24. package/dist/commands/remediate.d.ts +17 -0
  25. package/dist/commands/remediate.d.ts.map +1 -0
  26. package/dist/commands/remediate.js +252 -0
  27. package/dist/commands/remediate.js.map +1 -0
  28. package/dist/commands/ship.d.ts.map +1 -1
  29. package/dist/commands/ship.js +67 -14
  30. package/dist/commands/ship.js.map +1 -1
  31. package/dist/commands/verify.d.ts +14 -0
  32. package/dist/commands/verify.d.ts.map +1 -1
  33. package/dist/commands/verify.js +564 -14
  34. package/dist/commands/verify.js.map +1 -1
  35. package/dist/index.js +94 -0
  36. package/dist/index.js.map +1 -1
  37. package/dist/utils/artifact-signature.d.ts +34 -0
  38. package/dist/utils/artifact-signature.d.ts.map +1 -0
  39. package/dist/utils/artifact-signature.js +229 -0
  40. package/dist/utils/artifact-signature.js.map +1 -0
  41. package/dist/utils/change-contract.d.ts +2 -0
  42. package/dist/utils/change-contract.d.ts.map +1 -1
  43. package/dist/utils/change-contract.js +21 -1
  44. package/dist/utils/change-contract.js.map +1 -1
  45. package/dist/utils/policy-compiler.d.ts +2 -0
  46. package/dist/utils/policy-compiler.d.ts.map +1 -1
  47. package/dist/utils/policy-compiler.js +15 -0
  48. package/dist/utils/policy-compiler.js.map +1 -1
  49. package/dist/utils/policy-exceptions.d.ts +11 -1
  50. package/dist/utils/policy-exceptions.d.ts.map +1 -1
  51. package/dist/utils/policy-exceptions.js +94 -6
  52. package/dist/utils/policy-exceptions.js.map +1 -1
  53. package/dist/utils/policy-governance.d.ts +22 -1
  54. package/dist/utils/policy-governance.d.ts.map +1 -1
  55. package/dist/utils/policy-governance.js +178 -14
  56. package/dist/utils/policy-governance.js.map +1 -1
  57. package/dist/utils/policy-packs.d.ts +1 -1
  58. package/dist/utils/policy-packs.d.ts.map +1 -1
  59. package/dist/utils/policy-packs.js +185 -0
  60. package/dist/utils/policy-packs.js.map +1 -1
  61. package/package.json +15 -13
  62. package/LICENSE +0 -201
@@ -10,6 +10,7 @@ const policy_governance_1 = require("../utils/policy-governance");
10
10
  const policy_audit_1 = require("../utils/policy-audit");
11
11
  const policy_packs_1 = require("../utils/policy-packs");
12
12
  const policy_compiler_1 = require("../utils/policy-compiler");
13
+ const artifact_signature_1 = require("../utils/artifact-signature");
13
14
  // Import chalk with fallback
14
15
  let chalk;
15
16
  try {
@@ -63,6 +64,21 @@ function resolveActor(explicit) {
63
64
  return explicit.trim();
64
65
  return process.env.NEURCODE_ACTOR || process.env.GITHUB_ACTOR || process.env.USER || 'unknown';
65
66
  }
67
+ function validateExceptionWindowByGovernance(expiresAt, maxExpiryDays) {
68
+ const expiryMs = Date.parse(expiresAt);
69
+ if (!Number.isFinite(expiryMs)) {
70
+ throw new Error('expiresAt must be a valid ISO datetime');
71
+ }
72
+ const maxWindowMs = Math.max(1, maxExpiryDays) * 24 * 60 * 60 * 1000;
73
+ if (expiryMs - Date.now() > maxWindowMs) {
74
+ throw new Error(`exception expiry exceeds governance max window (${maxExpiryDays} days)`);
75
+ }
76
+ }
77
+ function normalizeListLimit(value, fallback, min, max) {
78
+ if (!Number.isFinite(value))
79
+ return fallback;
80
+ return Math.max(min, Math.min(max, Math.floor(Number(value))));
81
+ }
66
82
  async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
67
83
  if (!includeDashboardPolicies) {
68
84
  return {
@@ -153,7 +169,7 @@ function policyCommand(program) {
153
169
  policy
154
170
  .command('install')
155
171
  .description('Install a policy pack for this repository')
156
- .argument('<pack-id>', 'Policy pack ID (fintech|hipaa|soc2|startup-fast)')
172
+ .argument('<pack-id>', 'Policy pack ID (run `neurcode policy list` for all available stacks)')
157
173
  .option('--force', 'Replace any existing installed policy pack')
158
174
  .option('--json', 'Output as JSON')
159
175
  .action((packId, options) => {
@@ -372,6 +388,7 @@ function policyCommand(program) {
372
388
  .option('--intent <text>', 'Optional intent constraints to compile alongside policy rules')
373
389
  .option('--no-dashboard', 'Exclude dashboard custom policies from compiled artifact')
374
390
  .option('--require-dashboard', 'Fail if dashboard custom policies cannot be loaded')
391
+ .option('--require-deterministic-match', 'Fail if any intent statement cannot be compiled into deterministic enforcement rules')
375
392
  .option('--output <path>', 'Output file path (default: neurcode.policy.compiled.json)')
376
393
  .option('--json', 'Output as JSON')
377
394
  .action(async (options) => {
@@ -392,7 +409,7 @@ function policyCommand(program) {
392
409
  customRules,
393
410
  includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
394
411
  });
395
- const compiled = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
412
+ const compiledUnsigned = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
396
413
  includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
397
414
  policyLockPath: (0, policy_packs_1.getPolicyLockPath)(cwd),
398
415
  policyLockFingerprint: snapshot.effective.fingerprint,
@@ -410,6 +427,19 @@ function policyCommand(program) {
410
427
  intentConstraints: options.intent,
411
428
  policyRules: customPolicyResolution.customPolicies.map((policy) => policy.rule_text),
412
429
  });
430
+ const artifactSigningConfig = (0, artifact_signature_1.resolveGovernanceArtifactSigningConfigFromEnv)();
431
+ const compiled = (0, artifact_signature_1.signGovernanceArtifact)(compiledUnsigned, artifactSigningConfig);
432
+ if (options.requireDeterministicMatch === true
433
+ && compiled.compilation.unmatchedStatements.length > 0) {
434
+ const unmatchedError = new Error(`Deterministic policy compilation blocked: ${compiled.compilation.unmatchedStatements.length} intent statement(s) could not be converted into enforceable rules.`);
435
+ unmatchedError.code = 'POLICY_COMPILE_UNMATCHED_INTENT';
436
+ unmatchedError.unmatchedStatements = [
437
+ ...compiled.compilation.unmatchedStatements,
438
+ ];
439
+ unmatchedError.deterministicRuleCount =
440
+ compiled.compilation.deterministicRuleCount;
441
+ throw unmatchedError;
442
+ }
413
443
  const outputPath = (0, policy_compiler_1.writeCompiledPolicyArtifact)(cwd, compiled, options.output);
414
444
  const readBack = (0, policy_compiler_1.readCompiledPolicyArtifact)(cwd, options.output);
415
445
  try {
@@ -423,6 +453,8 @@ function policyCommand(program) {
423
453
  deterministicRuleCount: compiled.compilation.deterministicRuleCount,
424
454
  unmatchedStatements: compiled.compilation.unmatchedStatements.length,
425
455
  dashboardMode: compiled.source.includeDashboardPolicies ? 'dashboard' : 'disabled',
456
+ signaturePresent: Boolean(compiled.signature && compiled.signature.value),
457
+ signatureKeyId: compiled.signature?.keyId || null,
426
458
  },
427
459
  });
428
460
  }
@@ -444,6 +476,12 @@ function policyCommand(program) {
444
476
  console.log(chalk.dim(`Fingerprint: ${compiled.fingerprint}`));
445
477
  console.log(chalk.dim(`Deterministic rules: ${compiled.compilation.deterministicRuleCount}`));
446
478
  console.log(chalk.dim(`Unmatched statements: ${compiled.compilation.unmatchedStatements.length}`));
479
+ if (compiled.signature?.value) {
480
+ console.log(chalk.dim(`Artifact signature: signed (${compiled.signature.keyId ? `key ${compiled.signature.keyId}` : 'inline key'})`));
481
+ }
482
+ else {
483
+ console.log(chalk.dim('Artifact signature: unsigned (set NEURCODE_GOVERNANCE_SIGNING_KEY to sign artifacts)'));
484
+ }
447
485
  console.log(chalk.dim(`Policy source: ${compiled.source.includeDashboardPolicies ? 'dashboard + local packs' : 'local packs only'}`));
448
486
  if (customPolicyResolution.dashboardWarning) {
449
487
  console.log(chalk.yellow(`\n⚠️ ${customPolicyResolution.dashboardWarning}`));
@@ -453,7 +491,22 @@ function policyCommand(program) {
453
491
  catch (error) {
454
492
  const message = error instanceof Error ? error.message : 'Unknown error';
455
493
  if (options.json) {
456
- console.log(JSON.stringify({ error: message }, null, 2));
494
+ const payload = { error: message };
495
+ if (error && typeof error === 'object') {
496
+ const maybeCode = error.code;
497
+ const maybeUnmatched = error.unmatchedStatements;
498
+ const maybeRuleCount = error.deterministicRuleCount;
499
+ if (typeof maybeCode === 'string') {
500
+ payload.code = maybeCode;
501
+ }
502
+ if (Array.isArray(maybeUnmatched)) {
503
+ payload.unmatchedStatements = maybeUnmatched.filter((item) => typeof item === 'string');
504
+ }
505
+ if (typeof maybeRuleCount === 'number' && Number.isFinite(maybeRuleCount)) {
506
+ payload.deterministicRuleCount = maybeRuleCount;
507
+ }
508
+ }
509
+ console.log(JSON.stringify(payload, null, 2));
457
510
  process.exit(1);
458
511
  }
459
512
  console.error(chalk.red(`\n❌ ${message}\n`));
@@ -466,8 +519,61 @@ function policyCommand(program) {
466
519
  governance
467
520
  .command('status')
468
521
  .description('Show policy governance settings for this repository')
522
+ .option('--org', 'Fetch centralized organization governance settings from Neurcode Cloud')
469
523
  .option('--json', 'Output as JSON')
470
- .action((options) => {
524
+ .action(async (options) => {
525
+ if (options.org) {
526
+ try {
527
+ const config = loadPolicyRuntimeConfig();
528
+ const client = new api_client_1.ApiClient(config);
529
+ const settings = await client.getOrgGovernanceSettings();
530
+ if (!settings) {
531
+ throw new Error('Organization governance settings not found');
532
+ }
533
+ const policyGovernance = settings.policyGovernance || null;
534
+ if (options.json) {
535
+ console.log(JSON.stringify({
536
+ source: 'org',
537
+ settings,
538
+ }, null, 2));
539
+ return;
540
+ }
541
+ console.log(chalk.bold('\n🏢 Org Policy Governance\n'));
542
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/governance/settings)'));
543
+ if (!policyGovernance) {
544
+ console.log(chalk.yellow('No org-level policy governance configured.\n'));
545
+ return;
546
+ }
547
+ console.log(chalk.dim(`Exception approvals required: ${policyGovernance.exceptionApprovals?.required ? 'yes' : 'no'}`));
548
+ console.log(chalk.dim(`Minimum approvals: ${policyGovernance.exceptionApprovals?.minApprovals ?? 1}`));
549
+ console.log(chalk.dim(`Disallow self approval: ${policyGovernance.exceptionApprovals?.disallowSelfApproval !== false ? 'yes' : 'no'}`));
550
+ console.log(chalk.dim(`Reason required: ${policyGovernance.exceptionApprovals?.requireReason !== false ? 'yes' : 'no'}`));
551
+ console.log(chalk.dim(`Minimum reason length: ${policyGovernance.exceptionApprovals?.minReasonLength ?? 12}`));
552
+ console.log(chalk.dim(`Maximum exception window (days): ${policyGovernance.exceptionApprovals?.maxExpiryDays ?? 30}`));
553
+ console.log(chalk.dim(`Allowed approvers: ${Array.isArray(policyGovernance.exceptionApprovals?.allowedApprovers)
554
+ && policyGovernance.exceptionApprovals.allowedApprovers.length > 0
555
+ ? policyGovernance.exceptionApprovals.allowedApprovers.join(', ')
556
+ : '(any)'}`));
557
+ console.log(chalk.dim(`Critical rule patterns: ${Array.isArray(policyGovernance.exceptionApprovals?.criticalRulePatterns)
558
+ && policyGovernance.exceptionApprovals.criticalRulePatterns.length > 0
559
+ ? policyGovernance.exceptionApprovals.criticalRulePatterns.join(', ')
560
+ : '(none)'}`));
561
+ console.log(chalk.dim(`Critical minimum approvals: ${policyGovernance.exceptionApprovals?.criticalMinApprovals ?? 2}`));
562
+ console.log(chalk.dim(`Require audit integrity: ${policyGovernance.audit?.requireIntegrity ? 'yes' : 'no'}`));
563
+ console.log(chalk.dim(`Updated at: ${settings.updatedAt || '(unknown)'}`));
564
+ console.log('');
565
+ return;
566
+ }
567
+ catch (error) {
568
+ const message = error instanceof Error ? error.message : 'Unknown error';
569
+ if (options.json) {
570
+ console.log(JSON.stringify({ error: message }, null, 2));
571
+ process.exit(1);
572
+ }
573
+ console.error(chalk.red(`\n❌ ${message}\n`));
574
+ process.exit(1);
575
+ }
576
+ }
471
577
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
472
578
  const config = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
473
579
  if (options.json) {
@@ -482,37 +588,155 @@ function policyCommand(program) {
482
588
  console.log(chalk.dim(`Exception approvals required: ${config.exceptionApprovals.required ? 'yes' : 'no'}`));
483
589
  console.log(chalk.dim(`Minimum approvals: ${config.exceptionApprovals.minApprovals}`));
484
590
  console.log(chalk.dim(`Disallow self approval: ${config.exceptionApprovals.disallowSelfApproval ? 'yes' : 'no'}`));
591
+ console.log(chalk.dim(`Reason required: ${config.exceptionApprovals.requireReason ? 'yes' : 'no'}`));
592
+ console.log(chalk.dim(`Minimum reason length: ${config.exceptionApprovals.minReasonLength}`));
593
+ console.log(chalk.dim(`Maximum exception window (days): ${config.exceptionApprovals.maxExpiryDays}`));
485
594
  console.log(chalk.dim(`Allowed approvers: ${config.exceptionApprovals.allowedApprovers.length > 0 ? config.exceptionApprovals.allowedApprovers.join(', ') : '(any)'}`));
595
+ console.log(chalk.dim(`Critical rule patterns: ${config.exceptionApprovals.criticalRulePatterns.length > 0 ? config.exceptionApprovals.criticalRulePatterns.join(', ') : '(none)'}`));
596
+ console.log(chalk.dim(`Critical minimum approvals: ${config.exceptionApprovals.criticalMinApprovals}`));
486
597
  console.log(chalk.dim(`Require audit integrity: ${config.audit.requireIntegrity ? 'yes' : 'no'}`));
487
598
  console.log('');
488
599
  });
489
600
  governance
490
601
  .command('set')
491
602
  .description('Update policy governance settings')
603
+ .option('--org', 'Update centralized organization governance settings in Neurcode Cloud')
492
604
  .option('--require-approval', 'Require approvals before exceptions become effective')
493
605
  .option('--no-require-approval', 'Do not require approvals for exceptions')
494
606
  .option('--min-approvals <n>', 'Minimum approvals required when approval mode is enabled', (value) => parseInt(value, 10))
495
607
  .option('--allow-self-approval', 'Allow requester to approve their own exception')
496
608
  .option('--restrict-approvers <csv>', 'Comma-separated allow-list of approver identities')
497
609
  .option('--clear-approvers', 'Clear approver allow-list (allow any approver)')
610
+ .option('--require-reason', 'Require non-trivial exception reason text')
611
+ .option('--no-require-reason', 'Do not enforce minimum reason text length')
612
+ .option('--min-reason-length <n>', 'Minimum exception reason length (default: 12)', (value) => parseInt(value, 10))
613
+ .option('--max-expiry-days <n>', 'Maximum exception expiry window in days (default: 30)', (value) => parseInt(value, 10))
614
+ .option('--critical-rules <csv>', 'Comma-separated critical rule patterns requiring elevated approvals')
615
+ .option('--clear-critical-rules', 'Clear critical rule patterns')
616
+ .option('--critical-min-approvals <n>', 'Minimum approvals for critical rule exceptions (default: 2)', (value) => parseInt(value, 10))
498
617
  .option('--require-audit-integrity', 'Fail verify if policy audit chain integrity is broken')
499
618
  .option('--no-require-audit-integrity', 'Do not enforce policy audit integrity in verify')
500
619
  .option('--json', 'Output as JSON')
501
- .action((options) => {
620
+ .action(async (options) => {
621
+ const hasRestrictApprovers = typeof options.restrictApprovers === 'string';
622
+ const hasCriticalRules = typeof options.criticalRules === 'string';
623
+ const parsedApprovers = options.clearApprovers
624
+ ? []
625
+ : hasRestrictApprovers
626
+ ? options.restrictApprovers
627
+ .split(',')
628
+ .map((item) => item.trim())
629
+ .filter(Boolean)
630
+ : undefined;
631
+ const parsedCriticalRules = options.clearCriticalRules
632
+ ? []
633
+ : hasCriticalRules
634
+ ? options.criticalRules
635
+ .split(',')
636
+ .map((item) => item.trim())
637
+ .filter(Boolean)
638
+ : undefined;
639
+ if (options.org) {
640
+ try {
641
+ const exceptionApprovalsPatch = {};
642
+ if (typeof options.requireApproval === 'boolean') {
643
+ exceptionApprovalsPatch.required = options.requireApproval;
644
+ }
645
+ if (Number.isFinite(options.minApprovals)) {
646
+ exceptionApprovalsPatch.minApprovals = options.minApprovals;
647
+ }
648
+ if (typeof options.allowSelfApproval === 'boolean') {
649
+ exceptionApprovalsPatch.disallowSelfApproval = !options.allowSelfApproval;
650
+ }
651
+ if (parsedApprovers) {
652
+ exceptionApprovalsPatch.allowedApprovers = parsedApprovers;
653
+ }
654
+ if (typeof options.requireReason === 'boolean') {
655
+ exceptionApprovalsPatch.requireReason = options.requireReason;
656
+ }
657
+ if (Number.isFinite(options.minReasonLength)) {
658
+ exceptionApprovalsPatch.minReasonLength = options.minReasonLength;
659
+ }
660
+ if (Number.isFinite(options.maxExpiryDays)) {
661
+ exceptionApprovalsPatch.maxExpiryDays = options.maxExpiryDays;
662
+ }
663
+ if (parsedCriticalRules) {
664
+ exceptionApprovalsPatch.criticalRulePatterns = parsedCriticalRules;
665
+ }
666
+ if (Number.isFinite(options.criticalMinApprovals)) {
667
+ exceptionApprovalsPatch.criticalMinApprovals = options.criticalMinApprovals;
668
+ }
669
+ const auditPatch = {};
670
+ if (typeof options.requireAuditIntegrity === 'boolean') {
671
+ auditPatch.requireIntegrity = options.requireAuditIntegrity;
672
+ }
673
+ const policyGovernancePatch = {};
674
+ if (Object.keys(exceptionApprovalsPatch).length > 0) {
675
+ policyGovernancePatch.exceptionApprovals = exceptionApprovalsPatch;
676
+ }
677
+ if (Object.keys(auditPatch).length > 0) {
678
+ policyGovernancePatch.audit = auditPatch;
679
+ }
680
+ const config = loadPolicyRuntimeConfig();
681
+ const client = new api_client_1.ApiClient(config);
682
+ const settings = await client.updateOrgGovernanceSettings({
683
+ policyGovernance: policyGovernancePatch,
684
+ });
685
+ if (!settings) {
686
+ throw new Error('Failed to update org governance settings');
687
+ }
688
+ const next = settings.policyGovernance;
689
+ if (options.json) {
690
+ console.log(JSON.stringify({
691
+ source: 'org',
692
+ settings,
693
+ }, null, 2));
694
+ return;
695
+ }
696
+ console.log(chalk.green('\n✅ Organization policy governance updated.\n'));
697
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/governance/settings)'));
698
+ if (!next) {
699
+ console.log(chalk.yellow('No org-level policy governance payload returned.\n'));
700
+ return;
701
+ }
702
+ console.log(chalk.dim(`Approval required: ${next.exceptionApprovals?.required ? 'yes' : 'no'}`));
703
+ console.log(chalk.dim(`Min approvals: ${next.exceptionApprovals?.minApprovals ?? 1}`));
704
+ console.log(chalk.dim(`Disallow self approval: ${next.exceptionApprovals?.disallowSelfApproval !== false ? 'yes' : 'no'}`));
705
+ console.log(chalk.dim(`Reason required: ${next.exceptionApprovals?.requireReason !== false ? 'yes' : 'no'}`));
706
+ console.log(chalk.dim(`Min reason length: ${next.exceptionApprovals?.minReasonLength ?? 12}`));
707
+ console.log(chalk.dim(`Max expiry days: ${next.exceptionApprovals?.maxExpiryDays ?? 30}`));
708
+ console.log(chalk.dim(`Critical min approvals: ${next.exceptionApprovals?.criticalMinApprovals ?? 2}`));
709
+ console.log(chalk.dim(`Critical rule patterns: ${Array.isArray(next.exceptionApprovals?.criticalRulePatterns)
710
+ && next.exceptionApprovals.criticalRulePatterns.length > 0
711
+ ? next.exceptionApprovals.criticalRulePatterns.join(', ')
712
+ : '(none)'}`));
713
+ console.log(chalk.dim(`Require audit integrity: ${next.audit?.requireIntegrity ? 'yes' : 'no'}`));
714
+ console.log(chalk.dim(`Updated at: ${settings.updatedAt || '(unknown)'}`));
715
+ console.log('');
716
+ return;
717
+ }
718
+ catch (error) {
719
+ const message = error instanceof Error ? error.message : 'Unknown error';
720
+ if (options.json) {
721
+ console.log(JSON.stringify({ error: message }, null, 2));
722
+ process.exit(1);
723
+ }
724
+ console.error(chalk.red(`\n❌ ${message}\n`));
725
+ process.exit(1);
726
+ }
727
+ }
502
728
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
503
729
  try {
504
730
  const next = (0, policy_governance_1.updatePolicyGovernanceConfig)(cwd, {
505
731
  required: typeof options.requireApproval === 'boolean' ? options.requireApproval : undefined,
506
732
  minApprovals: Number.isFinite(options.minApprovals) ? options.minApprovals : undefined,
507
733
  disallowSelfApproval: typeof options.allowSelfApproval === 'boolean' ? !options.allowSelfApproval : undefined,
508
- allowedApprovers: options.clearApprovers
509
- ? []
510
- : typeof options.restrictApprovers === 'string'
511
- ? options.restrictApprovers
512
- .split(',')
513
- .map((item) => item.trim())
514
- .filter(Boolean)
515
- : undefined,
734
+ allowedApprovers: parsedApprovers,
735
+ requireReason: typeof options.requireReason === 'boolean' ? options.requireReason : undefined,
736
+ minReasonLength: Number.isFinite(options.minReasonLength) ? options.minReasonLength : undefined,
737
+ maxExpiryDays: Number.isFinite(options.maxExpiryDays) ? options.maxExpiryDays : undefined,
738
+ criticalRulePatterns: parsedCriticalRules,
739
+ criticalMinApprovals: Number.isFinite(options.criticalMinApprovals) ? options.criticalMinApprovals : undefined,
516
740
  requireAuditIntegrity: typeof options.requireAuditIntegrity === 'boolean' ? options.requireAuditIntegrity : undefined,
517
741
  });
518
742
  try {
@@ -526,6 +750,11 @@ function policyCommand(program) {
526
750
  minApprovals: next.exceptionApprovals.minApprovals,
527
751
  disallowSelfApproval: next.exceptionApprovals.disallowSelfApproval,
528
752
  allowedApprovers: next.exceptionApprovals.allowedApprovers,
753
+ requireReason: next.exceptionApprovals.requireReason,
754
+ minReasonLength: next.exceptionApprovals.minReasonLength,
755
+ maxExpiryDays: next.exceptionApprovals.maxExpiryDays,
756
+ criticalRulePatterns: next.exceptionApprovals.criticalRulePatterns,
757
+ criticalMinApprovals: next.exceptionApprovals.criticalMinApprovals,
529
758
  requireAuditIntegrity: next.audit.requireIntegrity,
530
759
  },
531
760
  });
@@ -542,6 +771,11 @@ function policyCommand(program) {
542
771
  console.log(chalk.dim(`Approval required: ${next.exceptionApprovals.required ? 'yes' : 'no'}`));
543
772
  console.log(chalk.dim(`Min approvals: ${next.exceptionApprovals.minApprovals}`));
544
773
  console.log(chalk.dim(`Disallow self approval: ${next.exceptionApprovals.disallowSelfApproval ? 'yes' : 'no'}`));
774
+ console.log(chalk.dim(`Reason required: ${next.exceptionApprovals.requireReason ? 'yes' : 'no'}`));
775
+ console.log(chalk.dim(`Min reason length: ${next.exceptionApprovals.minReasonLength}`));
776
+ console.log(chalk.dim(`Max expiry days: ${next.exceptionApprovals.maxExpiryDays}`));
777
+ console.log(chalk.dim(`Critical min approvals: ${next.exceptionApprovals.criticalMinApprovals}`));
778
+ console.log(chalk.dim(`Critical rule patterns: ${next.exceptionApprovals.criticalRulePatterns.length > 0 ? next.exceptionApprovals.criticalRulePatterns.join(', ') : '(none)'}`));
545
779
  console.log(chalk.dim(`Require audit integrity: ${next.audit.requireIntegrity ? 'yes' : 'no'}`));
546
780
  console.log(chalk.dim('Commit governance + audit files so CI can enforce approval and integrity rules.'));
547
781
  console.log('');
@@ -592,9 +826,59 @@ function policyCommand(program) {
592
826
  exception
593
827
  .command('list')
594
828
  .description('List policy exceptions for this repository')
829
+ .option('--org', 'List centralized organization policy exceptions from Neurcode Cloud')
595
830
  .option('--all', 'Include inactive/expired exceptions')
596
831
  .option('--json', 'Output as JSON')
597
- .action((options) => {
832
+ .action(async (options) => {
833
+ if (options.org) {
834
+ try {
835
+ const config = loadPolicyRuntimeConfig();
836
+ const client = new api_client_1.ApiClient(config);
837
+ const exceptions = await client.listOrgPolicyExceptions({ limit: 250 });
838
+ const items = options.all
839
+ ? exceptions
840
+ : exceptions.filter((entry) => entry.effectiveState !== 'revoked' && entry.effectiveState !== 'expired');
841
+ if (options.json) {
842
+ console.log(JSON.stringify({
843
+ source: 'org',
844
+ total: exceptions.length,
845
+ exceptions: items,
846
+ }, null, 2));
847
+ return;
848
+ }
849
+ if (items.length === 0) {
850
+ console.log(chalk.yellow('\n⚠️ No organization policy exceptions found.\n'));
851
+ console.log(chalk.dim('Add one: neurcode policy exception add --org --rule <pattern> --file <glob> --reason "<why>"\n'));
852
+ return;
853
+ }
854
+ console.log(chalk.bold('\n🏢 Org Policy Exceptions\n'));
855
+ items.forEach((entry) => {
856
+ console.log(chalk.cyan(`• ${entry.id}`));
857
+ console.log(chalk.dim(` state=${entry.effectiveState} workflow=${entry.workflowState}`));
858
+ console.log(chalk.dim(` rule=${entry.rulePattern} file=${entry.filePattern}`));
859
+ console.log(chalk.dim(` expires=${entry.expiresAt} active=${entry.active ? 'yes' : 'no'}`));
860
+ console.log(chalk.dim(` approvals=${entry.approvalCount}` +
861
+ `${entry.requiredApprovals > 0 ? ` required=${entry.requiredApprovals}` : ''}` +
862
+ `${entry.critical ? ' critical=yes' : ''}`));
863
+ console.log(chalk.dim(` reason=${entry.reason}`));
864
+ if (entry.ticket) {
865
+ console.log(chalk.dim(` ticket=${entry.ticket}`));
866
+ }
867
+ console.log('');
868
+ });
869
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
870
+ return;
871
+ }
872
+ catch (error) {
873
+ const message = error instanceof Error ? error.message : 'Unknown error';
874
+ if (options.json) {
875
+ console.log(JSON.stringify({ error: message }, null, 2));
876
+ process.exit(1);
877
+ }
878
+ console.error(chalk.red(`\n❌ ${message}\n`));
879
+ process.exit(1);
880
+ }
881
+ }
598
882
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
599
883
  const data = (0, policy_exceptions_1.listPolicyExceptions)(cwd);
600
884
  const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
@@ -609,17 +893,27 @@ function policyCommand(program) {
609
893
  if (allowedApprovers.size > 0) {
610
894
  effectiveApprovals = effectiveApprovals.filter((item) => allowedApprovers.has(item.approver.toLowerCase()));
611
895
  }
896
+ const requiredApprovalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(entry.rulePattern, governance);
897
+ const requiredApprovals = governance.exceptionApprovals.required
898
+ ? requiredApprovalResolution.requiredApprovals
899
+ : 0;
900
+ const reasonValid = !governance.exceptionApprovals.requireReason
901
+ || (entry.reason || '').trim().length >= governance.exceptionApprovals.minReasonLength;
612
902
  const status = !entry.active || !unexpired
613
903
  ? 'inactive'
614
- : !governance.exceptionApprovals.required
615
- ? 'active'
616
- : effectiveApprovals.length >= governance.exceptionApprovals.minApprovals
617
- ? 'approved'
618
- : 'pending';
904
+ : !reasonValid
905
+ ? 'invalid_reason'
906
+ : !governance.exceptionApprovals.required
907
+ ? 'active'
908
+ : effectiveApprovals.length >= requiredApprovals
909
+ ? 'approved'
910
+ : 'pending';
619
911
  return {
620
912
  ...entry,
621
913
  status,
622
914
  effectiveApprovals: effectiveApprovals.length,
915
+ requiredApprovals,
916
+ criticalRule: requiredApprovalResolution.critical,
623
917
  };
624
918
  });
625
919
  const items = options.all ? withStatus : withStatus.filter((entry) => entry.status !== 'inactive');
@@ -645,7 +939,10 @@ function policyCommand(program) {
645
939
  console.log(chalk.dim(` status=${entry.status || (entry.active ? 'active' : 'inactive')}`));
646
940
  console.log(chalk.dim(` rule=${entry.rulePattern} file=${entry.filePattern}`));
647
941
  console.log(chalk.dim(` expires=${entry.expiresAt} active=${entry.active ? 'yes' : 'no'}`));
648
- console.log(chalk.dim(` approvals=${entry.approvals.length}${typeof entry.effectiveApprovals === 'number' ? ` (effective=${entry.effectiveApprovals})` : ''}`));
942
+ console.log(chalk.dim(` approvals=${entry.approvals.length}` +
943
+ `${typeof entry.effectiveApprovals === 'number' ? ` (effective=${entry.effectiveApprovals})` : ''}` +
944
+ `${typeof entry.requiredApprovals === 'number' && entry.requiredApprovals > 0 ? ` required=${entry.requiredApprovals}` : ''}` +
945
+ `${entry.criticalRule ? ' critical=yes' : ''}`));
649
946
  console.log(chalk.dim(` reason=${entry.reason}`));
650
947
  console.log(chalk.dim(` requestedBy=${entry.requestedBy || entry.createdBy || 'unknown'}`));
651
948
  if (entry.ticket) {
@@ -661,6 +958,7 @@ function policyCommand(program) {
661
958
  exception
662
959
  .command('add')
663
960
  .description('Add a policy exception entry')
961
+ .option('--org', 'Create centralized organization policy exception in Neurcode Cloud')
664
962
  .requiredOption('--rule <pattern>', 'Rule pattern (exact, wildcard, or /regex/)')
665
963
  .requiredOption('--file <pattern>', 'File pattern (exact, wildcard, or /regex/)')
666
964
  .requiredOption('--reason <text>', 'Business justification for this exception')
@@ -669,7 +967,7 @@ function policyCommand(program) {
669
967
  .option('--expires-at <iso>', 'Expiry timestamp in ISO-8601')
670
968
  .option('--expires-in-days <n>', 'Expiry offset in days (default: 30)', (value) => parseInt(value, 10))
671
969
  .option('--json', 'Output as JSON')
672
- .action((options) => {
970
+ .action(async (options) => {
673
971
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
674
972
  try {
675
973
  const severity = options.severity === 'allow' || options.severity === 'warn' || options.severity === 'block'
@@ -682,6 +980,43 @@ function policyCommand(program) {
682
980
  expiresAt: options.expiresAt,
683
981
  expiresInDays: options.expiresInDays,
684
982
  });
983
+ if (options.org) {
984
+ const config = loadPolicyRuntimeConfig();
985
+ const client = new api_client_1.ApiClient(config);
986
+ const created = await client.createOrgPolicyException({
987
+ rulePattern: options.rule,
988
+ filePattern: options.file,
989
+ reason: options.reason,
990
+ ticket: options.ticket,
991
+ severity,
992
+ expiresAt,
993
+ });
994
+ if (options.json) {
995
+ console.log(JSON.stringify({
996
+ source: 'org',
997
+ exception: created,
998
+ }, null, 2));
999
+ return;
1000
+ }
1001
+ console.log(chalk.green('\n✅ Organization policy exception created\n'));
1002
+ console.log(chalk.cyan(`ID: ${created.id}`));
1003
+ console.log(chalk.dim(`State: ${created.effectiveState}`));
1004
+ console.log(chalk.dim(`Rule: ${created.rulePattern}`));
1005
+ console.log(chalk.dim(`File: ${created.filePattern}`));
1006
+ console.log(chalk.dim(`Expires: ${created.expiresAt}`));
1007
+ console.log(chalk.dim(`Approvals: ${created.approvalCount}/${created.requiredApprovals}`));
1008
+ if (created.ticket) {
1009
+ console.log(chalk.dim(`Ticket: ${created.ticket}`));
1010
+ }
1011
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
1012
+ return;
1013
+ }
1014
+ const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
1015
+ if (governance.exceptionApprovals.requireReason
1016
+ && options.reason.trim().length < governance.exceptionApprovals.minReasonLength) {
1017
+ throw new Error(`reason must be at least ${governance.exceptionApprovals.minReasonLength} characters (governance policy)`);
1018
+ }
1019
+ validateExceptionWindowByGovernance(expiresAt, governance.exceptionApprovals.maxExpiryDays);
685
1020
  const createdBy = resolveActor();
686
1021
  const created = (0, policy_exceptions_1.addPolicyException)(cwd, {
687
1022
  rulePattern: options.rule,
@@ -693,7 +1028,7 @@ function policyCommand(program) {
693
1028
  createdBy,
694
1029
  requestedBy: createdBy,
695
1030
  });
696
- const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
1031
+ const approvalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(created.rulePattern, governance);
697
1032
  try {
698
1033
  (0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
699
1034
  actor: createdBy,
@@ -705,6 +1040,10 @@ function policyCommand(program) {
705
1040
  filePattern: created.filePattern,
706
1041
  expiresAt: created.expiresAt,
707
1042
  requireApproval: governance.exceptionApprovals.required,
1043
+ requiredApprovals: governance.exceptionApprovals.required
1044
+ ? approvalResolution.requiredApprovals
1045
+ : 0,
1046
+ criticalRule: approvalResolution.critical,
708
1047
  },
709
1048
  });
710
1049
  }
@@ -716,6 +1055,10 @@ function policyCommand(program) {
716
1055
  created,
717
1056
  path: (0, policy_exceptions_1.getPolicyExceptionsPath)(cwd),
718
1057
  requiresApproval: governance.exceptionApprovals.required,
1058
+ requiredApprovals: governance.exceptionApprovals.required
1059
+ ? approvalResolution.requiredApprovals
1060
+ : 0,
1061
+ criticalRule: approvalResolution.critical,
719
1062
  }, null, 2));
720
1063
  return;
721
1064
  }
@@ -726,7 +1069,8 @@ function policyCommand(program) {
726
1069
  console.log(chalk.dim(`Expires: ${created.expiresAt}`));
727
1070
  console.log(chalk.dim(`Reason: ${created.reason}`));
728
1071
  if (governance.exceptionApprovals.required) {
729
- console.log(chalk.yellow(`Approval required: ${governance.exceptionApprovals.minApprovals} approver(s) before this exception is active.`));
1072
+ const requiredApprovals = approvalResolution.requiredApprovals;
1073
+ console.log(chalk.yellow(`Approval required: ${requiredApprovals} approver(s) before this exception is active${approvalResolution.critical ? ' (critical rule gate)' : ''}.`));
730
1074
  }
731
1075
  if (created.ticket) {
732
1076
  console.log(chalk.dim(`Ticket: ${created.ticket}`));
@@ -747,13 +1091,57 @@ function policyCommand(program) {
747
1091
  .command('approve')
748
1092
  .description('Approve a policy exception by ID')
749
1093
  .argument('<id>', 'Exception ID to approve')
1094
+ .option('--org', 'Approve centralized organization policy exception in Neurcode Cloud')
750
1095
  .option('--by <actor>', 'Approver identity (defaults to NEURCODE_ACTOR/GITHUB_ACTOR/USER)')
751
1096
  .option('--comment <text>', 'Approval comment')
752
1097
  .option('--json', 'Output as JSON')
753
- .action((id, options) => {
1098
+ .action(async (id, options) => {
754
1099
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
755
1100
  const approver = resolveActor(options.by);
756
1101
  try {
1102
+ if (options.org) {
1103
+ if (options.by) {
1104
+ throw new Error('--by is not supported with --org (identity comes from authenticated Neurcode user)');
1105
+ }
1106
+ const config = loadPolicyRuntimeConfig();
1107
+ const client = new api_client_1.ApiClient(config);
1108
+ const updated = await client.approveOrgPolicyException(String(id).trim(), {
1109
+ note: options.comment,
1110
+ });
1111
+ if (options.json) {
1112
+ console.log(JSON.stringify({
1113
+ source: 'org',
1114
+ approved: true,
1115
+ exception: updated,
1116
+ }, null, 2));
1117
+ return;
1118
+ }
1119
+ console.log(chalk.green('\n✅ Organization policy exception approval recorded.\n'));
1120
+ console.log(chalk.dim(`ID: ${updated.id}`));
1121
+ console.log(chalk.dim(`State: ${updated.effectiveState}`));
1122
+ console.log(chalk.dim(`Approvals: ${updated.approvalCount}/${updated.requiredApprovals}`));
1123
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
1124
+ return;
1125
+ }
1126
+ const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
1127
+ const target = (0, policy_exceptions_1.listPolicyExceptions)(cwd).all.find((entry) => entry.id === String(id).trim());
1128
+ if (!target) {
1129
+ if (options.json) {
1130
+ console.log(JSON.stringify({ error: 'Exception not found' }, null, 2));
1131
+ process.exit(1);
1132
+ }
1133
+ console.log(chalk.yellow('\n⚠️ Exception not found.\n'));
1134
+ process.exit(1);
1135
+ }
1136
+ const normalizedApprover = approver.toLowerCase();
1137
+ const requestedBy = (target.requestedBy || target.createdBy || '').toLowerCase();
1138
+ if (governance.exceptionApprovals.disallowSelfApproval && requestedBy && normalizedApprover === requestedBy) {
1139
+ throw new Error('self-approval is disallowed by governance policy');
1140
+ }
1141
+ if (governance.exceptionApprovals.allowedApprovers.length > 0
1142
+ && !governance.exceptionApprovals.allowedApprovers.map((item) => item.toLowerCase()).includes(normalizedApprover)) {
1143
+ throw new Error('approver is not in governance allow-list');
1144
+ }
757
1145
  const updated = (0, policy_exceptions_1.approvePolicyException)(cwd, String(id).trim(), {
758
1146
  approver,
759
1147
  comment: options.comment,
@@ -766,6 +1154,19 @@ function policyCommand(program) {
766
1154
  console.log(chalk.yellow('\n⚠️ Exception not found.\n'));
767
1155
  process.exit(1);
768
1156
  }
1157
+ const requiredApprovalResolution = (0, policy_governance_1.resolveRequiredApprovalsForRule)(updated.rulePattern, governance);
1158
+ const acceptedApprovals = updated.approvals.filter((item) => {
1159
+ const actor = item.approver.toLowerCase();
1160
+ if (governance.exceptionApprovals.allowedApprovers.length > 0
1161
+ && !governance.exceptionApprovals.allowedApprovers.map((entry) => entry.toLowerCase()).includes(actor)) {
1162
+ return false;
1163
+ }
1164
+ if (governance.exceptionApprovals.disallowSelfApproval && requestedBy && actor === requestedBy) {
1165
+ return false;
1166
+ }
1167
+ return true;
1168
+ });
1169
+ const effectiveApprovals = acceptedApprovals.length;
769
1170
  try {
770
1171
  (0, policy_audit_1.appendPolicyAuditEvent)(cwd, {
771
1172
  actor: approver,
@@ -775,6 +1176,9 @@ function policyCommand(program) {
775
1176
  metadata: {
776
1177
  comment: options.comment || null,
777
1178
  approvals: updated.approvals.length,
1179
+ effectiveApprovals,
1180
+ requiredApprovals: requiredApprovalResolution.requiredApprovals,
1181
+ criticalRule: requiredApprovalResolution.critical,
778
1182
  },
779
1183
  });
780
1184
  }
@@ -785,12 +1189,20 @@ function policyCommand(program) {
785
1189
  console.log(JSON.stringify({
786
1190
  approved: true,
787
1191
  exception: updated,
1192
+ effectiveApprovals,
1193
+ requiredApprovals: requiredApprovalResolution.requiredApprovals,
1194
+ approvalSatisfied: !governance.exceptionApprovals.required
1195
+ || effectiveApprovals >= requiredApprovalResolution.requiredApprovals,
1196
+ criticalRule: requiredApprovalResolution.critical,
788
1197
  }, null, 2));
789
1198
  return;
790
1199
  }
791
1200
  console.log(chalk.green('\n✅ Policy exception approval recorded.\n'));
792
1201
  console.log(chalk.dim(`ID: ${updated.id}`));
793
- console.log(chalk.dim(`Approvals: ${updated.approvals.length}`));
1202
+ console.log(chalk.dim(`Approvals: ${updated.approvals.length} (effective=${effectiveApprovals})`));
1203
+ if (governance.exceptionApprovals.required) {
1204
+ console.log(chalk.dim(`Required approvals: ${requiredApprovalResolution.requiredApprovals}${requiredApprovalResolution.critical ? ' (critical rule gate)' : ''}`));
1205
+ }
794
1206
  console.log(chalk.dim(`Approver: ${approver}`));
795
1207
  console.log('');
796
1208
  }
@@ -804,12 +1216,185 @@ function policyCommand(program) {
804
1216
  process.exit(1);
805
1217
  }
806
1218
  });
1219
+ exception
1220
+ .command('reject')
1221
+ .description('Reject a pending organization policy exception by ID')
1222
+ .argument('<id>', 'Exception ID to reject')
1223
+ .requiredOption('--reason <text>', 'Reason for rejecting this exception')
1224
+ .option('--org', 'Reject centralized organization policy exception in Neurcode Cloud')
1225
+ .option('--json', 'Output as JSON')
1226
+ .action(async (id, options) => {
1227
+ if (!options.org) {
1228
+ const message = '`policy exception reject` is only supported for --org exceptions. Use `policy exception remove` for local exceptions.';
1229
+ if (options.json) {
1230
+ console.log(JSON.stringify({ error: message }, null, 2));
1231
+ process.exit(1);
1232
+ }
1233
+ console.error(chalk.red(`\n❌ ${message}\n`));
1234
+ process.exit(1);
1235
+ }
1236
+ const reason = typeof options.reason === 'string' ? options.reason.trim() : '';
1237
+ if (!reason) {
1238
+ const message = '--reason is required';
1239
+ if (options.json) {
1240
+ console.log(JSON.stringify({ error: message }, null, 2));
1241
+ process.exit(1);
1242
+ }
1243
+ console.error(chalk.red(`\n❌ ${message}\n`));
1244
+ process.exit(1);
1245
+ }
1246
+ try {
1247
+ const config = loadPolicyRuntimeConfig();
1248
+ const client = new api_client_1.ApiClient(config);
1249
+ const updated = await client.rejectOrgPolicyException(String(id).trim(), { reason });
1250
+ if (options.json) {
1251
+ console.log(JSON.stringify({
1252
+ source: 'org',
1253
+ rejected: true,
1254
+ exception: updated,
1255
+ }, null, 2));
1256
+ return;
1257
+ }
1258
+ console.log(chalk.green('\n✅ Organization policy exception rejected.\n'));
1259
+ console.log(chalk.dim(`ID: ${updated.id}`));
1260
+ console.log(chalk.dim(`State: ${updated.effectiveState}`));
1261
+ if (updated.rejectionReason) {
1262
+ console.log(chalk.dim(`Reason: ${updated.rejectionReason}`));
1263
+ }
1264
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
1265
+ }
1266
+ catch (error) {
1267
+ const message = error instanceof Error ? error.message : 'Unknown error';
1268
+ if (options.json) {
1269
+ console.log(JSON.stringify({ error: message }, null, 2));
1270
+ process.exit(1);
1271
+ }
1272
+ console.error(chalk.red(`\n❌ ${message}\n`));
1273
+ process.exit(1);
1274
+ }
1275
+ });
1276
+ exception
1277
+ .command('events')
1278
+ .description('Show policy exception audit events')
1279
+ .argument('<id>', 'Exception ID')
1280
+ .option('--org', 'Read centralized organization policy exception events from Neurcode Cloud')
1281
+ .option('--limit <n>', 'Maximum events to return (default: 30)', (value) => parseInt(value, 10))
1282
+ .option('--json', 'Output as JSON')
1283
+ .action(async (id, options) => {
1284
+ const exceptionId = String(id).trim();
1285
+ const limit = normalizeListLimit(options.limit, 30, 1, 300);
1286
+ if (options.org) {
1287
+ try {
1288
+ const config = loadPolicyRuntimeConfig();
1289
+ const client = new api_client_1.ApiClient(config);
1290
+ const events = await client.listOrgPolicyExceptionEvents(exceptionId, limit);
1291
+ if (options.json) {
1292
+ console.log(JSON.stringify({
1293
+ source: 'org',
1294
+ exceptionId,
1295
+ total: events.length,
1296
+ events,
1297
+ }, null, 2));
1298
+ return;
1299
+ }
1300
+ if (events.length === 0) {
1301
+ console.log(chalk.yellow('\n⚠️ No organization exception events found.\n'));
1302
+ return;
1303
+ }
1304
+ console.log(chalk.bold('\n🧾 Organization Exception Events\n'));
1305
+ events.forEach((event) => {
1306
+ const actor = event.actorEmail ||
1307
+ [event.actorFirstName, event.actorLastName].filter(Boolean).join(' ').trim() ||
1308
+ event.actorUserId ||
1309
+ 'unknown';
1310
+ console.log(chalk.cyan(`• ${event.createdAt} ${event.action}`));
1311
+ console.log(chalk.dim(` actor=${actor}`));
1312
+ if (event.note) {
1313
+ console.log(chalk.dim(` note=${event.note}`));
1314
+ }
1315
+ console.log(chalk.dim(` eventId=${event.id}`));
1316
+ console.log('');
1317
+ });
1318
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions/:id/events)\n'));
1319
+ return;
1320
+ }
1321
+ catch (error) {
1322
+ const message = error instanceof Error ? error.message : 'Unknown error';
1323
+ if (options.json) {
1324
+ console.log(JSON.stringify({ error: message }, null, 2));
1325
+ process.exit(1);
1326
+ }
1327
+ console.error(chalk.red(`\n❌ ${message}\n`));
1328
+ process.exit(1);
1329
+ }
1330
+ }
1331
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
1332
+ const events = (0, policy_audit_1.readPolicyAuditEvents)(cwd)
1333
+ .filter((event) => event.entityType === 'policy_exception' && event.entityId === exceptionId)
1334
+ .sort((left, right) => right.timestamp.localeCompare(left.timestamp))
1335
+ .slice(0, limit);
1336
+ if (options.json) {
1337
+ console.log(JSON.stringify({
1338
+ source: 'local',
1339
+ exceptionId,
1340
+ total: events.length,
1341
+ events,
1342
+ path: (0, policy_audit_1.getPolicyAuditPath)(cwd),
1343
+ }, null, 2));
1344
+ return;
1345
+ }
1346
+ if (events.length === 0) {
1347
+ console.log(chalk.yellow('\n⚠️ No local exception audit events found.\n'));
1348
+ return;
1349
+ }
1350
+ console.log(chalk.bold('\n🧾 Local Exception Events\n'));
1351
+ events.forEach((event) => {
1352
+ console.log(chalk.cyan(`• ${event.timestamp} ${event.action}`));
1353
+ console.log(chalk.dim(` actor=${event.actor}`));
1354
+ if (event.metadata && Object.keys(event.metadata).length > 0) {
1355
+ console.log(chalk.dim(` metadata=${JSON.stringify(event.metadata)}`));
1356
+ }
1357
+ console.log(chalk.dim(` hash=${event.hash.slice(0, 12)}...`));
1358
+ console.log('');
1359
+ });
1360
+ console.log(chalk.dim(`Source: ${(0, policy_audit_1.getPolicyAuditPath)(cwd)}\n`));
1361
+ });
807
1362
  exception
808
1363
  .command('remove')
809
1364
  .description('Deactivate a policy exception by ID')
810
1365
  .argument('<id>', 'Exception ID to deactivate')
1366
+ .option('--org', 'Revoke centralized organization policy exception in Neurcode Cloud')
811
1367
  .option('--json', 'Output as JSON')
812
- .action((id, options) => {
1368
+ .action(async (id, options) => {
1369
+ if (options.org) {
1370
+ try {
1371
+ const config = loadPolicyRuntimeConfig();
1372
+ const client = new api_client_1.ApiClient(config);
1373
+ const updated = await client.revokeOrgPolicyException(String(id).trim());
1374
+ if (options.json) {
1375
+ console.log(JSON.stringify({
1376
+ source: 'org',
1377
+ removed: true,
1378
+ exception: updated,
1379
+ }, null, 2));
1380
+ return;
1381
+ }
1382
+ console.log(chalk.green('\n✅ Organization policy exception revoked.\n'));
1383
+ console.log(chalk.dim(`ID: ${updated.id}`));
1384
+ console.log(chalk.dim(`State: ${updated.effectiveState}`));
1385
+ console.log(chalk.dim('Source: Neurcode Cloud (/api/v1/org/policy-exceptions)\n'));
1386
+ return;
1387
+ }
1388
+ catch (error) {
1389
+ const message = error instanceof Error ? error.message : 'Unknown error';
1390
+ if (options.json) {
1391
+ console.log(JSON.stringify({ error: message }, null, 2));
1392
+ process.exit(1);
1393
+ }
1394
+ console.error(chalk.red(`\n❌ ${message}\n`));
1395
+ process.exit(1);
1396
+ }
1397
+ }
813
1398
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
814
1399
  const removed = (0, policy_exceptions_1.revokePolicyException)(cwd, String(id).trim());
815
1400
  if (removed) {