@neurcode-ai/cli 0.18.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.
- package/dist/commands/brain.d.ts.map +1 -1
- package/dist/commands/brain.js +451 -0
- package/dist/commands/brain.js.map +1 -1
- package/dist/commands/policy.d.ts.map +1 -1
- package/dist/commands/policy.js +296 -0
- package/dist/commands/policy.js.map +1 -1
- package/dist/commands/runtime-adapter.d.ts +2 -1
- package/dist/commands/runtime-adapter.d.ts.map +1 -1
- package/dist/commands/runtime-adapter.js +51 -2
- package/dist/commands/runtime-adapter.js.map +1 -1
- package/dist/commands/session-hook.d.ts +13 -0
- package/dist/commands/session-hook.d.ts.map +1 -1
- package/dist/commands/session-hook.js +115 -15
- package/dist/commands/session-hook.js.map +1 -1
- package/dist/runtime-build.json +4 -4
- package/dist/utils/agent-adapter-setup.js +1 -1
- package/dist/utils/agent-adapter-setup.js.map +1 -1
- package/dist/utils/agent-guard.d.ts +1 -0
- package/dist/utils/agent-guard.d.ts.map +1 -1
- package/dist/utils/agent-guard.js +18 -6
- package/dist/utils/agent-guard.js.map +1 -1
- package/dist/utils/git-coverage.d.ts.map +1 -1
- package/dist/utils/git-coverage.js +1 -0
- package/dist/utils/git-coverage.js.map +1 -1
- package/dist/utils/local-repo-brain.d.ts +7 -0
- package/dist/utils/local-repo-brain.d.ts.map +1 -1
- package/dist/utils/local-repo-brain.js +18 -5
- package/dist/utils/local-repo-brain.js.map +1 -1
- package/dist/utils/proposed-change-analysis.d.ts +20 -0
- package/dist/utils/proposed-change-analysis.d.ts.map +1 -0
- package/dist/utils/proposed-change-analysis.js +448 -0
- package/dist/utils/proposed-change-analysis.js.map +1 -0
- package/dist/utils/repo-intelligence-v2.d.ts +28 -0
- package/dist/utils/repo-intelligence-v2.d.ts.map +1 -0
- package/dist/utils/repo-intelligence-v2.js +174 -0
- package/dist/utils/repo-intelligence-v2.js.map +1 -0
- package/dist/utils/v0-governance.d.ts +1 -1
- package/dist/utils/v0-governance.d.ts.map +1 -1
- package/dist/utils/v0-governance.js +49 -17
- package/dist/utils/v0-governance.js.map +1 -1
- package/package.json +5 -5
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../src/commands/brain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"brain.d.ts","sourceRoot":"","sources":["../../src/commands/brain.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAsfpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA46DnD"}
|
package/dist/commands/brain.js
CHANGED
|
@@ -45,6 +45,7 @@ exports.brainCommand = brainCommand;
|
|
|
45
45
|
const child_process_1 = require("child_process");
|
|
46
46
|
const fs_1 = require("fs");
|
|
47
47
|
const path_1 = require("path");
|
|
48
|
+
const brain_1 = require("@neurcode-ai/brain");
|
|
48
49
|
const brain_cache_1 = require("../utils/brain-cache");
|
|
49
50
|
const local_repo_brain_1 = require("../utils/local-repo-brain");
|
|
50
51
|
const repo_brain_impact_1 = require("../utils/repo-brain-impact");
|
|
@@ -57,6 +58,7 @@ const messages_1 = require("../utils/messages");
|
|
|
57
58
|
const brain_context_1 = require("../utils/brain-context");
|
|
58
59
|
const semantic_1 = require("../semantic");
|
|
59
60
|
const ask_cache_1 = require("../utils/ask-cache");
|
|
61
|
+
const proposed_change_analysis_1 = require("../utils/proposed-change-analysis");
|
|
60
62
|
// Import chalk with fallback
|
|
61
63
|
let chalk;
|
|
62
64
|
try {
|
|
@@ -280,6 +282,86 @@ function splitChangedPathList(value) {
|
|
|
280
282
|
.map((path) => path.trim())
|
|
281
283
|
.filter(Boolean);
|
|
282
284
|
}
|
|
285
|
+
function parseRenameList(value) {
|
|
286
|
+
return splitChangedPathList(value)
|
|
287
|
+
.map((entry) => {
|
|
288
|
+
const delimiter = entry.includes('=>') ? '=>' : ':';
|
|
289
|
+
const [from, to] = entry.split(delimiter, 2).map((item) => item.trim());
|
|
290
|
+
return from && to ? { from, to } : null;
|
|
291
|
+
})
|
|
292
|
+
.filter((entry) => Boolean(entry));
|
|
293
|
+
}
|
|
294
|
+
function repositoryGraphError(error, json) {
|
|
295
|
+
const locked = error instanceof brain_1.RepositoryGraphLockedError;
|
|
296
|
+
const payload = {
|
|
297
|
+
ok: false,
|
|
298
|
+
code: locked ? 'repository_graph_locked' : 'repository_graph_failed',
|
|
299
|
+
message: error instanceof Error ? error.message : String(error),
|
|
300
|
+
exitCode: locked ? 3 : 1,
|
|
301
|
+
};
|
|
302
|
+
if (json) {
|
|
303
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
304
|
+
}
|
|
305
|
+
else {
|
|
306
|
+
(0, messages_1.printError)('Repository Graph V2 operation failed', payload.message);
|
|
307
|
+
}
|
|
308
|
+
process.exitCode = payload.exitCode;
|
|
309
|
+
}
|
|
310
|
+
function printRepositoryGraphIndexResult(repoRoot, result, json) {
|
|
311
|
+
const payload = {
|
|
312
|
+
ok: true,
|
|
313
|
+
graphPath: (0, brain_1.repositoryGraphPath)(repoRoot),
|
|
314
|
+
schemaVersion: result.graph.schemaVersion,
|
|
315
|
+
graphId: result.graph.graphId,
|
|
316
|
+
generation: result.graph.generation,
|
|
317
|
+
freshness: result.graph.freshness,
|
|
318
|
+
coverage: result.graph.coverage,
|
|
319
|
+
stats: result.stats,
|
|
320
|
+
privacy: result.graph.privacy,
|
|
321
|
+
};
|
|
322
|
+
if (json) {
|
|
323
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
console.log(chalk.bold('\n🧠 Repository Graph V2\n'));
|
|
327
|
+
console.log(chalk.dim(`Mode: ${result.stats.mode}`));
|
|
328
|
+
console.log(chalk.dim(`Generation: ${result.graph.generation}`));
|
|
329
|
+
console.log(chalk.dim(`Freshness: ${result.graph.freshness.state}`));
|
|
330
|
+
console.log(chalk.dim(`Files indexed: ${result.graph.coverage.filesIndexed}`));
|
|
331
|
+
console.log(chalk.dim(`Files parsed: ${result.stats.filesParsed}`));
|
|
332
|
+
console.log(chalk.dim(`Files reused: ${result.stats.filesReused}`));
|
|
333
|
+
console.log(chalk.dim(`Unsupported: ${result.graph.coverage.unsupportedPercent}%`));
|
|
334
|
+
console.log(chalk.dim(`Nodes / edges: ${result.graph.nodes.length} / ${result.graph.edges.length}`));
|
|
335
|
+
console.log(chalk.dim(`Graph size: ${formatBytes(result.stats.graphBytes)}`));
|
|
336
|
+
console.log(chalk.dim(`Duration: ${result.stats.durationMs}ms`));
|
|
337
|
+
console.log(chalk.dim(`Peak RSS: ${result.stats.peakMemoryMb} MB`));
|
|
338
|
+
console.log(chalk.dim(`Artifact: ${payload.graphPath}`));
|
|
339
|
+
console.log(chalk.dim('Privacy: source-free local structural facts; raw source is not retained.'));
|
|
340
|
+
}
|
|
341
|
+
function normalizeAdvisoryTarget(repoRoot, input) {
|
|
342
|
+
const absolutePath = (0, path_1.isAbsolute)(input) ? (0, path_1.resolve)(input) : (0, path_1.resolve)(repoRoot, input);
|
|
343
|
+
const relativePath = (0, path_1.relative)(repoRoot, absolutePath).replace(/\\/g, '/');
|
|
344
|
+
if (!relativePath || relativePath === '..' || relativePath.startsWith('../')) {
|
|
345
|
+
throw new Error(`Path is outside repository root: ${input}`);
|
|
346
|
+
}
|
|
347
|
+
return { relativePath, absolutePath };
|
|
348
|
+
}
|
|
349
|
+
function advisoryCategory(value) {
|
|
350
|
+
const categories = [
|
|
351
|
+
'behavior_similarity',
|
|
352
|
+
'reuse_suggestion',
|
|
353
|
+
'duplicate_module',
|
|
354
|
+
'architecture_deviation',
|
|
355
|
+
'cross_service_consequence',
|
|
356
|
+
'reviewer_question',
|
|
357
|
+
'missing_test',
|
|
358
|
+
'ownership_review',
|
|
359
|
+
];
|
|
360
|
+
if (!categories.includes(value)) {
|
|
361
|
+
throw new Error(`Unsupported advisory category: ${value}`);
|
|
362
|
+
}
|
|
363
|
+
return value;
|
|
364
|
+
}
|
|
283
365
|
function renderBrainExportMarkdown(input) {
|
|
284
366
|
const lines = [];
|
|
285
367
|
lines.push('# Neurcode Brain Export');
|
|
@@ -595,6 +677,375 @@ function brainCommand(program) {
|
|
|
595
677
|
}
|
|
596
678
|
(0, messages_1.printInfo)('Next', 'Run: neurcode brain inspect "<area or symbol>"');
|
|
597
679
|
});
|
|
680
|
+
// -- Repository Graph V2 ---------------------------------------------------
|
|
681
|
+
brain
|
|
682
|
+
.command('repo-index')
|
|
683
|
+
.description('Create or incrementally refresh the persistent Repository Graph V2')
|
|
684
|
+
.option('--changed <paths>', 'Changed paths separated by commas, spaces, or newlines')
|
|
685
|
+
.option('--deleted <paths>', 'Deleted paths separated by commas, spaces, or newlines')
|
|
686
|
+
.option('--rename <pairs>', 'Rename pairs as old:new or old=>new, separated by commas')
|
|
687
|
+
.option('--max-files <n>', 'Maximum files to inspect', (value) => parseInt(value, 10))
|
|
688
|
+
.option('--max-total-bytes <n>', 'Maximum total bytes to inspect', (value) => parseInt(value, 10))
|
|
689
|
+
.option('--max-bytes-per-file <n>', 'Maximum bytes per file', (value) => parseInt(value, 10))
|
|
690
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
691
|
+
.action(async (options) => {
|
|
692
|
+
const scope = getBrainScope();
|
|
693
|
+
try {
|
|
694
|
+
const limits = {
|
|
695
|
+
...(Number.isFinite(options.maxFiles) ? { maxFiles: options.maxFiles } : {}),
|
|
696
|
+
...(Number.isFinite(options.maxTotalBytes) ? { maxTotalBytes: options.maxTotalBytes } : {}),
|
|
697
|
+
...(Number.isFinite(options.maxBytesPerFile) ? { maxBytesPerFile: options.maxBytesPerFile } : {}),
|
|
698
|
+
};
|
|
699
|
+
const result = await (0, brain_1.indexRepositoryGraph)({
|
|
700
|
+
repoRoot: scope.cwd,
|
|
701
|
+
changedPaths: splitChangedPathList(options.changed),
|
|
702
|
+
deletedPaths: splitChangedPathList(options.deleted),
|
|
703
|
+
renamedPaths: parseRenameList(options.rename),
|
|
704
|
+
limits,
|
|
705
|
+
});
|
|
706
|
+
printRepositoryGraphIndexResult(scope.cwd, result, options.json);
|
|
707
|
+
}
|
|
708
|
+
catch (error) {
|
|
709
|
+
repositoryGraphError(error, options.json);
|
|
710
|
+
}
|
|
711
|
+
});
|
|
712
|
+
brain
|
|
713
|
+
.command('repo-refresh')
|
|
714
|
+
.description('Refresh stale Repository Graph V2 files using content hashes')
|
|
715
|
+
.option('--changed <paths>', 'Optional changed paths separated by commas, spaces, or newlines')
|
|
716
|
+
.option('--deleted <paths>', 'Optional deleted paths separated by commas, spaces, or newlines')
|
|
717
|
+
.option('--rename <pairs>', 'Optional rename pairs as old:new or old=>new')
|
|
718
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
719
|
+
.action(async (options) => {
|
|
720
|
+
const scope = getBrainScope();
|
|
721
|
+
try {
|
|
722
|
+
const result = await (0, brain_1.indexRepositoryGraph)({
|
|
723
|
+
repoRoot: scope.cwd,
|
|
724
|
+
changedPaths: splitChangedPathList(options.changed),
|
|
725
|
+
deletedPaths: splitChangedPathList(options.deleted),
|
|
726
|
+
renamedPaths: parseRenameList(options.rename),
|
|
727
|
+
});
|
|
728
|
+
printRepositoryGraphIndexResult(scope.cwd, result, options.json);
|
|
729
|
+
}
|
|
730
|
+
catch (error) {
|
|
731
|
+
repositoryGraphError(error, options.json);
|
|
732
|
+
}
|
|
733
|
+
});
|
|
734
|
+
brain
|
|
735
|
+
.command('repo-status')
|
|
736
|
+
.description('Show Repository Graph V2 freshness, coverage, and parser depth')
|
|
737
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
738
|
+
.action(async (options) => {
|
|
739
|
+
const scope = getBrainScope();
|
|
740
|
+
try {
|
|
741
|
+
const freshness = await (0, brain_1.repositoryGraphStatus)(scope.cwd);
|
|
742
|
+
const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
|
|
743
|
+
const payload = {
|
|
744
|
+
ok: freshness.state !== 'corrupt',
|
|
745
|
+
repoRoot: scope.cwd,
|
|
746
|
+
graphPath: (0, brain_1.repositoryGraphPath)(scope.cwd),
|
|
747
|
+
freshness,
|
|
748
|
+
graph: graph ? {
|
|
749
|
+
schemaVersion: graph.schemaVersion,
|
|
750
|
+
graphId: graph.graphId,
|
|
751
|
+
generation: graph.generation,
|
|
752
|
+
updatedAt: graph.updatedAt,
|
|
753
|
+
coverage: graph.coverage,
|
|
754
|
+
nodeCount: graph.nodes.length,
|
|
755
|
+
edgeCount: graph.edges.length,
|
|
756
|
+
privacy: graph.privacy,
|
|
757
|
+
} : null,
|
|
758
|
+
};
|
|
759
|
+
if (options.json) {
|
|
760
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
761
|
+
return;
|
|
762
|
+
}
|
|
763
|
+
console.log(chalk.bold('\n🧠 Repository Graph V2 Status\n'));
|
|
764
|
+
console.log(chalk.dim(`State: ${freshness.state}`));
|
|
765
|
+
console.log(chalk.dim(`Indexed at: ${freshness.indexedAt ?? 'never'}`));
|
|
766
|
+
console.log(chalk.dim(`Stale files: ${freshness.staleFileCount}`));
|
|
767
|
+
console.log(chalk.dim(`Unsupported files: ${freshness.unsupportedFileCount}`));
|
|
768
|
+
console.log(chalk.dim(`Reason codes: ${freshness.reasonCodes.join(', ') || 'none'}`));
|
|
769
|
+
if (graph) {
|
|
770
|
+
console.log(chalk.dim(`Generation: ${graph.generation}`));
|
|
771
|
+
console.log(chalk.dim(`Nodes / edges: ${graph.nodes.length} / ${graph.edges.length}`));
|
|
772
|
+
console.log(chalk.dim(`Unsupported: ${graph.coverage.unsupportedPercent}%`));
|
|
773
|
+
for (const language of graph.coverage.languages) {
|
|
774
|
+
console.log(chalk.dim(` ${language.language}: ${language.depth}; ${language.filesAnalyzed}/${language.filesSeen} analyzed`));
|
|
775
|
+
}
|
|
776
|
+
}
|
|
777
|
+
else {
|
|
778
|
+
console.log(chalk.dim('Run `neurcode brain repo-index` to create the graph.'));
|
|
779
|
+
}
|
|
780
|
+
}
|
|
781
|
+
catch (error) {
|
|
782
|
+
repositoryGraphError(error, options.json);
|
|
783
|
+
}
|
|
784
|
+
});
|
|
785
|
+
brain
|
|
786
|
+
.command('repo-explain <query...>')
|
|
787
|
+
.description('Explain a source-free path, symbol, package, service, or surface in Repository Graph V2')
|
|
788
|
+
.option('--limit <n>', 'Maximum matching nodes (default: 25)', (value) => parseInt(value, 10))
|
|
789
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
790
|
+
.action((queryParts, options) => {
|
|
791
|
+
const scope = getBrainScope();
|
|
792
|
+
const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
|
|
793
|
+
if (!graph) {
|
|
794
|
+
repositoryGraphError(new Error('Repository Graph V2 is missing or corrupt. Run `neurcode brain repo-index`.'), options.json);
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
const query = queryParts.join(' ').trim();
|
|
798
|
+
const matches = (0, brain_1.explainRepositoryGraph)(graph, query, options.limit);
|
|
799
|
+
const matchIds = new Set(matches.map((node) => node.id));
|
|
800
|
+
const edges = graph.edges.filter((edge) => matchIds.has(edge.fromId) || matchIds.has(edge.toId));
|
|
801
|
+
const payload = { ok: true, query, matches, edges, freshness: graph.freshness };
|
|
802
|
+
if (options.json) {
|
|
803
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
804
|
+
return;
|
|
805
|
+
}
|
|
806
|
+
console.log(chalk.bold(`\n🧠 Repository Graph V2: ${query}\n`));
|
|
807
|
+
if (matches.length === 0) {
|
|
808
|
+
console.log(chalk.dim('No source-free graph facts matched.'));
|
|
809
|
+
return;
|
|
810
|
+
}
|
|
811
|
+
matches.forEach((node, index) => {
|
|
812
|
+
console.log(chalk.white(` ${index + 1}. [${node.kind}] ${node.name ?? node.key}`));
|
|
813
|
+
if (node.path)
|
|
814
|
+
console.log(chalk.dim(` path: ${node.path}`));
|
|
815
|
+
console.log(chalk.dim(` parser: ${node.provenance.parserDepth}`));
|
|
816
|
+
});
|
|
817
|
+
console.log(chalk.dim(`Related edges: ${edges.length}`));
|
|
818
|
+
});
|
|
819
|
+
brain
|
|
820
|
+
.command('repo-query')
|
|
821
|
+
.description('Query deterministic Repository Graph V2 references, dependencies, imports, calls, tests, or boundaries')
|
|
822
|
+
.option('--path <path>', 'Seed path or path fragment')
|
|
823
|
+
.option('--symbol <name>', 'Seed symbol name or fragment')
|
|
824
|
+
.option('--relationship <type>', 'Edge type such as references, depends_on, imports, calls, tests, or crosses_boundary')
|
|
825
|
+
.option('--direction <direction>', 'in | out | both', 'both')
|
|
826
|
+
.option('--limit <n>', 'Maximum nodes and edges (default: 100)', (value) => parseInt(value, 10))
|
|
827
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
828
|
+
.action((options) => {
|
|
829
|
+
const scope = getBrainScope();
|
|
830
|
+
const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
|
|
831
|
+
if (!graph) {
|
|
832
|
+
repositoryGraphError(new Error('Repository Graph V2 is missing or corrupt. Run `neurcode brain repo-index`.'), options.json);
|
|
833
|
+
return;
|
|
834
|
+
}
|
|
835
|
+
const allowedRelationships = new Set([
|
|
836
|
+
'defines', 'references', 'imports', 'exports', 'calls', 'owns',
|
|
837
|
+
'belongs_to_package', 'belongs_to_service', 'tests', 'depends_on',
|
|
838
|
+
'structurally_resembles', 'crosses_boundary',
|
|
839
|
+
]);
|
|
840
|
+
const relationship = options.relationship;
|
|
841
|
+
if (relationship && !allowedRelationships.has(relationship)) {
|
|
842
|
+
repositoryGraphError(new Error(`Unsupported relationship: ${relationship}`), options.json);
|
|
843
|
+
return;
|
|
844
|
+
}
|
|
845
|
+
const direction = options.direction === 'in' || options.direction === 'out' ? options.direction : 'both';
|
|
846
|
+
const result = (0, brain_1.queryRepositoryGraph)(graph, {
|
|
847
|
+
path: options.path,
|
|
848
|
+
symbol: options.symbol,
|
|
849
|
+
relationship,
|
|
850
|
+
direction,
|
|
851
|
+
limit: options.limit,
|
|
852
|
+
});
|
|
853
|
+
const payload = { ok: true, query: options, ...result, freshness: graph.freshness };
|
|
854
|
+
if (options.json) {
|
|
855
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
856
|
+
return;
|
|
857
|
+
}
|
|
858
|
+
console.log(chalk.bold('\n🧠 Repository Graph V2 Query\n'));
|
|
859
|
+
console.log(chalk.dim(`Seeds: ${result.seeds.length} | Nodes: ${result.nodes.length} | Edges: ${result.edges.length}`));
|
|
860
|
+
result.edges.forEach((edge) => {
|
|
861
|
+
console.log(chalk.dim(` ${edge.type}: ${edge.fromId} -> ${edge.toId}`));
|
|
862
|
+
});
|
|
863
|
+
});
|
|
864
|
+
brain
|
|
865
|
+
.command('repo-rebuild')
|
|
866
|
+
.description('Rebuild Repository Graph V2 from local repository state')
|
|
867
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
868
|
+
.action(async (options) => {
|
|
869
|
+
const scope = getBrainScope();
|
|
870
|
+
try {
|
|
871
|
+
const result = await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd, forceRebuild: true });
|
|
872
|
+
printRepositoryGraphIndexResult(scope.cwd, result, options.json);
|
|
873
|
+
}
|
|
874
|
+
catch (error) {
|
|
875
|
+
repositoryGraphError(error, options.json);
|
|
876
|
+
}
|
|
877
|
+
});
|
|
878
|
+
brain
|
|
879
|
+
.command('repo-recover')
|
|
880
|
+
.description('Recover Repository Graph V2 from its last atomic backup or rebuild when unavailable')
|
|
881
|
+
.option('--json', 'Output stable machine-readable JSON')
|
|
882
|
+
.action(async (options) => {
|
|
883
|
+
const scope = getBrainScope();
|
|
884
|
+
try {
|
|
885
|
+
const result = await (0, brain_1.recoverRepositoryGraph)(scope.cwd);
|
|
886
|
+
printRepositoryGraphIndexResult(scope.cwd, result, options.json);
|
|
887
|
+
}
|
|
888
|
+
catch (error) {
|
|
889
|
+
repositoryGraphError(error, options.json);
|
|
890
|
+
}
|
|
891
|
+
});
|
|
892
|
+
brain
|
|
893
|
+
.command('advisory <path>')
|
|
894
|
+
.description('Run local-only advisory semantic intelligence for a proposed or current file')
|
|
895
|
+
.option('--content-file <path>', 'Local proposed content file; raw content is parsed locally and not retained')
|
|
896
|
+
.option('--path-only', 'Run without proposed content and expose coverage limitations')
|
|
897
|
+
.option('--include-suppressed', 'Include findings suppressed by local feedback')
|
|
898
|
+
.option('--no-index', 'Do not create or refresh Repository Graph V2')
|
|
899
|
+
.option('--json', 'Output stable source-free JSON')
|
|
900
|
+
.action(async (path, options) => {
|
|
901
|
+
const scope = getBrainScope();
|
|
902
|
+
try {
|
|
903
|
+
let graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
|
|
904
|
+
const status = await (0, brain_1.repositoryGraphStatus)(scope.cwd);
|
|
905
|
+
if (options.index !== false && (!graph || status.state !== 'fresh')) {
|
|
906
|
+
graph = (await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd })).graph;
|
|
907
|
+
}
|
|
908
|
+
else if (graph) {
|
|
909
|
+
graph = { ...graph, freshness: status };
|
|
910
|
+
}
|
|
911
|
+
if (!graph)
|
|
912
|
+
throw new Error('Repository Graph V2 is missing. Run `neurcode brain repo-index`.');
|
|
913
|
+
const target = normalizeAdvisoryTarget(scope.cwd, path);
|
|
914
|
+
let proposedSource = null;
|
|
915
|
+
let sourceKind = 'not_available';
|
|
916
|
+
if (!options.pathOnly) {
|
|
917
|
+
const contentPath = options.contentFile
|
|
918
|
+
? ((0, path_1.isAbsolute)(options.contentFile) ? options.contentFile : (0, path_1.resolve)(scope.cwd, options.contentFile))
|
|
919
|
+
: target.absolutePath;
|
|
920
|
+
if ((0, fs_1.existsSync)(contentPath)) {
|
|
921
|
+
proposedSource = (0, fs_1.readFileSync)(contentPath, 'utf8');
|
|
922
|
+
sourceKind = options.contentFile ? 'write_content' : 'post_write_disk_read';
|
|
923
|
+
}
|
|
924
|
+
}
|
|
925
|
+
const analysis = (0, proposed_change_analysis_1.analyzeProposedChange)({
|
|
926
|
+
repoRoot: scope.cwd,
|
|
927
|
+
filePath: target.relativePath,
|
|
928
|
+
proposedSource,
|
|
929
|
+
sourceKind,
|
|
930
|
+
adapterId: 'neurcode-cli',
|
|
931
|
+
timing: sourceKind === 'post_write_disk_read' ? 'after_write' : 'before_write',
|
|
932
|
+
sessionId: null,
|
|
933
|
+
planRevision: null,
|
|
934
|
+
});
|
|
935
|
+
analysis.envelope.target.operation = (0, fs_1.existsSync)(target.absolutePath) ? 'update' : 'create';
|
|
936
|
+
const result = await (0, brain_1.runSemanticAdvisory)({
|
|
937
|
+
repoRoot: scope.cwd,
|
|
938
|
+
graph,
|
|
939
|
+
change: analysis.envelope,
|
|
940
|
+
});
|
|
941
|
+
const findings = options.includeSuppressed
|
|
942
|
+
? result.findings
|
|
943
|
+
: result.findings.filter((finding) => !finding.suppressed);
|
|
944
|
+
const payload = {
|
|
945
|
+
ok: true,
|
|
946
|
+
truth: 'advisory',
|
|
947
|
+
blocking: false,
|
|
948
|
+
cacheHit: result.cacheHit,
|
|
949
|
+
cacheKey: result.cacheKey,
|
|
950
|
+
graph: {
|
|
951
|
+
graphId: graph.graphId,
|
|
952
|
+
generation: graph.generation,
|
|
953
|
+
freshness: graph.freshness,
|
|
954
|
+
},
|
|
955
|
+
host: analysis.envelope.host,
|
|
956
|
+
findings,
|
|
957
|
+
suppressedCount: result.findings.filter((finding) => finding.suppressed).length,
|
|
958
|
+
privacy: analysis.envelope.privacy,
|
|
959
|
+
};
|
|
960
|
+
if (options.json) {
|
|
961
|
+
console.log(JSON.stringify(payload, null, 2));
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
console.log(chalk.bold('\n🧠 Advisory Semantic Intelligence V2\n'));
|
|
965
|
+
console.log(chalk.dim('Truth: advisory · Blocking: never'));
|
|
966
|
+
console.log(chalk.dim(`Graph: ${graph.graphId} generation ${graph.generation} (${graph.freshness.state})`));
|
|
967
|
+
console.log(chalk.dim(`Cache: ${result.cacheHit ? 'hit' : 'miss'}`));
|
|
968
|
+
if (findings.length === 0) {
|
|
969
|
+
console.log(chalk.dim('No unsuppressed advisory findings.'));
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
findings.forEach((finding, index) => {
|
|
973
|
+
console.log(chalk.white(` ${index + 1}. ${finding.category} · ${(finding.confidence * 100).toFixed(0)}%`));
|
|
974
|
+
console.log(chalk.dim(` ${finding.rationaleCategories.join(', ')}`));
|
|
975
|
+
console.log(chalk.dim(` ${finding.related.map((item) => item.path || item.symbol || item.hash).filter(Boolean).join(', ')}`));
|
|
976
|
+
console.log(chalk.dim(` limitations: ${finding.limitations.join(' ')}`));
|
|
977
|
+
console.log(chalk.dim(` id: ${finding.findingId}`));
|
|
978
|
+
});
|
|
979
|
+
}
|
|
980
|
+
catch (error) {
|
|
981
|
+
repositoryGraphError(error, options.json);
|
|
982
|
+
}
|
|
983
|
+
});
|
|
984
|
+
brain
|
|
985
|
+
.command('advisory-suppress <key>')
|
|
986
|
+
.description('Suppress a finding ID, category:<name>, path:<path>, or fingerprint:<hash> locally')
|
|
987
|
+
.option('--remove', 'Remove the suppression instead')
|
|
988
|
+
.option('--json', 'Output stable source-free JSON')
|
|
989
|
+
.action((key, options) => {
|
|
990
|
+
const scope = getBrainScope();
|
|
991
|
+
try {
|
|
992
|
+
const registry = new brain_1.SemanticAdvisoryRegistry(scope.cwd);
|
|
993
|
+
const status = options.remove ? registry.unsuppress(key) : registry.suppress(key);
|
|
994
|
+
if (options.json)
|
|
995
|
+
console.log(JSON.stringify({ ok: true, status }, null, 2));
|
|
996
|
+
else
|
|
997
|
+
console.log(chalk.green(`\n✅ Advisory suppression ${options.remove ? 'removed' : 'saved'}: ${key}\n`));
|
|
998
|
+
}
|
|
999
|
+
catch (error) {
|
|
1000
|
+
repositoryGraphError(error, options.json);
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
brain
|
|
1004
|
+
.command('advisory-feedback <finding-id>')
|
|
1005
|
+
.description('Record source-free advisory feedback: accepted, dismissed, duplicate, or useful')
|
|
1006
|
+
.requiredOption('--category <category>', 'Advisory category')
|
|
1007
|
+
.requiredOption('--outcome <outcome>', 'accepted | dismissed | duplicate | useful')
|
|
1008
|
+
.option('--json', 'Output stable source-free JSON')
|
|
1009
|
+
.action((findingId, options) => {
|
|
1010
|
+
const scope = getBrainScope();
|
|
1011
|
+
try {
|
|
1012
|
+
const outcomes = ['accepted', 'dismissed', 'duplicate', 'useful'];
|
|
1013
|
+
if (!outcomes.includes(options.outcome)) {
|
|
1014
|
+
throw new Error(`Unsupported advisory feedback outcome: ${options.outcome}`);
|
|
1015
|
+
}
|
|
1016
|
+
const registry = new brain_1.SemanticAdvisoryRegistry(scope.cwd);
|
|
1017
|
+
const status = registry.recordFeedback({
|
|
1018
|
+
findingId,
|
|
1019
|
+
category: advisoryCategory(options.category),
|
|
1020
|
+
outcome: options.outcome,
|
|
1021
|
+
});
|
|
1022
|
+
if (options.json)
|
|
1023
|
+
console.log(JSON.stringify({ ok: true, status }, null, 2));
|
|
1024
|
+
else
|
|
1025
|
+
console.log(chalk.green(`\n✅ Advisory feedback recorded: ${options.outcome}\n`));
|
|
1026
|
+
}
|
|
1027
|
+
catch (error) {
|
|
1028
|
+
repositoryGraphError(error, options.json);
|
|
1029
|
+
}
|
|
1030
|
+
});
|
|
1031
|
+
brain
|
|
1032
|
+
.command('advisory-status')
|
|
1033
|
+
.description('Show local advisory cache, suppressions, feedback, and privacy mode')
|
|
1034
|
+
.option('--json', 'Output stable source-free JSON')
|
|
1035
|
+
.action((options) => {
|
|
1036
|
+
const scope = getBrainScope();
|
|
1037
|
+
const status = new brain_1.SemanticAdvisoryRegistry(scope.cwd).status();
|
|
1038
|
+
if (options.json) {
|
|
1039
|
+
console.log(JSON.stringify({ ok: true, status }, null, 2));
|
|
1040
|
+
return;
|
|
1041
|
+
}
|
|
1042
|
+
console.log(chalk.bold('\n🧠 Advisory Semantic Intelligence Status\n'));
|
|
1043
|
+
console.log(chalk.dim(`Registry: ${status.path}`));
|
|
1044
|
+
console.log(chalk.dim(`Cache entries: ${status.cacheEntries}`));
|
|
1045
|
+
console.log(chalk.dim(`Suppressions: ${status.suppressions.length}`));
|
|
1046
|
+
console.log(chalk.dim(`Feedback IDs: ${Object.keys(status.feedback).length}`));
|
|
1047
|
+
console.log(chalk.dim('Privacy: local-only; no source, diff, prompt, or chat upload.'));
|
|
1048
|
+
});
|
|
598
1049
|
// -- brain inspect ----------------------------------------------------------
|
|
599
1050
|
brain
|
|
600
1051
|
.command('inspect [query...]')
|