@neurcode-ai/cli 0.18.0 → 0.19.2

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 (76) hide show
  1. package/dist/commands/brain.d.ts.map +1 -1
  2. package/dist/commands/brain.js +537 -6
  3. package/dist/commands/brain.js.map +1 -1
  4. package/dist/commands/ops.d.ts +5 -0
  5. package/dist/commands/ops.d.ts.map +1 -1
  6. package/dist/commands/ops.js +32 -2
  7. package/dist/commands/ops.js.map +1 -1
  8. package/dist/commands/policy.d.ts.map +1 -1
  9. package/dist/commands/policy.js +346 -0
  10. package/dist/commands/policy.js.map +1 -1
  11. package/dist/commands/quickstart.d.ts.map +1 -1
  12. package/dist/commands/quickstart.js +11 -6
  13. package/dist/commands/quickstart.js.map +1 -1
  14. package/dist/commands/runtime-adapter.d.ts +2 -1
  15. package/dist/commands/runtime-adapter.d.ts.map +1 -1
  16. package/dist/commands/runtime-adapter.js +51 -2
  17. package/dist/commands/runtime-adapter.js.map +1 -1
  18. package/dist/commands/session-hook.d.ts +13 -0
  19. package/dist/commands/session-hook.d.ts.map +1 -1
  20. package/dist/commands/session-hook.js +115 -15
  21. package/dist/commands/session-hook.js.map +1 -1
  22. package/dist/commands/session.d.ts +5 -0
  23. package/dist/commands/session.d.ts.map +1 -1
  24. package/dist/commands/session.js +328 -53
  25. package/dist/commands/session.js.map +1 -1
  26. package/dist/commands/verify-output.d.ts +2 -0
  27. package/dist/commands/verify-output.d.ts.map +1 -1
  28. package/dist/commands/verify-output.js +4 -0
  29. package/dist/commands/verify-output.js.map +1 -1
  30. package/dist/commands/verify.d.ts.map +1 -1
  31. package/dist/commands/verify.js +108 -24
  32. package/dist/commands/verify.js.map +1 -1
  33. package/dist/governance/structural-on-diff.d.ts +11 -0
  34. package/dist/governance/structural-on-diff.d.ts.map +1 -1
  35. package/dist/governance/structural-on-diff.js +38 -5
  36. package/dist/governance/structural-on-diff.js.map +1 -1
  37. package/dist/index.js +4 -4
  38. package/dist/index.js.map +1 -1
  39. package/dist/runtime-build.json +5 -5
  40. package/dist/utils/agent-adapter-setup.js +1 -1
  41. package/dist/utils/agent-adapter-setup.js.map +1 -1
  42. package/dist/utils/agent-guard.d.ts +1 -0
  43. package/dist/utils/agent-guard.d.ts.map +1 -1
  44. package/dist/utils/agent-guard.js +18 -6
  45. package/dist/utils/agent-guard.js.map +1 -1
  46. package/dist/utils/brain-context.d.ts.map +1 -1
  47. package/dist/utils/brain-context.js +11 -2
  48. package/dist/utils/brain-context.js.map +1 -1
  49. package/dist/utils/git-coverage.d.ts.map +1 -1
  50. package/dist/utils/git-coverage.js +1 -0
  51. package/dist/utils/git-coverage.js.map +1 -1
  52. package/dist/utils/local-repo-brain.d.ts +7 -0
  53. package/dist/utils/local-repo-brain.d.ts.map +1 -1
  54. package/dist/utils/local-repo-brain.js +22 -5
  55. package/dist/utils/local-repo-brain.js.map +1 -1
  56. package/dist/utils/proposed-change-analysis.d.ts +20 -0
  57. package/dist/utils/proposed-change-analysis.d.ts.map +1 -0
  58. package/dist/utils/proposed-change-analysis.js +450 -0
  59. package/dist/utils/proposed-change-analysis.js.map +1 -0
  60. package/dist/utils/repo-intelligence-v2.d.ts +28 -0
  61. package/dist/utils/repo-intelligence-v2.d.ts.map +1 -0
  62. package/dist/utils/repo-intelligence-v2.js +215 -0
  63. package/dist/utils/repo-intelligence-v2.js.map +1 -0
  64. package/dist/utils/structural-understanding.d.ts +2 -2
  65. package/dist/utils/structural-understanding.d.ts.map +1 -1
  66. package/dist/utils/structural-understanding.js +1 -1
  67. package/dist/utils/structural-understanding.js.map +1 -1
  68. package/dist/utils/team-memory-path-hygiene.d.ts +4 -0
  69. package/dist/utils/team-memory-path-hygiene.d.ts.map +1 -0
  70. package/dist/utils/team-memory-path-hygiene.js +50 -0
  71. package/dist/utils/team-memory-path-hygiene.js.map +1 -0
  72. package/dist/utils/v0-governance.d.ts +8 -1
  73. package/dist/utils/v0-governance.d.ts.map +1 -1
  74. package/dist/utils/v0-governance.js +96 -17
  75. package/dist/utils/v0-governance.js.map +1 -1
  76. package/package.json +6 -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;AAgZpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAgiDnD"}
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;AAyfpC,wBAAgB,YAAY,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAy/DnD"}
@@ -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,8 @@ 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");
62
+ const team_memory_path_hygiene_1 = require("../utils/team-memory-path-hygiene");
60
63
  // Import chalk with fallback
