@neurcode-ai/cli 0.9.29 ā 0.9.30
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 +11 -0
- 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 +240 -7
- 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 {
|
|
@@ -294,10 +296,56 @@ function resolveAuditIntegrityStatus(requireIntegrity, auditIntegrity) {
|
|
|
294
296
|
* Execute policy-only verification (General Governance mode)
|
|
295
297
|
* Returns the exit code to use
|
|
296
298
|
*/
|
|
297
|
-
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source) {
|
|
299
|
+
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner) {
|
|
298
300
|
if (!options.json) {
|
|
299
301
|
console.log(chalk.cyan('š”ļø General Governance mode (policy only, no plan linked)\n'));
|
|
300
302
|
}
|
|
303
|
+
const governanceAnalysis = (0, governance_1.evaluateGovernance)({
|
|
304
|
+
projectRoot,
|
|
305
|
+
task: 'Policy-only verification',
|
|
306
|
+
expectedFiles: [],
|
|
307
|
+
diffFiles,
|
|
308
|
+
contextCandidates: diffFiles.map((file) => file.path),
|
|
309
|
+
orgGovernance: orgGovernanceSettings,
|
|
310
|
+
signingKey: aiLogSigningKey,
|
|
311
|
+
signer: aiLogSigner,
|
|
312
|
+
});
|
|
313
|
+
const contextPolicyViolations = governanceAnalysis.contextPolicy.violations.filter((item) => !ignoreFilter(item.file));
|
|
314
|
+
if (contextPolicyViolations.length > 0) {
|
|
315
|
+
const message = `Context policy violation: ${contextPolicyViolations.map((item) => item.file).join(', ')}`;
|
|
316
|
+
if (options.json) {
|
|
317
|
+
console.log(JSON.stringify({
|
|
318
|
+
grade: 'F',
|
|
319
|
+
score: 0,
|
|
320
|
+
verdict: 'FAIL',
|
|
321
|
+
violations: contextPolicyViolations.map((item) => ({
|
|
322
|
+
file: item.file,
|
|
323
|
+
rule: `context_policy:${item.rule}`,
|
|
324
|
+
severity: 'block',
|
|
325
|
+
message: item.reason,
|
|
326
|
+
})),
|
|
327
|
+
message,
|
|
328
|
+
scopeGuardPassed: false,
|
|
329
|
+
bloatCount: 0,
|
|
330
|
+
bloatFiles: [],
|
|
331
|
+
plannedFilesModified: 0,
|
|
332
|
+
totalPlannedFiles: 0,
|
|
333
|
+
adherenceScore: 0,
|
|
334
|
+
mode: 'policy_only',
|
|
335
|
+
policyOnly: true,
|
|
336
|
+
policyOnlySource: source,
|
|
337
|
+
...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
|
|
338
|
+
}, null, 2));
|
|
339
|
+
}
|
|
340
|
+
else {
|
|
341
|
+
console.log(chalk.red('ā Context policy violation detected (policy-only mode).'));
|
|
342
|
+
contextPolicyViolations.forEach((item) => {
|
|
343
|
+
console.log(chalk.red(` ⢠${item.file}: ${item.reason}`));
|
|
344
|
+
});
|
|
345
|
+
console.log(chalk.dim(`\n${message}`));
|
|
346
|
+
}
|
|
347
|
+
return 2;
|
|
348
|
+
}
|
|
301
349
|
let policyViolations = [];
|
|
302
350
|
let policyDecision = 'allow';
|
|
303
351
|
const requirePolicyLock = options.requirePolicyLock === true || isEnabledFlag(process.env.NEURCODE_VERIFY_REQUIRE_POLICY_LOCK);
|
|
@@ -385,6 +433,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
385
433
|
mode: 'policy_only',
|
|
386
434
|
policyOnly: true,
|
|
387
435
|
policyOnlySource: source,
|
|
436
|
+
...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
|
|
388
437
|
policyLock: {
|
|
389
438
|
enforced: true,
|
|
390
439
|
matched: false,
|
|
@@ -516,6 +565,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
516
565
|
mode: 'policy_only',
|
|
517
566
|
policyOnly: true,
|
|
518
567
|
policyOnlySource: source,
|
|
568
|
+
...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
|
|
519
569
|
policyLock: {
|
|
520
570
|
enforced: policyLockEvaluation.enforced,
|
|
521
571
|
matched: policyLockEvaluation.matched,
|
|
@@ -555,6 +605,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
555
605
|
if (governance.audit.requireIntegrity && !auditIntegrityStatus.valid) {
|
|
556
606
|
console.log(chalk.red(' Policy audit integrity check failed'));
|
|
557
607
|
}
|
|
608
|
+
displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
|
|
558
609
|
console.log(chalk.dim(`\n${message}`));
|
|
559
610
|
}
|
|
560
611
|
return effectiveVerdict === 'FAIL' ? 2 : effectiveVerdict === 'WARN' ? 1 : 0;
|
|
@@ -610,6 +661,31 @@ async function verifyCommand(options) {
|
|
|
610
661
|
orgId: (0, state_1.getOrgId)(),
|
|
611
662
|
projectId: projectId || null,
|
|
612
663
|
};
|
|
664
|
+
const aiLogSigningKey = process.env.NEURCODE_GOVERNANCE_SIGNING_KEY ||
|
|
665
|
+
process.env.NEURCODE_AI_LOG_SIGNING_KEY ||
|
|
666
|
+
null;
|
|
667
|
+
const aiLogSigner = process.env.NEURCODE_GOVERNANCE_SIGNER || process.env.USER || 'neurcode-cli';
|
|
668
|
+
let orgGovernanceSettings = null;
|
|
669
|
+
if (config.apiKey) {
|
|
670
|
+
try {
|
|
671
|
+
const remoteSettings = await client.getOrgGovernanceSettings();
|
|
672
|
+
if (remoteSettings) {
|
|
673
|
+
orgGovernanceSettings = {
|
|
674
|
+
contextPolicy: (0, policy_1.normalizeContextPolicy)(remoteSettings.contextPolicy),
|
|
675
|
+
requireSignedAiLogs: remoteSettings.requireSignedAiLogs === true,
|
|
676
|
+
requireManualApproval: remoteSettings.requireManualApproval !== false,
|
|
677
|
+
minimumManualApprovals: Math.max(1, Math.min(5, Math.floor(remoteSettings.minimumManualApprovals || 1))),
|
|
678
|
+
updatedAt: remoteSettings.updatedAt,
|
|
679
|
+
};
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
catch (error) {
|
|
683
|
+
if (!options.json) {
|
|
684
|
+
const message = error instanceof Error ? error.message : 'Unknown error';
|
|
685
|
+
console.log(chalk.dim(` Org governance settings unavailable, using local policy only (${message})`));
|
|
686
|
+
}
|
|
687
|
+
}
|
|
688
|
+
}
|
|
613
689
|
const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
|
|
614
690
|
if (!brainScope.orgId || !brainScope.projectId) {
|
|
615
691
|
return;
|
|
@@ -727,13 +803,65 @@ async function verifyCommand(options) {
|
|
|
727
803
|
const normalized = toUnixPath(filePath || '');
|
|
728
804
|
return ignoreFilter(normalized) || runtimeIgnoreSet.has(normalized);
|
|
729
805
|
};
|
|
806
|
+
const baselineContextPolicyLocal = (0, policy_1.loadContextPolicy)(projectRoot);
|
|
807
|
+
const baselineContextPolicy = orgGovernanceSettings?.contextPolicy
|
|
808
|
+
? (0, policy_1.mergeContextPolicies)(baselineContextPolicyLocal, orgGovernanceSettings.contextPolicy)
|
|
809
|
+
: baselineContextPolicyLocal;
|
|
810
|
+
const baselineContextPolicyEvaluation = (0, policy_1.evaluateContextPolicyForChanges)(diffFiles.map((file) => file.path), baselineContextPolicy, diffFiles.map((file) => file.path));
|
|
811
|
+
const baselineContextViolations = baselineContextPolicyEvaluation.violations.filter((item) => !shouldIgnore(item.file));
|
|
812
|
+
if (baselineContextViolations.length > 0) {
|
|
813
|
+
const baselineGovernance = (0, governance_1.evaluateGovernance)({
|
|
814
|
+
projectRoot,
|
|
815
|
+
task: 'Context policy validation',
|
|
816
|
+
expectedFiles: [],
|
|
817
|
+
diffFiles,
|
|
818
|
+
contextCandidates: diffFiles.map((file) => file.path),
|
|
819
|
+
orgGovernance: orgGovernanceSettings,
|
|
820
|
+
signingKey: aiLogSigningKey,
|
|
821
|
+
signer: aiLogSigner,
|
|
822
|
+
});
|
|
823
|
+
const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
|
|
824
|
+
recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
|
|
825
|
+
if (options.json) {
|
|
826
|
+
console.log(JSON.stringify({
|
|
827
|
+
grade: 'F',
|
|
828
|
+
score: 0,
|
|
829
|
+
verdict: 'FAIL',
|
|
830
|
+
violations: baselineContextViolations.map((item) => ({
|
|
831
|
+
file: item.file,
|
|
832
|
+
rule: `context_policy:${item.rule}`,
|
|
833
|
+
severity: 'block',
|
|
834
|
+
message: item.reason,
|
|
835
|
+
})),
|
|
836
|
+
adherenceScore: 0,
|
|
837
|
+
bloatCount: 0,
|
|
838
|
+
bloatFiles: [],
|
|
839
|
+
plannedFilesModified: 0,
|
|
840
|
+
totalPlannedFiles: 0,
|
|
841
|
+
message,
|
|
842
|
+
scopeGuardPassed: false,
|
|
843
|
+
...buildGovernancePayload(baselineGovernance, orgGovernanceSettings),
|
|
844
|
+
mode: 'policy_violation',
|
|
845
|
+
policyOnly: false,
|
|
846
|
+
}, null, 2));
|
|
847
|
+
}
|
|
848
|
+
else {
|
|
849
|
+
console.log(chalk.red('\nā Context Policy Violation'));
|
|
850
|
+
baselineContextViolations.forEach((item) => {
|
|
851
|
+
console.log(chalk.red(` ⢠${item.file}: ${item.reason}`));
|
|
852
|
+
});
|
|
853
|
+
console.log(chalk.dim(`\nRisk level: ${baselineGovernance.blastRadius.riskScore.toUpperCase()}`));
|
|
854
|
+
console.log(chalk.red('\nAction blocked.\n'));
|
|
855
|
+
}
|
|
856
|
+
process.exit(2);
|
|
857
|
+
}
|
|
730
858
|
if (!options.json) {
|
|
731
859
|
console.log(chalk.cyan('\nš Analyzing changes against plan...'));
|
|
732
860
|
console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
|
|
733
861
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
734
862
|
}
|
|
735
863
|
const runPolicyOnlyModeAndExit = async (source) => {
|
|
736
|
-
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source);
|
|
864
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
|
|
737
865
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
738
866
|
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
739
867
|
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
@@ -824,6 +952,7 @@ async function verifyCommand(options) {
|
|
|
824
952
|
}
|
|
825
953
|
// Track if scope guard passed - this takes priority over AI grading
|
|
826
954
|
let scopeGuardPassed = false;
|
|
955
|
+
let governanceResult = null;
|
|
827
956
|
try {
|
|
828
957
|
// Step A: Get Modified Files (already have from diffFiles)
|
|
829
958
|
const modifiedFiles = diffFiles.map(f => f.path);
|
|
@@ -831,10 +960,29 @@ async function verifyCommand(options) {
|
|
|
831
960
|
const planData = await client.getPlan(finalPlanId);
|
|
832
961
|
// Extract original intent from plan (for constraint checking)
|
|
833
962
|
const originalIntent = planData.intent || '';
|
|
963
|
+
const planTitle = typeof planData.content.title === 'string'
|
|
964
|
+
? planData.content.title?.trim()
|
|
965
|
+
: '';
|
|
966
|
+
const planSummary = typeof planData.content.summary === 'string' ? planData.content.summary.trim() : '';
|
|
967
|
+
const governanceTask = planTitle || planSummary || originalIntent || 'Plan verification';
|
|
834
968
|
// Get approved files from plan (only files with action CREATE or MODIFY)
|
|
835
969
|
const planFiles = planData.content.files
|
|
836
970
|
.filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
|
|
837
971
|
.map(f => f.path);
|
|
972
|
+
const planDependencies = Array.isArray(planData.content.dependencies)
|
|
973
|
+
? planData.content.dependencies.filter((item) => typeof item === 'string')
|
|
974
|
+
: [];
|
|
975
|
+
governanceResult = (0, governance_1.evaluateGovernance)({
|
|
976
|
+
projectRoot,
|
|
977
|
+
task: governanceTask,
|
|
978
|
+
expectedFiles: planFiles,
|
|
979
|
+
expectedDependencies: planDependencies,
|
|
980
|
+
diffFiles,
|
|
981
|
+
contextCandidates: planFiles,
|
|
982
|
+
orgGovernance: orgGovernanceSettings,
|
|
983
|
+
signingKey: aiLogSigningKey,
|
|
984
|
+
signer: aiLogSigner,
|
|
985
|
+
});
|
|
838
986
|
// Get sessionId from state file (.neurcode/state.json) first, then fallback to config
|
|
839
987
|
// Fallback to sessionId from plan if not in state/config
|
|
840
988
|
// This is the session_id string needed to fetch the session
|
|
@@ -903,6 +1051,9 @@ async function verifyCommand(options) {
|
|
|
903
1051
|
scopeGuardPassed: false,
|
|
904
1052
|
mode: 'plan_enforced',
|
|
905
1053
|
policyOnly: false,
|
|
1054
|
+
...(governanceResult
|
|
1055
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1056
|
+
: {}),
|
|
906
1057
|
};
|
|
907
1058
|
// CRITICAL: Print JSON first, then exit
|
|
908
1059
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
@@ -922,6 +1073,9 @@ async function verifyCommand(options) {
|
|
|
922
1073
|
filteredViolations.forEach(file => {
|
|
923
1074
|
console.log(chalk.dim(` neurcode allow ${file}`));
|
|
924
1075
|
});
|
|
1076
|
+
if (governanceResult) {
|
|
1077
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1078
|
+
}
|
|
925
1079
|
console.log('');
|
|
926
1080
|
process.exit(1);
|
|
927
1081
|
}
|
|
@@ -1026,6 +1180,9 @@ async function verifyCommand(options) {
|
|
|
1026
1180
|
scopeGuardPassed,
|
|
1027
1181
|
mode: 'plan_enforced',
|
|
1028
1182
|
policyOnly: false,
|
|
1183
|
+
...(governanceResult
|
|
1184
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1185
|
+
: {}),
|
|
1029
1186
|
policyLock: {
|
|
1030
1187
|
enforced: true,
|
|
1031
1188
|
matched: false,
|
|
@@ -1079,6 +1236,9 @@ async function verifyCommand(options) {
|
|
|
1079
1236
|
mode: 'plan_enforced',
|
|
1080
1237
|
policyOnly: false,
|
|
1081
1238
|
tier: 'FREE',
|
|
1239
|
+
...(governanceResult
|
|
1240
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1241
|
+
: {}),
|
|
1082
1242
|
policyLock: {
|
|
1083
1243
|
enforced: policyLockEvaluation.enforced,
|
|
1084
1244
|
matched: policyLockEvaluation.matched,
|
|
@@ -1309,6 +1469,9 @@ async function verifyCommand(options) {
|
|
|
1309
1469
|
totalPlannedFiles: verifyResult.totalPlannedFiles,
|
|
1310
1470
|
mode: 'plan_enforced',
|
|
1311
1471
|
policyOnly: false,
|
|
1472
|
+
...(governanceResult
|
|
1473
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1474
|
+
: {}),
|
|
1312
1475
|
policyLock: {
|
|
1313
1476
|
enforced: policyLockEvaluation.enforced,
|
|
1314
1477
|
matched: policyLockEvaluation.matched,
|
|
@@ -1347,8 +1510,10 @@ async function verifyCommand(options) {
|
|
|
1347
1510
|
})),
|
|
1348
1511
|
];
|
|
1349
1512
|
// 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
|
-
|
|
1513
|
+
reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true, // jsonMode = true
|
|
1514
|
+
governanceResult
|
|
1515
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1516
|
+
: undefined).catch(() => {
|
|
1352
1517
|
// Error already logged in reportVerification
|
|
1353
1518
|
});
|
|
1354
1519
|
}
|
|
@@ -1376,6 +1541,9 @@ async function verifyCommand(options) {
|
|
|
1376
1541
|
bloatFiles: displayBloatFiles,
|
|
1377
1542
|
bloatCount: displayBloatFiles.length,
|
|
1378
1543
|
}, policyViolations);
|
|
1544
|
+
if (governanceResult) {
|
|
1545
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1546
|
+
}
|
|
1379
1547
|
if (policyExceptionsSummary.suppressed > 0) {
|
|
1380
1548
|
console.log(chalk.yellow(`\nā ļø Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
|
|
1381
1549
|
if (policyExceptionsSummary.matchedExceptionIds.length > 0) {
|
|
@@ -1421,8 +1589,10 @@ async function verifyCommand(options) {
|
|
|
1421
1589
|
message: v.message,
|
|
1422
1590
|
})),
|
|
1423
1591
|
];
|
|
1424
|
-
await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false // jsonMode = false
|
|
1425
|
-
|
|
1592
|
+
await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false, // jsonMode = false
|
|
1593
|
+
governanceResult
|
|
1594
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1595
|
+
: undefined);
|
|
1426
1596
|
}
|
|
1427
1597
|
}
|
|
1428
1598
|
// Governance override: keep PASS only when scope guard passes and failure is due
|
|
@@ -1613,7 +1783,7 @@ function collectCIContext() {
|
|
|
1613
1783
|
/**
|
|
1614
1784
|
* Report verification results to Neurcode Cloud
|
|
1615
1785
|
*/
|
|
1616
|
-
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode) {
|
|
1786
|
+
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
|
|
1617
1787
|
try {
|
|
1618
1788
|
const ciContext = collectCIContext();
|
|
1619
1789
|
const payload = {
|
|
@@ -1629,6 +1799,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1629
1799
|
branch: ciContext.branch,
|
|
1630
1800
|
workflowRunId: ciContext.workflowRunId,
|
|
1631
1801
|
projectId,
|
|
1802
|
+
governance,
|
|
1632
1803
|
};
|
|
1633
1804
|
const response = await fetch(`${apiUrl}/api/v1/action/verifications`, {
|
|
1634
1805
|
method: 'POST',
|
|
@@ -1657,6 +1828,68 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1657
1828
|
}
|
|
1658
1829
|
}
|
|
1659
1830
|
}
|
|
1831
|
+
function buildGovernancePayload(governance, orgGovernanceSettings) {
|
|
1832
|
+
return {
|
|
1833
|
+
contextPolicy: governance.contextPolicy,
|
|
1834
|
+
blastRadius: governance.blastRadius,
|
|
1835
|
+
suspiciousChange: governance.suspiciousChange,
|
|
1836
|
+
changeJustification: governance.changeJustification,
|
|
1837
|
+
governanceDecision: governance.governanceDecision,
|
|
1838
|
+
aiChangeLog: {
|
|
1839
|
+
path: governance.aiChangeLogPath,
|
|
1840
|
+
auditPath: governance.aiChangeLogAuditPath,
|
|
1841
|
+
integrity: governance.aiChangeLogIntegrity,
|
|
1842
|
+
},
|
|
1843
|
+
policySources: governance.policySources,
|
|
1844
|
+
orgGovernance: orgGovernanceSettings
|
|
1845
|
+
? {
|
|
1846
|
+
requireSignedAiLogs: orgGovernanceSettings.requireSignedAiLogs,
|
|
1847
|
+
requireManualApproval: orgGovernanceSettings.requireManualApproval,
|
|
1848
|
+
minimumManualApprovals: orgGovernanceSettings.minimumManualApprovals,
|
|
1849
|
+
updatedAt: orgGovernanceSettings.updatedAt || null,
|
|
1850
|
+
}
|
|
1851
|
+
: null,
|
|
1852
|
+
};
|
|
1853
|
+
}
|
|
1854
|
+
function displayGovernanceInsights(governance, options = {}) {
|
|
1855
|
+
const maxUnexpectedFiles = options.maxUnexpectedFiles ?? 20;
|
|
1856
|
+
const decision = governance.governanceDecision;
|
|
1857
|
+
console.log(chalk.bold.white('\nBlast Radius:'));
|
|
1858
|
+
console.log(chalk.dim(` Files touched: ${governance.blastRadius.filesChanged}`));
|
|
1859
|
+
console.log(chalk.dim(` Functions impacted: ${governance.blastRadius.functionsAffected}`));
|
|
1860
|
+
console.log(chalk.dim(` Modules impacted: ${governance.blastRadius.modulesAffected.join(', ') || 'none'}`));
|
|
1861
|
+
if (governance.blastRadius.dependenciesAdded.length > 0) {
|
|
1862
|
+
console.log(chalk.dim(` Dependencies added: ${governance.blastRadius.dependenciesAdded.join(', ')}`));
|
|
1863
|
+
}
|
|
1864
|
+
console.log(chalk.dim(` Risk level: ${governance.blastRadius.riskScore.toUpperCase()}`));
|
|
1865
|
+
console.log(chalk.dim(` Governance decision: ${decision.decision.toUpperCase().replace('_', ' ')} | Avg relevance: ${decision.averageRelevanceScore}`));
|
|
1866
|
+
console.log(chalk.dim(` Policy source: ${governance.policySources.mode}${governance.policySources.orgPolicy ? ' (org + local)' : ' (local)'}`));
|
|
1867
|
+
console.log(governance.aiChangeLogIntegrity.valid
|
|
1868
|
+
? chalk.dim(` AI change-log integrity: valid (${governance.aiChangeLogIntegrity.signed ? 'signed' : 'unsigned'})`)
|
|
1869
|
+
: chalk.red(` AI change-log integrity: invalid (${governance.aiChangeLogIntegrity.issues.join('; ') || 'unknown'})`));
|
|
1870
|
+
if (governance.suspiciousChange.flagged) {
|
|
1871
|
+
console.log(chalk.red('\nSuspicious Change Detected'));
|
|
1872
|
+
console.log(chalk.red(` Plan expected files: ${governance.suspiciousChange.expectedFiles} | AI modified files: ${governance.suspiciousChange.actualFiles}`));
|
|
1873
|
+
governance.suspiciousChange.unexpectedFiles.slice(0, maxUnexpectedFiles).forEach((filePath) => {
|
|
1874
|
+
console.log(chalk.red(` ⢠${filePath}`));
|
|
1875
|
+
});
|
|
1876
|
+
console.log(chalk.red(` Confidence: ${governance.suspiciousChange.confidence}`));
|
|
1877
|
+
}
|
|
1878
|
+
if (decision.lowRelevanceFiles.length > 0) {
|
|
1879
|
+
console.log(chalk.yellow('\nLow Relevance Files'));
|
|
1880
|
+
decision.lowRelevanceFiles.slice(0, 10).forEach((item) => {
|
|
1881
|
+
console.log(chalk.yellow(` ⢠${item.file} (score ${item.relevanceScore}, ${item.planLink.replace('_', ' ')})`));
|
|
1882
|
+
});
|
|
1883
|
+
}
|
|
1884
|
+
if (options.explain) {
|
|
1885
|
+
console.log(chalk.bold.white('\nAI Change Justification:'));
|
|
1886
|
+
console.log(chalk.dim(` Task: ${governance.changeJustification.task}`));
|
|
1887
|
+
governance.changeJustification.changes.forEach((item) => {
|
|
1888
|
+
const relevance = typeof item.relevanceScore === 'number' ? ` [score ${item.relevanceScore}]` : '';
|
|
1889
|
+
console.log(chalk.dim(` ⢠${item.file} ā ${item.reason}${relevance}`));
|
|
1890
|
+
});
|
|
1891
|
+
}
|
|
1892
|
+
}
|
|
1660
1893
|
/**
|
|
1661
1894
|
* Display verification results in a formatted report card
|
|
1662
1895
|
*/
|