@neurcode-ai/cli 0.9.29 → 0.9.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +201 -0
- package/dist/api-client.d.ts +20 -0
- package/dist/api-client.d.ts.map +1 -1
- package/dist/api-client.js +35 -8
- package/dist/api-client.js.map +1 -1
- package/dist/commands/apply.d.ts.map +1 -1
- package/dist/commands/apply.js +33 -0
- package/dist/commands/apply.js.map +1 -1
- package/dist/commands/approve.d.ts +11 -0
- package/dist/commands/approve.d.ts.map +1 -0
- package/dist/commands/approve.js +116 -0
- package/dist/commands/approve.js.map +1 -0
- package/dist/commands/prompt.d.ts.map +1 -1
- package/dist/commands/prompt.js +47 -3
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/ship.d.ts +2 -0
- package/dist/commands/ship.d.ts.map +1 -1
- package/dist/commands/ship.js +228 -1
- package/dist/commands/ship.js.map +1 -1
- package/dist/commands/verify.d.ts +2 -0
- package/dist/commands/verify.d.ts.map +1 -1
- package/dist/commands/verify.js +504 -69
- package/dist/commands/verify.js.map +1 -1
- package/dist/index.js +26 -0
- package/dist/index.js.map +1 -1
- package/dist/utils/governance.d.ts +33 -0
- package/dist/utils/governance.d.ts.map +1 -0
- package/dist/utils/governance.js +68 -0
- package/dist/utils/governance.js.map +1 -0
- package/dist/utils/manual-approvals.d.ts +20 -0
- package/dist/utils/manual-approvals.d.ts.map +1 -0
- package/dist/utils/manual-approvals.js +104 -0
- package/dist/utils/manual-approvals.js.map +1 -0
- package/package.json +12 -9
package/dist/commands/ship.js
CHANGED
|
@@ -13,6 +13,7 @@ const config_1 = require("../config");
|
|
|
13
13
|
const project_root_1 = require("../utils/project-root");
|
|
14
14
|
const state_1 = require("../utils/state");
|
|
15
15
|
const breakage_simulator_1 = require("../utils/breakage-simulator");
|
|
16
|
+
const manual_approvals_1 = require("../utils/manual-approvals");
|
|
16
17
|
let chalk;
|
|
17
18
|
try {
|
|
18
19
|
chalk = require('chalk');
|
|
@@ -108,6 +109,7 @@ function createShipCheckpoint(input) {
|
|
|
108
109
|
requirePass: input.requirePass,
|
|
109
110
|
requirePolicyLock: input.requirePolicyLock,
|
|
110
111
|
skipPolicyLock: input.skipPolicyLock,
|
|
112
|
+
manualApproveHighRisk: input.options.manualApproveHighRisk === true,
|
|
111
113
|
publishCard: input.options.publishCard !== false,
|
|
112
114
|
},
|
|
113
115
|
baselineDirtyPaths: [],
|
|
@@ -542,6 +544,152 @@ function parseVerifyPayload(output) {
|
|
|
542
544
|
};
|
|
543
545
|
})()
|
|
544
546
|
: undefined;
|
|
547
|
+
const contextPolicy = record.contextPolicy && typeof record.contextPolicy === 'object' && !Array.isArray(record.contextPolicy)
|
|
548
|
+
? (() => {
|
|
549
|
+
const raw = record.contextPolicy;
|
|
550
|
+
const violations = Array.isArray(raw.violations)
|
|
551
|
+
? raw.violations
|
|
552
|
+
.filter((item) => !!item && typeof item === 'object')
|
|
553
|
+
.map((item) => ({
|
|
554
|
+
file: typeof item.file === 'string' ? item.file : 'unknown',
|
|
555
|
+
rule: typeof item.rule === 'string' ? item.rule : 'unknown',
|
|
556
|
+
reason: typeof item.reason === 'string' ? item.reason : '',
|
|
557
|
+
}))
|
|
558
|
+
: [];
|
|
559
|
+
return {
|
|
560
|
+
deniedModifyTouched: Array.isArray(raw.deniedModifyTouched)
|
|
561
|
+
? raw.deniedModifyTouched.filter((item) => typeof item === 'string')
|
|
562
|
+
: [],
|
|
563
|
+
violations,
|
|
564
|
+
};
|
|
565
|
+
})()
|
|
566
|
+
: undefined;
|
|
567
|
+
const blastRadius = record.blastRadius && typeof record.blastRadius === 'object' && !Array.isArray(record.blastRadius)
|
|
568
|
+
? (() => {
|
|
569
|
+
const raw = record.blastRadius;
|
|
570
|
+
const risk = typeof raw.riskScore === 'string' ? raw.riskScore.toLowerCase() : 'low';
|
|
571
|
+
const riskScore = risk === 'high' || risk === 'medium' ? risk : 'low';
|
|
572
|
+
return {
|
|
573
|
+
filesChanged: typeof raw.filesChanged === 'number' ? raw.filesChanged : 0,
|
|
574
|
+
functionsAffected: typeof raw.functionsAffected === 'number' ? raw.functionsAffected : 0,
|
|
575
|
+
modulesAffected: Array.isArray(raw.modulesAffected)
|
|
576
|
+
? raw.modulesAffected.filter((item) => typeof item === 'string')
|
|
577
|
+
: [],
|
|
578
|
+
dependenciesAdded: Array.isArray(raw.dependenciesAdded)
|
|
579
|
+
? raw.dependenciesAdded.filter((item) => typeof item === 'string')
|
|
580
|
+
: [],
|
|
581
|
+
riskScore: riskScore,
|
|
582
|
+
};
|
|
583
|
+
})()
|
|
584
|
+
: undefined;
|
|
585
|
+
const suspiciousChange = record.suspiciousChange && typeof record.suspiciousChange === 'object' && !Array.isArray(record.suspiciousChange)
|
|
586
|
+
? (() => {
|
|
587
|
+
const raw = record.suspiciousChange;
|
|
588
|
+
const confidenceRaw = typeof raw.confidence === 'string' ? raw.confidence.toLowerCase() : 'low';
|
|
589
|
+
const confidence = confidenceRaw === 'high' || confidenceRaw === 'medium' ? confidenceRaw : 'low';
|
|
590
|
+
return {
|
|
591
|
+
expectedFiles: typeof raw.expectedFiles === 'number' ? raw.expectedFiles : 0,
|
|
592
|
+
actualFiles: typeof raw.actualFiles === 'number' ? raw.actualFiles : 0,
|
|
593
|
+
unexpectedFiles: Array.isArray(raw.unexpectedFiles)
|
|
594
|
+
? raw.unexpectedFiles.filter((item) => typeof item === 'string')
|
|
595
|
+
: [],
|
|
596
|
+
flagged: raw.flagged === true,
|
|
597
|
+
confidence: confidence,
|
|
598
|
+
};
|
|
599
|
+
})()
|
|
600
|
+
: undefined;
|
|
601
|
+
const changeJustification = record.changeJustification && typeof record.changeJustification === 'object' && !Array.isArray(record.changeJustification)
|
|
602
|
+
? (() => {
|
|
603
|
+
const raw = record.changeJustification;
|
|
604
|
+
return {
|
|
605
|
+
task: typeof raw.task === 'string' ? raw.task : '',
|
|
606
|
+
changes: Array.isArray(raw.changes)
|
|
607
|
+
? raw.changes
|
|
608
|
+
.filter((item) => !!item && typeof item === 'object')
|
|
609
|
+
.map((item) => ({
|
|
610
|
+
file: typeof item.file === 'string' ? item.file : 'unknown',
|
|
611
|
+
reason: typeof item.reason === 'string' ? item.reason : '',
|
|
612
|
+
}))
|
|
613
|
+
: [],
|
|
614
|
+
};
|
|
615
|
+
})()
|
|
616
|
+
: undefined;
|
|
617
|
+
const governanceDecision = record.governanceDecision && typeof record.governanceDecision === 'object' && !Array.isArray(record.governanceDecision)
|
|
618
|
+
? (() => {
|
|
619
|
+
const raw = record.governanceDecision;
|
|
620
|
+
const decisionRaw = typeof raw.decision === 'string' ? raw.decision.toLowerCase() : 'allow';
|
|
621
|
+
const decision = decisionRaw === 'warn' || decisionRaw === 'manual_approval' || decisionRaw === 'block'
|
|
622
|
+
? decisionRaw
|
|
623
|
+
: 'allow';
|
|
624
|
+
return {
|
|
625
|
+
decision: decision,
|
|
626
|
+
reasonCodes: Array.isArray(raw.reasonCodes)
|
|
627
|
+
? raw.reasonCodes.filter((item) => typeof item === 'string')
|
|
628
|
+
: [],
|
|
629
|
+
summary: typeof raw.summary === 'string' ? raw.summary : undefined,
|
|
630
|
+
averageRelevanceScore: typeof raw.averageRelevanceScore === 'number' ? raw.averageRelevanceScore : undefined,
|
|
631
|
+
lowRelevanceFiles: Array.isArray(raw.lowRelevanceFiles)
|
|
632
|
+
? raw.lowRelevanceFiles
|
|
633
|
+
.filter((item) => !!item && typeof item === 'object')
|
|
634
|
+
.map((item) => ({
|
|
635
|
+
file: typeof item.file === 'string' ? item.file : 'unknown',
|
|
636
|
+
relevanceScore: typeof item.relevanceScore === 'number' ? item.relevanceScore : 0,
|
|
637
|
+
planLink: typeof item.planLink === 'string' ? item.planLink : 'unknown',
|
|
638
|
+
}))
|
|
639
|
+
: undefined,
|
|
640
|
+
requiresManualApproval: raw.requiresManualApproval === true,
|
|
641
|
+
};
|
|
642
|
+
})()
|
|
643
|
+
: undefined;
|
|
644
|
+
const aiChangeLog = record.aiChangeLog && typeof record.aiChangeLog === 'object' && !Array.isArray(record.aiChangeLog)
|
|
645
|
+
? (() => {
|
|
646
|
+
const raw = record.aiChangeLog;
|
|
647
|
+
const integrityRaw = raw.integrity && typeof raw.integrity === 'object' && !Array.isArray(raw.integrity)
|
|
648
|
+
? raw.integrity
|
|
649
|
+
: null;
|
|
650
|
+
return {
|
|
651
|
+
path: typeof raw.path === 'string' ? raw.path : undefined,
|
|
652
|
+
auditPath: typeof raw.auditPath === 'string' ? raw.auditPath : undefined,
|
|
653
|
+
integrity: integrityRaw
|
|
654
|
+
? {
|
|
655
|
+
valid: integrityRaw.valid === true,
|
|
656
|
+
required: integrityRaw.required === true,
|
|
657
|
+
signed: integrityRaw.signed === true,
|
|
658
|
+
issues: Array.isArray(integrityRaw.issues)
|
|
659
|
+
? integrityRaw.issues.filter((item) => typeof item === 'string')
|
|
660
|
+
: [],
|
|
661
|
+
payloadHash: typeof integrityRaw.payloadHash === 'string' ? integrityRaw.payloadHash : null,
|
|
662
|
+
chainHash: typeof integrityRaw.chainHash === 'string' ? integrityRaw.chainHash : null,
|
|
663
|
+
}
|
|
664
|
+
: undefined,
|
|
665
|
+
};
|
|
666
|
+
})()
|
|
667
|
+
: undefined;
|
|
668
|
+
const policySources = record.policySources && typeof record.policySources === 'object' && !Array.isArray(record.policySources)
|
|
669
|
+
? (() => {
|
|
670
|
+
const raw = record.policySources;
|
|
671
|
+
const modeRaw = typeof raw.mode === 'string' ? raw.mode.toLowerCase() : 'local';
|
|
672
|
+
const mode = modeRaw === 'merged' || modeRaw === 'org_only' ? modeRaw : 'local';
|
|
673
|
+
return {
|
|
674
|
+
localPolicy: raw.localPolicy !== false,
|
|
675
|
+
orgPolicy: raw.orgPolicy === true,
|
|
676
|
+
mode: mode,
|
|
677
|
+
};
|
|
678
|
+
})()
|
|
679
|
+
: undefined;
|
|
680
|
+
const orgGovernance = record.orgGovernance && typeof record.orgGovernance === 'object' && !Array.isArray(record.orgGovernance)
|
|
681
|
+
? (() => {
|
|
682
|
+
const raw = record.orgGovernance;
|
|
683
|
+
return {
|
|
684
|
+
requireSignedAiLogs: raw.requireSignedAiLogs === true,
|
|
685
|
+
requireManualApproval: raw.requireManualApproval !== false,
|
|
686
|
+
minimumManualApprovals: typeof raw.minimumManualApprovals === 'number'
|
|
687
|
+
? Math.max(1, Math.min(5, Math.floor(raw.minimumManualApprovals)))
|
|
688
|
+
: 1,
|
|
689
|
+
updatedAt: typeof raw.updatedAt === 'string' ? raw.updatedAt : null,
|
|
690
|
+
};
|
|
691
|
+
})()
|
|
692
|
+
: undefined;
|
|
545
693
|
return {
|
|
546
694
|
grade: record.grade,
|
|
547
695
|
score: typeof record.score === 'number' ? record.score : 0,
|
|
@@ -561,6 +709,14 @@ function parseVerifyPayload(output) {
|
|
|
561
709
|
policyLock,
|
|
562
710
|
policyExceptions,
|
|
563
711
|
policyGovernance,
|
|
712
|
+
contextPolicy,
|
|
713
|
+
blastRadius,
|
|
714
|
+
suspiciousChange,
|
|
715
|
+
changeJustification,
|
|
716
|
+
governanceDecision,
|
|
717
|
+
aiChangeLog,
|
|
718
|
+
policySources,
|
|
719
|
+
orgGovernance,
|
|
564
720
|
};
|
|
565
721
|
}
|
|
566
722
|
function parsePlanPayload(output) {
|
|
@@ -644,6 +800,20 @@ function runGit(cwd, args) {
|
|
|
644
800
|
stderr: result.stderr || '',
|
|
645
801
|
};
|
|
646
802
|
}
|
|
803
|
+
function getHeadSha(cwd) {
|
|
804
|
+
const head = runGit(cwd, ['rev-parse', 'HEAD']);
|
|
805
|
+
if (head.code !== 0)
|
|
806
|
+
return null;
|
|
807
|
+
const value = head.stdout.trim().toLowerCase();
|
|
808
|
+
return value || null;
|
|
809
|
+
}
|
|
810
|
+
function resolveDistinctManualApprovers(cwd, commitSha) {
|
|
811
|
+
const approvals = (0, manual_approvals_1.getManualApprovalsForCommit)(cwd, commitSha);
|
|
812
|
+
return {
|
|
813
|
+
approvals,
|
|
814
|
+
distinctApprovers: (0, manual_approvals_1.countDistinctApprovers)(approvals),
|
|
815
|
+
};
|
|
816
|
+
}
|
|
647
817
|
function getPrimaryGitRemoteUrl(cwd) {
|
|
648
818
|
const remote = runGit(cwd, ['remote', 'get-url', 'origin']);
|
|
649
819
|
if (remote.code !== 0)
|
|
@@ -1613,8 +1783,64 @@ async function shipCommand(goal, options) {
|
|
|
1613
1783
|
});
|
|
1614
1784
|
}
|
|
1615
1785
|
const finalVerifyPayload = verifyPayload;
|
|
1616
|
-
|
|
1786
|
+
let verifyPassedFinal = verifyExitCode === 0 &&
|
|
1617
1787
|
(finalVerifyPayload.verdict === 'PASS' || (!requirePass && isInfoOnlyGovernanceResult(finalVerifyPayload)));
|
|
1788
|
+
const manualApprovalBypass = options.manualApproveHighRisk === true || process.env.NEURCODE_MANUAL_APPROVE_HIGH_RISK === '1';
|
|
1789
|
+
const governanceDecision = finalVerifyPayload.governanceDecision?.decision;
|
|
1790
|
+
const orgGovernance = finalVerifyPayload.orgGovernance || null;
|
|
1791
|
+
const orgManualApprovalRequired = orgGovernance?.requireManualApproval === true;
|
|
1792
|
+
const minimumManualApprovals = orgGovernance
|
|
1793
|
+
? Math.max(1, Math.min(5, Math.floor(orgGovernance.minimumManualApprovals || 1)))
|
|
1794
|
+
: 1;
|
|
1795
|
+
const approvalHeadSha = getHeadSha(cwd);
|
|
1796
|
+
const manualApprovalState = approvalHeadSha
|
|
1797
|
+
? resolveDistinctManualApprovers(cwd, approvalHeadSha)
|
|
1798
|
+
: { approvals: [], distinctApprovers: 0 };
|
|
1799
|
+
if (verifyPassedFinal && governanceDecision === 'block') {
|
|
1800
|
+
verifyPassedFinal = false;
|
|
1801
|
+
verifyExitCode = verifyExitCode === 0 ? 2 : verifyExitCode;
|
|
1802
|
+
remediationActions.push('governance_decision_block');
|
|
1803
|
+
const summary = finalVerifyPayload.governanceDecision?.summary || 'Governance decision matrix returned BLOCK.';
|
|
1804
|
+
finalVerifyPayload.message = `${finalVerifyPayload.message || 'Governance verification completed.'} ${summary}`;
|
|
1805
|
+
console.log(chalk.red('\n⛔ Ship blocked by governance decision matrix (BLOCK).'));
|
|
1806
|
+
if (finalVerifyPayload.governanceDecision?.reasonCodes?.length) {
|
|
1807
|
+
console.log(chalk.dim(` Reasons: ${finalVerifyPayload.governanceDecision.reasonCodes.join(', ')}`));
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
const requiresManualApproval = governanceDecision === 'manual_approval' ||
|
|
1811
|
+
(!governanceDecision && finalVerifyPayload.blastRadius?.riskScore === 'high');
|
|
1812
|
+
let effectiveDistinctApprovers = manualApprovalState.distinctApprovers;
|
|
1813
|
+
if (orgManualApprovalRequired && manualApprovalBypass) {
|
|
1814
|
+
// Backward-compatible flag counts as the current operator approval in enterprise mode.
|
|
1815
|
+
effectiveDistinctApprovers += 1;
|
|
1816
|
+
}
|
|
1817
|
+
const manualApprovalSatisfied = orgManualApprovalRequired
|
|
1818
|
+
? effectiveDistinctApprovers >= minimumManualApprovals
|
|
1819
|
+
: manualApprovalBypass;
|
|
1820
|
+
if (verifyPassedFinal && requiresManualApproval && !manualApprovalSatisfied) {
|
|
1821
|
+
verifyPassedFinal = false;
|
|
1822
|
+
verifyExitCode = verifyExitCode === 0 ? 2 : verifyExitCode;
|
|
1823
|
+
remediationActions.push('governance_manual_approval_required');
|
|
1824
|
+
const summary = finalVerifyPayload.governanceDecision?.summary ||
|
|
1825
|
+
'High blast-radius risk requires manual approval before ship can continue.';
|
|
1826
|
+
finalVerifyPayload.message = `${finalVerifyPayload.message || 'Governance verification completed.'} ${summary}`;
|
|
1827
|
+
console.log(chalk.red('\n⛔ Ship blocked: governance requires manual approval before deploy.'));
|
|
1828
|
+
if (orgManualApprovalRequired) {
|
|
1829
|
+
const recordedCount = manualApprovalState.distinctApprovers;
|
|
1830
|
+
console.log(chalk.dim(` Manual approvals required: ${minimumManualApprovals}, recorded: ${recordedCount}${manualApprovalBypass ? ' (+1 current operator flag)' : ''}`));
|
|
1831
|
+
if (approvalHeadSha) {
|
|
1832
|
+
console.log(chalk.dim(` Commit: ${approvalHeadSha}`));
|
|
1833
|
+
}
|
|
1834
|
+
console.log(chalk.dim(' Record approvals with `neurcode approve --reason "<review context>"`.'));
|
|
1835
|
+
console.log(chalk.dim(' Then re-run `neurcode ship ...` to continue.'));
|
|
1836
|
+
}
|
|
1837
|
+
else {
|
|
1838
|
+
console.log(chalk.dim(' Re-run with --manual-approve-high-risk after manual review.'));
|
|
1839
|
+
}
|
|
1840
|
+
if (typeof finalVerifyPayload.governanceDecision?.averageRelevanceScore === 'number') {
|
|
1841
|
+
console.log(chalk.dim(` Avg relevance score: ${finalVerifyPayload.governanceDecision.averageRelevanceScore}`));
|
|
1842
|
+
}
|
|
1843
|
+
}
|
|
1618
1844
|
if (verifyPassedFinal && !options.skipTests) {
|
|
1619
1845
|
testsAttempts += 1;
|
|
1620
1846
|
if (!testCommand) {
|
|
@@ -2182,6 +2408,7 @@ async function shipResumeCommand(runId, options) {
|
|
|
2182
2408
|
requirePass: options.requirePass ?? checkpoint.options.requirePass,
|
|
2183
2409
|
requirePolicyLock: options.requirePolicyLock ?? checkpoint.options.requirePolicyLock,
|
|
2184
2410
|
skipPolicyLock: options.skipPolicyLock ?? checkpoint.options.skipPolicyLock,
|
|
2411
|
+
manualApproveHighRisk: options.manualApproveHighRisk ?? checkpoint.options.manualApproveHighRisk,
|
|
2185
2412
|
publishCard: options.publishCard ?? checkpoint.options.publishCard,
|
|
2186
2413
|
json: options.json === true,
|
|
2187
2414
|
resumeRunId: checkpoint.runId,
|