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.
- package/CONTRIBUTING.md +19 -15
- package/README.md +203 -286
- package/docs/anci/v0.1-DRAFT.md +420 -0
- package/docs/api/README.md +99 -0
- package/docs/api/did_we_discuss_this.md +34 -0
- package/docs/api/dismiss_suggestion.md +34 -0
- package/docs/api/explain_change_in_natural_language.md +33 -0
- package/docs/api/find_consumers_of_api.md +34 -0
- package/docs/api/get_action_patterns.md +32 -0
- package/docs/api/get_active_drift.md +32 -0
- package/docs/api/get_active_suggestions.md +25 -0
- package/docs/api/get_ai_cost_attribution.md +32 -0
- package/docs/api/get_arch_events.md +42 -0
- package/docs/api/get_architectural_drift.md +37 -0
- package/docs/api/get_architecture.md +25 -0
- package/docs/api/get_blast_radius.md +34 -0
- package/docs/api/get_canonical_pattern.md +39 -0
- package/docs/api/get_change_plan.md +34 -0
- package/docs/api/get_change_velocity.md +32 -0
- package/docs/api/get_churn_vs_blast_radius.md +32 -0
- package/docs/api/get_complexity_trend.md +39 -0
- package/docs/api/get_context.md +34 -0
- package/docs/api/get_conventions.md +32 -0
- package/docs/api/get_cross_domain.md +25 -0
- package/docs/api/get_cross_language_call_graph.md +25 -0
- package/docs/api/get_cross_repo_blast_radius.md +34 -0
- package/docs/api/get_cross_team_coupling.md +25 -0
- package/docs/api/get_data_flow.md +33 -0
- package/docs/api/get_dead_code_with_confidence.md +32 -0
- package/docs/api/get_decision_log.md +32 -0
- package/docs/api/get_dependency_surface.md +25 -0
- package/docs/api/get_domain.md +34 -0
- package/docs/api/get_domain_evolution.md +39 -0
- package/docs/api/get_domain_health.md +32 -0
- package/docs/api/get_domains_list.md +25 -0
- package/docs/api/get_drift_digest.md +32 -0
- package/docs/api/get_env_vars.md +32 -0
- package/docs/api/get_evolution_delta.md +36 -0
- package/docs/api/get_file_ownership.md +33 -0
- package/docs/api/get_file_summary.md +34 -0
- package/docs/api/get_high_impact_files.md +32 -0
- package/docs/api/get_hot_in_prod_no_tests.md +34 -0
- package/docs/api/get_hotspot_files.md +37 -0
- package/docs/api/get_iac_resources.md +25 -0
- package/docs/api/get_interface_contract.md +33 -0
- package/docs/api/get_intervention_history.md +32 -0
- package/docs/api/get_invariants.md +37 -0
- package/docs/api/get_llm_enrichment.md +33 -0
- package/docs/api/get_microservice_cut_points.md +32 -0
- package/docs/api/get_microservices_migration_cut_points.md +25 -0
- package/docs/api/get_minimal_context_for_intent.md +39 -0
- package/docs/api/get_models.md +32 -0
- package/docs/api/get_neighbors.md +39 -0
- package/docs/api/get_org_architecture.md +25 -0
- package/docs/api/get_org_domain_mapping.md +25 -0
- package/docs/api/get_pending_decisions.md +32 -0
- package/docs/api/get_predictive_risk.md +32 -0
- package/docs/api/get_progressive_disclosure_tree.md +25 -0
- package/docs/api/get_recent_decisions.md +37 -0
- package/docs/api/get_risk_weighted_blast_radius.md +32 -0
- package/docs/api/get_routes.md +25 -0
- package/docs/api/get_safety_checklist.md +33 -0
- package/docs/api/get_semantic_diff.md +34 -0
- package/docs/api/get_service_boundary_violations.md +25 -0
- package/docs/api/get_service_dependency_graph.md +25 -0
- package/docs/api/get_session_context.md +32 -0
- package/docs/api/get_similar_patterns.md +39 -0
- package/docs/api/get_stale_docs.md +25 -0
- package/docs/api/get_structure.md +25 -0
- package/docs/api/get_temporal_context.md +34 -0
- package/docs/api/get_test_coverage_map.md +25 -0
- package/docs/api/get_token_budget_report.md +36 -0
- package/docs/api/get_upgrade_risk.md +25 -0
- package/docs/api/get_working_memory.md +25 -0
- package/docs/api/ingest_otlp_traces.md +34 -0
- package/docs/api/scaffold_for_intent.md +34 -0
- package/docs/api/search_routes.md +34 -0
- package/docs/api/simulate_change_impact.md +37 -0
- package/docs/api/validate_change.md +39 -0
- package/docs/api/validate_diff.md +39 -0
- package/docs/concepts/anci.md +87 -0
- package/docs/concepts/blast-radius.md +66 -0
- package/docs/concepts/domains.md +91 -0
- package/docs/concepts/import-graph.md +102 -0
- package/docs/concepts/mcp-integration.md +148 -0
- package/docs/guides/adding-feature-safely.md +101 -0
- package/docs/guides/ci-integration.md +175 -0
- package/docs/guides/monorepo-setup.md +121 -0
- package/docs/guides/onboarding-new-engineer.md +121 -0
- package/docs/guides/pre-merge-review.md +139 -0
- package/docs/migration/v1-to-v2.md +110 -0
- package/docs/quickstart.md +95 -0
- package/docs/scale.md +129 -0
- package/docs/troubleshooting.md +180 -0
- package/package.json +12 -5
- package/scripts/gen-api-docs.js +170 -0
- package/scripts/postinstall.js +391 -24
- package/src/acp/agent.js +83 -11
- package/src/acp/config.js +64 -0
- package/src/acp/persistence.js +146 -0
- package/src/acp/providers/anthropic.js +179 -27
- package/src/acp/providers/index.js +15 -2
- package/src/acp/providers/openai.js +164 -38
- package/src/acp/providers/sse.js +82 -0
- package/src/acp/safety.js +128 -0
- package/src/acp/session.js +73 -0
- package/src/adjacent/call-graph.js +170 -0
- package/src/adjacent/iac.js +167 -0
- package/src/adjacent/llm-enrich.js +35 -0
- package/src/adjacent/runtime.js +216 -0
- package/src/adjacent/semantic-diff.js +143 -0
- package/src/agents/leiden.js +4 -4
- package/src/agents/scan-structure.js +2 -2
- package/src/ai/context-builder.js +215 -0
- package/src/ai/retrieval/lexical.js +122 -0
- package/src/ai/retrieval/rrf.js +121 -0
- package/src/ai/retrieval/semantic.js +35 -0
- package/src/ai/retrieval/structural.js +82 -0
- package/src/ai/tools.js +423 -0
- package/src/anci/consumer.js +305 -0
- package/src/anci/deserialize.js +160 -0
- package/src/anci/emit.js +85 -0
- package/src/anci/serialize.js +264 -0
- package/src/anci/yaml.js +401 -0
- package/src/bitmap/index.js +1 -1
- package/src/bitmap/tools.js +2 -2
- package/src/brain/conventions/index.js +185 -0
- package/src/brain/index.js +31 -0
- package/src/brain/invariants/index.js +252 -0
- package/src/brain/procedural/index.js +181 -0
- package/src/brain/suggestions/index.js +153 -0
- package/src/brain/working/index.js +170 -0
- package/src/cli/anci.js +237 -0
- package/src/cli/check.js +47 -1
- package/src/cli/diff.js +83 -0
- package/src/cli/doctor.js +270 -0
- package/src/cli/explain.js +61 -0
- package/src/cli/index.js +115 -0
- package/src/cli/init.js +144 -6
- package/src/cli/inspect.js +1 -1
- package/src/cli/org.js +172 -0
- package/src/cli/pr-impact.js +554 -0
- package/src/cli/serve.js +1 -1
- package/src/cli/status.js +211 -0
- package/src/cli/sync.js +2 -2
- package/src/cli/temporal.js +188 -0
- package/src/cli/validate.js +201 -0
- package/src/cli/watch.js +4 -4
- package/src/cli/why.js +101 -0
- package/src/extractors/frameworks.js +236 -0
- package/src/extractors/languages/dart.js +138 -0
- package/src/extractors/languages/go.js +11 -1
- package/src/extractors/languages/javascript.js +16 -6
- package/src/extractors/languages/kotlin.js +169 -0
- package/src/extractors/languages/php.js +195 -0
- package/src/extractors/languages/python.js +12 -1
- package/src/extractors/languages/swift.js +140 -0
- package/src/extractors/languages/typescript.js +15 -2
- package/src/extractors/plugin-api.js +102 -0
- package/src/mcp/change-plan.js +8 -8
- package/src/mcp/files-without-tests.js +285 -0
- package/src/mcp/middleware/index.js +451 -0
- package/src/mcp/server.js +2292 -0
- package/src/mcp/validate.js +1 -1
- package/src/org/detect.js +262 -0
- package/src/org/queries.js +144 -0
- package/src/org/store.js +173 -0
- package/src/org/sync.js +106 -0
- package/src/predictive/cut-points.js +83 -0
- package/src/predictive/drift-digest.js +88 -0
- package/src/predictive/ownership.js +145 -0
- package/src/predictive/risk-score.js +121 -0
- package/src/predictive/validate-change.js +55 -0
- package/src/store/store-adapter.js +3 -3
- package/src/store/{sync-v2.js → sync.js} +105 -16
- package/src/temporal/backfill.js +211 -0
- package/src/temporal/delta.js +85 -0
- package/src/temporal/events.js +180 -0
- package/src/temporal/queries.js +358 -0
- package/src/temporal/snapshot.js +151 -0
- package/src/temporal/store.js +400 -0
- 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 {
|
|
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
|
-
//
|
|
16
|
-
|
|
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
|
|
57
|
-
await
|
|
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
|
|
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 {
|
package/src/cli/inspect.js
CHANGED
|
@@ -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
|
|
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 };
|