61
64
  let chalk;
62
65
  try {
@@ -124,6 +127,9 @@ function scanFiles(dir, baseDir, maxFiles = 600) {
124
127
  continue;
125
128
  }
126
129
  const full = join(current, entry);
130
+ const relativePath = normalizeFsPath(relative(baseDir, full));
131
+ if (!(0, team_memory_path_hygiene_1.isTeamMemoryProjectPath)(relativePath))
132
+ continue;
127
133
  let st;
128
134
  try {
129
135
  st = statSync(full);
@@ -142,7 +148,7 @@ function scanFiles(dir, baseDir, maxFiles = 600) {
142
148
  const ext = entry.split('.').pop()?.toLowerCase();
143
149
  if (ext && ignoreExts.has(ext))
144
150
  continue;
145
- files.push(relative(baseDir, full));
151
+ files.push(relativePath);
146
152
  }
147
153
  }
148
154
  walk(dir);
@@ -226,7 +232,7 @@ function collectGitAuthorship(cwd, sinceDays) {
226
232
  if (!currentAuthor)
227
233
  continue;
228
234
  const path = normalizeFsPath(line);
229
- if (!path || path.startsWith('.git/') || path.startsWith('node_modules/'))
235
+ if (!path || path.startsWith('.git/') || path.startsWith('node_modules/') || !(0, team_memory_path_hygiene_1.isTeamMemoryProjectPath)(path))
230
236
  continue;
231
237
  authorTouches.set(currentAuthor, (authorTouches.get(currentAuthor) || 0) + 1);
232
238
  const byAuthor = fileTouches.get(path) || new Map();
@@ -280,6 +286,86 @@ function splitChangedPathList(value) {
280
286
  .map((path) => path.trim())
281
287
  .filter(Boolean);
282
288
  }
289
+ function parseRenameList(value) {
290
+ return splitChangedPathList(value)
291
+ .map((entry) => {
292
+ const delimiter = entry.includes('=>') ? '=>' : ':';
293
+ const [from, to] = entry.split(delimiter, 2).map((item) => item.trim());
294
+ return from && to ? { from, to } : null;
295
+ })
296
+ .filter((entry) => Boolean(entry));
297
+ }
298
+ function repositoryGraphError(error, json) {
299
+ const locked = error instanceof brain_1.RepositoryGraphLockedError;
300
+ const payload = {
301
+ ok: false,
302
+ code: locked ? 'repository_graph_locked' : 'repository_graph_failed',
303
+ message: error instanceof Error ? error.message : String(error),
304
+ exitCode: locked ? 3 : 1,
305
+ };
306
+ if (json) {
307
+ console.log(JSON.stringify(payload, null, 2));
308
+ }
309
+ else {
310
+ (0, messages_1.printError)('Repository Graph V2 operation failed', payload.message);
311
+ }
312
+ process.exitCode = payload.exitCode;
313
+ }
314
+ function printRepositoryGraphIndexResult(repoRoot, result, json) {
315
+ const payload = {
316
+ ok: true,
317
+ graphPath: (0, brain_1.repositoryGraphPath)(repoRoot),
318
+ schemaVersion: result.graph.schemaVersion,
319
+ graphId: result.graph.graphId,
320
+ generation: result.graph.generation,
321
+ freshness: result.graph.freshness,
322
+ coverage: result.graph.coverage,
323
+ stats: result.stats,
324
+ privacy: result.graph.privacy,
325
+ };
326
+ if (json) {
327
+ console.log(JSON.stringify(payload, null, 2));
328
+ return;
329
+ }
330
+ console.log(chalk.bold('\n🧠 Repository Graph V2\n'));
331
+ console.log(chalk.dim(`Mode: ${result.stats.mode}`));
332
+ console.log(chalk.dim(`Generation: ${result.graph.generation}`));
333
+ console.log(chalk.dim(`Freshness: ${result.graph.freshness.state}`));
334
+ console.log(chalk.dim(`Files indexed: ${result.graph.coverage.filesIndexed}`));
335
+ console.log(chalk.dim(`Files parsed: ${result.stats.filesParsed}`));
336
+ console.log(chalk.dim(`Files reused: ${result.stats.filesReused}`));
337
+ console.log(chalk.dim(`Unsupported: ${result.graph.coverage.unsupportedPercent}%`));
338
+ console.log(chalk.dim(`Nodes / edges: ${result.graph.nodes.length} / ${result.graph.edges.length}`));
339
+ console.log(chalk.dim(`Graph size: ${formatBytes(result.stats.graphBytes)}`));
340
+ console.log(chalk.dim(`Duration: ${result.stats.durationMs}ms`));
341
+ console.log(chalk.dim(`Peak RSS: ${result.stats.peakMemoryMb} MB`));
342
+ console.log(chalk.dim(`Artifact: ${payload.graphPath}`));
343
+ console.log(chalk.dim('Privacy: source-free local structural facts; raw source is not retained.'));
344
+ }
345
+ function normalizeAdvisoryTarget(repoRoot, input) {
346
+ const absolutePath = (0, path_1.isAbsolute)(input) ? (0, path_1.resolve)(input) : (0, path_1.resolve)(repoRoot, input);
347
+ const relativePath = (0, path_1.relative)(repoRoot, absolutePath).replace(/\\/g, '/');
348
+ if (!relativePath || relativePath === '..' || relativePath.startsWith('../')) {
349
+ throw new Error(`Path is outside repository root: ${input}`);
350
+ }
351
+ return { relativePath, absolutePath };
352
+ }
353
+ function advisoryCategory(value) {
354
+ const categories = [
355
+ 'behavior_similarity',
356
+ 'reuse_suggestion',
357
+ 'duplicate_module',
358
+ 'architecture_deviation',
359
+ 'cross_service_consequence',
360
+ 'reviewer_question',
361
+ 'missing_test',
362
+ 'ownership_review',
363
+ ];
364
+ if (!categories.includes(value)) {
365
+ throw new Error(`Unsupported advisory category: ${value}`);
366
+ }
367
+ return value;
368
+ }
283
369
  function renderBrainExportMarkdown(input) {
284
370
  const lines = [];
285
371
  lines.push('# Neurcode Brain Export');
@@ -539,6 +625,13 @@ function brainCommand(program) {
539
625
  .option('--json', 'Output as JSON')
540
626
  .action(async (options) => {
541
627
  const scope = getBrainScope();
628
+ const canonical = await (0, brain_1.indexRepositoryGraph)({
629
+ repoRoot: scope.cwd,
630
+ limits: {
631
+ ...(Number.isFinite(options.maxFiles) ? { maxFiles: options.maxFiles } : {}),
632
+ ...(Number.isFinite(options.maxBytesPerFile) ? { maxBytesPerFile: options.maxBytesPerFile } : {}),
633
+ },
634
+ });
542
635
  const artifact = (0, local_repo_brain_1.buildLocalRepoBrain)(scope.cwd, {
543
636
  maxFiles: options.maxFiles,
544
637
  maxBytesPerFile: options.maxBytesPerFile,
@@ -547,6 +640,24 @@ function brainCommand(program) {
547
640
  const paths = (0, local_repo_brain_1.writeLocalRepoBrain)(scope.cwd, artifact);
548
641
  const payload = {
549
642
  repoRoot: scope.cwd,
643
+ repositoryIntelligenceModel: {
644
+ canonicalLifecycle: 'repository_graph_v2',
645
+ surface: 'legacy_brain_compatibility_projection',
646
+ compatibilityOnly: true,
647
+ },
648
+ canonicalGraph: {
649
+ path: (0, brain_1.repositoryGraphPath)(scope.cwd),
650
+ graphId: canonical.graph.graphId,
651
+ schemaVersion: canonical.graph.schemaVersion,
652
+ freshness: canonical.graph.freshness,
653
+ coverage: canonical.graph.coverage,
654
+ nodeCount: canonical.graph.nodes.length,
655
+ edgeCount: canonical.graph.edges.length,
656
+ },
657
+ compatibilityProjection: {
658
+ artifactHash: artifact.artifactHash,
659
+ summary: artifact.summary,
660
+ },
550
661
  jsonPath: paths.jsonPath,
551
662
  markdownPath: paths.markdownPath,
552
663
  artifactHash: artifact.artifactHash,
@@ -559,8 +670,14 @@ function brainCommand(program) {
559
670
  console.log(JSON.stringify(payload, null, 2));
560
671
  return;
561
672
  }
562
- await (0, messages_1.printSuccessBanner)('Local Repo Brain Indexed');
673
+ await (0, messages_1.printSuccessBanner)('Canonical Repository Intelligence Indexed');
674
+ (0, messages_1.printSection)('Canonical lifecycle', '🧭');
675
+ console.log(chalk.dim(`Repository Graph: ${payload.canonicalGraph.graphId}`));
676
+ console.log(chalk.dim(`Posture: ${canonical.graph.freshness.posture ?? canonical.graph.freshness.state}`));
677
+ console.log(chalk.dim(`Analyzed/skipped: ${canonical.graph.coverage.filesAnalyzed}/${canonical.graph.coverage.filesSkipped}`));
678
+ console.log(chalk.dim(`Recovery: neurcode brain repo-recover`));
563
679
  (0, messages_1.printSection)('Artifact', '🧠');
680
+ console.log(chalk.yellow('Legacy Brain is a compatibility projection; its counts are not canonical lifecycle health.'));
564
681
  console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
565
682
  console.log(chalk.dim(`JSON: ${paths.jsonPath}`));
566
683
  console.log(chalk.dim(`Summary: ${paths.markdownPath}`));
@@ -595,6 +712,379 @@ function brainCommand(program) {
595
712
  }
596
713
  (0, messages_1.printInfo)('Next', 'Run: neurcode brain inspect "<area or symbol>"');
597
714
  });
715
+ // -- Repository Graph V2 ---------------------------------------------------
716
+ brain
717
+ .command('repo-index')
718
+ .description('Create or incrementally refresh the persistent Repository Graph V2')
719
+ .option('--changed <paths>', 'Changed paths separated by commas, spaces, or newlines')
720
+ .option('--deleted <paths>', 'Deleted paths separated by commas, spaces, or newlines')
721
+ .option('--rename <pairs>', 'Rename pairs as old:new or old=>new, separated by commas')
722
+ .option('--max-files <n>', 'Maximum files to inspect', (value) => parseInt(value, 10))
723
+ .option('--max-total-bytes <n>', 'Maximum total bytes to inspect', (value) => parseInt(value, 10))
724
+ .option('--max-bytes-per-file <n>', 'Maximum bytes per file', (value) => parseInt(value, 10))
725
+ .option('--json', 'Output stable machine-readable JSON')
726
+ .action(async (options) => {
727
+ const scope = getBrainScope();
728
+ try {
729
+ const limits = {
730
+ ...(Number.isFinite(options.maxFiles) ? { maxFiles: options.maxFiles } : {}),
731
+ ...(Number.isFinite(options.maxTotalBytes) ? { maxTotalBytes: options.maxTotalBytes } : {}),
732
+ ...(Number.isFinite(options.maxBytesPerFile) ? { maxBytesPerFile: options.maxBytesPerFile } : {}),
733
+ };
734
+ const result = await (0, brain_1.indexRepositoryGraph)({
735
+ repoRoot: scope.cwd,
736
+ changedPaths: splitChangedPathList(options.changed),
737
+ deletedPaths: splitChangedPathList(options.deleted),
738
+ renamedPaths: parseRenameList(options.rename),
739
+ limits,
740
+ });
741
+ printRepositoryGraphIndexResult(scope.cwd, result, options.json);
742
+ }
743
+ catch (error) {
744
+ repositoryGraphError(error, options.json);
745
+ }
746
+ });
747
+ brain
748
+ .command('repo-refresh')
749
+ .description('Refresh stale Repository Graph V2 files using content hashes')
750
+ .option('--changed <paths>', 'Optional changed paths separated by commas, spaces, or newlines')
751
+ .option('--deleted <paths>', 'Optional deleted paths separated by commas, spaces, or newlines')
752
+ .option('--rename <pairs>', 'Optional rename pairs as old:new or old=>new')
753
+ .option('--json', 'Output stable machine-readable JSON')
754
+ .action(async (options) => {
755
+ const scope = getBrainScope();
756
+ try {
757
+ const result = await (0, brain_1.indexRepositoryGraph)({
758
+ repoRoot: scope.cwd,
759
+ changedPaths: splitChangedPathList(options.changed),
760
+ deletedPaths: splitChangedPathList(options.deleted),
761
+ renamedPaths: parseRenameList(options.rename),
762
+ });
763
+ printRepositoryGraphIndexResult(scope.cwd, result, options.json);
764
+ }
765
+ catch (error) {
766
+ repositoryGraphError(error, options.json);
767
+ }
768
+ });
769
+ brain
770
+ .command('repo-status')
771
+ .description('Show Repository Graph V2 freshness, coverage, and parser depth')
772
+ .option('--json', 'Output stable machine-readable JSON')
773
+ .action(async (options) => {
774
+ const scope = getBrainScope();
775
+ try {
776
+ const freshness = await (0, brain_1.repositoryGraphStatus)(scope.cwd);
777
+ const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
778
+ const payload = {
779
+ ok: freshness.state !== 'corrupt',
780
+ repoRoot: scope.cwd,
781
+ graphPath: (0, brain_1.repositoryGraphPath)(scope.cwd),
782
+ freshness,
783
+ graph: graph ? {
784
+ schemaVersion: graph.schemaVersion,
785
+ graphId: graph.graphId,
786
+ generation: graph.generation,
787
+ updatedAt: graph.updatedAt,
788
+ coverage: graph.coverage,
789
+ nodeCount: graph.nodes.length,
790
+ edgeCount: graph.edges.length,
791
+ privacy: graph.privacy,
792
+ } : null,
793
+ };
794
+ if (options.json) {
795
+ console.log(JSON.stringify(payload, null, 2));
796
+ return;
797
+ }
798
+ console.log(chalk.bold('\n🧠 Repository Graph V2 Status\n'));
799
+ console.log(chalk.dim(`State: ${freshness.state}`));
800
+ console.log(chalk.dim(`Indexed at: ${freshness.indexedAt ?? 'never'}`));
801
+ console.log(chalk.dim(`Stale files: ${freshness.staleFileCount}`));
802
+ console.log(chalk.dim(`Unsupported files: ${freshness.unsupportedFileCount}`));
803
+ console.log(chalk.dim(`Reason codes: ${freshness.reasonCodes.join(', ') || 'none'}`));
804
+ if (graph) {
805
+ console.log(chalk.dim(`Generation: ${graph.generation}`));
806
+ console.log(chalk.dim(`Nodes / edges: ${graph.nodes.length} / ${graph.edges.length}`));
807
+ console.log(chalk.dim(`Unsupported: ${graph.coverage.unsupportedPercent}%`));
808
+ for (const language of graph.coverage.languages) {
809
+ console.log(chalk.dim(` ${language.language}: ${language.depth}; ${language.filesAnalyzed}/${language.filesSeen} analyzed`));
810
+ }
811
+ }
812
+ else {
813
+ console.log(chalk.dim('Run `neurcode brain repo-index` to create the graph.'));
814
+ }
815
+ }
816
+ catch (error) {
817
+ repositoryGraphError(error, options.json);
818
+ }
819
+ });
820
+ brain
821
+ .command('repo-explain <query...>')
822
+ .description('Explain a source-free path, symbol, package, service, or surface in Repository Graph V2')
823
+ .option('--limit <n>', 'Maximum matching nodes (default: 25)', (value) => parseInt(value, 10))
824
+ .option('--json', 'Output stable machine-readable JSON')
825
+ .action((queryParts, options) => {
826
+ const scope = getBrainScope();
827
+ const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
828
+ if (!graph) {
829
+ repositoryGraphError(new Error('Repository Graph V2 is missing or corrupt. Run `neurcode brain repo-index`.'), options.json);
830
+ return;
831
+ }
832
+ const query = queryParts.join(' ').trim();
833
+ const matches = (0, brain_1.explainRepositoryGraph)(graph, query, options.limit);
834
+ const matchIds = new Set(matches.map((node) => node.id));
835
+ const edges = graph.edges.filter((edge) => matchIds.has(edge.fromId) || matchIds.has(edge.toId));
836
+ const payload = { ok: true, query, matches, edges, freshness: graph.freshness };
837
+ if (options.json) {
838
+ console.log(JSON.stringify(payload, null, 2));
839
+ return;
840
+ }
841
+ console.log(chalk.bold(`\n🧠 Repository Graph V2: ${query}\n`));
842
+ if (matches.length === 0) {
843
+ console.log(chalk.dim('No source-free graph facts matched.'));
844
+ return;
845
+ }
846
+ matches.forEach((node, index) => {
847
+ console.log(chalk.white(` ${index + 1}. [${node.kind}] ${node.name ?? node.key}`));
848
+ if (node.path)
849
+ console.log(chalk.dim(` path: ${node.path}`));
850
+ console.log(chalk.dim(` parser: ${node.provenance.parserDepth}`));
851
+ });
852
+ console.log(chalk.dim(`Related edges: ${edges.length}`));
853
+ });
854
+ brain
855
+ .command('repo-query')
856
+ .description('Query deterministic Repository Graph V2 references, dependencies, imports, calls, tests, or boundaries')
857
+ .option('--path <path>', 'Seed path or path fragment')
858
+ .option('--symbol <name>', 'Seed symbol name or fragment')
859
+ .option('--relationship <type>', 'Edge type such as references, depends_on, imports, calls, tests, or crosses_boundary')
860
+ .option('--direction <direction>', 'in | out | both', 'both')
861
+ .option('--limit <n>', 'Maximum nodes and edges (default: 100)', (value) => parseInt(value, 10))
862
+ .option('--json', 'Output stable machine-readable JSON')
863
+ .action((options) => {
864
+ const scope = getBrainScope();
865
+ const graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
866
+ if (!graph) {
867
+ repositoryGraphError(new Error('Repository Graph V2 is missing or corrupt. Run `neurcode brain repo-index`.'), options.json);
868
+ return;
869
+ }
870
+ const allowedRelationships = new Set([
871
+ 'defines', 'references', 'imports', 'exports', 'calls', 'owns',
872
+ 'belongs_to_package', 'belongs_to_service', 'tests', 'depends_on',
873
+ 'structurally_resembles', 'crosses_boundary',
874
+ ]);
875
+ const relationship = options.relationship;
876
+ if (relationship && !allowedRelationships.has(relationship)) {
877
+ repositoryGraphError(new Error(`Unsupported relationship: ${relationship}`), options.json);
878
+ return;
879
+ }
880
+ const direction = options.direction === 'in' || options.direction === 'out' ? options.direction : 'both';
881
+ const result = (0, brain_1.queryRepositoryGraph)(graph, {
882
+ path: options.path,
883
+ symbol: options.symbol,
884
+ relationship,
885
+ direction,
886
+ limit: options.limit,
887
+ });
888
+ const payload = { ok: true, query: options, ...result, freshness: graph.freshness };
889
+ if (options.json) {
890
+ console.log(JSON.stringify(payload, null, 2));
891
+ return;
892
+ }
893
+ console.log(chalk.bold('\n🧠 Repository Graph V2 Query\n'));
894
+ console.log(chalk.dim(`Total matches: ${result.totalMatches} | Returned: ${result.returnedMatches} | Limit: ${result.limit} | Truncated: ${result.truncated}`));
895
+ console.log(chalk.dim(`Seeds: ${result.seeds.length} | Nodes: ${result.nodes.length} | Edges: ${result.edges.length}`));
896
+ result.edges.forEach((edge) => {
897
+ const posture = edge.enforcementEligible === false
898
+ ? 'advisory/non-enforcement'
899
+ : 'enforcement-eligible';
900
+ console.log(chalk.dim(` ${edge.type}: ${edge.fromId} -> ${edge.toId} [${posture}]`));
901
+ });
902
+ });
903
+ brain
904
+ .command('repo-rebuild')
905
+ .description('Rebuild Repository Graph V2 from local repository state')
906
+ .option('--json', 'Output stable machine-readable JSON')
907
+ .action(async (options) => {
908
+ const scope = getBrainScope();
909
+ try {
910
+ const result = await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd, forceRebuild: true });
911
+ printRepositoryGraphIndexResult(scope.cwd, result, options.json);
912
+ }
913
+ catch (error) {
914
+ repositoryGraphError(error, options.json);
915
+ }
916
+ });
917
+ brain
918
+ .command('repo-recover')
919
+ .description('Recover Repository Graph V2 from its last atomic backup or rebuild when unavailable')
920
+ .option('--json', 'Output stable machine-readable JSON')
921
+ .action(async (options) => {
922
+ const scope = getBrainScope();
923
+ try {
924
+ const result = await (0, brain_1.recoverRepositoryGraph)(scope.cwd);
925
+ printRepositoryGraphIndexResult(scope.cwd, result, options.json);
926
+ }
927
+ catch (error) {
928
+ repositoryGraphError(error, options.json);
929
+ }
930
+ });
931
+ brain
932
+ .command('advisory <path>')
933
+ .description('Run local-only advisory semantic intelligence for a proposed or current file')
934
+ .option('--content-file <path>', 'Local proposed content file; raw content is parsed locally and not retained')
935
+ .option('--path-only', 'Run without proposed content and expose coverage limitations')
936
+ .option('--include-suppressed', 'Include findings suppressed by local feedback')
937
+ .option('--no-index', 'Do not create or refresh Repository Graph V2')
938
+ .option('--json', 'Output stable source-free JSON')
939
+ .action(async (path, options) => {
940
+ const scope = getBrainScope();
941
+ try {
942
+ let graph = (0, brain_1.readRepositoryGraph)(scope.cwd);
943
+ const status = await (0, brain_1.repositoryGraphStatus)(scope.cwd);
944
+ if (options.index !== false && (!graph || status.state !== 'fresh')) {
945
+ graph = (await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd })).graph;
946
+ }
947
+ else if (graph) {
948
+ graph = { ...graph, freshness: status };
949
+ }
950
+ if (!graph)
951
+ throw new Error('Repository Graph V2 is missing. Run `neurcode brain repo-index`.');
952
+ const target = normalizeAdvisoryTarget(scope.cwd, path);
953
+ let proposedSource = null;
954
+ let sourceKind = 'not_available';
955
+ if (!options.pathOnly) {
956
+ const contentPath = options.contentFile
957
+ ? ((0, path_1.isAbsolute)(options.contentFile) ? options.contentFile : (0, path_1.resolve)(scope.cwd, options.contentFile))
958
+ : target.absolutePath;
959
+ if ((0, fs_1.existsSync)(contentPath)) {
960
+ proposedSource = (0, fs_1.readFileSync)(contentPath, 'utf8');
961
+ sourceKind = options.contentFile ? 'write_content' : 'post_write_disk_read';
962
+ }
963
+ }
964
+ const analysis = (0, proposed_change_analysis_1.analyzeProposedChange)({
965
+ repoRoot: scope.cwd,
966
+ filePath: target.relativePath,
967
+ proposedSource,
968
+ sourceKind,
969
+ adapterId: 'neurcode-cli',
970
+ timing: sourceKind === 'post_write_disk_read' ? 'after_write' : 'before_write',
971
+ sessionId: null,
972
+ planRevision: null,
973
+ });
974
+ analysis.envelope.target.operation = (0, fs_1.existsSync)(target.absolutePath) ? 'update' : 'create';
975
+ const result = await (0, brain_1.runSemanticAdvisory)({
976
+ repoRoot: scope.cwd,
977
+ graph,
978
+ change: analysis.envelope,
979
+ });
980
+ const findings = options.includeSuppressed
981
+ ? result.findings
982
+ : result.findings.filter((finding) => !finding.suppressed);
983
+ const payload = {
984
+ ok: true,
985
+ truth: 'advisory',
986
+ blocking: false,
987
+ cacheHit: result.cacheHit,
988
+ cacheKey: result.cacheKey,
989
+ graph: {
990
+ graphId: graph.graphId,
991
+ generation: graph.generation,
992
+ freshness: graph.freshness,
993
+ },
994
+ host: analysis.envelope.host,
995
+ findings,
996
+ suppressedCount: result.findings.filter((finding) => finding.suppressed).length,
997
+ privacy: analysis.envelope.privacy,
998
+ };
999
+ if (options.json) {
1000
+ console.log(JSON.stringify(payload, null, 2));
1001
+ return;
1002
+ }
1003
+ console.log(chalk.bold('\n🧠 Advisory Semantic Intelligence V2\n'));
1004
+ console.log(chalk.dim('Truth: advisory Ā· Blocking: never'));
1005
+ console.log(chalk.dim(`Graph: ${graph.graphId} generation ${graph.generation} (${graph.freshness.state})`));
1006
+ console.log(chalk.dim(`Cache: ${result.cacheHit ? 'hit' : 'miss'}`));
1007
+ if (findings.length === 0) {
1008
+ console.log(chalk.dim('No unsuppressed advisory findings.'));
1009
+ return;
1010
+ }
1011
+ findings.forEach((finding, index) => {
1012
+ console.log(chalk.white(` ${index + 1}. ${finding.category} Ā· ${(finding.confidence * 100).toFixed(0)}%`));
1013
+ console.log(chalk.dim(` ${finding.rationaleCategories.join(', ')}`));
1014
+ console.log(chalk.dim(` ${finding.related.map((item) => item.path || item.symbol || item.hash).filter(Boolean).join(', ')}`));
1015
+ console.log(chalk.dim(` limitations: ${finding.limitations.join(' ')}`));
1016
+ console.log(chalk.dim(` id: ${finding.findingId}`));
1017
+ });
1018
+ }
1019
+ catch (error) {
1020
+ repositoryGraphError(error, options.json);
1021
+ }
1022
+ });
1023
+ brain
1024
+ .command('advisory-suppress <key>')
1025
+ .description('Suppress a finding ID, category:<name>, path:<path>, or fingerprint:<hash> locally')
1026
+ .option('--remove', 'Remove the suppression instead')
1027
+ .option('--json', 'Output stable source-free JSON')
1028
+ .action((key, options) => {
1029
+ const scope = getBrainScope();
1030
+ try {
1031
+ const registry = new brain_1.SemanticAdvisoryRegistry(scope.cwd);
1032
+ const status = options.remove ? registry.unsuppress(key) : registry.suppress(key);
1033
+ if (options.json)
1034
+ console.log(JSON.stringify({ ok: true, status }, null, 2));
1035
+ else
1036
+ console.log(chalk.green(`\nāœ… Advisory suppression ${options.remove ? 'removed' : 'saved'}: ${key}\n`));
1037
+ }
1038
+ catch (error) {
1039
+ repositoryGraphError(error, options.json);
1040
+ }
1041
+ });
1042
+ brain
1043
+ .command('advisory-feedback <finding-id>')
1044
+ .description('Record source-free advisory feedback: accepted, dismissed, duplicate, or useful')
1045
+ .requiredOption('--category <category>', 'Advisory category')
1046
+ .requiredOption('--outcome <outcome>', 'accepted | dismissed | duplicate | useful')
1047
+ .option('--json', 'Output stable source-free JSON')
1048
+ .action((findingId, options) => {
1049
+ const scope = getBrainScope();
1050
+ try {
1051
+ const outcomes = ['accepted', 'dismissed', 'duplicate', 'useful'];
1052
+ if (!outcomes.includes(options.outcome)) {
1053
+ throw new Error(`Unsupported advisory feedback outcome: ${options.outcome}`);
1054
+ }
1055
+ const registry = new brain_1.SemanticAdvisoryRegistry(scope.cwd);
1056
+ const status = registry.recordFeedback({
1057
+ findingId,
1058
+ category: advisoryCategory(options.category),
1059
+ outcome: options.outcome,
1060
+ });
1061
+ if (options.json)
1062
+ console.log(JSON.stringify({ ok: true, status }, null, 2));
1063
+ else
1064
+ console.log(chalk.green(`\nāœ… Advisory feedback recorded: ${options.outcome}\n`));
1065
+ }
1066
+ catch (error) {
1067
+ repositoryGraphError(error, options.json);
1068
+ }
1069
+ });
1070
+ brain
1071
+ .command('advisory-status')
1072
+ .description('Show local advisory cache, suppressions, feedback, and privacy mode')
1073
+ .option('--json', 'Output stable source-free JSON')
1074
+ .action((options) => {
1075
+ const scope = getBrainScope();
1076
+ const status = new brain_1.SemanticAdvisoryRegistry(scope.cwd).status();
1077
+ if (options.json) {
1078
+ console.log(JSON.stringify({ ok: true, status }, null, 2));
1079
+ return;
1080
+ }
1081
+ console.log(chalk.bold('\n🧠 Advisory Semantic Intelligence Status\n'));
1082
+ console.log(chalk.dim(`Registry: ${status.path}`));
1083
+ console.log(chalk.dim(`Cache entries: ${status.cacheEntries}`));
1084
+ console.log(chalk.dim(`Suppressions: ${status.suppressions.length}`));
1085
+ console.log(chalk.dim(`Feedback IDs: ${Object.keys(status.feedback).length}`));
1086
+ console.log(chalk.dim('Privacy: local-only; no source, diff, prompt, or chat upload.'));
1087
+ });
598
1088
  // -- brain inspect ----------------------------------------------------------
599
1089
  brain
600
1090
  .command('inspect [query...]')
@@ -608,6 +1098,11 @@ function brainCommand(program) {
608
1098
  const limit = Number.isFinite(options.limit) ? Math.max(1, Math.min(50, options.limit)) : 12;
609
1099
  let artifact = options.rebuild ? null : (0, local_repo_brain_1.readLocalRepoBrain)(scope.cwd);
610
1100
  let rebuilt = false;
1101
+ let canonicalGraph = (0, brain_1.readRepositoryGraph)(scope.cwd);
1102
+ if (!canonicalGraph || options.rebuild) {
1103
+ canonicalGraph = (await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd, forceRebuild: options.rebuild === true })).graph;
1104
+ }
1105
+ const canonicalFreshness = await (0, brain_1.repositoryGraphStatus)(scope.cwd);
611
1106
  if (!artifact) {
612
1107
  artifact = (0, local_repo_brain_1.buildLocalRepoBrain)(scope.cwd, { experimentalFingerprintReuse: options.experimentalFingerprintReuse });
613
1108
  (0, local_repo_brain_1.writeLocalRepoBrain)(scope.cwd, artifact);
@@ -617,6 +1112,17 @@ function brainCommand(program) {
617
1112
  const results = query ? (0, local_repo_brain_1.searchLocalRepoBrain)(artifact, query, limit) : [];
618
1113
  const payload = {
619
1114
  repoRoot: scope.cwd,
1115
+ repositoryIntelligenceModel: {
1116
+ canonicalLifecycle: 'repository_graph_v2',
1117
+ surface: 'legacy_brain_compatibility_projection',
1118
+ compatibilityOnly: true,
1119
+ },
1120
+ canonicalGraph: {
1121
+ graphId: canonicalGraph.graphId,
1122
+ schemaVersion: canonicalGraph.schemaVersion,
1123
+ freshness: canonicalFreshness,
1124
+ coverage: canonicalGraph.coverage,
1125
+ },
620
1126
  jsonPath: (0, local_repo_brain_1.localRepoBrainPath)(scope.cwd),
621
1127
  markdownPath: (0, local_repo_brain_1.localRepoBrainMarkdownPath)(scope.cwd),
622
1128
  rebuilt,
@@ -638,6 +1144,7 @@ function brainCommand(program) {
638
1144
  console.log(chalk.dim(`Repo Root: ${scope.cwd}`));
639
1145
  console.log(chalk.dim(`Artifact: ${(0, local_repo_brain_1.localRepoBrainPath)(scope.cwd)}`));
640
1146
  console.log(chalk.dim(`Artifact Hash: ${artifact.artifactHash}`));
1147
+ console.log(chalk.yellow('Compatibility projection: use `brain repo-status` for canonical freshness and completeness.'));
641
1148
  if (rebuilt) {
642
1149
  (0, messages_1.printInfo)('Brain index created', 'No existing local repo brain was found, so Neurcode built one first.');
643
1150
  }
@@ -714,13 +1221,34 @@ function brainCommand(program) {
714
1221
  process.exit(1);
715
1222
  }
716
1223
  if (options.rebuild) {
1224
+ await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd, forceRebuild: true });
717
1225
  const rebuilt = (0, local_repo_brain_1.buildLocalRepoBrain)(scope.cwd);
718
1226
  (0, local_repo_brain_1.writeLocalRepoBrain)(scope.cwd, rebuilt);
719
1227
  }
1228
+ else if (options.index !== false && !(0, brain_1.readRepositoryGraph)(scope.cwd)) {
1229
+ await (0, brain_1.indexRepositoryGraph)({ repoRoot: scope.cwd });
1230
+ }
720
1231
  const report = (0, repo_brain_impact_1.buildRepoBrainImpactForRepo)(scope.cwd, paths, { autoBuild: options.index !== false });
721
1232
  const summary = (0, repo_brain_impact_1.summarizeImpact)(report);
1233
+ const canonicalGraph = (0, brain_1.readRepositoryGraph)(scope.cwd);
1234
+ const canonicalFreshness = canonicalGraph ? await (0, brain_1.repositoryGraphStatus)(scope.cwd) : null;
722
1235
  if (options.json) {
723
- console.log(JSON.stringify({ repoRoot: scope.cwd, report, summary }, null, 2));
1236
+ console.log(JSON.stringify({
1237
+ repoRoot: scope.cwd,
1238
+ repositoryIntelligenceModel: {
1239
+ canonicalLifecycle: 'repository_graph_v2',
1240
+ surface: 'legacy_impact_compatibility_projection',
1241
+ compatibilityOnly: true,
1242
+ },
1243
+ canonicalGraph: canonicalGraph ? {
1244
+ graphId: canonicalGraph.graphId,
1245
+ schemaVersion: canonicalGraph.schemaVersion,
1246
+ freshness: canonicalFreshness,
1247
+ coverage: canonicalGraph.coverage,
1248
+ } : null,
1249
+ report,
1250
+ summary,
1251
+ }, null, 2));
724
1252
  return;
725
1253
  }
726
1254
  if (options.summary) {
@@ -747,6 +1275,7 @@ function brainCommand(program) {
747
1275
  (0, messages_1.printInfo)('Brain index created', 'No existing local repo brain was found, so Neurcode built one first.');
748
1276
  }
749
1277
  console.log(chalk.dim(`Repo Root: ${scope.cwd}\n`));
1278
+ console.log(chalk.yellow('Impact is a compatibility projection; Repository Graph V2 owns canonical freshness/completeness.'));
750
1279
  console.log((0, repo_brain_impact_1.renderRepoBrainImpactText)(report));
751
1280
  (0, messages_1.printInfo)('Labels', 'Deterministic = compiled path/CODEOWNERS/import-graph facts. Advisory = heuristic reuse/proximity/reviewer guidance.');
752
1281
  });
@@ -1111,7 +1640,7 @@ function brainCommand(program) {
1111
1640
  updatedAt: entry.updatedAt,
1112
1641
  lastSeenAt: entry.lastSeenAt,
1113
1642
  }))
1114
- .filter((entry) => Boolean(entry.path));
1643
+ .filter((entry) => Boolean(entry.path) && (0, team_memory_path_hygiene_1.isTeamMemoryProjectPath)(entry.path));
1115
1644
  const scopedFiles = scopedFilesRaw.length > 0
1116
1645
  ? scopedFilesRaw
1117
1646
  : scanFiles(scope.cwd, scope.cwd, 300).map((path) => ({
@@ -1161,7 +1690,9 @@ function brainCommand(program) {
1161
1690
  const effectiveAuthorTouches = focusAuthorTouches.size > 0 ? focusAuthorTouches : authorTouches;
1162
1691
  const topContributors = rankTopEntries(effectiveAuthorTouches.entries(), ([, count]) => count, limit)
1163
1692
  .map(([author, touches]) => ({ author, touches }));
1164
- const events = Array.isArray(scopeStore.events) ? scopeStore.events : [];
1693
+ const events = Array.isArray(scopeStore.events)
1694
+ ? scopeStore.events.filter((event) => !event.filePath || (0, team_memory_path_hygiene_1.isTeamMemoryProjectPath)(event.filePath))
1695
+ : [];
1165
1696
  const recentDecisions = [...events]
1166
1697
  .sort((a, b) => sortIsoDesc(a.timestamp, b.timestamp))
1167
1698
  .slice(0, Math.max(limit, 12))