@neurcode-ai/cli 0.17.0 → 0.19.0

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.
Files changed (43) hide show
  1. package/README.md +3 -2
  2. package/dist/commands/brain.d.ts.map +1 -1
  3. package/dist/commands/brain.js +451 -0
  4. package/dist/commands/brain.js.map +1 -1
  5. package/dist/commands/policy.d.ts.map +1 -1
  6. package/dist/commands/policy.js +296 -0
  7. package/dist/commands/policy.js.map +1 -1
  8. package/dist/commands/runtime-adapter.d.ts +2 -1
  9. package/dist/commands/runtime-adapter.d.ts.map +1 -1
  10. package/dist/commands/runtime-adapter.js +51 -2
  11. package/dist/commands/runtime-adapter.js.map +1 -1
  12. package/dist/commands/session-hook.d.ts +17 -0
  13. package/dist/commands/session-hook.d.ts.map +1 -1
  14. package/dist/commands/session-hook.js +279 -14
  15. package/dist/commands/session-hook.js.map +1 -1
  16. package/dist/runtime-build.json +4 -4
  17. package/dist/utils/agent-adapter-setup.js +1 -1
  18. package/dist/utils/agent-adapter-setup.js.map +1 -1
  19. package/dist/utils/agent-guard.d.ts +1 -0
  20. package/dist/utils/agent-guard.d.ts.map +1 -1
  21. package/dist/utils/agent-guard.js +18 -6
  22. package/dist/utils/agent-guard.js.map +1 -1
  23. package/dist/utils/git-coverage.d.ts.map +1 -1
  24. package/dist/utils/git-coverage.js +1 -0
  25. package/dist/utils/git-coverage.js.map +1 -1
  26. package/dist/utils/local-repo-brain.d.ts +85 -0
  27. package/dist/utils/local-repo-brain.d.ts.map +1 -1
  28. package/dist/utils/local-repo-brain.js +259 -4
  29. package/dist/utils/local-repo-brain.js.map +1 -1
  30. package/dist/utils/proposed-change-analysis.d.ts +20 -0
  31. package/dist/utils/proposed-change-analysis.d.ts.map +1 -0
  32. package/dist/utils/proposed-change-analysis.js +448 -0
  33. package/dist/utils/proposed-change-analysis.js.map +1 -0
  34. package/dist/utils/repo-intelligence-v2.d.ts +28 -0
  35. package/dist/utils/repo-intelligence-v2.d.ts.map +1 -0
  36. package/dist/utils/repo-intelligence-v2.js +174 -0
  37. package/dist/utils/repo-intelligence-v2.js.map +1 -0
  38. package/dist/utils/v0-governance.d.ts +1 -1
  39. package/dist/utils/v0-governance.d.ts.map +1 -1
  40. package/dist/utils/v0-governance.js +86 -15
  41. package/dist/utils/v0-governance.js.map +1 -1
  42. package/package.json +12 -11
  43. package/LICENSE +0 -201
@@ -1,6 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.policyCommand = policyCommand;
4
+ const node_fs_1 = require("node:fs");
5
+ const node_path_1 = require("node:path");
6
+ const brain_1 = require("@neurcode-ai/brain");
7
+ const policy_engine_1 = require("@neurcode-ai/policy-engine");
4
8
  const project_root_1 = require("../utils/project-root");
5
9
  const config_1 = require("../config");
6
10
  const api_client_1 = require("../api-client");
@@ -11,6 +15,8 @@ const policy_audit_1 = require("../utils/policy-audit");
11
15
  const policy_packs_1 = require("../utils/policy-packs");
12
16
  const policy_compiler_1 = require("../utils/policy-compiler");
13
17
  const artifact_signature_1 = require("../utils/artifact-signature");
18
+ const proposed_change_analysis_1 = require("../utils/proposed-change-analysis");
19
+ const repo_intelligence_v2_1 = require("../utils/repo-intelligence-v2");
14
20
  // Import chalk with fallback
15
21
  let chalk;
