carto-md 2.1.0 → 2.1.1
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/CHANGELOG.md +135 -0
- package/CONTRIBUTING.md +11 -8
- package/README.md +165 -213
- package/docs/anci/v0.1-DRAFT.md +132 -2
- package/docs/api/README.md +27 -2
- package/docs/api/did_we_discuss_this.md +2 -0
- package/docs/api/dismiss_gap.md +39 -0
- package/docs/api/find_consumers_of_api.md +2 -0
- package/docs/api/get_action_patterns.md +2 -0
- package/docs/api/get_arch_events.md +2 -0
- package/docs/api/get_architectural_drift.md +2 -0
- package/docs/api/get_blast_radius.md +2 -0
- package/docs/api/get_canonical_pattern.md +2 -0
- package/docs/api/get_change_velocity.md +2 -0
- package/docs/api/get_churn_vs_blast_radius.md +2 -0
- package/docs/api/get_complexity_trend.md +2 -0
- package/docs/api/get_conventions.md +2 -0
- package/docs/api/get_cross_repo_blast_radius.md +2 -0
- package/docs/api/get_data_flow.md +2 -0
- package/docs/api/get_decision_log.md +2 -0
- package/docs/api/get_domain_evolution.md +2 -0
- package/docs/api/get_domain_health.md +2 -0
- package/docs/api/get_file_receipts.md +34 -0
- package/docs/api/get_gaps.md +52 -0
- package/docs/api/get_hotspot_files.md +2 -0
- package/docs/api/get_intent.md +25 -0
- package/docs/api/get_intervention_history.md +2 -0
- package/docs/api/get_invariants.md +2 -0
- package/docs/api/get_microservices_migration_cut_points.md +2 -0
- package/docs/api/get_neighbors.md +2 -0
- package/docs/api/get_org_architecture.md +2 -0
- package/docs/api/get_org_domain_mapping.md +2 -0
- package/docs/api/get_pending_decisions.md +2 -0
- package/docs/api/get_recent_decisions.md +2 -0
- package/docs/api/get_service_boundary_violations.md +2 -0
- package/docs/api/get_service_dependency_graph.md +2 -0
- package/docs/api/get_session_context.md +2 -0
- package/docs/api/get_temporal_context.md +2 -0
- package/docs/api/history.md +78 -0
- package/docs/api/impact.md +56 -0
- package/docs/api/memory.md +70 -0
- package/docs/api/org.md +51 -0
- package/docs/api/patterns.md +63 -0
- package/docs/api/set_intent.md +45 -0
- package/docs/api/simulate_change_impact.md +2 -0
- package/docs/concepts/domains.md +75 -50
- package/docs/concepts/mcp-integration.md +4 -2
- package/docs/guides/pre-merge-review.md +2 -2
- package/docs/migration/v1-to-v2.md +2 -2
- package/docs/screenshots/carto-supabase-blast-radius.png +0 -0
- package/package.json +2 -1
- package/scripts/gen-api-docs.js +51 -19
- package/src/agents/domains.js +72 -24
- package/src/ai/tools.js +4 -2
- package/src/anci/consumer.js +83 -3
- package/src/anci/emit.js +18 -1
- package/src/anci/git-meta.js +171 -0
- package/src/anci/pack.js +288 -0
- package/src/anci/serialize.js +77 -6
- package/src/anci/staleness.js +161 -0
- package/src/brain/conventions/index.js +134 -20
- package/src/brain/invariants/index.js +76 -22
- package/src/cli/anci.js +27 -1
- package/src/cli/doctor.js +69 -0
- package/src/cli/export.js +128 -0
- package/src/cli/index.js +19 -1
- package/src/cli/init.js +84 -1
- package/src/cli/load.js +125 -0
- package/src/extractors/imports.js +35 -10
- package/src/extractors/languages/javascript.js +8 -4
- package/src/extractors/languages/prisma.js +1 -1
- package/src/extractors/languages/typescript.js +13 -49
- package/src/extractors/schemas.js +60 -0
- package/src/extractors/tree-sitter-parser.js +39 -0
- package/src/mcp/change-plan.js +131 -12
- package/src/mcp/impact.js +119 -0
- package/src/mcp/server.js +708 -82
- package/src/rules/concepts/auth-middleware.md +111 -0
- package/src/rules/concepts/money-as-float.md +80 -0
- package/src/rules/engine.js +178 -0
- package/src/rules/intent.js +178 -0
- package/src/rules/registry.js +24 -0
- package/src/rules/rules/auth-missing-on-mutating-route.js +255 -0
- package/src/rules/rules/money-as-float.js +166 -0
- package/src/store/config-loader.js +77 -3
- package/src/store/sqlite-store.js +248 -7
- package/src/store/sync.js +69 -39
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# `history`
|
|
2
|
+
|
|
3
|
+
Temporal/architectural history: domain drift (default), domain evolution, hotspots, arch events, a file's timeline, change velocity, complexity trend, churn-vs-blast, domain health. Requires `carto temporal init`.
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"view": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": [
|
|
14
|
+
"drift",
|
|
15
|
+
"evolution",
|
|
16
|
+
"hotspots",
|
|
17
|
+
"events",
|
|
18
|
+
"file",
|
|
19
|
+
"velocity",
|
|
20
|
+
"complexity",
|
|
21
|
+
"churn",
|
|
22
|
+
"health"
|
|
23
|
+
],
|
|
24
|
+
"description": "Which historical view to return (default drift)."
|
|
25
|
+
},
|
|
26
|
+
"domain": {
|
|
27
|
+
"type": "string",
|
|
28
|
+
"description": "Domain filter (drift/evolution/health)."
|
|
29
|
+
},
|
|
30
|
+
"file": {
|
|
31
|
+
"type": "string",
|
|
32
|
+
"description": "File (file/complexity views)."
|
|
33
|
+
},
|
|
34
|
+
"time_range": {
|
|
35
|
+
"type": "string",
|
|
36
|
+
"description": "Window like \"30d\", \"90d\"."
|
|
37
|
+
},
|
|
38
|
+
"limit": {
|
|
39
|
+
"type": "number",
|
|
40
|
+
"description": "Row cap (hotspots)."
|
|
41
|
+
},
|
|
42
|
+
"severity": {
|
|
43
|
+
"type": "string",
|
|
44
|
+
"description": "Event severity (events)."
|
|
45
|
+
},
|
|
46
|
+
"kind": {
|
|
47
|
+
"type": "string",
|
|
48
|
+
"description": "Event kind (events)."
|
|
49
|
+
},
|
|
50
|
+
"days": {
|
|
51
|
+
"type": "number",
|
|
52
|
+
"description": "Lookback days (velocity)."
|
|
53
|
+
}
|
|
54
|
+
},
|
|
55
|
+
"required": []
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Required arguments
|
|
60
|
+
|
|
61
|
+
_None._
|
|
62
|
+
|
|
63
|
+
## Properties
|
|
64
|
+
|
|
65
|
+
| Name | Type | Description |
|
|
66
|
+
|------|------|-------------|
|
|
67
|
+
| `view` | string | Which historical view to return (default drift). |
|
|
68
|
+
| `domain` | string | Domain filter (drift/evolution/health). |
|
|
69
|
+
| `file` | string | File (file/complexity views). |
|
|
70
|
+
| `time_range` | string | Window like "30d", "90d". |
|
|
71
|
+
| `limit` | number | Row cap (hotspots). |
|
|
72
|
+
| `severity` | string | Event severity (events). |
|
|
73
|
+
| `kind` | string | Event kind (events). |
|
|
74
|
+
| `days` | number | Lookback days (velocity). |
|
|
75
|
+
|
|
76
|
+
## See also
|
|
77
|
+
|
|
78
|
+
- [Index of all MCP tools](./README.md)
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# `impact`
|
|
2
|
+
|
|
3
|
+
Impact of changing code: blast radius (default), multi-file simulate, import neighbors, or data flow. Signature capability — "what breaks if I touch this?"
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"file": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Relative file path (modes: blast, neighbors, data_flow)."
|
|
14
|
+
},
|
|
15
|
+
"files": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"description": "File paths for mode=\"simulate\" (change a set at once)."
|
|
21
|
+
},
|
|
22
|
+
"mode": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"enum": [
|
|
25
|
+
"blast",
|
|
26
|
+
"simulate",
|
|
27
|
+
"neighbors",
|
|
28
|
+
"data_flow"
|
|
29
|
+
],
|
|
30
|
+
"description": "blast=transitive dependents (default); simulate=union for a file set; neighbors=import graph neighbors; data_flow=upstream/downstream + routes/models/env."
|
|
31
|
+
},
|
|
32
|
+
"hops": {
|
|
33
|
+
"type": "number",
|
|
34
|
+
"description": "Neighbor hops (mode=\"neighbors\", default 1, max 3)."
|
|
35
|
+
}
|
|
36
|
+
},
|
|
37
|
+
"required": []
|
|
38
|
+
}
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Required arguments
|
|
42
|
+
|
|
43
|
+
_None._
|
|
44
|
+
|
|
45
|
+
## Properties
|
|
46
|
+
|
|
47
|
+
| Name | Type | Description |
|
|
48
|
+
|------|------|-------------|
|
|
49
|
+
| `file` | string | Relative file path (modes: blast, neighbors, data_flow). |
|
|
50
|
+
| `files` | array | File paths for mode="simulate" (change a set at once). |
|
|
51
|
+
| `mode` | string | blast=transitive dependents (default); simulate=union for a file set; neighbors=import graph neighbors; data_flow=upstream/downstream + routes/models/env. |
|
|
52
|
+
| `hops` | number | Neighbor hops (mode="neighbors", default 1, max 3). |
|
|
53
|
+
|
|
54
|
+
## See also
|
|
55
|
+
|
|
56
|
+
- [Index of all MCP tools](./README.md)
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# `memory`
|
|
2
|
+
|
|
3
|
+
Episodic memory: search past decisions (default), decision log, recent decisions, session recap, pending work, or a file's intervention history. Ask "did we already decide this?"
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"kind": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": [
|
|
14
|
+
"search",
|
|
15
|
+
"log",
|
|
16
|
+
"recent",
|
|
17
|
+
"session",
|
|
18
|
+
"pending",
|
|
19
|
+
"interventions"
|
|
20
|
+
],
|
|
21
|
+
"description": "search=substring over the log (default); log=recent decision log; recent=validation decisions; session=full session recap; pending=unfinished/HIGH-risk work; interventions=warnings on a file."
|
|
22
|
+
},
|
|
23
|
+
"query": {
|
|
24
|
+
"type": "string",
|
|
25
|
+
"description": "Search topic (kind=\"search\")."
|
|
26
|
+
},
|
|
27
|
+
"file": {
|
|
28
|
+
"type": "string",
|
|
29
|
+
"description": "File filter (kind=\"interventions\")."
|
|
30
|
+
},
|
|
31
|
+
"hours": {
|
|
32
|
+
"type": "number",
|
|
33
|
+
"description": "Lookback hours (kind=\"log\"/\"pending\")."
|
|
34
|
+
},
|
|
35
|
+
"time_range": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"description": "Window like \"7d\" (kind=\"recent\")."
|
|
38
|
+
},
|
|
39
|
+
"filter": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"description": "Decision-kind filter (kind=\"recent\")."
|
|
42
|
+
},
|
|
43
|
+
"session_id": {
|
|
44
|
+
"type": "number",
|
|
45
|
+
"description": "Session id (kind=\"session\")."
|
|
46
|
+
}
|
|
47
|
+
},
|
|
48
|
+
"required": []
|
|
49
|
+
}
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
## Required arguments
|
|
53
|
+
|
|
54
|
+
_None._
|
|
55
|
+
|
|
56
|
+
## Properties
|
|
57
|
+
|
|
58
|
+
| Name | Type | Description |
|
|
59
|
+
|------|------|-------------|
|
|
60
|
+
| `kind` | string | search=substring over the log (default); log=recent decision log; recent=validation decisions; session=full session recap; pending=unfinished/HIGH-risk work; interventions=warnings on a file. |
|
|
61
|
+
| `query` | string | Search topic (kind="search"). |
|
|
62
|
+
| `file` | string | File filter (kind="interventions"). |
|
|
63
|
+
| `hours` | number | Lookback hours (kind="log"/"pending"). |
|
|
64
|
+
| `time_range` | string | Window like "7d" (kind="recent"). |
|
|
65
|
+
| `filter` | string | Decision-kind filter (kind="recent"). |
|
|
66
|
+
| `session_id` | number | Session id (kind="session"). |
|
|
67
|
+
|
|
68
|
+
## See also
|
|
69
|
+
|
|
70
|
+
- [Index of all MCP tools](./README.md)
|
package/docs/api/org.md
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# `org`
|
|
2
|
+
|
|
3
|
+
Cross-repo / multi-repo view: org architecture (default), service dependency graph, cross-repo blast radius, API consumers, per-repo domains, boundary violations, or migration cut points. Requires `carto org init`.
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"view": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": [
|
|
14
|
+
"architecture",
|
|
15
|
+
"graph",
|
|
16
|
+
"blast",
|
|
17
|
+
"consumers",
|
|
18
|
+
"domains",
|
|
19
|
+
"violations",
|
|
20
|
+
"migration"
|
|
21
|
+
],
|
|
22
|
+
"description": "Which org-wide view to return (default architecture)."
|
|
23
|
+
},
|
|
24
|
+
"repo": {
|
|
25
|
+
"type": "string",
|
|
26
|
+
"description": "Producer repo (view=\"blast\")."
|
|
27
|
+
},
|
|
28
|
+
"target": {
|
|
29
|
+
"type": "string",
|
|
30
|
+
"description": "Package/module (view=\"consumers\")."
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
"required": []
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## Required arguments
|
|
38
|
+
|
|
39
|
+
_None._
|
|
40
|
+
|
|
41
|
+
## Properties
|
|
42
|
+
|
|
43
|
+
| Name | Type | Description |
|
|
44
|
+
|------|------|-------------|
|
|
45
|
+
| `view` | string | Which org-wide view to return (default architecture). |
|
|
46
|
+
| `repo` | string | Producer repo (view="blast"). |
|
|
47
|
+
| `target` | string | Package/module (view="consumers"). |
|
|
48
|
+
|
|
49
|
+
## See also
|
|
50
|
+
|
|
51
|
+
- [Index of all MCP tools](./README.md)
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
# `patterns`
|
|
2
|
+
|
|
3
|
+
Semantic + procedural patterns mined from the repo: architectural invariants (default), naming/export/dir conventions, canonical exemplar, or "when X changes, Y changes" action patterns.
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"kind": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"enum": [
|
|
14
|
+
"invariants",
|
|
15
|
+
"conventions",
|
|
16
|
+
"canonical",
|
|
17
|
+
"actions"
|
|
18
|
+
],
|
|
19
|
+
"description": "invariants=mined rules (default); conventions=naming/export/dir; canonical=best exemplar; actions=git co-change patterns."
|
|
20
|
+
},
|
|
21
|
+
"domain": {
|
|
22
|
+
"type": "string",
|
|
23
|
+
"description": "Domain filter (invariants/canonical)."
|
|
24
|
+
},
|
|
25
|
+
"threshold": {
|
|
26
|
+
"type": "number",
|
|
27
|
+
"description": "Confidence threshold (invariants)."
|
|
28
|
+
},
|
|
29
|
+
"file": {
|
|
30
|
+
"type": "string",
|
|
31
|
+
"description": "File or directory (conventions)."
|
|
32
|
+
},
|
|
33
|
+
"pattern_type": {
|
|
34
|
+
"type": "string",
|
|
35
|
+
"description": "route_handler | model_definition (canonical)."
|
|
36
|
+
},
|
|
37
|
+
"intent": {
|
|
38
|
+
"type": "string",
|
|
39
|
+
"description": "Intent filter (actions)."
|
|
40
|
+
}
|
|
41
|
+
},
|
|
42
|
+
"required": []
|
|
43
|
+
}
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## Required arguments
|
|
47
|
+
|
|
48
|
+
_None._
|
|
49
|
+
|
|
50
|
+
## Properties
|
|
51
|
+
|
|
52
|
+
| Name | Type | Description |
|
|
53
|
+
|------|------|-------------|
|
|
54
|
+
| `kind` | string | invariants=mined rules (default); conventions=naming/export/dir; canonical=best exemplar; actions=git co-change patterns. |
|
|
55
|
+
| `domain` | string | Domain filter (invariants/canonical). |
|
|
56
|
+
| `threshold` | number | Confidence threshold (invariants). |
|
|
57
|
+
| `file` | string | File or directory (conventions). |
|
|
58
|
+
| `pattern_type` | string | route_handler \| model_definition (canonical). |
|
|
59
|
+
| `intent` | string | Intent filter (actions). |
|
|
60
|
+
|
|
61
|
+
## See also
|
|
62
|
+
|
|
63
|
+
- [Index of all MCP tools](./README.md)
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# `set_intent`
|
|
2
|
+
|
|
3
|
+
Capture a user-stated intent about this project — product type, stack, or a scope note ("single-user for now"). Product-type gates every rule in the rule engine, so calling this correctly is how the AI unlocks (or narrows) gap detection. Notes accumulate — this tool never overwrites prior notes, only appends.
|
|
4
|
+
|
|
5
|
+
## Input schema
|
|
6
|
+
|
|
7
|
+
```json
|
|
8
|
+
{
|
|
9
|
+
"type": "object",
|
|
10
|
+
"properties": {
|
|
11
|
+
"product_type": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "The product classification, e.g. \"saas-with-auth\" or \"unsupported\"."
|
|
14
|
+
},
|
|
15
|
+
"stack": {
|
|
16
|
+
"type": "array",
|
|
17
|
+
"items": {
|
|
18
|
+
"type": "string"
|
|
19
|
+
},
|
|
20
|
+
"description": "Optional explicit stack list, e.g. [\"Next.js\", \"Supabase\"]. Replaces the auto-detected stack."
|
|
21
|
+
},
|
|
22
|
+
"note": {
|
|
23
|
+
"type": "string",
|
|
24
|
+
"description": "A single scope statement from the user. Timestamped and appended to the notes array."
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"required": []
|
|
28
|
+
}
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Required arguments
|
|
32
|
+
|
|
33
|
+
_None._
|
|
34
|
+
|
|
35
|
+
## Properties
|
|
36
|
+
|
|
37
|
+
| Name | Type | Description |
|
|
38
|
+
|------|------|-------------|
|
|
39
|
+
| `product_type` | string | The product classification, e.g. "saas-with-auth" or "unsupported". |
|
|
40
|
+
| `stack` | array | Optional explicit stack list, e.g. ["Next.js", "Supabase"]. Replaces the auto-detected stack. |
|
|
41
|
+
| `note` | string | A single scope statement from the user. Timestamped and appended to the notes array. |
|
|
42
|
+
|
|
43
|
+
## See also
|
|
44
|
+
|
|
45
|
+
- [Index of all MCP tools](./README.md)
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# `simulate_change_impact`
|
|
2
2
|
|
|
3
|
+
> ⚠️ **Deprecated (CF-7).** Use `impact(files, mode="simulate")` instead. This name still resolves and returns byte-identical output for a deprecation window, but is no longer listed by default and emits a one-line notice.
|
|
4
|
+
|
|
3
5
|
Given a list of files, returns all files transitively affected by changing them simultaneously, with hop distance. Powered by the bitmap engine — only feasible at this speed (sub-millisecond) with bitmap OR-aggregation. Use when planning a refactor that touches multiple files.
|
|
4
6
|
|
|
5
7
|
## Input schema
|
package/docs/concepts/domains.md
CHANGED
|
@@ -1,91 +1,116 @@
|
|
|
1
1
|
# Concept: Domains
|
|
2
2
|
|
|
3
|
-
>
|
|
3
|
+
> Named groupings of related files — Carto's "AUTH", "PAYMENTS", "DATABASE". Declared in config when you want them exact, inferred from the code when you don't.
|
|
4
4
|
|
|
5
5
|
## The intuition
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
A *domain* is a slice of the codebase that hangs together: the auth code, the billing code, the database layer. Carto assigns every file to exactly one domain and everything downstream — `get_cross_domain`, `get_data_flow`, the impact report, `validate_diff`'s cross-boundary signal — reads off that single assignment.
|
|
8
8
|
|
|
9
|
-
|
|
9
|
+
There are two ways a file gets its domain, in strict priority order:
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
1. **Declared** — you told Carto in `carto.config.json`. Deterministic, always wins.
|
|
12
|
+
2. **Inferred** — Carto guessed from path keywords and the import graph. The fallback for anything you didn't declare.
|
|
12
13
|
|
|
13
|
-
|
|
14
|
+
## Source of truth (why the numbers always agree)
|
|
14
15
|
|
|
15
|
-
-
|
|
16
|
-
- Legacy projects where the directory structure doesn't match the actual coupling (auth code mysteriously lives in `lib/middleware/`)
|
|
17
|
-
- Frameworks that hide the structure (Next.js routes spread across `pages/`, `app/`, `api/`)
|
|
16
|
+
Domain assignments live in the `domain_assignments` table. The `files.domain_id` column is a **denormalized mirror** of that table, written in the same call (`assignFileToDomain`) and cleared in the same call (`clearDomainAssignments`) — the two can never drift. Every tool resolves a file's domain through one accessor, `store.getDomainOf(fileIdOrPath)`, so `get_data_flow` and `get_cross_domain` can never report different domains for the same file. (Before v2.1 they could: `data_flow` read the never-populated `files.domain_id` and showed blank while `cross_domain` read `domain_assignments` and showed a domain — that split is now closed.)
|
|
18
17
|
|
|
19
|
-
|
|
18
|
+
## 1. Declared domains (primary)
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
Two override shapes, both under `domains` in `carto.config.json`.
|
|
22
21
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
1. Tokenize each file path: `src/auth/login.ts` → `[src, auth, login]`
|
|
26
|
-
2. Drop stopwords (`src`, `lib`, `app`, `index`, `utils`, `helpers`)
|
|
27
|
-
3. Cross-reference with a small built-in dictionary of well-known domain hints: `AUTH`, `PAYMENTS`, `DATABASE`, `NOTIFICATIONS`, `EVENTS`, `TRPC`, `CORE`
|
|
28
|
-
4. Most-frequent matching token wins. Ties broken by dictionary priority (security-relevant domains first).
|
|
29
|
-
5. If no token matches the dictionary, the most-common path token becomes the domain name (uppercased).
|
|
30
|
-
|
|
31
|
-
The result: real repos light up with sensible labels — vscode gets `EXTENSIONS`, `EVENTS`, `DATABASE`, `CLI`, `CORE`; zed (Rust) gets `DATABASE`, `AUTH`, `EVENTS`, `CORE`. A game engine would get `RENDERER`, `PHYSICS`, `AUDIO` automatically because those tokens dominate the path namespace.
|
|
32
|
-
|
|
33
|
-
## Adaptive gamma
|
|
34
|
-
|
|
35
|
-
The Leiden+CPM algorithm has a resolution parameter γ (gamma). High γ → many small clusters. Low γ → few big clusters. Carto picks γ based on repo size:
|
|
36
|
-
|
|
37
|
-
- **<100 files:** keyword-only fallback (graph clustering over-fragments tiny repos)
|
|
38
|
-
- **100–1,000 files:** γ = 0.04
|
|
39
|
-
- **1,000–10,000 files:** γ scales linearly to 0.02
|
|
40
|
-
- **>10,000 files:** γ = 0.015 (large repos have dense graphs; tighter resolution prevents one giant component)
|
|
41
|
-
|
|
42
|
-
These constants come from validation on 12 real repos. See `src/store/sync.js` for the exact formula.
|
|
43
|
-
|
|
44
|
-
## Custom domains via `carto.config.json`
|
|
45
|
-
|
|
46
|
-
Override the auto-detection if your repo needs it. Two shapes:
|
|
47
|
-
|
|
48
|
-
**Simple — keyword hints:**
|
|
22
|
+
**Globs → domain (recommended for monorepos).** Any file whose path matches a domain's globs is assigned that domain regardless of what inference would have guessed. First matching domain in declaration order wins.
|
|
49
23
|
|
|
50
24
|
```json
|
|
51
25
|
{
|
|
52
26
|
"domains": {
|
|
53
|
-
"EDITOR":
|
|
54
|
-
"WORKBENCH":
|
|
27
|
+
"EDITOR": { "globs": ["src/editor/**", "packages/*/editor/**"] },
|
|
28
|
+
"WORKBENCH": { "globs": ["src/workbench/**"] }
|
|
55
29
|
}
|
|
56
30
|
}
|
|
57
31
|
```
|
|
58
32
|
|
|
59
|
-
|
|
33
|
+
Supported glob syntax: `**` (any number of path segments), `*` (any run of characters except `/`), `?` (a single non-`/` character). Everything else matches literally, paths are compared with `/` separators, and the match is anchored (full-path).
|
|
60
34
|
|
|
61
|
-
**
|
|
35
|
+
**Keywords + anchors (finer control).**
|
|
62
36
|
|
|
63
37
|
```json
|
|
64
38
|
{
|
|
65
39
|
"domains": {
|
|
66
40
|
"AUTH": {
|
|
67
41
|
"keywords": ["auth", "login", "session"],
|
|
68
|
-
"anchor":
|
|
42
|
+
"anchor": ["src/auth/session.ts", "src/auth/middleware.ts"]
|
|
69
43
|
}
|
|
70
44
|
}
|
|
71
45
|
}
|
|
72
46
|
```
|
|
73
47
|
|
|
74
|
-
|
|
48
|
+
- `keywords` feed the inference seed list (below) — hints, not hard rules.
|
|
49
|
+
- `anchor` files are *pinned*: they always land in the named domain. Anchors are the most specific override and win even over globs.
|
|
50
|
+
|
|
51
|
+
Precedence: **anchors > globs > inference.** Declared assignments are recorded with confidence `1.0`.
|
|
52
|
+
|
|
53
|
+
## 2. Inference (the fallback)
|
|
54
|
+
|
|
55
|
+
For everything not covered by config, Carto picks a strategy based on repo size and import-graph density (`selectClusteringStrategy` in `src/store/sync.js`):
|
|
56
|
+
|
|
57
|
+
| Condition | Strategy |
|
|
58
|
+
|---|---|
|
|
59
|
+
| `< 100` files | keyword + vote |
|
|
60
|
+
| import density `< 1.5` edges/file (sparse graphs, most monorepos) | keyword + vote |
|
|
61
|
+
| otherwise (dense, larger repos) | Leiden+CPM graph clustering |
|
|
62
|
+
|
|
63
|
+
> The docs used to claim Leiden ran on *every* repo. It doesn't — sparse and small repos (which includes big monorepos like supabase, whose graph is sparse) use the keyword-and-vote path. Leiden is reserved for dense graphs where community detection actually beats keywords.
|
|
64
|
+
|
|
65
|
+
### 2a. Keyword + vote (small / sparse repos)
|
|
66
|
+
|
|
67
|
+
Implemented in `buildFileAssignments` (`src/agents/domains.js`):
|
|
68
|
+
|
|
69
|
+
1. **Seed by keyword** — a file is seeded into a domain when a built-in keyword matches a **path segment** of its path (`/auth/`, `/auth.`, `-auth.`, `_auth.`, `/auth-`, `/auth_`), never an arbitrary substring. Confidence `0.9`.
|
|
70
|
+
The seed list is deliberately narrow: `auth/login/logout/oauth/jwt/session/password/credential` (AUTH), `payment/billing/stripe/invoice/subscription/checkout` (PAYMENTS), `trpc/procedure` (TRPC), `prisma/drizzle/migration/sqlalchemy` (DATABASE), `webhook/queue/worker/cron` (EVENTS), `notification/mailer/sms` (NOTIFICATIONS). Over-broad tokens that caused false positives — `token`, `db`, `model`, `schema`, `event`, `job`, `task`, `router` — were removed.
|
|
71
|
+
2. **Expand by a conservative import vote** — up to 2 hops, an unseeded file adopts a neighbor domain only when that domain has **≥ 2 votes from seed-strength neighbors**, is a **strict plurality**, and is **≥ 50 % of all domain votes** among its neighbors. Otherwise it stays unassigned. Confidence `0.5`. This is what stops a single AUTH-labelled neighbor from painting an entire import chain AUTH (the old "theme → AUTH", "byte-util → AUTH" bug).
|
|
72
|
+
3. **Everything left → `CORE`**, confidence `0.2`. A low-confidence file is honestly `CORE`, never a wrong specific domain.
|
|
73
|
+
|
|
74
|
+
### 2b. Leiden+CPM (dense repos)
|
|
75
|
+
|
|
76
|
+
`clusterByGraph` (`src/agents/leiden.js`) runs Leiden community detection with the Constant Potts Model quality function over the undirected import graph. Communities are then **named** — by keyword-seed match first, then by the most common distinctive path segment (stopwords like `src`, `lib`, `app`, `utils` dropped). Communities smaller than `minSize` are merged into `CORE`.
|
|
77
|
+
|
|
78
|
+
**Adaptive resolution (γ).** Higher γ → more, smaller communities.
|
|
79
|
+
|
|
80
|
+
```
|
|
81
|
+
γ = min(0.10, 0.02 + 0.02 · log10(fileCount / 10))
|
|
82
|
+
minSize = clamp(round(sqrt(fileCount)), 5, 20)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
See `selectClusteringStrategy` in `src/store/sync.js` for the exact formula.
|
|
86
|
+
|
|
87
|
+
## Confidence
|
|
88
|
+
|
|
89
|
+
Every assignment carries a confidence in `domain_assignments.confidence`:
|
|
90
|
+
|
|
91
|
+
| Source | Confidence |
|
|
92
|
+
|---|---|
|
|
93
|
+
| Declared glob / anchor | `1.0` |
|
|
94
|
+
| Keyword seed (path-segment match) | `0.9` |
|
|
95
|
+
| Import vote | `0.5` |
|
|
96
|
+
| `CORE` fallback | `0.2` |
|
|
97
|
+
|
|
98
|
+
Low confidence is a feature: Carto would rather label a file `CORE` than guess a specific domain it isn't sure about.
|
|
75
99
|
|
|
76
100
|
## Stability across syncs
|
|
77
101
|
|
|
78
|
-
Re-clustering after a code change can shuffle
|
|
102
|
+
Re-clustering after a code change can shuffle assignments. Carto stores the previous mapping and warns via `carto check` when > 5 % of files change domain between syncs. Declared globs and anchors give you deterministic stability where it matters.
|
|
79
103
|
|
|
80
104
|
## Where domains show up
|
|
81
105
|
|
|
82
|
-
- **`AGENTS.md`** lists them with file counts
|
|
83
|
-
- **`.carto/context/<DOMAIN>.md`**
|
|
84
|
-
- **`get_domain(name)`**
|
|
85
|
-
- **`
|
|
106
|
+
- **`AGENTS.md`** lists them with file counts.
|
|
107
|
+
- **`.carto/context/<DOMAIN>.md`** holds per-domain context the AI reads when working in that area.
|
|
108
|
+
- **`get_domain(name)`** returns the files, routes, and models in a domain.
|
|
109
|
+
- **`get_cross_domain`** lists every import edge whose endpoints are in different domains.
|
|
110
|
+
- **`validate_diff`** treats new cross-domain edges as a risk signal.
|
|
86
111
|
|
|
87
112
|
## Related
|
|
88
113
|
|
|
89
|
-
- [`get_cross_domain`](./mcp-integration.md#get_cross_domain) — every import edge that crosses a domain
|
|
90
|
-
- [`carto check`](../../README.md#cli
|
|
91
|
-
- [Leiden algorithm paper](https://www.nature.com/articles/s41598-019-41695-z) — the algorithm
|
|
114
|
+
- [`get_cross_domain`](./mcp-integration.md#get_cross_domain) — every import edge that crosses a domain boundary
|
|
115
|
+
- [`carto check`](../../README.md#cli) — flags new cross-domain violations and clustering instability
|
|
116
|
+
- [Leiden algorithm paper](https://www.nature.com/articles/s41598-019-41695-z) — the community-detection algorithm used for dense repos
|
|
@@ -24,9 +24,11 @@ For each AI tool it detects on your machine, init writes the appropriate MCP con
|
|
|
24
24
|
|
|
25
25
|
Each config tells the tool: *"when the user starts a chat, run `carto serve` as a child process and route MCP traffic to it"*.
|
|
26
26
|
|
|
27
|
-
## The
|
|
27
|
+
## The tools Carto exposes
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
Carto exposes a small **default core (~10 tools)** plus a handful of **parameterized families** (`impact`, `memory`, `history`, `patterns`, `org`), so your AI tool spends its context budget on your codebase instead of a long tool menu. The full capability set stays reachable: the ~30 former sibling tools (`get_blast_radius`, `simulate_change_impact`, `did_we_discuss_this`, ...) resolve as **deprecated shims** with byte-identical output, and advanced/experimental tools are available by widening the surface (`CARTO_MCP_TIER=advanced`, or `carto.config.json` -> `mcp.tier`).
|
|
30
|
+
|
|
31
|
+
The groups below map every capability by what it answers. Categories match the [`docs/api/`](../api/) layout, which is the full per-tool reference (auto-generated from `src/mcp/server.js`). Your AI tool picks whichever fits the question mid-task; you don't choose by hand.
|
|
30
32
|
|
|
31
33
|
### Core graph (16) — project shape, blast radius, change planning
|
|
32
34
|
|
|
@@ -31,7 +31,7 @@ jobs:
|
|
|
31
31
|
steps:
|
|
32
32
|
- uses: actions/checkout@v4
|
|
33
33
|
with: { fetch-depth: 0 } # full history — pr-impact needs the base ref
|
|
34
|
-
- uses: theanshsonkar/carto@v2.0
|
|
34
|
+
- uses: theanshsonkar/carto@v2.1.0
|
|
35
35
|
```
|
|
36
36
|
|
|
37
37
|
That's the entire config. The action handles npm install, cache restore, sync, pr-impact, and the comment.
|
|
@@ -96,7 +96,7 @@ Look at the four signals in order:
|
|
|
96
96
|
For repos that want a harder gate, set `fail-on` in the workflow:
|
|
97
97
|
|
|
98
98
|
```yaml
|
|
99
|
-
- uses: theanshsonkar/carto@v2.0
|
|
99
|
+
- uses: theanshsonkar/carto@v2.1.0
|
|
100
100
|
with:
|
|
101
101
|
fail-on: HIGH
|
|
102
102
|
```
|
|
@@ -97,9 +97,9 @@ If you're upgrading in CI, pin a known-good Carto version:
|
|
|
97
97
|
|
|
98
98
|
```yaml
|
|
99
99
|
# .github/workflows/carto.yml
|
|
100
|
-
- uses: theanshsonkar/carto@v2.0
|
|
100
|
+
- uses: theanshsonkar/carto@v2.1.0
|
|
101
101
|
with:
|
|
102
|
-
carto-version: '2.0
|
|
102
|
+
carto-version: '2.1.0' # pin instead of `latest`
|
|
103
103
|
```
|
|
104
104
|
|
|
105
105
|
This way the action's output (and the PR-comment shape) stays stable across CI runs even when newer Carto versions ship.
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "carto-md",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.1",
|
|
4
4
|
"description": "Structural intelligence + episodic memory for AI coding tools. Indexes your codebase into SQLite — routes, models, import graph, blast radius, domains — and exposes ~75 MCP tools (validate_diff, get_change_plan, did_we_discuss_this, get_predictive_risk, …) for Cursor, Claude Code, Codex, Kiro, and any other MCP client.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"carto": "src/cli/index.js"
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
"test:correctness": "node test/correctness.js",
|
|
12
12
|
"test:benchmark": "node test/benchmark.js",
|
|
13
13
|
"test:bench-ci": "node test/bench-ci.js",
|
|
14
|
+
"test:audit-supabase": "node test/audit-supabase.js",
|
|
14
15
|
"postinstall": "node scripts/postinstall.js",
|
|
15
16
|
"bench:bitmap": "node bench/bitmap-validation/index.js",
|
|
16
17
|
"bench:scale": "node bench/scale-test/index.js"
|