carto-md 2.0.8 → 2.1.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 (182) hide show
  1. package/CONTRIBUTING.md +19 -15
  2. package/README.md +203 -286
  3. package/docs/anci/v0.1-DRAFT.md +420 -0
  4. package/docs/api/README.md +99 -0
  5. package/docs/api/did_we_discuss_this.md +34 -0
  6. package/docs/api/dismiss_suggestion.md +34 -0
  7. package/docs/api/explain_change_in_natural_language.md +33 -0
  8. package/docs/api/find_consumers_of_api.md +34 -0
  9. package/docs/api/get_action_patterns.md +32 -0
  10. package/docs/api/get_active_drift.md +32 -0
  11. package/docs/api/get_active_suggestions.md +25 -0
  12. package/docs/api/get_ai_cost_attribution.md +32 -0
  13. package/docs/api/get_arch_events.md +42 -0
  14. package/docs/api/get_architectural_drift.md +37 -0
  15. package/docs/api/get_architecture.md +25 -0
  16. package/docs/api/get_blast_radius.md +34 -0
  17. package/docs/api/get_canonical_pattern.md +39 -0
  18. package/docs/api/get_change_plan.md +34 -0
  19. package/docs/api/get_change_velocity.md +32 -0
  20. package/docs/api/get_churn_vs_blast_radius.md +32 -0
  21. package/docs/api/get_complexity_trend.md +39 -0
  22. package/docs/api/get_context.md +34 -0
  23. package/docs/api/get_conventions.md +32 -0
  24. package/docs/api/get_cross_domain.md +25 -0
  25. package/docs/api/get_cross_language_call_graph.md +25 -0
  26. package/docs/api/get_cross_repo_blast_radius.md +34 -0
  27. package/docs/api/get_cross_team_coupling.md +25 -0
  28. package/docs/api/get_data_flow.md +33 -0
  29. package/docs/api/get_dead_code_with_confidence.md +32 -0
  30. package/docs/api/get_decision_log.md +32 -0
  31. package/docs/api/get_dependency_surface.md +25 -0
  32. package/docs/api/get_domain.md +34 -0
  33. package/docs/api/get_domain_evolution.md +39 -0
  34. package/docs/api/get_domain_health.md +32 -0
  35. package/docs/api/get_domains_list.md +25 -0
  36. package/docs/api/get_drift_digest.md +32 -0
  37. package/docs/api/get_env_vars.md +32 -0
  38. package/docs/api/get_evolution_delta.md +36 -0
  39. package/docs/api/get_file_ownership.md +33 -0
  40. package/docs/api/get_file_summary.md +34 -0
  41. package/docs/api/get_high_impact_files.md +32 -0
  42. package/docs/api/get_hot_in_prod_no_tests.md +34 -0
  43. package/docs/api/get_hotspot_files.md +37 -0
  44. package/docs/api/get_iac_resources.md +25 -0
  45. package/docs/api/get_interface_contract.md +33 -0
  46. package/docs/api/get_intervention_history.md +32 -0
  47. package/docs/api/get_invariants.md +37 -0
  48. package/docs/api/get_llm_enrichment.md +33 -0
  49. package/docs/api/get_microservice_cut_points.md +32 -0
  50. package/docs/api/get_microservices_migration_cut_points.md +25 -0
  51. package/docs/api/get_minimal_context_for_intent.md +39 -0
  52. package/docs/api/get_models.md +32 -0
  53. package/docs/api/get_neighbors.md +39 -0
  54. package/docs/api/get_org_architecture.md +25 -0
  55. package/docs/api/get_org_domain_mapping.md +25 -0
  56. package/docs/api/get_pending_decisions.md +32 -0
  57. package/docs/api/get_predictive_risk.md +32 -0
  58. package/docs/api/get_progressive_disclosure_tree.md +25 -0
  59. package/docs/api/get_recent_decisions.md +37 -0
  60. package/docs/api/get_risk_weighted_blast_radius.md +32 -0
  61. package/docs/api/get_routes.md +25 -0
  62. package/docs/api/get_safety_checklist.md +33 -0
  63. package/docs/api/get_semantic_diff.md +34 -0
  64. package/docs/api/get_service_boundary_violations.md +25 -0
  65. package/docs/api/get_service_dependency_graph.md +25 -0
  66. package/docs/api/get_session_context.md +32 -0
  67. package/docs/api/get_similar_patterns.md +39 -0
  68. package/docs/api/get_stale_docs.md +25 -0
  69. package/docs/api/get_structure.md +25 -0
  70. package/docs/api/get_temporal_context.md +34 -0
  71. package/docs/api/get_test_coverage_map.md +25 -0
  72. package/docs/api/get_token_budget_report.md +36 -0
  73. package/docs/api/get_upgrade_risk.md +25 -0
  74. package/docs/api/get_working_memory.md +25 -0
  75. package/docs/api/ingest_otlp_traces.md +34 -0
  76. package/docs/api/scaffold_for_intent.md +34 -0
  77. package/docs/api/search_routes.md +34 -0
  78. package/docs/api/simulate_change_impact.md +37 -0
  79. package/docs/api/validate_change.md +39 -0
  80. package/docs/api/validate_diff.md +39 -0
  81. package/docs/concepts/anci.md +87 -0
  82. package/docs/concepts/blast-radius.md +66 -0
  83. package/docs/concepts/domains.md +91 -0
  84. package/docs/concepts/import-graph.md +102 -0
  85. package/docs/concepts/mcp-integration.md +148 -0
  86. package/docs/guides/adding-feature-safely.md +101 -0
  87. package/docs/guides/ci-integration.md +175 -0
  88. package/docs/guides/monorepo-setup.md +121 -0
  89. package/docs/guides/onboarding-new-engineer.md +121 -0
  90. package/docs/guides/pre-merge-review.md +139 -0
  91. package/docs/migration/v1-to-v2.md +110 -0
  92. package/docs/quickstart.md +95 -0
  93. package/docs/scale.md +129 -0
  94. package/docs/troubleshooting.md +180 -0
  95. package/package.json +12 -5
  96. package/scripts/gen-api-docs.js +170 -0
  97. package/scripts/postinstall.js +391 -24
  98. package/src/acp/agent.js +83 -11
  99. package/src/acp/config.js +64 -0
  100. package/src/acp/persistence.js +146 -0
  101. package/src/acp/providers/anthropic.js +179 -27
  102. package/src/acp/providers/index.js +15 -2
  103. package/src/acp/providers/openai.js +164 -38
  104. package/src/acp/providers/sse.js +82 -0
  105. package/src/acp/safety.js +128 -0
  106. package/src/acp/session.js +73 -0
  107. package/src/adjacent/call-graph.js +170 -0
  108. package/src/adjacent/iac.js +167 -0
  109. package/src/adjacent/llm-enrich.js +35 -0
  110. package/src/adjacent/runtime.js +216 -0
  111. package/src/adjacent/semantic-diff.js +143 -0
  112. package/src/agents/leiden.js +4 -4
  113. package/src/agents/scan-structure.js +2 -2
  114. package/src/ai/context-builder.js +215 -0
  115. package/src/ai/retrieval/lexical.js +122 -0
  116. package/src/ai/retrieval/rrf.js +121 -0
  117. package/src/ai/retrieval/semantic.js +35 -0
  118. package/src/ai/retrieval/structural.js +82 -0
  119. package/src/ai/tools.js +423 -0
  120. package/src/anci/consumer.js +305 -0
  121. package/src/anci/deserialize.js +160 -0
  122. package/src/anci/emit.js +85 -0
  123. package/src/anci/serialize.js +264 -0
  124. package/src/anci/yaml.js +401 -0
  125. package/src/bitmap/index.js +1 -1
  126. package/src/bitmap/tools.js +2 -2
  127. package/src/brain/conventions/index.js +185 -0
  128. package/src/brain/index.js +31 -0
  129. package/src/brain/invariants/index.js +252 -0
  130. package/src/brain/procedural/index.js +181 -0
  131. package/src/brain/suggestions/index.js +153 -0
  132. package/src/brain/working/index.js +170 -0
  133. package/src/cli/anci.js +237 -0
  134. package/src/cli/check.js +47 -1
  135. package/src/cli/diff.js +83 -0
  136. package/src/cli/doctor.js +270 -0
  137. package/src/cli/explain.js +61 -0
  138. package/src/cli/index.js +115 -0
  139. package/src/cli/init.js +144 -6
  140. package/src/cli/inspect.js +1 -1
  141. package/src/cli/org.js +172 -0
  142. package/src/cli/pr-impact.js +554 -0
  143. package/src/cli/serve.js +1 -1
  144. package/src/cli/status.js +211 -0
  145. package/src/cli/sync.js +2 -2
  146. package/src/cli/temporal.js +188 -0
  147. package/src/cli/validate.js +201 -0
  148. package/src/cli/watch.js +4 -4
  149. package/src/cli/why.js +101 -0
  150. package/src/extractors/frameworks.js +236 -0
  151. package/src/extractors/languages/dart.js +138 -0
  152. package/src/extractors/languages/go.js +11 -1
  153. package/src/extractors/languages/javascript.js +16 -6
  154. package/src/extractors/languages/kotlin.js +169 -0
  155. package/src/extractors/languages/php.js +195 -0
  156. package/src/extractors/languages/python.js +12 -1
  157. package/src/extractors/languages/swift.js +140 -0
  158. package/src/extractors/languages/typescript.js +15 -2
  159. package/src/extractors/plugin-api.js +102 -0
  160. package/src/mcp/change-plan.js +8 -8
  161. package/src/mcp/files-without-tests.js +285 -0
  162. package/src/mcp/middleware/index.js +451 -0
  163. package/src/mcp/server.js +2292 -0
  164. package/src/mcp/validate.js +1 -1
  165. package/src/org/detect.js +262 -0
  166. package/src/org/queries.js +144 -0
  167. package/src/org/store.js +173 -0
  168. package/src/org/sync.js +106 -0
  169. package/src/predictive/cut-points.js +83 -0
  170. package/src/predictive/drift-digest.js +88 -0
  171. package/src/predictive/ownership.js +145 -0
  172. package/src/predictive/risk-score.js +121 -0
  173. package/src/predictive/validate-change.js +55 -0
  174. package/src/store/store-adapter.js +3 -3
  175. package/src/store/{sync-v2.js → sync.js} +105 -16
  176. package/src/temporal/backfill.js +211 -0
  177. package/src/temporal/delta.js +85 -0
  178. package/src/temporal/events.js +180 -0
  179. package/src/temporal/queries.js +358 -0
  180. package/src/temporal/snapshot.js +151 -0
  181. package/src/temporal/store.js +400 -0
  182. package/src/mcp/server-v2.js +0 -986