16
22
  try {
@@ -89,6 +95,107 @@ function normalizeListLimit(value, fallback, min, max) {
89
95
  return fallback;
90
96
  return Math.max(min, Math.min(max, Math.floor(Number(value))));
91
97
  }
98
+ const STRUCTURAL_POLICY_DEFAULT_PATH = '.neurcode/structural-policy-v2.json';
99
+ function resolveStructuralPolicyPath(cwd, input) {
100
+ const selected = input?.trim() || STRUCTURAL_POLICY_DEFAULT_PATH;
101
+ return (0, node_path_1.isAbsolute)(selected) ? selected : (0, node_path_1.join)(cwd, selected);
102
+ }
103
+ function readJsonValue(path) {
104
+ try {
105
+ return JSON.parse((0, node_fs_1.readFileSync)(path, 'utf8'));
106
+ }
107
+ catch (error) {
108
+ const detail = error instanceof Error ? error.message : String(error);
109
+ throw new Error(`Invalid JSON in ${path}: ${detail}`);
110
+ }
111
+ }
112
+ function normalizeRepositoryPath(cwd, input) {
113
+ const absolutePath = (0, node_path_1.isAbsolute)(input) ? (0, node_path_1.resolve)(input) : (0, node_path_1.resolve)(cwd, input);
114
+ const relativePath = (0, node_path_1.relative)(cwd, absolutePath).replace(/\\/g, '/');
115
+ if (!relativePath || relativePath === '..' || relativePath.startsWith('../')) {
116
+ throw new Error(`Path is outside repository root: ${input}`);
117
+ }
118
+ return { relativePath, absolutePath };
119
+ }
120
+ function structuralExitCode(verdict) {
121
+ if (verdict === 'block')
122
+ return 2;
123
+ if (verdict === 'not_evaluated')
124
+ return 3;
125
+ return 0;
126
+ }
127
+ function structuralStatements(value, previous) {
128
+ return [...previous, value];
129
+ }
130
+ async function evaluateStructuralPolicyPath(input) {
131
+ const target = normalizeRepositoryPath(input.cwd, input.targetPath);
132
+ let graph = (0, brain_1.readRepositoryGraph)(input.cwd);
133
+ let freshness = await (0, brain_1.repositoryGraphStatus)(input.cwd);
134
+ if (input.index !== false && (!graph || freshness.state !== 'fresh')) {
135
+ graph = (await (0, brain_1.indexRepositoryGraph)({ repoRoot: input.cwd })).graph;
136
+ freshness = graph.freshness;
137
+ }
138
+ else if (graph) {
139
+ graph = { ...graph, freshness };
140
+ }
141
+ const operation = input.operation
142
+ ?? ((0, node_fs_1.existsSync)(target.absolutePath) ? 'update' : 'create');
143
+ let proposedSource = null;
144
+ let sourceKind = 'not_available';
145
+ if (!input.pathOnly && operation !== 'delete') {
146
+ const contentPath = input.contentFile
147
+ ? ((0, node_path_1.isAbsolute)(input.contentFile) ? input.contentFile : (0, node_path_1.resolve)(input.cwd, input.contentFile))
148
+ : target.absolutePath;
149
+ if ((0, node_fs_1.existsSync)(contentPath)) {
150
+ proposedSource = (0, node_fs_1.readFileSync)(contentPath, 'utf8');
151
+ sourceKind = input.contentFile ? 'write_content' : 'post_write_disk_read';
152
+ }
153
+ }
154
+ const analysis = (0, proposed_change_analysis_1.analyzeProposedChange)({
155
+ repoRoot: input.cwd,
156
+ filePath: target.relativePath,
157
+ proposedSource,
158
+ sourceKind,
159
+ adapterId: 'neurcode-cli',
160
+ timing: sourceKind === 'post_write_disk_read' ? 'after_write' : 'before_write',
161
+ sessionId: null,
162
+ planRevision: null,
163
+ });
164
+ analysis.envelope.target.operation = operation;
165
+ const approvalsValue = input.approvalsFile
166
+ ? readJsonValue((0, node_path_1.isAbsolute)(input.approvalsFile) ? input.approvalsFile : (0, node_path_1.resolve)(input.cwd, input.approvalsFile))
167
+ : [];
168
+ if (!Array.isArray(approvalsValue))
169
+ throw new Error('Approvals file must contain a JSON array.');
170
+ const approvals = approvalsValue
171
+ .filter((value) => Boolean(value)
172
+ && typeof value === 'object'
173
+ && typeof value.path === 'string'
174
+ && Array.isArray(value.owners)
175
+ && typeof value.approvedBy === 'string')
176
+ .map((value) => ({
177
+ path: value.path,
178
+ owners: value.owners.filter((owner) => typeof owner === 'string'),
179
+ approvedBy: value.approvedBy,
180
+ }));
181
+ const repoIntelligence = await (0, repo_intelligence_v2_1.evaluateLocalRepoIntelligenceV2)({
182
+ repoRoot: input.cwd,
183
+ change: analysis.envelope,
184
+ approvals,
185
+ policyPath: input.artifactPath,
186
+ });
187
+ if (!repoIntelligence.policyConfigured) {
188
+ throw new Error(`Structural policy artifact is missing or invalid: ${repoIntelligence.policyPath}. ` +
189
+ 'Run `neurcode policy structural-compile`.');
190
+ }
191
+ return {
192
+ artifactPath: resolveStructuralPolicyPath(input.cwd, input.artifactPath),
193
+ graphId: repoIntelligence.evidence.graph.graphId,
194
+ envelope: analysis.envelope,
195
+ evaluation: repoIntelligence.evaluation,
196
+ evidence: repoIntelligence.evidence,
197
+ };
198
+ }
92
199
  async function resolveCustomPolicies(client, includeDashboardPolicies, requireDashboardPolicies) {
93
200
  if (!includeDashboardPolicies) {
94
201
  return {
@@ -487,6 +594,195 @@ function policyCommand(program) {
487
594
  }
488
595
  process.exit(pass ? 0 : 1);
489
596
  });
597
+ policy
598
+ .command('structural-compile')
599
+ .description('Compile bounded Repository Policy V2 rules into a deterministic source-free artifact')
600
+ .option('--input <path>', 'JSON file containing organizationRules, repositoryRules, and naturalLanguageStatements')
601
+ .option('--statement <text>', 'Add a bounded natural-language statement', structuralStatements, [])
602
+ .option('--output <path>', `Output artifact (default: ${STRUCTURAL_POLICY_DEFAULT_PATH})`)
603
+ .option('--require-deterministic', 'Fail when any statement is advisory, not evaluated, or rejected')
604
+ .option('--json', 'Output stable machine-readable JSON')
605
+ .action((options) => {
606
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
607
+ try {
608
+ const fromFile = options.input
609
+ ? readJsonValue((0, node_path_1.isAbsolute)(options.input) ? options.input : (0, node_path_1.resolve)(cwd, options.input))
610
+ : {};
611
+ if (!fromFile || typeof fromFile !== 'object' || Array.isArray(fromFile)) {
612
+ throw new Error('Structural policy input must be a JSON object.');
613
+ }
614
+ const parsed = fromFile;
615
+ const naturalLanguageStatements = [
616
+ ...(Array.isArray(parsed.naturalLanguageStatements)
617
+ ? parsed.naturalLanguageStatements.filter((value) => typeof value === 'string')
618
+ : []),
619
+ ...(options.statement ?? []),
620
+ ];
621
+ const compilation = (0, policy_engine_1.compileStructuralPolicies)({
622
+ organizationRules: Array.isArray(parsed.organizationRules) ? parsed.organizationRules : [],
623
+ repositoryRules: Array.isArray(parsed.repositoryRules) ? parsed.repositoryRules : [],
624
+ naturalLanguageStatements,
625
+ });
626
+ const artifact = (0, policy_engine_1.createStructuralPolicyArtifact)(compilation);
627
+ const outputPath = resolveStructuralPolicyPath(cwd, options.output);
628
+ (0, node_fs_1.mkdirSync)((0, node_path_1.dirname)(outputPath), { recursive: true });
629
+ (0, node_fs_1.writeFileSync)(outputPath, `${JSON.stringify(artifact, null, 2)}\n`, { encoding: 'utf8', mode: 0o600 });
630
+ const incomplete = compilation.advisory.length
631
+ + compilation.notEvaluated.length
632
+ + compilation.rejected.length;
633
+ const exitCode = compilation.rejected.length > 0
634
+ || (options.requireDeterministic && incomplete > 0)
635
+ ? 4
636
+ : 0;
637
+ const payload = {
638
+ ok: exitCode === 0,
639
+ path: outputPath,
640
+ artifact,
641
+ counts: {
642
+ compiled: compilation.compiled.length,
643
+ advisory: compilation.advisory.length,
644
+ notEvaluated: compilation.notEvaluated.length,
645
+ rejected: compilation.rejected.length,
646
+ },
647
+ exitCode,
648
+ };
649
+ if (options.json) {
650
+ console.log(JSON.stringify(payload, null, 2));
651
+ }
652
+ else {
653
+ console.log(chalk.bold('\n🛡️ Structural Policy V2 Compilation\n'));
654
+ console.log(chalk.dim(`Artifact: ${outputPath}`));
655
+ console.log(chalk.dim(`Compiled: ${payload.counts.compiled}`));
656
+ console.log(chalk.dim(`Advisory: ${payload.counts.advisory}`));
657
+ console.log(chalk.dim(`Not evaluated: ${payload.counts.notEvaluated}`));
658
+ console.log(chalk.dim(`Rejected: ${payload.counts.rejected}`));
659
+ for (const rule of compilation.compiled) {
660
+ console.log(chalk.dim(` ${rule.ruleId} · ${rule.family} · ${rule.mode}`));
661
+ }
662
+ }
663
+ process.exitCode = exitCode;
664
+ }
665
+ catch (error) {
666
+ const message = error instanceof Error ? error.message : String(error);
667
+ if (options.json)
668
+ console.log(JSON.stringify({ ok: false, error: message, exitCode: 1 }, null, 2));
669
+ else
670
+ console.error(chalk.red(`\n❌ ${message}\n`));
671
+ process.exitCode = 1;
672
+ }
673
+ });
674
+ policy
675
+ .command('structural-test <path>')
676
+ .alias('check-change')
677
+ .description('Evaluate a proposed or current local file against Structural Policy V2')
678
+ .option('--artifact <path>', `Compiled artifact (default: ${STRUCTURAL_POLICY_DEFAULT_PATH})`)
679
+ .option('--content-file <path>', 'Local proposed content file; raw content is parsed locally and not retained')
680
+ .option('--operation <type>', 'create | update | delete | rename')
681
+ .option('--path-only', 'Evaluate the honest path-only host case without proposed content')
682
+ .option('--approvals <path>', 'JSON array of exact source-free approvals')
683
+ .option('--no-index', 'Do not create or refresh Repository Graph V2')
684
+ .option('--json', 'Output stable machine-readable JSON')
685
+ .action(async (path, options) => {
686
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
687
+ try {
688
+ const result = await evaluateStructuralPolicyPath({
689
+ cwd,
690
+ targetPath: path,
691
+ artifactPath: options.artifact,
692
+ contentFile: options.contentFile,
693
+ operation: options.operation,
694
+ pathOnly: options.pathOnly,
695
+ index: options.index,
696
+ approvalsFile: options.approvals,
697
+ });
698
+ const exitCode = structuralExitCode(result.evaluation.verdict);
699
+ const payload = { ok: exitCode === 0, ...result, exitCode };
700
+ if (options.json) {
701
+ console.log(JSON.stringify(payload, null, 2));
702
+ }
703
+ else {
704
+ console.log(chalk.bold('\n🛡️ Structural Policy V2 Test\n'));
705
+ console.log(chalk.dim(`Path: ${result.envelope.target.path}`));
706
+ console.log(chalk.dim(`Verdict: ${result.evaluation.verdict}`));
707
+ console.log(chalk.dim(`Truth: ${result.evaluation.truth}`));
708
+ console.log(chalk.dim(`Host timing: ${result.envelope.host.capability} / ${result.envelope.host.timing}`));
709
+ console.log(chalk.dim(`Evaluated: ${result.evaluation.evaluatedRuleIds.length}`));
710
+ console.log(chalk.dim(`Not evaluated: ${result.evaluation.notEvaluatedRuleIds.length}`));
711
+ console.log(chalk.dim(`Advisory: ${result.evidence.advisory.length} (never blocking)`));
712
+ result.evaluation.findings.forEach((item) => {
713
+ console.log(chalk.yellow(` [${item.verdict}] ${item.ruleId}: ${item.explanation}`));
714
+ console.log(chalk.dim(` ${item.remediation}`));
715
+ });
716
+ }
717
+ process.exitCode = exitCode;
718
+ }
719
+ catch (error) {
720
+ const message = error instanceof Error ? error.message : String(error);
721
+ if (options.json)
722
+ console.log(JSON.stringify({ ok: false, error: message, exitCode: 1 }, null, 2));
723
+ else
724
+ console.error(chalk.red(`\n❌ ${message}\n`));
725
+ process.exitCode = 1;
726
+ }
727
+ });
728
+ policy
729
+ .command('structural-explain <path>')
730
+ .description('Explain matched facts, deterministic rules, verdicts, and remediation for a path')
731
+ .option('--artifact <path>', `Compiled artifact (default: ${STRUCTURAL_POLICY_DEFAULT_PATH})`)
732
+ .option('--content-file <path>', 'Local proposed content file')
733
+ .option('--operation <type>', 'create | update | delete | rename')
734
+ .option('--path-only', 'Explain the path-only not-evaluated boundary')
735
+ .option('--approvals <path>', 'JSON array of exact source-free approvals')
736
+ .option('--no-index', 'Do not create or refresh Repository Graph V2')
737
+ .option('--json', 'Output stable machine-readable JSON')
738
+ .action(async (path, options) => {
739
+ const cwd = (0, project_root_1.resolveNeurcodeProjectRoot)(process.cwd());
740
+ try {
741
+ const result = await evaluateStructuralPolicyPath({
742
+ cwd,
743
+ targetPath: path,
744
+ artifactPath: options.artifact,
745
+ contentFile: options.contentFile,
746
+ operation: options.operation,
747
+ pathOnly: options.pathOnly,
748
+ index: options.index,
749
+ approvalsFile: options.approvals,
750
+ });
751
+ const exitCode = structuralExitCode(result.evaluation.verdict);
752
+ if (options.json) {
753
+ console.log(JSON.stringify({ ok: exitCode === 0, ...result, exitCode }, null, 2));
754
+ }
755
+ else {
756
+ console.log(chalk.bold(`\n🛡️ Structural Policy V2 Explain: ${result.envelope.target.path}\n`));
757
+ console.log(chalk.dim(`Classification: ${result.evaluation.truth}`));
758
+ console.log(chalk.dim(`Verdict: ${result.evaluation.verdict}`));
759
+ console.log(chalk.dim(`Graph: ${result.graphId ?? 'not available'} (${result.evaluation.graphFreshness.state})`));
760
+ if (result.evaluation.findings.length === 0) {
761
+ console.log(chalk.dim(result.evaluation.verdict === 'not_evaluated'
762
+ ? 'Required facts were unavailable; no deterministic pass is claimed.'
763
+ : 'No deterministic structural violations matched.'));
764
+ }
765
+ for (const item of result.evaluation.findings) {
766
+ console.log(chalk.yellow(`\n${item.ruleId} · ${item.family} · ${item.verdict}`));
767
+ console.log(` ${item.explanation}`);
768
+ console.log(chalk.dim(` Matched facts: ${item.matchedFacts.map((fact) => fact.factId).join(', ')}`));
769
+ console.log(chalk.dim(` Remediation: ${item.remediation}`));
770
+ }
771
+ if (result.evaluation.notEvaluatedRuleIds.length > 0) {
772
+ console.log(chalk.dim(`\nNot evaluated rules: ${result.evaluation.notEvaluatedRuleIds.join(', ')}`));
773
+ }
774
+ }
775
+ process.exitCode = exitCode;
776
+ }
777
+ catch (error) {
778
+ const message = error instanceof Error ? error.message : String(error);
779
+ if (options.json)
780
+ console.log(JSON.stringify({ ok: false, error: message, exitCode: 1 }, null, 2));
781
+ else
782
+ console.error(chalk.red(`\n❌ ${message}\n`));
783
+ process.exitCode = 1;
784
+ }
785
+ });
490
786
  policy
491
787
  .command('compile')
492
788
  .description('Compile deterministic policy constraints into a committed artifact')