@neurcode-ai/cli 0.9.36 → 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 (49) hide show
  1. package/README.md +4 -2
  2. package/dist/api-client.d.ts +300 -1
  3. package/dist/api-client.d.ts.map +1 -1
  4. package/dist/api-client.js +225 -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 +329 -6
  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 +12 -0
  32. package/dist/commands/verify.d.ts.map +1 -1
  33. package/dist/commands/verify.js +477 -13
  34. package/dist/commands/verify.js.map +1 -1
  35. package/dist/index.js +60 -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/package.json +1 -1
@@ -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 {
@@ -73,6 +74,11 @@ function validateExceptionWindowByGovernance(expiresAt, maxExpiryDays) {
73
74
  throw new Error(`exception expiry exceeds governance max window (${maxExpiryDays} days)`);
74
75
  }
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
+ }
76
82
  async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
77
83
  if (!includeDashboardPolicies) {
78
84
  return {
@@ -382,6 +388,7 @@ function policyCommand(program) {
382
388
  .option('--intent <text>', 'Optional intent constraints to compile alongside policy rules')
383
389
  .option('--no-dashboard', 'Exclude dashboard custom policies from compiled artifact')
384
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')
385
392
  .option('--output <path>', 'Output file path (default: neurcode.policy.compiled.json)')
386
393
  .option('--json', 'Output as JSON')
387
394
  .action(async (options) => {
@@ -402,7 +409,7 @@ function policyCommand(program) {
402
409
  customRules,
403
410
  includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
404
411
  });
405
- const compiled = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
412
+ const compiledUnsigned = (0, policy_compiler_1.buildCompiledPolicyArtifact)({
406
413
  includeDashboardPolicies: customPolicyResolution.includeDashboardPolicies,
407
414
  policyLockPath: (0, policy_packs_1.getPolicyLockPath)(cwd),
408
415
  policyLockFingerprint: snapshot.effective.fingerprint,
@@ -420,6 +427,19 @@ function policyCommand(program) {
420
427
  intentConstraints: options.intent,
421
428
  policyRules: customPolicyResolution.customPolicies.map((policy) => policy.rule_text),
422
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
+ }
423
443
  const outputPath = (0, policy_compiler_1.writeCompiledPolicyArtifact)(cwd, compiled, options.output);
424
444
  const readBack = (0, policy_compiler_1.readCompiledPolicyArtifact)(cwd, options.output);
425
445
  try {
@@ -433,6 +453,8 @@ function policyCommand(program) {
433
453
  deterministicRuleCount: compiled.compilation.deterministicRuleCount,
434
454
  unmatchedStatements: compiled.compilation.unmatchedStatements.length,
435
455
  dashboardMode: compiled.source.includeDashboardPolicies ? 'dashboard' : 'disabled',
456
+ signaturePresent: Boolean(compiled.signature && compiled.signature.value),
457
+ signatureKeyId: compiled.signature?.keyId || null,
436
458
  },
437
459
  });
438
460
  }
@@ -454,6 +476,12 @@ function policyCommand(program) {
454
476
  console.log(chalk.dim(`Fingerprint: ${compiled.fingerprint}`));
455
477
  console.log(chalk.dim(`Deterministic rules: ${compiled.compilation.deterministicRuleCount}`));
456
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
+ }
457
485
  console.log(chalk.dim(`Policy source: ${compiled.source.includeDashboardPolicies ? 'dashboard + local packs' : 'local packs only'}`));
458
486
  if (customPolicyResolution.dashboardWarning) {
459
487
  console.log(chalk.yellow(`\n⚠️ ${customPolicyResolution.dashboardWarning}`));
@@ -463,7 +491,22 @@ function policyCommand(program) {
463
491
  catch (error) {
464
492
  const message = error instanceof Error ? error.message : 'Unknown error';
465
493
  if (options.json) {
466
- 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));
467
510
  process.exit(1);
468
511
  }
469
512
  console.error(chalk.red(`\n❌ ${message}\n`));
@@ -783,9 +826,59 @@ function policyCommand(program) {
783
826
  exception
784
827
  .command('list')
785
828
  .description('List policy exceptions for this repository')
829
+ .option('--org', 'List centralized organization policy exceptions from Neurcode Cloud')
786
830
  .option('--all', 'Include inactive/expired exceptions')
787
831
  .option('--json', 'Output as JSON')
788
- .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
+ }
789
882
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
790
883
  const data = (0, policy_exceptions_1.listPolicyExceptions)(cwd);
791
884
  const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
@@ -865,6 +958,7 @@ function policyCommand(program) {
865
958
  exception
866
959
  .command('add')
867
960
  .description('Add a policy exception entry')
961
+ .option('--org', 'Create centralized organization policy exception in Neurcode Cloud')
868
962
  .requiredOption('--rule <pattern>', 'Rule pattern (exact, wildcard, or /regex/)')
869
963
  .requiredOption('--file <pattern>', 'File pattern (exact, wildcard, or /regex/)')
870
964
  .requiredOption('--reason <text>', 'Business justification for this exception')
@@ -873,7 +967,7 @@ function policyCommand(program) {
873
967
  .option('--expires-at <iso>', 'Expiry timestamp in ISO-8601')
874
968
  .option('--expires-in-days <n>', 'Expiry offset in days (default: 30)', (value) => parseInt(value, 10))
875
969
  .option('--json', 'Output as JSON')
876
- .action((options) => {
970
+ .action(async (options) => {
877
971
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
878
972
  try {
879
973
  const severity = options.severity === 'allow' || options.severity === 'warn' || options.severity === 'block'
@@ -886,6 +980,37 @@ function policyCommand(program) {
886
980
  expiresAt: options.expiresAt,
887
981
  expiresInDays: options.expiresInDays,
888
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
+ }
889
1014
  const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
890
1015
  if (governance.exceptionApprovals.requireReason
891
1016
  && options.reason.trim().length < governance.exceptionApprovals.minReasonLength) {
@@ -966,13 +1091,38 @@ function policyCommand(program) {
966
1091
  .command('approve')
967
1092
  .description('Approve a policy exception by ID')
968
1093
  .argument('<id>', 'Exception ID to approve')
1094
+ .option('--org', 'Approve centralized organization policy exception in Neurcode Cloud')
969
1095
  .option('--by <actor>', 'Approver identity (defaults to NEURCODE_ACTOR/GITHUB_ACTOR/USER)')
970
1096
  .option('--comment <text>', 'Approval comment')
971
1097
  .option('--json', 'Output as JSON')
972
- .action((id, options) => {
1098
+ .action(async (id, options) => {
973
1099
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
974
1100
  const approver = resolveActor(options.by);
975
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
+ }
976
1126
  const governance = (0, policy_governance_1.readPolicyGovernanceConfig)(cwd);
977
1127
  const target = (0, policy_exceptions_1.listPolicyExceptions)(cwd).all.find((entry) => entry.id === String(id).trim());
978
1128
  if (!target) {
@@ -1066,12 +1216,185 @@ function policyCommand(program) {
1066
1216
  process.exit(1);
1067
1217
  }
1068
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
+ });
1069
1362
  exception
1070
1363
  .command('remove')
1071
1364
  .description('Deactivate a policy exception by ID')
1072
1365
  .argument('<id>', 'Exception ID to deactivate')
1366
+ .option('--org', 'Revoke centralized organization policy exception in Neurcode Cloud')
1073
1367
  .option('--json', 'Output as JSON')
1074
- .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
+ }
1075
1398
  const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
1076
1399
  const removed = (0, policy_exceptions_1.revokePolicyException)(cwd, String(id).trim());
1077
1400
  if (removed) {