@neurcode-ai/cli 0.9.31 → 0.9.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +22 -0
- package/dist/commands/apply.d.ts.map +1 -1
- package/dist/commands/apply.js +45 -3
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/map.d.ts.map +1 -1
- package/dist/commands/map.js +78 -1
- package/dist/commands/map.js.map +1 -1
- package/dist/commands/plan-slo.d.ts +7 -0
- package/dist/commands/plan-slo.d.ts.map +1 -0
- package/dist/commands/plan-slo.js +205 -0
- package/dist/commands/plan-slo.js.map +1 -0
- package/dist/commands/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +665 -29
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/repo.d.ts +3 -0
- package/dist/commands/repo.d.ts.map +1 -0
- package/dist/commands/repo.js +166 -0
- package/dist/commands/repo.js.map +1 -0
- package/dist/commands/ship.d.ts.map +1 -1
- package/dist/commands/ship.js +29 -0
- package/dist/commands/ship.js.map +1 -1
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +261 -9
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +17 -0
- package/dist/index.js.map +1 -1
- package/dist/services/mapper/ProjectScanner.d.ts +76 -2
- package/dist/services/mapper/ProjectScanner.d.ts.map +1 -1
- package/dist/services/mapper/ProjectScanner.js +545 -40
- package/dist/services/mapper/ProjectScanner.js.map +1 -1
- package/dist/services/security/SecurityGuard.d.ts +21 -2
- package/dist/services/security/SecurityGuard.d.ts.map +1 -1
- package/dist/services/security/SecurityGuard.js +130 -27
- package/dist/services/security/SecurityGuard.js.map +1 -1
- package/dist/utils/governance.d.ts +2 -0
- package/dist/utils/governance.d.ts.map +1 -1
- package/dist/utils/governance.js +2 -0
- package/dist/utils/governance.js.map +1 -1
- package/dist/utils/plan-slo.d.ts +73 -0
- package/dist/utils/plan-slo.d.ts.map +1 -0
- package/dist/utils/plan-slo.js +271 -0
- package/dist/utils/plan-slo.js.map +1 -0
- package/dist/utils/project-root.d.ts +5 -4
- package/dist/utils/project-root.d.ts.map +1 -1
- package/dist/utils/project-root.js +82 -7
- package/dist/utils/project-root.js.map +1 -1
- package/dist/utils/repo-links.d.ts +17 -0
- package/dist/utils/repo-links.d.ts.map +1 -0
- package/dist/utils/repo-links.js +136 -0
- package/dist/utils/repo-links.js.map +1 -0
- package/package.json +3 -3
package/dist/commands/verify.js
CHANGED
|
@@ -241,6 +241,57 @@ function isEnabledFlag(value) {
|
|
|
241
241
|
const normalized = value.trim().toLowerCase();
|
|
242
242
|
return normalized === '1' || normalized === 'true' || normalized === 'yes' || normalized === 'on';
|
|
243
243
|
}
|
|
244
|
+
function parseSigningKeyRing(raw) {
|
|
245
|
+
if (!raw || !raw.trim()) {
|
|
246
|
+
return {};
|
|
247
|
+
}
|
|
248
|
+
const out = {};
|
|
249
|
+
for (const token of raw.split(/[,\n;]+/)) {
|
|
250
|
+
const trimmed = token.trim();
|
|
251
|
+
if (!trimmed)
|
|
252
|
+
continue;
|
|
253
|
+
const separator = trimmed.indexOf('=');
|
|
254
|
+
if (separator <= 0)
|
|
255
|
+
continue;
|
|
256
|
+
const keyId = trimmed.slice(0, separator).trim();
|
|
257
|
+
const key = trimmed.slice(separator + 1).trim();
|
|
258
|
+
if (!keyId || !key)
|
|
259
|
+
continue;
|
|
260
|
+
out[keyId] = key;
|
|
261
|
+
}
|
|
262
|
+
return out;
|
|
263
|
+
}
|
|
264
|
+
function resolveGovernanceSigningConfig() {
|
|
265
|
+
const signingKeys = parseSigningKeyRing(process.env.NEURCODE_GOVERNANCE_SIGNING_KEYS);
|
|
266
|
+
const envSigningKey = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY?.trim() ||
|
|
267
|
+
process.env.NEURCODE_AI_LOG_SIGNING_KEY?.trim() ||
|
|
268
|
+
'';
|
|
269
|
+
const requestedKeyId = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY_ID?.trim() || '';
|
|
270
|
+
const signer = process.env.NEURCODE_GOVERNANCE_SIGNER || process.env.USER || 'neurcode-cli';
|
|
271
|
+
let signingKey = envSigningKey || null;
|
|
272
|
+
let signingKeyId = requestedKeyId || null;
|
|
273
|
+
if (!signingKey && Object.keys(signingKeys).length > 0) {
|
|
274
|
+
if (signingKeyId && signingKeys[signingKeyId]) {
|
|
275
|
+
signingKey = signingKeys[signingKeyId];
|
|
276
|
+
}
|
|
277
|
+
else {
|
|
278
|
+
const fallbackKeyId = Object.keys(signingKeys).sort((a, b) => a.localeCompare(b))[0];
|
|
279
|
+
signingKey = signingKeys[fallbackKeyId];
|
|
280
|
+
signingKeyId = signingKeyId || fallbackKeyId;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
return {
|
|
284
|
+
signingKey,
|
|
285
|
+
signingKeyId,
|
|
286
|
+
signingKeys,
|
|
287
|
+
signer,
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
function isSignedAiLogsRequired(orgGovernanceSettings) {
|
|
291
|
+
return (orgGovernanceSettings?.requireSignedAiLogs === true ||
|
|
292
|
+
isEnabledFlag(process.env.NEURCODE_GOVERNANCE_REQUIRE_SIGNED_LOGS) ||
|
|
293
|
+
isEnabledFlag(process.env.NEURCODE_AI_LOG_REQUIRE_SIGNED));
|
|
294
|
+
}
|
|
244
295
|
function policyLockMismatchMessage(mismatches) {
|
|
245
296
|
if (mismatches.length === 0) {
|
|
246
297
|
return 'Policy lock baseline check failed';
|
|
@@ -309,7 +360,7 @@ async function recordVerificationIfRequested(options, config, payload) {
|
|
|
309
360
|
* Execute policy-only verification (General Governance mode)
|
|
310
361
|
* Returns the exit code to use
|
|
311
362
|
*/
|
|
312
|
-
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigner) {
|
|
363
|
+
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner) {
|
|
313
364
|
if (!options.json) {
|
|
314
365
|
console.log(chalk.cyan('🛡️ General Governance mode (policy only, no plan linked)\n'));
|
|
315
366
|
}
|
|
@@ -321,10 +372,128 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
321
372
|
contextCandidates: diffFiles.map((file) => file.path),
|
|
322
373
|
orgGovernance: orgGovernanceSettings,
|
|
323
374
|
signingKey: aiLogSigningKey,
|
|
375
|
+
signingKeyId: aiLogSigningKeyId,
|
|
376
|
+
signingKeys: aiLogSigningKeys,
|
|
324
377
|
signer: aiLogSigner,
|
|
325
378
|
});
|
|
326
379
|
const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings);
|
|
327
380
|
const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
|
|
381
|
+
const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
|
|
382
|
+
if (signedLogsRequired && !governanceAnalysis.aiChangeLogIntegrity.valid) {
|
|
383
|
+
const message = `AI change-log integrity check failed: ${governanceAnalysis.aiChangeLogIntegrity.issues.join('; ') || 'unknown issue'}`;
|
|
384
|
+
if (options.json) {
|
|
385
|
+
console.log(JSON.stringify({
|
|
386
|
+
grade: 'F',
|
|
387
|
+
score: 0,
|
|
388
|
+
verdict: 'FAIL',
|
|
389
|
+
violations: [
|
|
390
|
+
{
|
|
391
|
+
file: '.neurcode/ai-change-log.json',
|
|
392
|
+
rule: 'ai_change_log_integrity',
|
|
393
|
+
severity: 'block',
|
|
394
|
+
message,
|
|
395
|
+
},
|
|
396
|
+
],
|
|
397
|
+
message,
|
|
398
|
+
scopeGuardPassed: false,
|
|
399
|
+
bloatCount: 0,
|
|
400
|
+
bloatFiles: [],
|
|
401
|
+
plannedFilesModified: 0,
|
|
402
|
+
totalPlannedFiles: 0,
|
|
403
|
+
adherenceScore: 0,
|
|
404
|
+
mode: 'policy_only',
|
|
405
|
+
policyOnly: true,
|
|
406
|
+
policyOnlySource: source,
|
|
407
|
+
...governancePayload,
|
|
408
|
+
}, null, 2));
|
|
409
|
+
}
|
|
410
|
+
else {
|
|
411
|
+
console.log(chalk.red('❌ AI change-log integrity validation failed (policy-only mode).'));
|
|
412
|
+
console.log(chalk.red(` ${message}`));
|
|
413
|
+
}
|
|
414
|
+
await recordVerificationIfRequested(options, config, {
|
|
415
|
+
grade: 'F',
|
|
416
|
+
violations: [
|
|
417
|
+
{
|
|
418
|
+
file: '.neurcode/ai-change-log.json',
|
|
419
|
+
rule: 'ai_change_log_integrity',
|
|
420
|
+
severity: 'block',
|
|
421
|
+
message,
|
|
422
|
+
},
|
|
423
|
+
],
|
|
424
|
+
verifyResult: {
|
|
425
|
+
adherenceScore: 0,
|
|
426
|
+
verdict: 'FAIL',
|
|
427
|
+
bloatCount: 0,
|
|
428
|
+
bloatFiles: [],
|
|
429
|
+
message,
|
|
430
|
+
},
|
|
431
|
+
projectId,
|
|
432
|
+
jsonMode: Boolean(options.json),
|
|
433
|
+
governance: governancePayload,
|
|
434
|
+
});
|
|
435
|
+
return 2;
|
|
436
|
+
}
|
|
437
|
+
if (governanceAnalysis.governanceDecision.decision === 'block') {
|
|
438
|
+
const message = governanceAnalysis.governanceDecision.summary
|
|
439
|
+
|| 'Governance decision matrix returned BLOCK.';
|
|
440
|
+
const reasonCodes = governanceAnalysis.governanceDecision.reasonCodes || [];
|
|
441
|
+
if (options.json) {
|
|
442
|
+
console.log(JSON.stringify({
|
|
443
|
+
grade: 'F',
|
|
444
|
+
score: 0,
|
|
445
|
+
verdict: 'FAIL',
|
|
446
|
+
violations: [
|
|
447
|
+
{
|
|
448
|
+
file: '.neurcode/ai-change-log.json',
|
|
449
|
+
rule: 'governance_decision_block',
|
|
450
|
+
severity: 'block',
|
|
451
|
+
message,
|
|
452
|
+
},
|
|
453
|
+
],
|
|
454
|
+
message,
|
|
455
|
+
scopeGuardPassed: false,
|
|
456
|
+
bloatCount: 0,
|
|
457
|
+
bloatFiles: [],
|
|
458
|
+
plannedFilesModified: 0,
|
|
459
|
+
totalPlannedFiles: 0,
|
|
460
|
+
adherenceScore: 0,
|
|
461
|
+
mode: 'policy_only',
|
|
462
|
+
policyOnly: true,
|
|
463
|
+
policyOnlySource: source,
|
|
464
|
+
...governancePayload,
|
|
465
|
+
}, null, 2));
|
|
466
|
+
}
|
|
467
|
+
else {
|
|
468
|
+
console.log(chalk.red('❌ Governance decision blocked this change set (policy-only mode).'));
|
|
469
|
+
if (reasonCodes.length > 0) {
|
|
470
|
+
console.log(chalk.red(` Reasons: ${reasonCodes.join(', ')}`));
|
|
471
|
+
}
|
|
472
|
+
console.log(chalk.red(` ${message}`));
|
|
473
|
+
}
|
|
474
|
+
await recordVerificationIfRequested(options, config, {
|
|
475
|
+
grade: 'F',
|
|
476
|
+
violations: [
|
|
477
|
+
{
|
|
478
|
+
file: '.neurcode/ai-change-log.json',
|
|
479
|
+
rule: 'governance_decision_block',
|
|
480
|
+
severity: 'block',
|
|
481
|
+
message,
|
|
482
|
+
},
|
|
483
|
+
],
|
|
484
|
+
verifyResult: {
|
|
485
|
+
adherenceScore: 0,
|
|
486
|
+
verdict: 'FAIL',
|
|
487
|
+
bloatCount: 0,
|
|
488
|
+
bloatFiles: [],
|
|
489
|
+
message,
|
|
490
|
+
},
|
|
491
|
+
projectId,
|
|
492
|
+
jsonMode: Boolean(options.json),
|
|
493
|
+
governance: governancePayload,
|
|
494
|
+
});
|
|
495
|
+
return 2;
|
|
496
|
+
}
|
|
328
497
|
if (contextPolicyViolations.length > 0) {
|
|
329
498
|
const message = `Context policy violation: ${contextPolicyViolations.map((item) => item.file).join(', ')}`;
|
|
330
499
|
const contextPolicyViolationItems = contextPolicyViolations.map((item) => ({
|
|
@@ -719,10 +888,11 @@ async function verifyCommand(options) {
|
|
|
719
888
|
orgId: (0, state_1.getOrgId)(),
|
|
720
889
|
projectId: projectId || null,
|
|
721
890
|
};
|
|
722
|
-
const
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
const
|
|
891
|
+
const signingConfig = resolveGovernanceSigningConfig();
|
|
892
|
+
const aiLogSigningKey = signingConfig.signingKey;
|
|
893
|
+
const aiLogSigningKeyId = signingConfig.signingKeyId;
|
|
894
|
+
const aiLogSigningKeys = signingConfig.signingKeys;
|
|
895
|
+
const aiLogSigner = signingConfig.signer;
|
|
726
896
|
let orgGovernanceSettings = null;
|
|
727
897
|
if (config.apiKey) {
|
|
728
898
|
try {
|
|
@@ -744,6 +914,8 @@ async function verifyCommand(options) {
|
|
|
744
914
|
}
|
|
745
915
|
}
|
|
746
916
|
}
|
|
917
|
+
const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
|
|
918
|
+
const hasSigningMaterial = Boolean(aiLogSigningKey) || Object.keys(aiLogSigningKeys).length > 0;
|
|
747
919
|
const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
|
|
748
920
|
if (!brainScope.orgId || !brainScope.projectId) {
|
|
749
921
|
return;
|
|
@@ -765,6 +937,60 @@ async function verifyCommand(options) {
|
|
|
765
937
|
// Never block verify flow on Brain persistence failures.
|
|
766
938
|
}
|
|
767
939
|
};
|
|
940
|
+
if (signedLogsRequired && !hasSigningMaterial) {
|
|
941
|
+
const message = 'Signed AI change-logs are required but no signing key is configured. Set NEURCODE_GOVERNANCE_SIGNING_KEY or NEURCODE_GOVERNANCE_SIGNING_KEYS.';
|
|
942
|
+
recordVerifyEvent('FAIL', 'missing_signing_key_material');
|
|
943
|
+
if (options.json) {
|
|
944
|
+
console.log(JSON.stringify({
|
|
945
|
+
grade: 'F',
|
|
946
|
+
score: 0,
|
|
947
|
+
verdict: 'FAIL',
|
|
948
|
+
violations: [
|
|
949
|
+
{
|
|
950
|
+
file: '.neurcode/ai-change-log.json',
|
|
951
|
+
rule: 'ai_change_log_signing_required',
|
|
952
|
+
severity: 'block',
|
|
953
|
+
message,
|
|
954
|
+
},
|
|
955
|
+
],
|
|
956
|
+
adherenceScore: 0,
|
|
957
|
+
bloatCount: 0,
|
|
958
|
+
bloatFiles: [],
|
|
959
|
+
plannedFilesModified: 0,
|
|
960
|
+
totalPlannedFiles: 0,
|
|
961
|
+
message,
|
|
962
|
+
scopeGuardPassed: false,
|
|
963
|
+
mode: 'plan_enforced',
|
|
964
|
+
policyOnly: false,
|
|
965
|
+
}, null, 2));
|
|
966
|
+
}
|
|
967
|
+
else {
|
|
968
|
+
console.log(chalk.red('\n⛔ Governance Signing Key Missing'));
|
|
969
|
+
console.log(chalk.red(` ${message}`));
|
|
970
|
+
console.log(chalk.dim(' Recommended: set NEURCODE_GOVERNANCE_SIGNING_KEY_ID and key ring via NEURCODE_GOVERNANCE_SIGNING_KEYS.'));
|
|
971
|
+
}
|
|
972
|
+
await recordVerificationIfRequested(options, config, {
|
|
973
|
+
grade: 'F',
|
|
974
|
+
violations: [
|
|
975
|
+
{
|
|
976
|
+
file: '.neurcode/ai-change-log.json',
|
|
977
|
+
rule: 'ai_change_log_signing_required',
|
|
978
|
+
severity: 'block',
|
|
979
|
+
message,
|
|
980
|
+
},
|
|
981
|
+
],
|
|
982
|
+
verifyResult: {
|
|
983
|
+
adherenceScore: 0,
|
|
984
|
+
verdict: 'FAIL',
|
|
985
|
+
bloatCount: 0,
|
|
986
|
+
bloatFiles: [],
|
|
987
|
+
message,
|
|
988
|
+
},
|
|
989
|
+
projectId: projectId || undefined,
|
|
990
|
+
jsonMode: Boolean(options.json),
|
|
991
|
+
});
|
|
992
|
+
process.exit(2);
|
|
993
|
+
}
|
|
768
994
|
// Determine which diff to capture (staged + unstaged for full current work)
|
|
769
995
|
let diffText;
|
|
770
996
|
if (options.staged) {
|
|
@@ -876,6 +1102,8 @@ async function verifyCommand(options) {
|
|
|
876
1102
|
contextCandidates: diffFiles.map((file) => file.path),
|
|
877
1103
|
orgGovernance: orgGovernanceSettings,
|
|
878
1104
|
signingKey: aiLogSigningKey,
|
|
1105
|
+
signingKeyId: aiLogSigningKeyId,
|
|
1106
|
+
signingKeys: aiLogSigningKeys,
|
|
879
1107
|
signer: aiLogSigner,
|
|
880
1108
|
});
|
|
881
1109
|
const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings);
|
|
@@ -935,7 +1163,7 @@ async function verifyCommand(options) {
|
|
|
935
1163
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
936
1164
|
}
|
|
937
1165
|
const runPolicyOnlyModeAndExit = async (source) => {
|
|
938
|
-
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
|
|
1166
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner);
|
|
939
1167
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
940
1168
|
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
941
1169
|
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
@@ -1069,6 +1297,8 @@ async function verifyCommand(options) {
|
|
|
1069
1297
|
contextCandidates: planFiles,
|
|
1070
1298
|
orgGovernance: orgGovernanceSettings,
|
|
1071
1299
|
signingKey: aiLogSigningKey,
|
|
1300
|
+
signingKeyId: aiLogSigningKeyId,
|
|
1301
|
+
signingKeys: aiLogSigningKeys,
|
|
1072
1302
|
signer: aiLogSigner,
|
|
1073
1303
|
});
|
|
1074
1304
|
// Get sessionId from state file (.neurcode/state.json) first, then fallback to config
|
|
@@ -1517,13 +1747,23 @@ async function verifyCommand(options) {
|
|
|
1517
1747
|
const verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraints);
|
|
1518
1748
|
// Apply custom policy verdict: block from dashboard overrides API verdict
|
|
1519
1749
|
const policyBlock = policyDecision === 'block' && policyViolations.length > 0;
|
|
1520
|
-
const
|
|
1750
|
+
const governanceDecisionBlock = governanceResult?.governanceDecision?.decision === 'block';
|
|
1751
|
+
const governanceIntegrityBlock = signedLogsRequired && governanceResult ? governanceResult.aiChangeLogIntegrity.valid !== true : false;
|
|
1752
|
+
const governanceHardBlock = governanceDecisionBlock || governanceIntegrityBlock;
|
|
1753
|
+
const effectiveVerdict = policyBlock || governanceHardBlock
|
|
1754
|
+
? 'FAIL'
|
|
1755
|
+
: verifyResult.verdict;
|
|
1521
1756
|
const policyMessageBase = policyBlock
|
|
1522
1757
|
? `Custom policy violations: ${policyViolations.map(v => `${v.file}: ${v.message || v.rule}`).join('; ')}. ${verifyResult.message}`
|
|
1523
1758
|
: verifyResult.message;
|
|
1524
|
-
const
|
|
1759
|
+
const governanceBlockReason = governanceIntegrityBlock
|
|
1760
|
+
? `AI change-log integrity failed: ${governanceResult?.aiChangeLogIntegrity?.issues?.join('; ') || 'unknown issue'}`
|
|
1761
|
+
: governanceDecisionBlock
|
|
1762
|
+
? governanceResult?.governanceDecision?.summary || 'Governance decision matrix returned BLOCK.'
|
|
1763
|
+
: null;
|
|
1764
|
+
const effectiveMessage = (policyExceptionsSummary.suppressed > 0
|
|
1525
1765
|
? `${policyMessageBase} Policy exceptions suppressed ${policyExceptionsSummary.suppressed} violation(s).`
|
|
1526
|
-
: policyMessageBase;
|
|
1766
|
+
: policyMessageBase) + (governanceBlockReason ? ` ${governanceBlockReason}` : '');
|
|
1527
1767
|
// Calculate grade from effective verdict and score
|
|
1528
1768
|
// CRITICAL: 0/0 planned files = 'F' (Incomplete), not 'B'
|
|
1529
1769
|
// Bloat automatically drops grade by at least one letter
|
|
@@ -1572,6 +1812,7 @@ async function verifyCommand(options) {
|
|
|
1572
1812
|
recordVerifyEvent(effectiveVerdict, `adherence=${verifyResult.adherenceScore};bloat=${verifyResult.bloatCount};scopeGuard=${scopeGuardPassed ? 1 : 0};policy=${policyDecision};policyExceptions=${policyExceptionsSummary.suppressed}`, changedPathsForBrain, finalPlanId);
|
|
1573
1813
|
const shouldForceGovernancePass = scopeGuardPassed &&
|
|
1574
1814
|
!policyBlock &&
|
|
1815
|
+
!governanceHardBlock &&
|
|
1575
1816
|
(effectiveVerdict === 'PASS' ||
|
|
1576
1817
|
((verifyResult.verdict === 'FAIL' || verifyResult.verdict === 'WARN') &&
|
|
1577
1818
|
policyViolations.length === 0 &&
|
|
@@ -2069,6 +2310,17 @@ function displayGovernanceInsights(governance, options = {}) {
|
|
|
2069
2310
|
console.log(governance.aiChangeLogIntegrity.valid
|
|
2070
2311
|
? chalk.dim(` AI change-log integrity: valid (${governance.aiChangeLogIntegrity.signed ? 'signed' : 'unsigned'})`)
|
|
2071
2312
|
: chalk.red(` AI change-log integrity: invalid (${governance.aiChangeLogIntegrity.issues.join('; ') || 'unknown'})`));
|
|
2313
|
+
if (governance.aiChangeLogIntegrity.signed) {
|
|
2314
|
+
const keyId = typeof governance.aiChangeLogIntegrity.keyId === 'string'
|
|
2315
|
+
? governance.aiChangeLogIntegrity.keyId
|
|
2316
|
+
: null;
|
|
2317
|
+
const verifiedWithKeyId = typeof governance.aiChangeLogIntegrity.verifiedWithKeyId === 'string'
|
|
2318
|
+
? governance.aiChangeLogIntegrity.verifiedWithKeyId
|
|
2319
|
+
: null;
|
|
2320
|
+
if (keyId || verifiedWithKeyId) {
|
|
2321
|
+
console.log(chalk.dim(` Signing key: ${keyId || 'n/a'}${verifiedWithKeyId ? ` (verified via ${verifiedWithKeyId})` : ''}`));
|
|
2322
|
+
}
|
|
2323
|
+
}
|
|
2072
2324
|
if (governance.suspiciousChange.flagged) {
|
|
2073
2325
|
console.log(chalk.red('\nSuspicious Change Detected'));
|
|
2074
2326
|
console.log(chalk.red(` Plan expected files: ${governance.suspiciousChange.expectedFiles} | AI modified files: ${governance.suspiciousChange.actualFiles}`));
|