@neurcode-ai/cli 0.9.30 → 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/api-client.d.ts.map +1 -1
- package/dist/api-client.js +24 -8
- package/dist/api-client.js.map +1 -1
- 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 +548 -94
- 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 +4 -4
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';
|
|
@@ -292,11 +343,24 @@ function resolveAuditIntegrityStatus(requireIntegrity, auditIntegrity) {
|
|
|
292
343
|
issues,
|
|
293
344
|
};
|
|
294
345
|
}
|
|
346
|
+
async function recordVerificationIfRequested(options, config, payload) {
|
|
347
|
+
if (!options.record) {
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
if (!config.apiKey) {
|
|
351
|
+
if (!payload.jsonMode) {
|
|
352
|
+
console.log(chalk.yellow('\n⚠️ --record flag requires API key'));
|
|
353
|
+
console.log(chalk.dim(' Set NEURCODE_API_KEY environment variable or use --api-key flag'));
|
|
354
|
+
}
|
|
355
|
+
return;
|
|
356
|
+
}
|
|
357
|
+
await reportVerification(payload.grade, payload.violations, payload.verifyResult, config.apiKey, config.apiUrl || 'https://api.neurcode.com', payload.projectId, payload.jsonMode, payload.governance);
|
|
358
|
+
}
|
|
295
359
|
/**
|
|
296
360
|
* Execute policy-only verification (General Governance mode)
|
|
297
361
|
* Returns the exit code to use
|
|
298
362
|
*/
|
|
299
|
-
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner) {
|
|
363
|
+
async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRoot, config, client, source, projectId, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner) {
|
|
300
364
|
if (!options.json) {
|
|
301
365
|
console.log(chalk.cyan('🛡️ General Governance mode (policy only, no plan linked)\n'));
|
|
302
366
|
}
|
|
@@ -308,22 +372,142 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
308
372
|
contextCandidates: diffFiles.map((file) => file.path),
|
|
309
373
|
orgGovernance: orgGovernanceSettings,
|
|
310
374
|
signingKey: aiLogSigningKey,
|
|
375
|
+
signingKeyId: aiLogSigningKeyId,
|
|
376
|
+
signingKeys: aiLogSigningKeys,
|
|
311
377
|
signer: aiLogSigner,
|
|
312
378
|
});
|
|
379
|
+
const governancePayload = buildGovernancePayload(governanceAnalysis, orgGovernanceSettings);
|
|
313
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
|
+
}
|
|
314
497
|
if (contextPolicyViolations.length > 0) {
|
|
315
498
|
const message = `Context policy violation: ${contextPolicyViolations.map((item) => item.file).join(', ')}`;
|
|
499
|
+
const contextPolicyViolationItems = contextPolicyViolations.map((item) => ({
|
|
500
|
+
file: item.file,
|
|
501
|
+
rule: `context_policy:${item.rule}`,
|
|
502
|
+
severity: 'block',
|
|
503
|
+
message: item.reason,
|
|
504
|
+
}));
|
|
316
505
|
if (options.json) {
|
|
317
506
|
console.log(JSON.stringify({
|
|
318
507
|
grade: 'F',
|
|
319
508
|
score: 0,
|
|
320
509
|
verdict: 'FAIL',
|
|
321
|
-
violations:
|
|
322
|
-
file: item.file,
|
|
323
|
-
rule: `context_policy:${item.rule}`,
|
|
324
|
-
severity: 'block',
|
|
325
|
-
message: item.reason,
|
|
326
|
-
})),
|
|
510
|
+
violations: contextPolicyViolationItems,
|
|
327
511
|
message,
|
|
328
512
|
scopeGuardPassed: false,
|
|
329
513
|
bloatCount: 0,
|
|
@@ -334,7 +518,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
334
518
|
mode: 'policy_only',
|
|
335
519
|
policyOnly: true,
|
|
336
520
|
policyOnlySource: source,
|
|
337
|
-
...
|
|
521
|
+
...governancePayload,
|
|
338
522
|
}, null, 2));
|
|
339
523
|
}
|
|
340
524
|
else {
|
|
@@ -344,6 +528,20 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
344
528
|
});
|
|
345
529
|
console.log(chalk.dim(`\n${message}`));
|
|
346
530
|
}
|
|
531
|
+
await recordVerificationIfRequested(options, config, {
|
|
532
|
+
grade: 'F',
|
|
533
|
+
violations: contextPolicyViolationItems,
|
|
534
|
+
verifyResult: {
|
|
535
|
+
adherenceScore: 0,
|
|
536
|
+
verdict: 'FAIL',
|
|
537
|
+
bloatCount: 0,
|
|
538
|
+
bloatFiles: [],
|
|
539
|
+
message,
|
|
540
|
+
},
|
|
541
|
+
projectId,
|
|
542
|
+
jsonMode: Boolean(options.json),
|
|
543
|
+
governance: governancePayload,
|
|
544
|
+
});
|
|
347
545
|
return 2;
|
|
348
546
|
}
|
|
349
547
|
let policyViolations = [];
|
|
@@ -417,12 +615,13 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
417
615
|
}
|
|
418
616
|
if (policyLockEvaluation.enforced && !policyLockEvaluation.matched) {
|
|
419
617
|
const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
|
|
618
|
+
const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
|
|
420
619
|
if (options.json) {
|
|
421
620
|
console.log(JSON.stringify({
|
|
422
621
|
grade: 'F',
|
|
423
622
|
score: 0,
|
|
424
623
|
verdict: 'FAIL',
|
|
425
|
-
violations:
|
|
624
|
+
violations: lockViolationItems,
|
|
426
625
|
message,
|
|
427
626
|
scopeGuardPassed: true,
|
|
428
627
|
bloatCount: 0,
|
|
@@ -433,7 +632,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
433
632
|
mode: 'policy_only',
|
|
434
633
|
policyOnly: true,
|
|
435
634
|
policyOnlySource: source,
|
|
436
|
-
...
|
|
635
|
+
...governancePayload,
|
|
437
636
|
policyLock: {
|
|
438
637
|
enforced: true,
|
|
439
638
|
matched: false,
|
|
@@ -450,6 +649,20 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
450
649
|
});
|
|
451
650
|
console.log(chalk.dim('\n If drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
|
|
452
651
|
}
|
|
652
|
+
await recordVerificationIfRequested(options, config, {
|
|
653
|
+
grade: 'F',
|
|
654
|
+
violations: lockViolationItems,
|
|
655
|
+
verifyResult: {
|
|
656
|
+
adherenceScore: 0,
|
|
657
|
+
verdict: 'FAIL',
|
|
658
|
+
bloatCount: 0,
|
|
659
|
+
bloatFiles: [],
|
|
660
|
+
message,
|
|
661
|
+
},
|
|
662
|
+
projectId,
|
|
663
|
+
jsonMode: Boolean(options.json),
|
|
664
|
+
governance: governancePayload,
|
|
665
|
+
});
|
|
453
666
|
return 2;
|
|
454
667
|
}
|
|
455
668
|
if (!options.json && effectiveRules.customRules.length > 0) {
|
|
@@ -565,7 +778,7 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
565
778
|
mode: 'policy_only',
|
|
566
779
|
policyOnly: true,
|
|
567
780
|
policyOnlySource: source,
|
|
568
|
-
...
|
|
781
|
+
...governancePayload,
|
|
569
782
|
policyLock: {
|
|
570
783
|
enforced: policyLockEvaluation.enforced,
|
|
571
784
|
matched: policyLockEvaluation.matched,
|
|
@@ -608,6 +821,20 @@ async function executePolicyOnlyMode(options, diffFiles, ignoreFilter, projectRo
|
|
|
608
821
|
displayGovernanceInsights(governanceAnalysis, { explain: options.explain });
|
|
609
822
|
console.log(chalk.dim(`\n${message}`));
|
|
610
823
|
}
|
|
824
|
+
await recordVerificationIfRequested(options, config, {
|
|
825
|
+
grade,
|
|
826
|
+
violations: violationsOutput,
|
|
827
|
+
verifyResult: {
|
|
828
|
+
adherenceScore: score,
|
|
829
|
+
verdict: effectiveVerdict,
|
|
830
|
+
bloatCount: 0,
|
|
831
|
+
bloatFiles: [],
|
|
832
|
+
message,
|
|
833
|
+
},
|
|
834
|
+
projectId,
|
|
835
|
+
jsonMode: Boolean(options.json),
|
|
836
|
+
governance: governancePayload,
|
|
837
|
+
});
|
|
611
838
|
return effectiveVerdict === 'FAIL' ? 2 : effectiveVerdict === 'WARN' ? 1 : 0;
|
|
612
839
|
}
|
|
613
840
|
async function verifyCommand(options) {
|
|
@@ -661,10 +888,11 @@ async function verifyCommand(options) {
|
|
|
661
888
|
orgId: (0, state_1.getOrgId)(),
|
|
662
889
|
projectId: projectId || null,
|
|
663
890
|
};
|
|
664
|
-
const
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
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;
|
|
668
896
|
let orgGovernanceSettings = null;
|
|
669
897
|
if (config.apiKey) {
|
|
670
898
|
try {
|
|
@@ -686,6 +914,8 @@ async function verifyCommand(options) {
|
|
|
686
914
|
}
|
|
687
915
|
}
|
|
688
916
|
}
|
|
917
|
+
const signedLogsRequired = isSignedAiLogsRequired(orgGovernanceSettings);
|
|
918
|
+
const hasSigningMaterial = Boolean(aiLogSigningKey) || Object.keys(aiLogSigningKeys).length > 0;
|
|
689
919
|
const recordVerifyEvent = (verdict, note, changedFiles, planId) => {
|
|
690
920
|
if (!brainScope.orgId || !brainScope.projectId) {
|
|
691
921
|
return;
|
|
@@ -707,6 +937,60 @@ async function verifyCommand(options) {
|
|
|
707
937
|
// Never block verify flow on Brain persistence failures.
|
|
708
938
|
}
|
|
709
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
|
+
}
|
|
710
994
|
// Determine which diff to capture (staged + unstaged for full current work)
|
|
711
995
|
let diffText;
|
|
712
996
|
if (options.staged) {
|
|
@@ -818,21 +1102,25 @@ async function verifyCommand(options) {
|
|
|
818
1102
|
contextCandidates: diffFiles.map((file) => file.path),
|
|
819
1103
|
orgGovernance: orgGovernanceSettings,
|
|
820
1104
|
signingKey: aiLogSigningKey,
|
|
1105
|
+
signingKeyId: aiLogSigningKeyId,
|
|
1106
|
+
signingKeys: aiLogSigningKeys,
|
|
821
1107
|
signer: aiLogSigner,
|
|
822
1108
|
});
|
|
1109
|
+
const baselineGovernancePayload = buildGovernancePayload(baselineGovernance, orgGovernanceSettings);
|
|
823
1110
|
const message = `Context access policy violation: ${baselineContextViolations.map((item) => item.file).join(', ')}`;
|
|
1111
|
+
const baselineContextViolationItems = baselineContextViolations.map((item) => ({
|
|
1112
|
+
file: item.file,
|
|
1113
|
+
rule: `context_policy:${item.rule}`,
|
|
1114
|
+
severity: 'block',
|
|
1115
|
+
message: item.reason,
|
|
1116
|
+
}));
|
|
824
1117
|
recordVerifyEvent('FAIL', `context_policy_violations=${baselineContextViolations.length}`, diffFiles.map((f) => f.path));
|
|
825
1118
|
if (options.json) {
|
|
826
1119
|
console.log(JSON.stringify({
|
|
827
1120
|
grade: 'F',
|
|
828
1121
|
score: 0,
|
|
829
1122
|
verdict: 'FAIL',
|
|
830
|
-
violations:
|
|
831
|
-
file: item.file,
|
|
832
|
-
rule: `context_policy:${item.rule}`,
|
|
833
|
-
severity: 'block',
|
|
834
|
-
message: item.reason,
|
|
835
|
-
})),
|
|
1123
|
+
violations: baselineContextViolationItems,
|
|
836
1124
|
adherenceScore: 0,
|
|
837
1125
|
bloatCount: 0,
|
|
838
1126
|
bloatFiles: [],
|
|
@@ -840,7 +1128,7 @@ async function verifyCommand(options) {
|
|
|
840
1128
|
totalPlannedFiles: 0,
|
|
841
1129
|
message,
|
|
842
1130
|
scopeGuardPassed: false,
|
|
843
|
-
...
|
|
1131
|
+
...baselineGovernancePayload,
|
|
844
1132
|
mode: 'policy_violation',
|
|
845
1133
|
policyOnly: false,
|
|
846
1134
|
}, null, 2));
|
|
@@ -853,6 +1141,20 @@ async function verifyCommand(options) {
|
|
|
853
1141
|
console.log(chalk.dim(`\nRisk level: ${baselineGovernance.blastRadius.riskScore.toUpperCase()}`));
|
|
854
1142
|
console.log(chalk.red('\nAction blocked.\n'));
|
|
855
1143
|
}
|
|
1144
|
+
await recordVerificationIfRequested(options, config, {
|
|
1145
|
+
grade: 'F',
|
|
1146
|
+
violations: baselineContextViolationItems,
|
|
1147
|
+
verifyResult: {
|
|
1148
|
+
adherenceScore: 0,
|
|
1149
|
+
verdict: 'FAIL',
|
|
1150
|
+
bloatCount: 0,
|
|
1151
|
+
bloatFiles: [],
|
|
1152
|
+
message,
|
|
1153
|
+
},
|
|
1154
|
+
projectId: projectId || undefined,
|
|
1155
|
+
jsonMode: Boolean(options.json),
|
|
1156
|
+
governance: baselineGovernancePayload,
|
|
1157
|
+
});
|
|
856
1158
|
process.exit(2);
|
|
857
1159
|
}
|
|
858
1160
|
if (!options.json) {
|
|
@@ -861,7 +1163,7 @@ async function verifyCommand(options) {
|
|
|
861
1163
|
console.log(chalk.dim(` ${summary.totalAdded} lines added, ${summary.totalRemoved} lines removed\n`));
|
|
862
1164
|
}
|
|
863
1165
|
const runPolicyOnlyModeAndExit = async (source) => {
|
|
864
|
-
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, orgGovernanceSettings, aiLogSigningKey, aiLogSigner);
|
|
1166
|
+
const exitCode = await executePolicyOnlyMode(options, diffFiles, shouldIgnore, projectRoot, config, client, source, projectId || undefined, orgGovernanceSettings, aiLogSigningKey, aiLogSigningKeyId, aiLogSigningKeys, aiLogSigner);
|
|
865
1167
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
866
1168
|
const verdict = exitCode === 2 ? 'FAIL' : exitCode === 1 ? 'WARN' : 'PASS';
|
|
867
1169
|
recordVerifyEvent(verdict, `policy_only_source=${source};exit=${exitCode}`, changedFiles);
|
|
@@ -910,6 +1212,7 @@ async function verifyCommand(options) {
|
|
|
910
1212
|
if (!planId) {
|
|
911
1213
|
if (requirePlan) {
|
|
912
1214
|
const changedFiles = diffFiles.map((f) => f.path);
|
|
1215
|
+
const message = 'Plan ID is required in strict mode. Run "neurcode plan" first or pass --plan-id.';
|
|
913
1216
|
recordVerifyEvent('FAIL', 'missing_plan_id;require_plan=true', changedFiles);
|
|
914
1217
|
if (options.json) {
|
|
915
1218
|
console.log(JSON.stringify({
|
|
@@ -922,7 +1225,7 @@ async function verifyCommand(options) {
|
|
|
922
1225
|
bloatFiles: [],
|
|
923
1226
|
plannedFilesModified: 0,
|
|
924
1227
|
totalPlannedFiles: 0,
|
|
925
|
-
message
|
|
1228
|
+
message,
|
|
926
1229
|
scopeGuardPassed: false,
|
|
927
1230
|
mode: 'plan_required',
|
|
928
1231
|
policyOnly: false,
|
|
@@ -933,6 +1236,19 @@ async function verifyCommand(options) {
|
|
|
933
1236
|
console.log(chalk.dim(' Run "neurcode plan" first or pass --plan-id <id>.'));
|
|
934
1237
|
console.log(chalk.dim(' Use --policy-only only when intentionally running general governance checks.'));
|
|
935
1238
|
}
|
|
1239
|
+
await recordVerificationIfRequested(options, config, {
|
|
1240
|
+
grade: 'F',
|
|
1241
|
+
violations: [],
|
|
1242
|
+
verifyResult: {
|
|
1243
|
+
adherenceScore: 0,
|
|
1244
|
+
verdict: 'FAIL',
|
|
1245
|
+
bloatCount: 0,
|
|
1246
|
+
bloatFiles: [],
|
|
1247
|
+
message,
|
|
1248
|
+
},
|
|
1249
|
+
projectId: projectId || undefined,
|
|
1250
|
+
jsonMode: Boolean(options.json),
|
|
1251
|
+
});
|
|
936
1252
|
process.exit(1);
|
|
937
1253
|
}
|
|
938
1254
|
if (!options.json) {
|
|
@@ -981,6 +1297,8 @@ async function verifyCommand(options) {
|
|
|
981
1297
|
contextCandidates: planFiles,
|
|
982
1298
|
orgGovernance: orgGovernanceSettings,
|
|
983
1299
|
signingKey: aiLogSigningKey,
|
|
1300
|
+
signingKeyId: aiLogSigningKeyId,
|
|
1301
|
+
signingKeys: aiLogSigningKeys,
|
|
984
1302
|
signer: aiLogSigner,
|
|
985
1303
|
});
|
|
986
1304
|
// Get sessionId from state file (.neurcode/state.json) first, then fallback to config
|
|
@@ -1029,25 +1347,26 @@ async function verifyCommand(options) {
|
|
|
1029
1347
|
// Step D: The Block (only report scope violations for non-ignored files)
|
|
1030
1348
|
if (filteredViolations.length > 0) {
|
|
1031
1349
|
recordVerifyEvent('FAIL', `scope_violation=${filteredViolations.length}`, modifiedFiles, finalPlanId);
|
|
1350
|
+
const scopeViolationItems = filteredViolations.map((file) => ({
|
|
1351
|
+
file,
|
|
1352
|
+
rule: 'scope_guard',
|
|
1353
|
+
severity: 'block',
|
|
1354
|
+
message: 'File modified outside the plan',
|
|
1355
|
+
}));
|
|
1356
|
+
const scopeViolationMessage = `Scope violation: ${filteredViolations.length} file(s) modified outside the plan`;
|
|
1032
1357
|
if (options.json) {
|
|
1033
1358
|
// Output JSON for scope violation BEFORE exit. Must include violations for GitHub Action annotations.
|
|
1034
|
-
const violationsOutput = filteredViolations.map((file) => ({
|
|
1035
|
-
file,
|
|
1036
|
-
rule: 'scope_guard',
|
|
1037
|
-
severity: 'block',
|
|
1038
|
-
message: 'File modified outside the plan',
|
|
1039
|
-
}));
|
|
1040
1359
|
const jsonOutput = {
|
|
1041
1360
|
grade: 'F',
|
|
1042
1361
|
score: 0,
|
|
1043
1362
|
verdict: 'FAIL',
|
|
1044
|
-
violations:
|
|
1363
|
+
violations: scopeViolationItems,
|
|
1045
1364
|
adherenceScore: 0,
|
|
1046
1365
|
bloatCount: filteredViolations.length,
|
|
1047
1366
|
bloatFiles: filteredViolations,
|
|
1048
1367
|
plannedFilesModified: 0,
|
|
1049
1368
|
totalPlannedFiles: planFiles.length,
|
|
1050
|
-
message:
|
|
1369
|
+
message: scopeViolationMessage,
|
|
1051
1370
|
scopeGuardPassed: false,
|
|
1052
1371
|
mode: 'plan_enforced',
|
|
1053
1372
|
policyOnly: false,
|
|
@@ -1057,6 +1376,22 @@ async function verifyCommand(options) {
|
|
|
1057
1376
|
};
|
|
1058
1377
|
// CRITICAL: Print JSON first, then exit
|
|
1059
1378
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1379
|
+
await recordVerificationIfRequested(options, config, {
|
|
1380
|
+
grade: 'F',
|
|
1381
|
+
violations: scopeViolationItems,
|
|
1382
|
+
verifyResult: {
|
|
1383
|
+
adherenceScore: 0,
|
|
1384
|
+
verdict: 'FAIL',
|
|
1385
|
+
bloatCount: filteredViolations.length,
|
|
1386
|
+
bloatFiles: filteredViolations,
|
|
1387
|
+
message: scopeViolationMessage,
|
|
1388
|
+
},
|
|
1389
|
+
projectId: projectId || undefined,
|
|
1390
|
+
jsonMode: true,
|
|
1391
|
+
governance: governanceResult
|
|
1392
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1393
|
+
: undefined,
|
|
1394
|
+
});
|
|
1060
1395
|
process.exit(1);
|
|
1061
1396
|
}
|
|
1062
1397
|
else {
|
|
@@ -1077,6 +1412,22 @@ async function verifyCommand(options) {
|
|
|
1077
1412
|
displayGovernanceInsights(governanceResult, { explain: options.explain });
|
|
1078
1413
|
}
|
|
1079
1414
|
console.log('');
|
|
1415
|
+
await recordVerificationIfRequested(options, config, {
|
|
1416
|
+
grade: 'F',
|
|
1417
|
+
violations: scopeViolationItems,
|
|
1418
|
+
verifyResult: {
|
|
1419
|
+
adherenceScore: 0,
|
|
1420
|
+
verdict: 'FAIL',
|
|
1421
|
+
bloatCount: filteredViolations.length,
|
|
1422
|
+
bloatFiles: filteredViolations,
|
|
1423
|
+
message: scopeViolationMessage,
|
|
1424
|
+
},
|
|
1425
|
+
projectId: projectId || undefined,
|
|
1426
|
+
jsonMode: false,
|
|
1427
|
+
governance: governanceResult
|
|
1428
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1429
|
+
: undefined,
|
|
1430
|
+
});
|
|
1080
1431
|
process.exit(1);
|
|
1081
1432
|
}
|
|
1082
1433
|
}
|
|
@@ -1164,13 +1515,14 @@ async function verifyCommand(options) {
|
|
|
1164
1515
|
}
|
|
1165
1516
|
if (policyLockEvaluation.enforced && !policyLockEvaluation.matched) {
|
|
1166
1517
|
const message = policyLockMismatchMessage(policyLockEvaluation.mismatches);
|
|
1518
|
+
const lockViolationItems = toPolicyLockViolations(policyLockEvaluation.mismatches);
|
|
1167
1519
|
recordVerifyEvent('FAIL', 'policy_lock_mismatch', diffFiles.map((f) => f.path), finalPlanId);
|
|
1168
1520
|
if (options.json) {
|
|
1169
1521
|
console.log(JSON.stringify({
|
|
1170
1522
|
grade: 'F',
|
|
1171
1523
|
score: 0,
|
|
1172
1524
|
verdict: 'FAIL',
|
|
1173
|
-
violations:
|
|
1525
|
+
violations: lockViolationItems,
|
|
1174
1526
|
adherenceScore: 0,
|
|
1175
1527
|
bloatCount: 0,
|
|
1176
1528
|
bloatFiles: [],
|
|
@@ -1199,6 +1551,22 @@ async function verifyCommand(options) {
|
|
|
1199
1551
|
});
|
|
1200
1552
|
console.log(chalk.dim('\n If this drift is intentional, regenerate baseline with `neurcode policy lock`.\n'));
|
|
1201
1553
|
}
|
|
1554
|
+
await recordVerificationIfRequested(options, config, {
|
|
1555
|
+
grade: 'F',
|
|
1556
|
+
violations: lockViolationItems,
|
|
1557
|
+
verifyResult: {
|
|
1558
|
+
adherenceScore: 0,
|
|
1559
|
+
verdict: 'FAIL',
|
|
1560
|
+
bloatCount: 0,
|
|
1561
|
+
bloatFiles: [],
|
|
1562
|
+
message,
|
|
1563
|
+
},
|
|
1564
|
+
projectId: projectId || undefined,
|
|
1565
|
+
jsonMode: Boolean(options.json),
|
|
1566
|
+
governance: governanceResult
|
|
1567
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1568
|
+
: undefined,
|
|
1569
|
+
});
|
|
1202
1570
|
process.exit(2);
|
|
1203
1571
|
}
|
|
1204
1572
|
}
|
|
@@ -1379,13 +1747,23 @@ async function verifyCommand(options) {
|
|
|
1379
1747
|
const verifyResult = await client.verifyPlan(finalPlanId, diffStats, changedFiles, projectId, intentConstraints);
|
|
1380
1748
|
// Apply custom policy verdict: block from dashboard overrides API verdict
|
|
1381
1749
|
const policyBlock = policyDecision === 'block' && policyViolations.length > 0;
|
|
1382
|
-
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;
|
|
1383
1756
|
const policyMessageBase = policyBlock
|
|
1384
1757
|
? `Custom policy violations: ${policyViolations.map(v => `${v.file}: ${v.message || v.rule}`).join('; ')}. ${verifyResult.message}`
|
|
1385
1758
|
: verifyResult.message;
|
|
1386
|
-
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
|
|
1387
1765
|
? `${policyMessageBase} Policy exceptions suppressed ${policyExceptionsSummary.suppressed} violation(s).`
|
|
1388
|
-
: policyMessageBase;
|
|
1766
|
+
: policyMessageBase) + (governanceBlockReason ? ` ${governanceBlockReason}` : '');
|
|
1389
1767
|
// Calculate grade from effective verdict and score
|
|
1390
1768
|
// CRITICAL: 0/0 planned files = 'F' (Incomplete), not 'B'
|
|
1391
1769
|
// Bloat automatically drops grade by at least one letter
|
|
@@ -1434,6 +1812,7 @@ async function verifyCommand(options) {
|
|
|
1434
1812
|
recordVerifyEvent(effectiveVerdict, `adherence=${verifyResult.adherenceScore};bloat=${verifyResult.bloatCount};scopeGuard=${scopeGuardPassed ? 1 : 0};policy=${policyDecision};policyExceptions=${policyExceptionsSummary.suppressed}`, changedPathsForBrain, finalPlanId);
|
|
1435
1813
|
const shouldForceGovernancePass = scopeGuardPassed &&
|
|
1436
1814
|
!policyBlock &&
|
|
1815
|
+
!governanceHardBlock &&
|
|
1437
1816
|
(effectiveVerdict === 'PASS' ||
|
|
1438
1817
|
((verifyResult.verdict === 'FAIL' || verifyResult.verdict === 'WARN') &&
|
|
1439
1818
|
policyViolations.length === 0 &&
|
|
@@ -1493,30 +1872,22 @@ async function verifyCommand(options) {
|
|
|
1493
1872
|
: {}),
|
|
1494
1873
|
};
|
|
1495
1874
|
console.log(JSON.stringify(jsonOutput, null, 2));
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
message: v.message,
|
|
1510
|
-
})),
|
|
1511
|
-
];
|
|
1512
|
-
// Report in background (don't await to avoid blocking JSON output)
|
|
1513
|
-
reportVerification(grade, violations, verifyResult, config.apiKey, config.apiUrl, projectId || undefined, true, // jsonMode = true
|
|
1514
|
-
governanceResult
|
|
1875
|
+
await recordVerificationIfRequested(options, config, {
|
|
1876
|
+
grade,
|
|
1877
|
+
violations: violations,
|
|
1878
|
+
verifyResult: {
|
|
1879
|
+
adherenceScore: verifyResult.adherenceScore,
|
|
1880
|
+
verdict: effectiveVerdict,
|
|
1881
|
+
bloatCount: filteredBloatFiles.length,
|
|
1882
|
+
bloatFiles: filteredBloatFiles,
|
|
1883
|
+
message: effectiveMessage,
|
|
1884
|
+
},
|
|
1885
|
+
projectId: projectId || undefined,
|
|
1886
|
+
jsonMode: true,
|
|
1887
|
+
governance: governanceResult
|
|
1515
1888
|
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1516
|
-
: undefined
|
|
1517
|
-
|
|
1518
|
-
});
|
|
1519
|
-
}
|
|
1889
|
+
: undefined,
|
|
1890
|
+
});
|
|
1520
1891
|
// Exit based on effective verdict (same logic as below)
|
|
1521
1892
|
if (shouldForceGovernancePass) {
|
|
1522
1893
|
process.exit(0);
|
|
@@ -1565,36 +1936,37 @@ async function verifyCommand(options) {
|
|
|
1565
1936
|
}
|
|
1566
1937
|
}
|
|
1567
1938
|
// Report to Neurcode Cloud if --record flag is set
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1939
|
+
const filteredBloatForReport = (verifyResult.bloatFiles || []).filter((f) => !shouldIgnore(f));
|
|
1940
|
+
const reportViolations = [
|
|
1941
|
+
...filteredBloatForReport.map((file) => ({
|
|
1942
|
+
rule: 'scope_guard',
|
|
1943
|
+
file: file,
|
|
1944
|
+
severity: 'block',
|
|
1945
|
+
message: 'File modified outside the plan',
|
|
1946
|
+
})),
|
|
1947
|
+
...policyViolations.map((v) => ({
|
|
1948
|
+
rule: v.rule,
|
|
1949
|
+
file: v.file,
|
|
1950
|
+
severity: v.severity,
|
|
1951
|
+
message: v.message,
|
|
1952
|
+
})),
|
|
1953
|
+
];
|
|
1954
|
+
await recordVerificationIfRequested(options, config, {
|
|
1955
|
+
grade,
|
|
1956
|
+
violations: reportViolations,
|
|
1957
|
+
verifyResult: {
|
|
1958
|
+
adherenceScore: verifyResult.adherenceScore,
|
|
1959
|
+
verdict: effectiveVerdict,
|
|
1960
|
+
bloatCount: filteredBloatForReport.length,
|
|
1961
|
+
bloatFiles: filteredBloatForReport,
|
|
1962
|
+
message: effectiveMessage,
|
|
1963
|
+
},
|
|
1964
|
+
projectId: projectId || undefined,
|
|
1965
|
+
jsonMode: false,
|
|
1966
|
+
governance: governanceResult
|
|
1967
|
+
? buildGovernancePayload(governanceResult, orgGovernanceSettings)
|
|
1968
|
+
: undefined,
|
|
1969
|
+
});
|
|
1598
1970
|
// Governance override: keep PASS only when scope guard passes and failure is due
|
|
1599
1971
|
// to server-side bloat mismatch (allowed files unknown to verify API).
|
|
1600
1972
|
if (shouldForceGovernancePass) {
|
|
@@ -1780,6 +2152,63 @@ function collectCIContext() {
|
|
|
1780
2152
|
}
|
|
1781
2153
|
return context;
|
|
1782
2154
|
}
|
|
2155
|
+
const REPORT_MAX_ARRAY_ITEMS = 120;
|
|
2156
|
+
const REPORT_MAX_STRING_LENGTH = 4000;
|
|
2157
|
+
const REPORT_MAX_OBJECT_DEPTH = 6;
|
|
2158
|
+
function compactReportValue(value, depth = 0, seen = new WeakSet()) {
|
|
2159
|
+
if (value == null) {
|
|
2160
|
+
return value;
|
|
2161
|
+
}
|
|
2162
|
+
if (typeof value === 'string') {
|
|
2163
|
+
return value.length > REPORT_MAX_STRING_LENGTH
|
|
2164
|
+
? `${value.slice(0, REPORT_MAX_STRING_LENGTH)}...[truncated]`
|
|
2165
|
+
: value;
|
|
2166
|
+
}
|
|
2167
|
+
if (typeof value !== 'object') {
|
|
2168
|
+
return value;
|
|
2169
|
+
}
|
|
2170
|
+
if (depth >= REPORT_MAX_OBJECT_DEPTH) {
|
|
2171
|
+
return '[truncated]';
|
|
2172
|
+
}
|
|
2173
|
+
if (Array.isArray(value)) {
|
|
2174
|
+
const items = value.slice(0, REPORT_MAX_ARRAY_ITEMS).map((item) => compactReportValue(item, depth + 1, seen));
|
|
2175
|
+
if (value.length > REPORT_MAX_ARRAY_ITEMS) {
|
|
2176
|
+
items.push(`[truncated ${value.length - REPORT_MAX_ARRAY_ITEMS} item(s)]`);
|
|
2177
|
+
}
|
|
2178
|
+
return items;
|
|
2179
|
+
}
|
|
2180
|
+
if (seen.has(value)) {
|
|
2181
|
+
return '[circular]';
|
|
2182
|
+
}
|
|
2183
|
+
seen.add(value);
|
|
2184
|
+
const compacted = {};
|
|
2185
|
+
for (const [key, nestedValue] of Object.entries(value)) {
|
|
2186
|
+
compacted[key] = compactReportValue(nestedValue, depth + 1, seen);
|
|
2187
|
+
}
|
|
2188
|
+
return compacted;
|
|
2189
|
+
}
|
|
2190
|
+
function buildCompactVerificationPayload(payload) {
|
|
2191
|
+
const compactViolations = payload.violations.slice(0, REPORT_MAX_ARRAY_ITEMS);
|
|
2192
|
+
if (payload.violations.length > REPORT_MAX_ARRAY_ITEMS) {
|
|
2193
|
+
compactViolations.push({
|
|
2194
|
+
rule: 'report_payload_compaction',
|
|
2195
|
+
file: '__meta__',
|
|
2196
|
+
severity: 'warn',
|
|
2197
|
+
message: `Truncated ${payload.violations.length - REPORT_MAX_ARRAY_ITEMS} additional violation(s) for upload`,
|
|
2198
|
+
});
|
|
2199
|
+
}
|
|
2200
|
+
return {
|
|
2201
|
+
...payload,
|
|
2202
|
+
violations: compactViolations,
|
|
2203
|
+
bloatFiles: payload.bloatFiles.slice(0, REPORT_MAX_ARRAY_ITEMS),
|
|
2204
|
+
message: payload.message.length > REPORT_MAX_STRING_LENGTH
|
|
2205
|
+
? `${payload.message.slice(0, REPORT_MAX_STRING_LENGTH)}...[truncated]`
|
|
2206
|
+
: payload.message,
|
|
2207
|
+
governance: payload.governance
|
|
2208
|
+
? compactReportValue(payload.governance)
|
|
2209
|
+
: undefined,
|
|
2210
|
+
};
|
|
2211
|
+
}
|
|
1783
2212
|
/**
|
|
1784
2213
|
* Report verification results to Neurcode Cloud
|
|
1785
2214
|
*/
|
|
@@ -1801,22 +2230,36 @@ async function reportVerification(grade, violations, verifyResult, apiKey, apiUr
|
|
|
1801
2230
|
projectId,
|
|
1802
2231
|
governance,
|
|
1803
2232
|
};
|
|
1804
|
-
const
|
|
2233
|
+
const postPayload = async (requestPayload) => fetch(`${apiUrl}/api/v1/action/verifications`, {
|
|
1805
2234
|
method: 'POST',
|
|
1806
2235
|
headers: {
|
|
1807
2236
|
'Content-Type': 'application/json',
|
|
1808
2237
|
'Authorization': `Bearer ${apiKey}`,
|
|
1809
2238
|
},
|
|
1810
|
-
body: JSON.stringify(
|
|
2239
|
+
body: JSON.stringify(requestPayload),
|
|
1811
2240
|
});
|
|
2241
|
+
let response = await postPayload(payload);
|
|
2242
|
+
let compactedUpload = false;
|
|
2243
|
+
if (response.status === 413) {
|
|
2244
|
+
response = await postPayload(buildCompactVerificationPayload(payload));
|
|
2245
|
+
compactedUpload = true;
|
|
2246
|
+
}
|
|
1812
2247
|
if (!response.ok) {
|
|
1813
2248
|
const errorText = await response.text();
|
|
1814
|
-
|
|
2249
|
+
const compactError = errorText.replace(/\s+/g, ' ').trim().slice(0, 400);
|
|
2250
|
+
throw new Error(`HTTP ${response.status}: ${compactError}`);
|
|
2251
|
+
}
|
|
2252
|
+
let result = {};
|
|
2253
|
+
try {
|
|
2254
|
+
result = (await response.json());
|
|
2255
|
+
}
|
|
2256
|
+
catch {
|
|
2257
|
+
// Some proxies may return empty success bodies; treat as recorded.
|
|
1815
2258
|
}
|
|
1816
|
-
const result = await response.json();
|
|
1817
2259
|
// Only log if not in json mode to avoid polluting stdout
|
|
1818
2260
|
if (!jsonMode) {
|
|
1819
|
-
|
|
2261
|
+
const suffix = compactedUpload ? ' (compact payload)' : '';
|
|
2262
|
+
console.log(chalk.dim(`\n✅ Verification result reported to Neurcode Cloud (ID: ${result.id || 'ok'})${suffix}`));
|
|
1820
2263
|
}
|
|
1821
2264
|
}
|
|
1822
2265
|
catch (error) {
|
|
@@ -1867,6 +2310,17 @@ function displayGovernanceInsights(governance, options = {}) {
|
|
|
1867
2310
|
console.log(governance.aiChangeLogIntegrity.valid
|
|
1868
2311
|
? chalk.dim(` AI change-log integrity: valid (${governance.aiChangeLogIntegrity.signed ? 'signed' : 'unsigned'})`)
|
|
1869
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
|
+
}
|
|
1870
2324
|
if (governance.suspiciousChange.flagged) {
|
|
1871
2325
|
console.log(chalk.red('\nSuspicious Change Detected'));
|
|
1872
2326
|
console.log(chalk.red(` Plan expected files: ${governance.suspiciousChange.expectedFiles} | AI modified files: ${governance.suspiciousChange.actualFiles}`));
|