@neurcode-ai/cli 0.9.29 → 0.9.31
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/LICENSE +201 -0
- package/dist/api-client.d.ts +20 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +35 -8
- package/dist/api-client.js.map +1 -1
- package/dist/commands/apply.d.ts.map +1 -1
- package/dist/commands/apply.js +33 -0
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/approve.d.ts +11 -0
- package/dist/commands/approve.d.ts.map +1 -0
- package/dist/commands/approve.js +116 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +47 -3
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/ship.d.ts +2 -0
- package/dist/commands/ship.d.ts.map +1 -1
- package/dist/commands/ship.js +228 -1
- package/dist/commands/ship.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +504 -69
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/governance.d.ts +33 -0
- package/dist/utils/governance.d.ts.map +1 -0
- package/dist/utils/governance.js +68 -0
- package/dist/utils/governance.js.map +1 -0
- package/dist/utils/manual-approvals.d.ts +20 -0
- package/dist/utils/manual-approvals.d.ts.map +1 -0
- package/dist/utils/manual-approvals.js +104 -0
- package/dist/utils/manual-approvals.js.map +1 -0
- package/package.json +12 -9
package/dist/commands/verify.js
CHANGED
|
@@ -58,6 +58,8 @@ const custom_policy_rules_1 = require("../utils/custom-policy-rules");
|
|
|
58
58
|
const policy_exceptions_1 = require("../utils/policy-exceptions");
|
|
59
59
|
const policy_governance_1 = require("../utils/policy-governance");
|
|
60
60
|
const policy_audit_1 = require("../utils/policy-audit");
|
|
61
|
+
const governance_1 = require("../utils/governance");
|
|
62
|
+
const policy_1 = require("@neurcode-ai/policy");
|
|
61
63
|
// Import chalk with fallback
|
|
62
64
|
let chalk;
|
|
63
65
|
try {
|
|
@@ -290,14 +292,89 @@ function resolveAuditIntegrityStatus(requireIntegrity, auditIntegrity) {
|
|
|
290
292
|
issues,
|
|
291
293
|
};
|
|
292
294
|
}
|
|
295
|
+
async function recordVerificationIfRequested(options, config, payload) {
|
|
296
|
+
if (!options.record) {
|
|
297
|
+
return;
|
|
298
|
+
}
|
|
299
|
+
if (!config.apiKey) {
|
|
300
|
+
if (!payload.jsonMode) {
|
|
301
|
+
console.log(chalk.yellow('\n⚠️ --record flag requires API key'));
|
|
302
|
+
console.log(chalk.dim(' Set NEURCODE_API_KEY environment variable or use --api-key flag'));
|
|
303
|
+
}
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance);
|
|
307
|
+
}
|
|
293
308
|
/**
|
|
294
309
|
* Execute policy-only verification (General Governance mode)
|
|
295
310
|
* Returns the exit code to use
|
|
296
311
|
*/
|
|
297
|
-
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source) {
|
|
312
|
+
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigner) {
|
|
298
313
|
if (!options.json) {
|
|
299
314
|
console.log(chalk.cyan('🛡️ General Governance mode (policy only, no plan linked)\n'));
|
|
300
315
|
}
|
|
316
|
+
const governanceAnalysis = (0, governance_1.evaluateGovernance)({
|
|
317
|
+
projectRoot,
|
|
318
|
+
task: 'Policy-only verification',
|
|
319
|
+
expectedFiles: [],
|
|
320
|
+
diffFiles,
|
|
321
|
+
contextCandidates: diffFiles.map((file) => file.path),
|
|
322
|
+
orgGovernance: orgGovernanceSettings,
|
|
323
|
+
signingKey: aiLogSigningKey,
|
|
324
|
+
signer: aiLogSigner,
|
|
325
|
+
});
|
|
326
|
+
const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings);
|
|
327
|
+
const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
|
|
328
|
+
if (contextPolicyViolations.length > 0) {
|
|
329
|
+
const message = `Context policy violation: ${contextPolicyViolations.map((item) => item.file).join(', ')}`;
|
|
330
|
+
const contextPolicyViolationItems = contextPolicyViolations.map((item) => ({
|
|
331
|
+
file: item.file,
|
|
332
|
+
rule: `context_policy:${item.rule}`,
|
|
333
|
+
severity: 'block',
|
|
334
|
+
message: item.reason,
|
|
335
|
+
}));
|
|
336
|
+
if (options.json) {
|
|
337
|
+
console.log(JSON.stringify({
|
|
338
|
+
grade: 'F',
|
|
339
|
+
score: 0,
|
|
340
|
+
verdict: 'FAIL',
|
|
341
|
+
violations: contextPolicyViolationItems,
|
|
342
|
+
message,
|
|
343
|
+
scopeGuardPassed: false,
|
|
344
|
+
bloatCount: 0,
|
|
345
|
+
bloatFiles: [],
|
|
346
|
+
plannedFilesModified: 0,
|
|
347
|
+
totalPlannedFiles: 0,
|
|
348
|
+
adherenceScore: 0,
|
|
349
|
+
mode: 'policy_only',
|
|
350
|
+
policyOnly: true,
|
|
351
|
+
policyOnlySource: source,
|
|
352
|
+
...governancePayload,
|
|
353
|
+
}, null, 2));
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
console.log(chalk.red('❌ Context policy violation detected (policy-only mode).'));
|
|
357
|
+
contextPolicyViolations.forEach((item) => {
|
|
358
|
+
console.log(chalk.red(` • ${item.file}: ${item.reason}`));
|
|
359
|
+
});
|
|
360
|
+
console.log(chalk.dim(`\n${message}`));
|
|
361
|
+
}
|
|
362
|
+
await recordVerificationIfRequested(options, config, {
|
|
363
|
+
grade: 'F',
|
|
364
|
+
violations: contextPolicyViolationItems,
|
|
365
|
+
verifyResult: {
|
|
366
|
+
adherenceScore: 0,
|
|
367
|
+
verdict: 'FAIL',
|
|
368
|
+
bloatCount: 0,
|
|
369
|
+
bloatFiles: [],
|
|
370
|
+
message,
|
|
371
|
+
},
|
|
372
|
+
projectId,
|
|
373
|
+
jsonMode: Boolean(options.json),
|
|
374
|
+
governance: governancePayload,
|
|
375
|
+
});
|
|
376
|
+
return 2;
|
|
377
|
+
}
|
|
301
378
|
let policyViolations = [];
|
|
302
379
|
let policyDecision = 'allow';
|
|
303
380
|
const requirePolicyLock = options.requirePolicyLock === true || isEnabledFlag(process.env.NEURCODE_VERIFY_REQUIRE_POLICY_LOCK);
|
|
@@ -369,12 +446,13 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
369
446
|
}
|
|
370
447
|
if (policyLockEvaluation.enforced && !policyLockEvaluation.matched) {
|
|
371
448
|
const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
|
|
449
|
+
const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
|
|
372
450
|
if (options.json) {
|
|
373
451
|
console.log(JSON.stringify({
|
|
374
452
|
grade: 'F',
|
|
375
453
|
score: 0,
|
|
376
454
|
verdict: 'FAIL',
|
|
377
|
-
violations:
|
|
455
|
+
violations: lockViolationItems,
|
|
378
456
|
message,
|
|
379
457
|
scopeGuardPassed: true,
|
|
380
458
|
bloatCount: 0,
|
|
@@ -385,6 +463,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
385
463
|
mode: 'policy_only',
|
|
386
464
|
policyOnly: true,
|
|
387
465
|
policyOnlySource: source,
|
|
466
|
+
...governancePayload,
|
|
388
467
|
policyLock: {
|
|
389
468
|
enforced: true,
|
|
390
469
|
matched: false,
|
|
@@ -401,6 +480,20 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
401
480
|
});
|
|
402
481
|
console.log(chalk.dim('\n If drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
|
|
403
482
|
}
|
|
483
|
+
await recordVerificationIfRequested(options, config, {
|
|
484
|
+
grade: 'F',
|
|
485
|
+
violations: lockViolationItems,
|
|
486
|
+
verifyResult: {
|
|
487
|
+
adherenceScore: 0,
|
|
488
|
+
verdict: 'FAIL',
|
|
489
|
+
bloatCount: 0,
|
|
490
|
+
bloatFiles: [],
|
|
491
|
+
message,
|
|
492
|
+
},
|
|
493
|
+
projectId,
|
|
494
|
+
jsonMode: Boolean(options.json),
|
|
495
|
+
governance: governancePayload,
|
|
496
|
+
});
|
|
404
497
|
return 2;
|
|
405
498
|
}
|
|
406
499
|
if (!options.json && effectiveRules.customRules.length > 0) {
|
|
@@ -516,6 +609,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
516
609
|
mode: 'policy_only',
|
|
517
610
|
policyOnly: true,
|
|
518
611
|
policyOnlySource: source,
|
|
612
|
+
...governancePayload,
|
|
519
613
|
policyLock: {
|
|
520
614
|
enforced: policyLockEvaluation.enforced,
|
|
521
615
|
matched: policyLockEvaluation.matched,
|
|
@@ -555,8 +649,23 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
555
649
|
if (governance.audit.requireIntegrity && !auditIntegrityStatus.valid) {
|
|
556
650
|
console.log(chalk.red(' Policy audit integrity check failed'));
|
|
557
651
|
}
|
|
652
|
+
displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
|
|
558
653
|
console.log(chalk.dim(`\n${message}`));
|
|
559
654
|
}
|
|
655
|
+
await recordVerificationIfRequested(options, config, {
|
|
656
|
+
grade,
|
|
657
|
+
violations: violationsOutput,
|
|
658
|
+
verifyResult: {
|
|
659
|
+
adherenceScore: score,
|
|
660
|
+
verdict: effectiveVerdict,
|
|
661
|
+
bloatCount: 0,
|
|
662
|
+
bloatFiles: [],
|
|
663
|
+
message,
|
|
664
|
+
},
|
|
665
|
+
projectId,
|
|
666
|
+
jsonMode: Boolean(options.json),
|
|
667
|
+
governance: governancePayload,
|
|
668
|
+
});
|
|
560
669
|
return effectiveVerdict === 'FAIL' ? 2 : effectiveVerdict === 'WARN' ? 1 : 0;
|
|
561
670
|
}
|
|
562
671
|
async function verifyCommand(options) {
|
|
@@ -610,6 +719,31 @@ async function verifyCommand(options) {
|
|
|
610
719
|
orgId: (0, state_1.getOrgId)(),
|
|
611
720
|
projectId: projectId || null,
|
|
612
721
|
};
|
|
722
|
+
const aiLogSigningKey = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY ||
|
|
723
|
+
process.env.NEURCODE_AI_LOG_SIGNING_KEY ||
|
|
724
|
+
null;
|
|
725
|
+
const aiLogSigner = process.env.NEURCODE_GOVERNANCE_SIGNER || process.env.USER || 'neurcode-cli';
|
|
726
|
+
let orgGovernanceSettings = null;
|
|
727
|
+
if (config.apiKey) {
|
|
728
|
+
try {
|
|
729
|
+
const remoteSettings = await client.getOrgGovernanceSettings();
|
|
730
|
+
if (remoteSettings) {
|
|
731
|
+
orgGovernanceSettings = {
|
|
732
|
+
contextPolicy: (0, policy_1.normalizeContextPolicy)(remoteSettings.contextPolicy),
|
|
733
|
+
requireSignedAiLogs: remoteSettings.requireSignedAiLogs === true,
|
|
734
|
+
requireManualApproval: remoteSettings.requireManualApproval !== false,
|
|
735
|
+
minimumManualApprovals: Math.max(1, Math.min(5, Math.floor(remoteSettings.minimumManualApprovals || 1))),
|
|
736
|
+
updatedAt: remoteSettings.updatedAt,
|
|
737
|
+
};
|
|
738
|
+
}
|
|
739
|
+
}
|
|
740
|
+
catch (error) {
|
|
741
|
+
if (!options.json) {
|
|
742
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
743
|
+
console.log(chalk.dim(` Org governance settings unavailable, using local policy only (${message})`));
|
|
744
|
+
}
|
|
745
|
+
}
|
|
746
|
+
}
|
|
613
747
|
const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
|
|
614
748
|
if (!brainScope.orgId || !brainScope.projectId) {
|
|
615
749
|
return;
|
|
@@ -727,13 +861,81 @@ async function verifyCommand(options) {
|
|
|
727
861
|
const normalized = toUnixPath(filePath || '');
|
|
728
862
|
return ignoreFilter(normalized) || runtimeIgnoreSet.has(normalized);
|
|
729
863
|
};
|
|
864
|
+
const baselineContextPolicyLocal = (0, policy_1.loadContextPolicy)(projectRoot);
|
|
865
|
+
const baselineContextPolicy = orgGovernanceSettings?.contextPolicy
|
|
866
|
+
? (0, policy_1.mergeContextPolicies)(baselineContextPolicyLocal, orgGovernanceSettings.contextPolicy)
|
|
867
|
+
: baselineContextPolicyLocal;
|
|
868
|
+
const baselineContextPolicyEvaluation = (0, policy_1.evaluateContextPolicyForChanges)(diffFiles.map((file) => file.path), baselineContextPolicy, diffFiles.map((file) => file.path));
|
|
869
|
+
const baselineContextViolations = baselineContextPolicyEvaluation.violations.filter((item) => !shouldIgnore(item.file));
|
|
870
|
+
if (baselineContextViolations.length > 0) {
|
|
871
|
+
const baselineGovernance = (0, governance_1.evaluateGovernance)({
|
|
872
|
+
projectRoot,
|
|
873
|
+
task: 'Context policy validation',
|
|
874
|
+
expectedFiles: [],
|
|
875
|
+
diffFiles,
|
|
876
|
+
contextCandidates: diffFiles.map((file) => file.path),
|
|
877
|
+
orgGovernance: orgGovernanceSettings,
|
|
878
|
+
signingKey: aiLogSigningKey,
|
|
879
|
+
signer: aiLogSigner,
|
|
880
|
+
});
|
|
881
|
+
const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings);
|
|
882
|
+
const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
|
|
883
|
+
const baselineContextViolationItems = baselineContextViolations.map((item) => ({
|
|
884
|
+
file: item.file,
|
|
885
|
+
rule: `context_policy:${item.rule}`,
|
|
886
|
+
severity: 'block',
|
|
887
|
+
message: item.reason,
|
|
888
|
+
}));
|
|
889
|
+
recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
|
|
890
|
+
if (options.json) {
|
|
891
|
+
console.log(JSON.stringify({
|
|
892
|
+
grade: 'F',
|
|
893
|
+
score: 0,
|
|
894
|
+
verdict: 'FAIL',
|
|
895
|
+
violations: baselineContextViolationItems,
|
|
896
|
+
adherenceScore: 0,
|
|
897
|
+
bloatCount: 0,
|
|
898
|
+
bloatFiles: [],
|
|
899
|
+
plannedFilesModified: 0,
|
|
900
|
+
totalPlannedFiles: 0,
|
|
901
|
+
message,
|
|
902
|
+
scopeGuardPassed: false,
|
|
903
|
+
...baselineGovernancePayload,
|
|
904
|
+
mode: 'policy_violation',
|
|
905
|
+
policyOnly: false,
|
|
906
|
+
}, null, 2));
|
|
907
|
+
}
|
|
908
|
+
else {
|
|
909
|
+
console.log(chalk.red('\n⛔ Context Policy Violation'));
|
|
910
|
+
baselineContextViolations.forEach((item) => {
|
|
911
|
+
console.log(chalk.red(` • ${item.file}: ${item.reason}`));
|
|
912
|
+
});
|
|
913
|
+
console.log(chalk.dim(`\nRisk level: ${baselineGovernance.blastRadius.riskScore.toUpperCase()}`));
|
|
914
|
+
console.log(chalk.red('\nAction blocked.\n'));
|
|
915
|
+
}
|
|
916
|
+
await recordVerificationIfRequested(options, config, {
|
|
917
|
+
grade: 'F',
|
|
918
|
+
violations: baselineContextViolationItems,
|
|
919
|
+
verifyResult: {
|
|
920
|
+
adherenceScore: 0,
|
|
921
|
+
verdict: 'FAIL',
|
|
922
|
+
bloatCount: 0,
|
|
923
|
+
bloatFiles: [],
|
|
924
|
+
message,
|
|
925
|
+
},
|
|
926
|
+
projectId: projectId || undefined,
|
|
927
|
+
jsonMode: Boolean(options.json),
|
|
928
|
+
governance: baselineGovernancePayload,
|
|
929
|
+
});
|
|
930
|
+
process.exit(2);
|
|
931
|
+
}
|
|
730
932
|
if (!options.json) {
|
|
731
933
|
console.log(chalk.cyan('\n📊 Analyzing changes against plan...'));
|
|
732
934
|
console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
|
|
733
935
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
734
936
|
}
|
|
735
937
|
const runPolicyOnlyModeAndExit = async (source) => {
|
|
736
|
-
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source);
|
|
938
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
|
|
737
939
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
738
940
|
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
739
941
|
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
@@ -782,6 +984,7 @@ async function verifyCommand(options) {
|
|
|
782
984
|
if (!planId) {
|
|
783
985
|
if (requirePlan) {
|
|
784
986
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
987
|
+
const message = 'Plan ID is required in strict mode. Run "neurcode plan" first or pass --plan-id.';
|
|
785
988
|
recordVerifyEvent('FAIL', 'missing_plan_id;require_plan=true', changedFiles);
|
|
786
989
|
if (options.json) {
|
|
787
990
|
console.log(JSON.stringify({
|
|
@@ -794,7 +997,7 @@ async function verifyCommand(options) {
|
|
|
794
997
|
bloatFiles: [],
|
|
795
998
|
plannedFilesModified: 0,
|
|
796
999
|
totalPlannedFiles: 0,
|
|
797
|
-
message
|
|
1000
|
+
message,
|
|
798
1001
|
scopeGuardPassed: false,
|
|
799
1002
|
mode: 'plan_required',
|
|
800
1003
|
policyOnly: false,
|
|
@@ -805,6 +1008,19 @@ async function verifyCommand(options) {
|
|
|
805
1008
|
console.log(chalk.dim(' Run "neurcode plan" first or pass --plan-id <id>.'));
|
|
806
1009
|
console.log(chalk.dim(' Use --policy-only only when intentionally running general governance checks.'));
|
|
807
1010
|
}
|
|
1011
|
+
await recordVerificationIfRequested(options, config, {
|
|
1012
|
+
grade: 'F',
|
|
1013
|
+
violations: [],
|
|
1014
|
+
verifyResult: {
|
|
1015
|
+
adherenceScore: 0,
|
|
1016
|
+
verdict: 'FAIL',
|
|
1017
|
+
bloatCount: 0,
|
|
1018
|
+
bloatFiles: [],
|
|
1019
|
+
message,
|
|
1020
|
+
},
|
|
1021
|
+
projectId: projectId || undefined,
|
|
1022
|
+
jsonMode: Boolean(options.json),
|
|
1023
|
+
});
|
|
808
1024
|
process.exit(1);
|
|
809
1025
|
}
|
|
810
1026
|
if (!options.json) {
|
|
@@ -824,6 +1040,7 @@ async function verifyCommand(options) {
|
|
|
824
1040
|
}
|
|
825
1041
|
// Track if scope guard passed - this takes priority over AI grading
|
|
826
1042
|
let scopeGuardPassed = false;
|
|
1043
|
+
let governanceResult = null;
|
|
827
1044
|
try {
|
|
828
1045
|
// Step A: Get Modified Files (already have from diffFiles)
|
|
829
1046
|
const modifiedFiles = diffFiles.map(f => f.path);
|
|
@@ -831,10 +1048,29 @@ async function verifyCommand(options) {
|
|
|
831
1048
|
const planData = await client.getPlan(finalPlanId);
|
|
832
1049
|
// Extract original intent from plan (for constraint checking)
|
|
833
1050
|
const originalIntent = planData.intent || '';
|
|
1051
|
+
const planTitle = typeof planData.content.title === 'string'
|
|
1052
|
+
? planData.content.title?.trim()
|
|
1053
|
+
: '';
|
|
1054
|
+
const planSummary = typeof planData.content.summary === 'string' ? planData.content.summary.trim() : '';
|
|
1055
|
+
const governanceTask = planTitle || planSummary || originalIntent || 'Plan verification';
|
|
834
1056
|
// Get approved files from plan (only files with action CREATE or MODIFY)
|
|
835
1057
|
const planFiles = planData.content.files
|
|
836
1058
|
.filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
|
|
837
1059
|
.map(f => f.path);
|
|
1060
|
+
const planDependencies = Array.isArray(planData.content.dependencies)
|
|
1061
|
+
? planData.content.dependencies.filter((item) => typeof item === 'string')
|
|
1062
|
+
: [];
|
|
1063
|
+
governanceResult = (0, governance_1.evaluateGovernance)({
|
|
1064
|
+
projectRoot,
|
|
1065
|
+
task: governanceTask,
|
|
1066
|
+
expectedFiles: planFiles,
|
|
1067
|
+
expectedDependencies: planDependencies,
|
|
1068
|
+
diffFiles,
|
|
1069
|
+
contextCandidates: planFiles,
|
|
1070
|
+
orgGovernance: orgGovernanceSettings,
|
|
1071
|
+
signingKey: aiLogSigningKey,
|
|
1072
|
+
signer: aiLogSigner,
|
|
1073
|
+
});
|
|
838
1074
|
// Get sessionId from state file (.neurcode/state.json) first, then fallback to config
|
|
839
1075
|
// Fallback to sessionId from plan if not in state/config
|
|
840
1076
|
// This is the session_id string needed to fetch the session
|
|
@@ -881,31 +1117,51 @@ async function verifyCommand(options) {
|
|
|
881
1117
|
// Step D: The Block (only report scope violations for non-ignored files)
|
|
882
1118
|
if (filteredViolations.length > 0) {
|
|
883
1119
|
recordVerifyEvent('FAIL', `scope_violation=${filteredViolations.length}`, modifiedFiles, finalPlanId);
|
|
1120
|
+
const scopeViolationItems = filteredViolations.map((file) => ({
|
|
1121
|
+
file,
|
|
1122
|
+
rule: 'scope_guard',
|
|
1123
|
+
severity: 'block',
|
|
1124
|
+
message: 'File modified outside the plan',
|
|
1125
|
+
}));
|
|
1126
|
+
const scopeViolationMessage = `Scope violation: ${filteredViolations.length} file(s) modified outside the plan`;
|
|
884
1127
|
if (options.json) {
|
|
885
1128
|
// Output JSON for scope violation BEFORE exit. Must include violations for GitHub Action annotations.
|
|
886
|
-
const violationsOutput = filteredViolations.map((file) => ({
|
|
887
|
-
file,
|
|
888
|
-
rule: 'scope_guard',
|
|
889
|
-
severity: 'block',
|
|
890
|
-
message: 'File modified outside the plan',
|
|
891
|
-
}));
|
|
892
1129
|
const jsonOutput = {
|
|
893
1130
|
grade: 'F',
|
|
894
1131
|
score: 0,
|
|
895
1132
|
verdict: 'FAIL',
|
|
896
|
-
violations:
|
|
1133
|
+
violations: scopeViolationItems,
|
|
897
1134
|
adherenceScore: 0,
|
|
898
1135
|
bloatCount: filteredViolations.length,
|
|
899
1136
|
bloatFiles: filteredViolations,
|
|
900
1137
|
plannedFilesModified: 0,
|
|
901
1138
|
totalPlannedFiles: planFiles.length,
|
|
902
|
-
message:
|
|
1139
|
+
message: scopeViolationMessage,
|
|
903
1140
|
scopeGuardPassed: false,
|
|
904
1141
|
mode: 'plan_enforced',
|
|
905
1142
|
policyOnly: false,
|
|
1143
|
+
...(governanceResult
|
|
1144
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1145
|
+
: {}),
|
|
906
1146
|
};
|
|
907
1147
|
// CRITICAL: Print JSON first, then exit
|
|
908
1148
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1149
|
+
await recordVerificationIfRequested(options, config, {
|
|
1150
|
+
grade: 'F',
|
|
1151
|
+
violations: scopeViolationItems,
|
|
1152
|
+
verifyResult: {
|
|
1153
|
+
adherenceScore: 0,
|
|
1154
|
+
verdict: 'FAIL',
|
|
1155
|
+
bloatCount: filteredViolations.length,
|
|
1156
|
+
bloatFiles: filteredViolations,
|
|
1157
|
+
message: scopeViolationMessage,
|
|
1158
|
+
},
|
|
1159
|
+
projectId: projectId || undefined,
|
|
1160
|
+
jsonMode: true,
|
|
1161
|
+
governance: governanceResult
|
|
1162
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1163
|
+
: undefined,
|
|
1164
|
+
});
|
|
909
1165
|
process.exit(1);
|
|
910
1166
|
}
|
|
911
1167
|
else {
|
|
@@ -922,7 +1178,26 @@ async function verifyCommand(options) {
|
|
|
922
1178
|
filteredViolations.forEach(file => {
|
|
923
1179
|
console.log(chalk.dim(` neurcode allow ${file}`));
|
|
924
1180
|
});
|
|
1181
|
+
if (governanceResult) {
|
|
1182
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1183
|
+
}
|
|
925
1184
|
console.log('');
|
|
1185
|
+
await recordVerificationIfRequested(options, config, {
|
|
1186
|
+
grade: 'F',
|
|
1187
|
+
violations: scopeViolationItems,
|
|
1188
|
+
verifyResult: {
|
|
1189
|
+
adherenceScore: 0,
|
|
1190
|
+
verdict: 'FAIL',
|
|
1191
|
+
bloatCount: filteredViolations.length,
|
|
1192
|
+
bloatFiles: filteredViolations,
|
|
1193
|
+
message: scopeViolationMessage,
|
|
1194
|
+
},
|
|
1195
|
+
projectId: projectId || undefined,
|
|
1196
|
+
jsonMode: false,
|
|
1197
|
+
governance: governanceResult
|
|
1198
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1199
|
+
: undefined,
|
|
1200
|
+
});
|
|
926
1201
|
process.exit(1);
|
|
927
1202
|
}
|
|
928
1203
|
}
|
|
@@ -1010,13 +1285,14 @@ async function verifyCommand(options) {
|
|
|
1010
1285
|
}
|
|
1011
1286
|
if (policyLockEvaluation.enforced && !policyLockEvaluation.matched) {
|
|
1012
1287
|
const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
|
|
1288
|
+
const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
|
|
1013
1289
|
recordVerifyEvent('FAIL', 'policy_lock_mismatch', diffFiles.map((f) => f.path), finalPlanId);
|
|
1014
1290
|
if (options.json) {
|
|
1015
1291
|
console.log(JSON.stringify({
|
|
1016
1292
|
grade: 'F',
|
|
1017
1293
|
score: 0,
|
|
1018
1294
|
verdict: 'FAIL',
|
|
1019
|
-
violations:
|
|
1295
|
+
violations: lockViolationItems,
|
|
1020
1296
|
adherenceScore: 0,
|
|
1021
1297
|
bloatCount: 0,
|
|
1022
1298
|
bloatFiles: [],
|
|
@@ -1026,6 +1302,9 @@ async function verifyCommand(options) {
|
|
|
1026
1302
|
scopeGuardPassed,
|
|
1027
1303
|
mode: 'plan_enforced',
|
|
1028
1304
|
policyOnly: false,
|
|
1305
|
+
...(governanceResult
|
|
1306
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1307
|
+
: {}),
|
|
1029
1308
|
policyLock: {
|
|
1030
1309
|
enforced: true,
|
|
1031
1310
|
matched: false,
|
|
@@ -1042,6 +1321,22 @@ async function verifyCommand(options) {
|
|
|
1042
1321
|
});
|
|
1043
1322
|
console.log(chalk.dim('\n If this drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
|
|
1044
1323
|
}
|
|
1324
|
+
await recordVerificationIfRequested(options, config, {
|
|
1325
|
+
grade: 'F',
|
|
1326
|
+
violations: lockViolationItems,
|
|
1327
|
+
verifyResult: {
|
|
1328
|
+
adherenceScore: 0,
|
|
1329
|
+
verdict: 'FAIL',
|
|
1330
|
+
bloatCount: 0,
|
|
1331
|
+
bloatFiles: [],
|
|
1332
|
+
message,
|
|
1333
|
+
},
|
|
1334
|
+
projectId: projectId || undefined,
|
|
1335
|
+
jsonMode: Boolean(options.json),
|
|
1336
|
+
governance: governanceResult
|
|
1337
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1338
|
+
: undefined,
|
|
1339
|
+
});
|
|
1045
1340
|
process.exit(2);
|
|
1046
1341
|
}
|
|
1047
1342
|
}
|
|
@@ -1079,6 +1374,9 @@ async function verifyCommand(options) {
|
|
|
1079
1374
|
mode: 'plan_enforced',
|
|
1080
1375
|
policyOnly: false,
|
|
1081
1376
|
tier: 'FREE',
|
|
1377
|
+
...(governanceResult
|
|
1378
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1379
|
+
: {}),
|
|
1082
1380
|
policyLock: {
|
|
1083
1381
|
enforced: policyLockEvaluation.enforced,
|
|
1084
1382
|
matched: policyLockEvaluation.matched,
|
|
@@ -1309,6 +1607,9 @@ async function verifyCommand(options) {
|
|
|
1309
1607
|
totalPlannedFiles: verifyResult.totalPlannedFiles,
|
|
1310
1608
|
mode: 'plan_enforced',
|
|
1311
1609
|
policyOnly: false,
|
|
1610
|
+
...(governanceResult
|
|
1611
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1612
|
+
: {}),
|
|
1312
1613
|
policyLock: {
|
|
1313
1614
|
enforced: policyLockEvaluation.enforced,
|
|
1314
1615
|
matched: policyLockEvaluation.matched,
|
|
@@ -1330,28 +1631,22 @@ async function verifyCommand(options) {
|
|
|
1330
1631
|
: {}),
|
|
1331
1632
|
};
|
|
1332
1633
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
// Report in background (don't await to avoid blocking JSON output)
|
|
1350
|
-
reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true // jsonMode = true
|
|
1351
|
-
).catch(() => {
|
|
1352
|
-
// Error already logged in reportVerification
|
|
1353
|
-
});
|
|
1354
|
-
}
|
|
1634
|
+
await recordVerificationIfRequested(options, config, {
|
|
1635
|
+
grade,
|
|
1636
|
+
violations: violations,
|
|
1637
|
+
verifyResult: {
|
|
1638
|
+
adherenceScore: verifyResult.adherenceScore,
|
|
1639
|
+
verdict: effectiveVerdict,
|
|
1640
|
+
bloatCount: filteredBloatFiles.length,
|
|
1641
|
+
bloatFiles: filteredBloatFiles,
|
|
1642
|
+
message: effectiveMessage,
|
|
1643
|
+
},
|
|
1644
|
+
projectId: projectId || undefined,
|
|
1645
|
+
jsonMode: true,
|
|
1646
|
+
governance: governanceResult
|
|
1647
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1648
|
+
: undefined,
|
|
1649
|
+
});
|
|
1355
1650
|
// Exit based on effective verdict (same logic as below)
|
|
1356
1651
|
if (shouldForceGovernancePass) {
|
|
1357
1652
|
process.exit(0);
|
|
@@ -1376,6 +1671,9 @@ async function verifyCommand(options) {
|
|
|
1376
1671
|
bloatFiles: displayBloatFiles,
|
|
1377
1672
|
bloatCount: displayBloatFiles.length,
|
|
1378
1673
|
}, policyViolations);
|
|
1674
|
+
if (governanceResult) {
|
|
1675
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1676
|
+
}
|
|
1379
1677
|
if (policyExceptionsSummary.suppressed > 0) {
|
|
1380
1678
|
console.log(chalk.yellow(`\n⚠️ Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
|
|
1381
1679
|
if (policyExceptionsSummary.matchedExceptionIds.length > 0) {
|
|
@@ -1397,34 +1695,37 @@ async function verifyCommand(options) {
|
|
|
1397
1695
|
}
|
|
1398
1696
|
}
|
|
1399
1697
|
// Report to Neurcode Cloud if --record flag is set
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1698
|
+
const filteredBloatForReport = (verifyResult.bloatFiles || []).filter((f) => !shouldIgnore(f));
|
|
1699
|
+
const reportViolations = [
|
|
1700
|
+
...filteredBloatForReport.map((file) => ({
|
|
1701
|
+
rule: 'scope_guard',
|
|
1702
|
+
file: file,
|
|
1703
|
+
severity: 'block',
|
|
1704
|
+
message: 'File modified outside the plan',
|
|
1705
|
+
})),
|
|
1706
|
+
...policyViolations.map((v) => ({
|
|
1707
|
+
rule: v.rule,
|
|
1708
|
+
file: v.file,
|
|
1709
|
+
severity: v.severity,
|
|
1710
|
+
message: v.message,
|
|
1711
|
+
})),
|
|
1712
|
+
];
|
|
1713
|
+
await recordVerificationIfRequested(options, config, {
|
|
1714
|
+
grade,
|
|
1715
|
+
violations: reportViolations,
|
|
1716
|
+
verifyResult: {
|
|
1717
|
+
adherenceScore: verifyResult.adherenceScore,
|
|
1718
|
+
verdict: effectiveVerdict,
|
|
1719
|
+
bloatCount: filteredBloatForReport.length,
|
|
1720
|
+
bloatFiles: filteredBloatForReport,
|
|
1721
|
+
message: effectiveMessage,
|
|
1722
|
+
},
|
|
1723
|
+
projectId: projectId || undefined,
|
|
1724
|
+
jsonMode: false,
|
|
1725
|
+
governance: governanceResult
|
|
1726
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1727
|
+
: undefined,
|
|
1728
|
+
});
|
|
1428
1729
|
// Governance override: keep PASS only when scope guard passes and failure is due
|
|
1429
1730
|
// to server-side bloat mismatch (allowed files unknown to verify API).
|
|
1430
1731
|
if (shouldForceGovernancePass) {
|
|
@@ -1610,10 +1911,67 @@ function collectCIContext() {
|
|
|
1610
1911
|
}
|
|
1611
1912
|
return context;
|
|
1612
1913
|
}
|
|
1914
|
+
const REPORT_MAX_ARRAY_ITEMS = 120;
|
|
1915
|
+
const REPORT_MAX_STRING_LENGTH = 4000;
|
|
1916
|
+
const REPORT_MAX_OBJECT_DEPTH = 6;
|
|
1917
|
+
function compactReportValue(value, depth = 0, seen = new WeakSet()) {
|
|
1918
|
+
if (value == null) {
|
|
1919
|
+
return value;
|
|
1920
|
+
}
|
|
1921
|
+
if (typeof value === 'string') {
|
|
1922
|
+
return value.length > REPORT_MAX_STRING_LENGTH
|
|
1923
|
+
? `${value.slice(0, REPORT_MAX_STRING_LENGTH)}...[truncated]`
|
|
1924
|
+
: value;
|
|
1925
|
+
}
|
|
1926
|
+
if (typeof value !== 'object') {
|
|
1927
|
+
return value;
|
|
1928
|
+
}
|
|
1929
|
+
if (depth >= REPORT_MAX_OBJECT_DEPTH) {
|
|
1930
|
+
return '[truncated]';
|
|
1931
|
+
}
|
|
1932
|
+
if (Array.isArray(value)) {
|
|
1933
|
+
const items = value.slice(0, REPORT_MAX_ARRAY_ITEMS).map((item) => compactReportValue(item, depth + 1, seen));
|
|
1934
|
+
if (value.length > REPORT_MAX_ARRAY_ITEMS) {
|
|
1935
|
+
items.push(`[truncated ${value.length - REPORT_MAX_ARRAY_ITEMS} item(s)]`);
|
|
1936
|
+
}
|
|
1937
|
+
return items;
|
|
1938
|
+
}
|
|
1939
|
+
if (seen.has(value)) {
|
|
1940
|
+
return '[circular]';
|
|
1941
|
+
}
|
|
1942
|
+
seen.add(value);
|
|
1943
|
+
const compacted = {};
|
|
1944
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
1945
|
+
compacted[key] = compactReportValue(nestedValue, depth + 1, seen);
|
|
1946
|
+
}
|
|
1947
|
+
return compacted;
|
|
1948
|
+
}
|
|
1949
|
+
function buildCompactVerificationPayload(payload) {
|
|
1950
|
+
const compactViolations = payload.violations.slice(0, REPORT_MAX_ARRAY_ITEMS);
|
|
1951
|
+
if (payload.violations.length > REPORT_MAX_ARRAY_ITEMS) {
|
|
1952
|
+
compactViolations.push({
|
|
1953
|
+
rule: 'report_payload_compaction',
|
|
1954
|
+
file: '__meta__',
|
|
1955
|
+
severity: 'warn',
|
|
1956
|
+
message: `Truncated ${payload.violations.length - REPORT_MAX_ARRAY_ITEMS} additional violation(s) for upload`,
|
|
1957
|
+
});
|
|
1958
|
+
}
|
|
1959
|
+
return {
|
|
1960
|
+
...payload,
|
|
1961
|
+
violations: compactViolations,
|
|
1962
|
+
bloatFiles: payload.bloatFiles.slice(0, REPORT_MAX_ARRAY_ITEMS),
|
|
1963
|
+
message: payload.message.length > REPORT_MAX_STRING_LENGTH
|
|
1964
|
+
? `${payload.message.slice(0, REPORT_MAX_STRING_LENGTH)}...[truncated]`
|
|
1965
|
+
: payload.message,
|
|
1966
|
+
governance: payload.governance
|
|
1967
|
+
? compactReportValue(payload.governance)
|
|
1968
|
+
: undefined,
|
|
1969
|
+
};
|
|
1970
|
+
}
|
|
1613
1971
|
/**
|
|
1614
1972
|
* Report verification results to Neurcode Cloud
|
|
1615
1973
|
*/
|
|
1616
|
-
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode) {
|
|
1974
|
+
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
|
|
1617
1975
|
try {
|
|
1618
1976
|
const ciContext = collectCIContext();
|
|
1619
1977
|
const payload = {
|
|
@@ -1629,23 +1987,38 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1629
1987
|
branch: ciContext.branch,
|
|
1630
1988
|
workflowRunId: ciContext.workflowRunId,
|
|
1631
1989
|
projectId,
|
|
1990
|
+
governance,
|
|
1632
1991
|
};
|
|
1633
|
-
const
|
|
1992
|
+
const postPayload = async (requestPayload) => fetch(`${apiUrl}/api/v1/action/verifications`, {
|
|
1634
1993
|
method: 'POST',
|
|
1635
1994
|
headers: {
|
|
1636
1995
|
'Content-Type': 'application/json',
|
|
1637
1996
|
'Authorization': `Bearer ${apiKey}`,
|
|
1638
1997
|
},
|
|
1639
|
-
body: JSON.stringify(
|
|
1998
|
+
body: JSON.stringify(requestPayload),
|
|
1640
1999
|
});
|
|
2000
|
+
let response = await postPayload(payload);
|
|
2001
|
+
let compactedUpload = false;
|
|
2002
|
+
if (response.status === 413) {
|
|
2003
|
+
response = await postPayload(buildCompactVerificationPayload(payload));
|
|
2004
|
+
compactedUpload = true;
|
|
2005
|
+
}
|
|
1641
2006
|
if (!response.ok) {
|
|
1642
2007
|
const errorText = await response.text();
|
|
1643
|
-
|
|
2008
|
+
const compactError = errorText.replace(/\s+/g, ' ').trim().slice(0, 400);
|
|
2009
|
+
throw new Error(`HTTP ${response.status}: ${compactError}`);
|
|
2010
|
+
}
|
|
2011
|
+
let result = {};
|
|
2012
|
+
try {
|
|
2013
|
+
result = (await response.json());
|
|
2014
|
+
}
|
|
2015
|
+
catch {
|
|
2016
|
+
// Some proxies may return empty success bodies; treat as recorded.
|
|
1644
2017
|
}
|
|
1645
|
-
const result = await response.json();
|
|
1646
2018
|
// Only log if not in json mode to avoid polluting stdout
|
|
1647
2019
|
if (!jsonMode) {
|
|
1648
|
-
|
|
2020
|
+
const suffix = compactedUpload ? ' (compact payload)' : '';
|
|
2021
|
+
console.log(chalk.dim(`\n✅ Verification result reported to Neurcode Cloud (ID: ${result.id || 'ok'})${suffix}`));
|
|
1649
2022
|
}
|
|
1650
2023
|
}
|
|
1651
2024
|
catch (error) {
|
|
@@ -1657,6 +2030,68 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1657
2030
|
}
|
|
1658
2031
|
}
|
|
1659
2032
|
}
|
|
2033
|
+
function buildGovernancePayload(governance, orgGovernanceSettings) {
|
|
2034
|
+
return {
|
|
2035
|
+
contextPolicy: governance.contextPolicy,
|
|
2036
|
+
blastRadius: governance.blastRadius,
|
|
2037
|
+
suspiciousChange: governance.suspiciousChange,
|
|
2038
|
+
changeJustification: governance.changeJustification,
|
|
2039
|
+
governanceDecision: governance.governanceDecision,
|
|
2040
|
+
aiChangeLog: {
|
|
2041
|
+
path: governance.aiChangeLogPath,
|
|
2042
|
+
auditPath: governance.aiChangeLogAuditPath,
|
|
2043
|
+
integrity: governance.aiChangeLogIntegrity,
|
|
2044
|
+
},
|
|
2045
|
+
policySources: governance.policySources,
|
|
2046
|
+
orgGovernance: orgGovernanceSettings
|
|
2047
|
+
? {
|
|
2048
|
+
requireSignedAiLogs: orgGovernanceSettings.requireSignedAiLogs,
|
|
2049
|
+
requireManualApproval: orgGovernanceSettings.requireManualApproval,
|
|
2050
|
+
minimumManualApprovals: orgGovernanceSettings.minimumManualApprovals,
|
|
2051
|
+
updatedAt: orgGovernanceSettings.updatedAt || null,
|
|
2052
|
+
}
|
|
2053
|
+
: null,
|
|
2054
|
+
};
|
|
2055
|
+
}
|
|
2056
|
+
function displayGovernanceInsights(governance, options = {}) {
|
|
2057
|
+
const maxUnexpectedFiles = options.maxUnexpectedFiles ?? 20;
|
|
2058
|
+
const decision = governance.governanceDecision;
|
|
2059
|
+
console.log(chalk.bold.white('\nBlast Radius:'));
|
|
2060
|
+
console.log(chalk.dim(` Files touched: ${governance.blastRadius.filesChanged}`));
|
|
2061
|
+
console.log(chalk.dim(` Functions impacted: ${governance.blastRadius.functionsAffected}`));
|
|
2062
|
+
console.log(chalk.dim(` Modules impacted: ${governance.blastRadius.modulesAffected.join(', ') || 'none'}`));
|
|
2063
|
+
if (governance.blastRadius.dependenciesAdded.length > 0) {
|
|
2064
|
+
console.log(chalk.dim(` Dependencies added: ${governance.blastRadius.dependenciesAdded.join(', ')}`));
|
|
2065
|
+
}
|
|
2066
|
+
console.log(chalk.dim(` Risk level: ${governance.blastRadius.riskScore.toUpperCase()}`));
|
|
2067
|
+
console.log(chalk.dim(` Governance decision: ${decision.decision.toUpperCase().replace('_', ' ')} | Avg relevance: ${decision.averageRelevanceScore}`));
|
|
2068
|
+
console.log(chalk.dim(` Policy source: ${governance.policySources.mode}${governance.policySources.orgPolicy ? ' (org + local)' : ' (local)'}`));
|
|
2069
|
+
console.log(governance.aiChangeLogIntegrity.valid
|
|
2070
|
+
? chalk.dim(` AI change-log integrity: valid (${governance.aiChangeLogIntegrity.signed ? 'signed' : 'unsigned'})`)
|
|
2071
|
+
: chalk.red(` AI change-log integrity: invalid (${governance.aiChangeLogIntegrity.issues.join('; ') || 'unknown'})`));
|
|
2072
|
+
if (governance.suspiciousChange.flagged) {
|
|
2073
|
+
console.log(chalk.red('\nSuspicious Change Detected'));
|
|
2074
|
+
console.log(chalk.red(` Plan expected files: ${governance.suspiciousChange.expectedFiles} | AI modified files: ${governance.suspiciousChange.actualFiles}`));
|
|
2075
|
+
governance.suspiciousChange.unexpectedFiles.slice(0, maxUnexpectedFiles).forEach((filePath) => {
|
|
2076
|
+
console.log(chalk.red(` • ${filePath}`));
|
|
2077
|
+
});
|
|
2078
|
+
console.log(chalk.red(` Confidence: ${governance.suspiciousChange.confidence}`));
|
|
2079
|
+
}
|
|
2080
|
+
if (decision.lowRelevanceFiles.length > 0) {
|
|
2081
|
+
console.log(chalk.yellow('\nLow Relevance Files'));
|
|
2082
|
+
decision.lowRelevanceFiles.slice(0, 10).forEach((item) => {
|
|
2083
|
+
console.log(chalk.yellow(` • ${item.file} (score ${item.relevanceScore}, ${item.planLink.replace('_', ' ')})`));
|
|
2084
|
+
});
|
|
2085
|
+
}
|
|
2086
|
+
if (options.explain) {
|
|
2087
|
+
console.log(chalk.bold.white('\nAI Change Justification:'));
|
|
2088
|
+
console.log(chalk.dim(` Task: ${governance.changeJustification.task}`));
|
|
2089
|
+
governance.changeJustification.changes.forEach((item) => {
|
|
2090
|
+
const relevance = typeof item.relevanceScore === 'number' ? ` [score ${item.relevanceScore}]` : '';
|
|
2091
|
+
console.log(chalk.dim(` • ${item.file} — ${item.reason}${relevance}`));
|
|
2092
|
+
});
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
1660
2095
|
/**
|
|
1661
2096
|
* Display verification results in a formatted report card
|
|
1662
2097
|
*/
|