@neurcode-ai/cli 0.9.28 ā 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 +78 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +49 -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/plan.d.ts.map +1 -1
- package/dist/commands/plan.js +244 -64
- package/dist/commands/plan.js.map +1 -1
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +71 -4
- 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 +253 -18
- 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) {
|
|
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);
|
|
@@ -384,6 +432,8 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
384
432
|
adherenceScore: 0,
|
|
385
433
|
mode: 'policy_only',
|
|
386
434
|
policyOnly: true,
|
|
435
|
+
policyOnlySource: source,
|
|
436
|
+
...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
|
|
387
437
|
policyLock: {
|
|
388
438
|
enforced: true,
|
|
389
439
|
matched: false,
|
|
@@ -514,6 +564,8 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
514
564
|
adherenceScore: score,
|
|
515
565
|
mode: 'policy_only',
|
|
516
566
|
policyOnly: true,
|
|
567
|
+
policyOnlySource: source,
|
|
568
|
+
...buildGovernancePayload(governanceAnalysis, orgGovernanceSettings),
|
|
517
569
|
policyLock: {
|
|
518
570
|
enforced: policyLockEvaluation.enforced,
|
|
519
571
|
matched: policyLockEvaluation.matched,
|
|
@@ -553,6 +605,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
553
605
|
if (governance.audit.requireIntegrity && !auditIntegrityStatus.valid) {
|
|
554
606
|
console.log(chalk.red(' Policy audit integrity check failed'));
|
|
555
607
|
}
|
|
608
|
+
displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
|
|
556
609
|
console.log(chalk.dim(`\n${message}`));
|
|
557
610
|
}
|
|
558
611
|
return effectiveVerdict === 'FAIL' ? 2 : effectiveVerdict === 'WARN' ? 1 : 0;
|
|
@@ -608,6 +661,31 @@ async function verifyCommand(options) {
|
|
|
608
661
|
orgId: (0, state_1.getOrgId)(),
|
|
609
662
|
projectId: projectId || null,
|
|
610
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
|
+
}
|
|
611
689
|
const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
|
|
612
690
|
if (!brainScope.orgId || !brainScope.projectId) {
|
|
613
691
|
return;
|
|
@@ -725,20 +803,75 @@ async function verifyCommand(options) {
|
|
|
725
803
|
const normalized = toUnixPath(filePath || '');
|
|
726
804
|
return ignoreFilter(normalized) || runtimeIgnoreSet.has(normalized);
|
|
727
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
|
+
}
|
|
728
858
|
if (!options.json) {
|
|
729
859
|
console.log(chalk.cyan('\nš Analyzing changes against plan...'));
|
|
730
860
|
console.log(chalk.dim(` Found ${summary.totalFiles} file(s) changed`));
|
|
731
861
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
732
862
|
}
|
|
863
|
+
const runPolicyOnlyModeAndExit = async (source) => {
|
|
864
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
|
|
865
|
+
const changedFiles = diffFiles.map((f) => f.path);
|
|
866
|
+
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
867
|
+
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
868
|
+
process.exit(exitCode);
|
|
869
|
+
};
|
|
733
870
|
// ============================================
|
|
734
871
|
// --policy-only: General Governance (policy only, no plan enforcement)
|
|
735
872
|
// ============================================
|
|
736
873
|
if (options.policyOnly) {
|
|
737
|
-
|
|
738
|
-
const changedFiles = diffFiles.map((f) => f.path);
|
|
739
|
-
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
740
|
-
recordVerifyEvent(verdict, `policy_only=true;exit=${exitCode}`, changedFiles);
|
|
741
|
-
process.exit(exitCode);
|
|
874
|
+
await runPolicyOnlyModeAndExit('explicit');
|
|
742
875
|
}
|
|
743
876
|
const requirePlan = options.requirePlan === true || process.env.NEURCODE_VERIFY_REQUIRE_PLAN === '1';
|
|
744
877
|
// Get planId: Priority 1: options flag, Priority 2: state file (.neurcode/config.json), Priority 3: legacy config
|
|
@@ -805,14 +938,11 @@ async function verifyCommand(options) {
|
|
|
805
938
|
if (!options.json) {
|
|
806
939
|
console.log(chalk.yellow('ā ļø No Plan ID found. Falling back to General Governance (Policy Only).'));
|
|
807
940
|
}
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
recordVerifyEvent(verdict, `policy_only=fallback;exit=${exitCode}`, changedFiles);
|
|
813
|
-
process.exit(exitCode);
|
|
941
|
+
await runPolicyOnlyModeAndExit('fallback_missing_plan');
|
|
942
|
+
}
|
|
943
|
+
if (!planId) {
|
|
944
|
+
throw new Error('Plan ID resolution failed unexpectedly');
|
|
814
945
|
}
|
|
815
|
-
// At this point, planId is guaranteed to be defined
|
|
816
946
|
const finalPlanId = planId;
|
|
817
947
|
// ============================================
|
|
818
948
|
// STRICT SCOPE GUARD - Deterministic Check
|
|
@@ -822,6 +952,7 @@ async function verifyCommand(options) {
|
|
|
822
952
|
}
|
|
823
953
|
// Track if scope guard passed - this takes priority over AI grading
|
|
824
954
|
let scopeGuardPassed = false;
|
|
955
|
+
let governanceResult = null;
|
|
825
956
|
try {
|
|
826
957
|
// Step A: Get Modified Files (already have from diffFiles)
|
|
827
958
|
const modifiedFiles = diffFiles.map(f => f.path);
|
|
@@ -829,10 +960,29 @@ async function verifyCommand(options) {
|
|
|
829
960
|
const planData = await client.getPlan(finalPlanId);
|
|
830
961
|
// Extract original intent from plan (for constraint checking)
|
|
831
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';
|
|
832
968
|
// Get approved files from plan (only files with action CREATE or MODIFY)
|
|
833
969
|
const planFiles = planData.content.files
|
|
834
970
|
.filter(f => f.action === 'CREATE' || f.action === 'MODIFY')
|
|
835
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
|
+
});
|
|
836
986
|
// Get sessionId from state file (.neurcode/state.json) first, then fallback to config
|
|
837
987
|
// Fallback to sessionId from plan if not in state/config
|
|
838
988
|
// This is the session_id string needed to fetch the session
|
|
@@ -901,6 +1051,9 @@ async function verifyCommand(options) {
|
|
|
901
1051
|
scopeGuardPassed: false,
|
|
902
1052
|
mode: 'plan_enforced',
|
|
903
1053
|
policyOnly: false,
|
|
1054
|
+
...(governanceResult
|
|
1055
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1056
|
+
: {}),
|
|
904
1057
|
};
|
|
905
1058
|
// CRITICAL: Print JSON first, then exit
|
|
906
1059
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
@@ -920,6 +1073,9 @@ async function verifyCommand(options) {
|
|
|
920
1073
|
filteredViolations.forEach(file => {
|
|
921
1074
|
console.log(chalk.dim(` neurcode allow ${file}`));
|
|
922
1075
|
});
|
|
1076
|
+
if (governanceResult) {
|
|
1077
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1078
|
+
}
|
|
923
1079
|
console.log('');
|
|
924
1080
|
process.exit(1);
|
|
925
1081
|
}
|
|
@@ -1024,6 +1180,9 @@ async function verifyCommand(options) {
|
|
|
1024
1180
|
scopeGuardPassed,
|
|
1025
1181
|
mode: 'plan_enforced',
|
|
1026
1182
|
policyOnly: false,
|
|
1183
|
+
...(governanceResult
|
|
1184
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1185
|
+
: {}),
|
|
1027
1186
|
policyLock: {
|
|
1028
1187
|
enforced: true,
|
|
1029
1188
|
matched: false,
|
|
@@ -1077,6 +1236,9 @@ async function verifyCommand(options) {
|
|
|
1077
1236
|
mode: 'plan_enforced',
|
|
1078
1237
|
policyOnly: false,
|
|
1079
1238
|
tier: 'FREE',
|
|
1239
|
+
...(governanceResult
|
|
1240
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1241
|
+
: {}),
|
|
1080
1242
|
policyLock: {
|
|
1081
1243
|
enforced: policyLockEvaluation.enforced,
|
|
1082
1244
|
matched: policyLockEvaluation.matched,
|
|
@@ -1307,6 +1469,9 @@ async function verifyCommand(options) {
|
|
|
1307
1469
|
totalPlannedFiles: verifyResult.totalPlannedFiles,
|
|
1308
1470
|
mode: 'plan_enforced',
|
|
1309
1471
|
policyOnly: false,
|
|
1472
|
+
...(governanceResult
|
|
1473
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1474
|
+
: {}),
|
|
1310
1475
|
policyLock: {
|
|
1311
1476
|
enforced: policyLockEvaluation.enforced,
|
|
1312
1477
|
matched: policyLockEvaluation.matched,
|
|
@@ -1345,8 +1510,10 @@ async function verifyCommand(options) {
|
|
|
1345
1510
|
})),
|
|
1346
1511
|
];
|
|
1347
1512
|
// Report in background (don't await to avoid blocking JSON output)
|
|
1348
|
-
reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true // jsonMode = true
|
|
1349
|
-
|
|
1513
|
+
reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true, // jsonMode = true
|
|
1514
|
+
governanceResult
|
|
1515
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1516
|
+
: undefined).catch(() => {
|
|
1350
1517
|
// Error already logged in reportVerification
|
|
1351
1518
|
});
|
|
1352
1519
|
}
|
|
@@ -1374,6 +1541,9 @@ async function verifyCommand(options) {
|
|
|
1374
1541
|
bloatFiles: displayBloatFiles,
|
|
1375
1542
|
bloatCount: displayBloatFiles.length,
|
|
1376
1543
|
}, policyViolations);
|
|
1544
|
+
if (governanceResult) {
|
|
1545
|
+
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1546
|
+
}
|
|
1377
1547
|
if (policyExceptionsSummary.suppressed > 0) {
|
|
1378
1548
|
console.log(chalk.yellow(`\nā ļø Policy exceptions applied: ${policyExceptionsSummary.suppressed}`));
|
|
1379
1549
|
if (policyExceptionsSummary.matchedExceptionIds.length > 0) {
|
|
@@ -1419,8 +1589,10 @@ async function verifyCommand(options) {
|
|
|
1419
1589
|
message: v.message,
|
|
1420
1590
|
})),
|
|
1421
1591
|
];
|
|
1422
|
-
await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false // jsonMode = false
|
|
1423
|
-
|
|
1592
|
+
await reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, false, // jsonMode = false
|
|
1593
|
+
governanceResult
|
|
1594
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1595
|
+
: undefined);
|
|
1424
1596
|
}
|
|
1425
1597
|
}
|
|
1426
1598
|
// Governance override: keep PASS only when scope guard passes and failure is due
|
|
@@ -1611,7 +1783,7 @@ function collectCIContext() {
|
|
|
1611
1783
|
/**
|
|
1612
1784
|
* Report verification results to Neurcode Cloud
|
|
1613
1785
|
*/
|
|
1614
|
-
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode) {
|
|
1786
|
+
async function reportVerification(grade, violations, verifyResult, apiKey, apiUrl, projectId, jsonMode, governance) {
|
|
1615
1787
|
try {
|
|
1616
1788
|
const ciContext = collectCIContext();
|
|
1617
1789
|
const payload = {
|
|
@@ -1627,6 +1799,7 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1627
1799
|
branch: ciContext.branch,
|
|
1628
1800
|
workflowRunId: ciContext.workflowRunId,
|
|
1629
1801
|
projectId,
|
|
1802
|
+
governance,
|
|
1630
1803
|
};
|
|
1631
1804
|
const response = await fetch(`${apiUrl}/api/v1/action/verifications`, {
|
|
1632
1805
|
method: 'POST',
|
|
@@ -1655,6 +1828,68 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1655
1828
|
}
|
|
1656
1829
|
}
|
|
1657
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
|
+
}
|
|
1658
1893
|
/**
|
|
1659
1894
|
* Display verification results in a formatted report card
|
|
1660
1895
|
*/
|