package/src/cli/init.js CHANGED
@@ -2,18 +2,151 @@ const fs = require('fs');
2
2
  const path = require('path');
3
3
  const { detectFramework } = require('../detector/framework');
4
4
  const { parseCartoIgnore } = require('../security/ignore');
5
- const { runSyncV2, discoverFiles: discoverFilesV2 } = require('../store/sync-v2');
5
+ const { runSync, discoverFiles: discoverFilesV2 } = require('../store/sync');
6
6
  const { checkForUpdate } = require('./update-check');
7
7
 
8
+ const REQUIRED_NODE_MAJOR = 18;
9
+ const LARGE_REPO_THRESHOLD = 100_000;
10
+
11
+ /**
12
+ * Pre-flight checks that block or warn before we touch the filesystem.
13
+ * Returns true if init should proceed, false if it should bail.
14
+ */
15
+ function preflightChecks(projectRoot) {
16
+ // Node version — postinstall blocks too, but a defensive double-check
17
+ // costs nothing and gives the user a clearer message than a crash 30
18
+ // lines later.
19
+ const nodeMajor = parseInt(process.versions.node.split('.')[0], 10);
20
+ if (nodeMajor < REQUIRED_NODE_MAJOR) {
21
+ console.error(
22
+ `[CARTO] Node ${process.versions.node} is too old. ` +
23
+ `carto-md requires Node ≥ v${REQUIRED_NODE_MAJOR}. ` +
24
+ `Upgrade at https://nodejs.org/ and re-run.`,
25
+ );
26
+ return false;
27
+ }
28
+
29
+ // Already-initialized notice — we don't refuse to re-init, just call
30
+ // it out so the user knows what's happening.
31
+ const cartoDir = path.join(projectRoot, '.carto');
32
+ const dbPath = path.join(cartoDir, 'carto.db');
33
+ if (fs.existsSync(dbPath)) {
34
+ console.log('[CARTO] Re-indexing existing project (run `carto remove` first to start fresh).');
35
+ }
36
+
37
+ // Submodules warning — `discoverFiles` may or may not descend into
38
+ // them depending on .cartoignore. We surface so the user can choose.
39
+ const gitmodules = path.join(projectRoot, '.gitmodules');
40
+ if (fs.existsSync(gitmodules)) {
41
+ try {
42
+ const text = fs.readFileSync(gitmodules, 'utf8');
43
+ const count = (text.match(/\[submodule /g) || []).length;
44
+ if (count > 0) {
45
+ console.log(`[CARTO] Detected ${count} git submodule${count === 1 ? '' : 's'}. ` +
46
+ `Add their paths to .cartoignore if you don't want them indexed.`);
47
+ }
48
+ } catch { /* ignore */ }
49
+ }
50
+
51
+ // No .git? Hooks won't fire — git-based freshness mechanism is gone.
52
+ // Lazy MCP re-parse still handles staleness, but operators should know.
53
+ if (!fs.existsSync(path.join(projectRoot, '.git'))) {
54
+ console.log('[CARTO] No .git directory — skipping git-hooks install. The index will refresh on `carto sync` and on MCP queries.');
55
+ }
56
+
57
+ return true;
58
+ }
59
+
60
+ /**
61
+ * Render a small box with the headline numbers + one surprising fact
62
+ * after the first sync completes. The "mirror moment" — Carto reflects
63
+ * something the developer didn't already know.
64
+ */
65
+ function printSummaryBox(projectRoot) {
66
+ try {
67
+ const { SQLiteStore } = require('../store/sqlite-store');
68
+ const store = new SQLiteStore(projectRoot);
69
+ store.open({ readonly: true });
70
+ const structure = store.getStructure();
71
+ const domains = store.getDomainsList() || [];
72
+ const highImpact = store.getHighImpactFiles(1) || [];
73
+ const crossDomain = store.getCrossDomainDeps() || [];
74
+ store.close();
75
+
76
+ const totalFiles = (structure.meta && structure.meta.totalFiles) || 0;
77
+ const totalRoutes = (structure.meta && structure.meta.totalRoutes) || 0;
78
+ const totalImports = (structure.meta && structure.meta.totalImportEdges) || 0;
79
+ if (totalFiles === 0) return; // empty repo — skip the box
80
+
81
+ const topDomains = domains.slice().sort((a, b) => b.fileCount - a.fileCount).slice(0, 4);
82
+ const lines = [];
83
+ lines.push('');
84
+ lines.push('┌─ Carto · indexed ─────────────────────────────────────────');
85
+ lines.push(`│ ${totalFiles} file${totalFiles === 1 ? '' : 's'} · ${domains.length} domain${domains.length === 1 ? '' : 's'} · ${totalRoutes} route${totalRoutes === 1 ? '' : 's'} · ${totalImports} import edge${totalImports === 1 ? '' : 's'}`);
86
+ if (topDomains.length > 0) {
87
+ lines.push('│');
88
+ lines.push('│ Top domains:');
89
+ for (const d of topDomains) {
90
+ lines.push(`│ ${d.name.padEnd(12)} (${d.fileCount} file${d.fileCount === 1 ? '' : 's'})`);
91
+ }
92
+ }
93
+ // The "mirror moment" — facts the user couldn't have known.
94
+ if (highImpact.length > 0) {
95
+ const top = highImpact[0];
96
+ const filePath = top.path || top.file || top;
97
+ const deps = top.centrality || top.dependents || top.dependentCount;
98
+ if (deps && deps > 0) {
99
+ lines.push('│');
100
+ lines.push(`│ 💡 Highest-risk file: ${filePath}`);
101
+ const verb = deps === 1 ? 'file depends' : 'files depend';
102
+ lines.push(`│ (${deps} ${verb} on it — try \`carto why ${filePath}\`)`);
103
+ }
104
+ }
105
+ if (crossDomain.length > 0) {
106
+ lines.push('│');
107
+ lines.push(`│ ⚠️ ${crossDomain.length} cross-domain edge${crossDomain.length === 1 ? '' : 's'} — run \`carto check\` for details.`);
108
+ }
109
+ lines.push('└───────────────────────────────────────────────────────────');
110
+ lines.push('');
111
+ console.log(lines.join('\n'));
112
+ } catch (err) {
113
+ // Box is decorative — never let it fail the init flow.
114
+ if (process.env.CARTO_DEBUG) console.error(`[CARTO] (debug) summary box failed: ${err.message}`);
115
+ }
116
+ }
117
+
8
118
  async function run(projectRoot) {
9
119
  checkForUpdate(); // fire and forget
120
+ if (!preflightChecks(projectRoot)) return;
10
121
  console.log('[CARTO] Detecting project...');
11
122
 
12
123
  const detection = detectFramework(projectRoot);
13
124
  console.log(`[CARTO] Detected: ${detection.framework} (${detection.language})`);
14
125
 
15
- // V2: discover all files without cap
16
- const relFiles = discoverFilesV2(projectRoot);
126
+ // Discover every file with no cap. The single-line counter refreshes
127
+ // every 500 files so users on big repos don't stare at a frozen
128
+ // "Detecting..." prompt for seconds.
129
+ const isTty = !!(process.stdout.isTTY && process.stdout.clearLine);
130
+ let lastTickAt = 0;
131
+ const onProgress = (count) => {
132
+ if (!isTty) return;
133
+ const now = Date.now();
134
+ if (now - lastTickAt < 50) return; // throttle to ~20 Hz
135
+ lastTickAt = now;
136
+ process.stdout.write(`\r[CARTO] Discovering files: ${count.toLocaleString()}`);
137
+ };
138
+ const relFiles = discoverFilesV2(projectRoot, { onProgress });
139
+ if (isTty) process.stdout.write(`\r[CARTO] Discovered ${relFiles.length.toLocaleString()} source files.\n`);
140
+
141
+ // Large-repo ETA — at >100K files, indexing takes minutes, not
142
+ // seconds. Surface that so the user doesn't ctrl-C thinking we hung.
143
+ if (relFiles.length > LARGE_REPO_THRESHOLD) {
144
+ // Empirical: ~1ms / file for first-run on a recent laptop. Round
145
+ // up generously — better to over-estimate than have the user wait
146
+ // longer than promised.
147
+ const etaSec = Math.ceil(relFiles.length / 1000);
148
+ console.log(`[CARTO] Large repo (${relFiles.length.toLocaleString()} files). Estimated index time: ~${etaSec}s. Sit tight.`);
149
+ }
17
150
 
18
151
  const pyCount = relFiles.filter(f => f.endsWith('.py')).length;
19
152
  const jsCount = relFiles.filter(f => /\.(js|ts|jsx|tsx|mjs|cjs)$/.test(f)).length;
@@ -53,8 +186,8 @@ async function run(projectRoot) {
53
186
  // Install git hooks (silent freshness on git events)
54
187
  installGitHooks(projectRoot);
55
188
 
56
- // Run first sync V2 SQLite-backed indexer.
57
- await runSyncV2({
189
+ // Run the first sync against the SQLite-backed indexer.
190
+ await runSync({
58
191
  projectRoot,
59
192
  output: path.resolve(projectRoot, config.output || 'AGENTS.md')
60
193
  });
@@ -63,6 +196,11 @@ async function run(projectRoot) {
63
196
  wireIDEs(projectRoot);
64
197
 
65
198
  console.log('[CARTO] AGENTS.md generated. Index stays fresh via git hooks + lazy MCP re-parse.');
199
+
200
+ // The "mirror moment" — print the headline numbers + a surprising
201
+ // fact (highest-risk file, cross-domain count). Decorative, never
202
+ // throws, gated on a valid index.
203
+ printSummaryBox(projectRoot);
66
204
  }
67
205
 
68
206
  /**
@@ -357,7 +495,7 @@ function wireIDEs(projectRoot) {
357
495
  // ─── VS Code Copilot ───────────────────────────────────────────────
358
496
  // Detection: `code` binary on PATH (most reliable; user-profile path
359
497
  // varies too much across macOS/Linux/Windows + Insiders/stable to be
360
- // a good detection signal). Schema is different from MCP-spec default:
498
+ // a good detection signal). Schema differs from the standard MCP shape:
361
499
  // VS Code uses `servers` (not `mcpServers`) and requires `type: stdio`.
362
500
  if (binaryExists('code')) {
363
501
  try {
@@ -113,7 +113,7 @@ function collect(projectRoot) {
113
113
  try {
114
114
  const structure = store.getStructure();
115
115
  const domainsList = store.getDomainsList();
116
- // Defensive: getExtractionErrorCount throws on pre-Spec-11 schemas
116
+ // Defensive: getExtractionErrorCount throws on older schemas
117
117
  // (no `extraction_errors` table). Inspect must work on every DB
118
118
  // we've ever shipped — fall back to 0 on schema mismatch.
119
119
  let errCount = 0;
package/src/cli/org.js ADDED
@@ -0,0 +1,172 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * `carto org` CLI commands.
5
+ *
6
+ * Subcommands:
7
+ * carto org init [--name NAME] — register the current cwd
8
+ * carto org add <name> <root-path> — register a repo
9
+ * carto org remove <name> — drop a repo
10
+ * carto org list — list registered repos
11
+ * carto org sync [--npm scope...] [--pypi prefix...] [--go prefix...] [--maven prefix...]
12
+ * — scan all repos, detect edges
13
+ * carto org overview — print summary
14
+ * carto org consumers <target> — who imports this target
15
+ */
16
+
17
+ const path = require('path');
18
+ const fs = require('fs');
19
+ const { OrgStore, defaultOrgDbDir } = require('../org/store');
20
+ const { orgSync } = require('../org/sync');
21
+ const queries = require('../org/queries');
22
+
23
+ function parseFlags(args) {
24
+ const out = { _: [] };
25
+ for (let i = 0; i < args.length; i++) {
26
+ const a = args[i];
27
+ if (a.startsWith('--')) {
28
+ const key = a.slice(2);
29
+ const next = args[i + 1];
30
+ // Multi-value flags collect into arrays.
31
+ if (next && !next.startsWith('--')) {
32
+ if (!out[key]) out[key] = next;
33
+ else if (Array.isArray(out[key])) out[key].push(next);
34
+ else out[key] = [out[key], next];
35
+ i++;
36
+ } else {
37
+ out[key] = true;
38
+ }
39
+ } else {
40
+ out._.push(a);
41
+ }
42
+ }
43
+ return out;
44
+ }
45
+
46
+ function help() {
47
+ console.log(`carto org <subcommand>
48
+
49
+ Subcommands:
50
+ init [--name NAME] Register cwd as a repo (name = basename if omitted)
51
+ add <name> <root-path> Register a repo
52
+ remove <name> Drop a repo
53
+ list List registered repos
54
+ sync [--npm @scope] [--pypi prefix] [--go prefix] [--maven prefix]
55
+ Scan + detect cross-repo edges
56
+ overview Summary: repos + edge counts
57
+ consumers <target> Who depends on this target (npm name, etc.)
58
+ `);
59
+ }
60
+
61
+ async function main(args) {
62
+ const sub = args[0];
63
+ if (!sub || sub === '--help' || sub === '-h') { help(); return; }
64
+ const flags = parseFlags(args.slice(1));
65
+ const orgDir = flags['org-dir'] || defaultOrgDbDir();
66
+
67
+ if (sub === 'init') {
68
+ const root = process.cwd();
69
+ const name = flags.name || path.basename(root);
70
+ const cartoDb = path.join(root, '.carto', 'carto.db');
71
+ const store = new OrgStore(orgDir).open();
72
+ try {
73
+ store.addRepo({ name, rootPath: root, cartoDbPath: fs.existsSync(cartoDb) ? cartoDb : null });
74
+ console.log(`[CARTO] org init: registered "${name}" at ${root}`);
75
+ } finally { store.close(); }
76
+ return;
77
+ }
78
+
79
+ if (sub === 'add') {
80
+ const name = flags._[0];
81
+ const rootPath = flags._[1] ? path.resolve(flags._[1]) : null;
82
+ if (!name || !rootPath) { console.error('Usage: carto org add <name> <root-path>'); process.exit(2); }
83
+ if (!fs.existsSync(rootPath)) { console.error(`[CARTO] root path not found: ${rootPath}`); process.exit(2); }
84
+ const store = new OrgStore(orgDir).open();
85
+ try {
86
+ const cartoDb = path.join(rootPath, '.carto', 'carto.db');
87
+ store.addRepo({ name, rootPath, cartoDbPath: fs.existsSync(cartoDb) ? cartoDb : null });
88
+ console.log(`[CARTO] org add: "${name}" → ${rootPath}`);
89
+ } finally { store.close(); }
90
+ return;
91
+ }
92
+
93
+ if (sub === 'remove') {
94
+ const name = flags._[0];
95
+ if (!name) { console.error('Usage: carto org remove <name>'); process.exit(2); }
96
+ const store = new OrgStore(orgDir).open();
97
+ try { store.removeRepo(name); console.log(`[CARTO] org remove: "${name}"`); }
98
+ finally { store.close(); }
99
+ return;
100
+ }
101
+
102
+ if (sub === 'list') {
103
+ const store = OrgStore.openIfExists(orgDir);
104
+ if (!store) { console.log('[CARTO] no org store yet. Run `carto org init`.'); return; }
105
+ try {
106
+ const repos = store.listRepos();
107
+ if (repos.length === 0) { console.log('[CARTO] no repos registered.'); return; }
108
+ console.log('| Name | Root | Last sync |');
109
+ console.log('|------|------|-----------|');
110
+ for (const r of repos) {
111
+ const ls = r.last_sync_at ? new Date(r.last_sync_at).toISOString() : '—';
112
+ console.log(`| ${r.name} | ${r.root_path} | ${ls} |`);
113
+ }
114
+ } finally { store.close(); }
115
+ return;
116
+ }
117
+
118
+ if (sub === 'sync') {
119
+ const scopes = {
120
+ npm: toArr(flags.npm),
121
+ pypi: toArr(flags.pypi),
122
+ go: toArr(flags.go),
123
+ maven: toArr(flags.maven),
124
+ };
125
+ const r = orgSync({ orgDir, scopes });
126
+ console.log(`[CARTO] org sync: ${r.repos} repo(s), ${r.edges_inserted} edge(s) inserted.`);
127
+ for (const [name, n] of Object.entries(r.edges_by_repo || {})) console.log(` - ${name}: ${n} edges`);
128
+ return;
129
+ }
130
+
131
+ if (sub === 'overview') {
132
+ const store = OrgStore.openIfExists(orgDir);
133
+ if (!store) { console.log('[CARTO] no org store yet.'); return; }
134
+ try {
135
+ const o = queries.orgArchitectureOverview(store);
136
+ console.log(`# Org Architecture Overview\n`);
137
+ console.log(`Repos: ${o.summary.total_repos}`);
138
+ console.log(`Edges: ${o.summary.total_edges}`);
139
+ console.log(`\nBy kind:`);
140
+ for (const e of o.summary.edges_by_kind) console.log(` ${e.edge_kind}: ${e.c}`);
141
+ } finally { store.close(); }
142
+ return;
143
+ }
144
+
145
+ if (sub === 'consumers') {
146
+ const target = flags._[0];
147
+ if (!target) { console.error('Usage: carto org consumers <target>'); process.exit(2); }
148
+ const store = OrgStore.openIfExists(orgDir);
149
+ if (!store) { console.log('[CARTO] no org store yet.'); return; }
150
+ try {
151
+ const rows = queries.findConsumersOfApi(store, target);
152
+ console.log(`# Consumers of ${target}\n`);
153
+ if (rows.length === 0) { console.log('_No consumers found._'); return; }
154
+ console.log('| From repo | Kind | File |');
155
+ console.log('|-----------|------|------|');
156
+ for (const r of rows.slice(0, 50)) console.log(`| ${r.from_repo} | ${r.edge_kind} | ${r.from_file || '—'} |`);
157
+ } finally { store.close(); }
158
+ return;
159
+ }
160
+
161
+ console.error(`Unknown subcommand: ${sub}`);
162
+ help();
163
+ process.exit(2);
164
+ }
165
+
166
+ function toArr(v) {
167
+ if (!v) return [];
168
+ if (Array.isArray(v)) return v;
169
+ return [v];
170
+ }
171
+
172
+ module.exports = { main };