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.
Files changed (87) hide show
  1. package/CHANGELOG.md +135 -0
  2. package/CONTRIBUTING.md +11 -8
  3. package/README.md +165 -213
  4. package/docs/anci/v0.1-DRAFT.md +132 -2
  5. package/docs/api/README.md +27 -2
  6. package/docs/api/did_we_discuss_this.md +2 -0
  7. package/docs/api/dismiss_gap.md +39 -0
  8. package/docs/api/find_consumers_of_api.md +2 -0
  9. package/docs/api/get_action_patterns.md +2 -0
  10. package/docs/api/get_arch_events.md +2 -0
  11. package/docs/api/get_architectural_drift.md +2 -0
  12. package/docs/api/get_blast_radius.md +2 -0
  13. package/docs/api/get_canonical_pattern.md +2 -0
  14. package/docs/api/get_change_velocity.md +2 -0
  15. package/docs/api/get_churn_vs_blast_radius.md +2 -0
  16. package/docs/api/get_complexity_trend.md +2 -0
  17. package/docs/api/get_conventions.md +2 -0
  18. package/docs/api/get_cross_repo_blast_radius.md +2 -0
  19. package/docs/api/get_data_flow.md +2 -0
  20. package/docs/api/get_decision_log.md +2 -0
  21. package/docs/api/get_domain_evolution.md +2 -0
  22. package/docs/api/get_domain_health.md +2 -0
  23. package/docs/api/get_file_receipts.md +34 -0
  24. package/docs/api/get_gaps.md +52 -0
  25. package/docs/api/get_hotspot_files.md +2 -0
  26. package/docs/api/get_intent.md +25 -0
  27. package/docs/api/get_intervention_history.md +2 -0
  28. package/docs/api/get_invariants.md +2 -0
  29. package/docs/api/get_microservices_migration_cut_points.md +2 -0
  30. package/docs/api/get_neighbors.md +2 -0
  31. package/docs/api/get_org_architecture.md +2 -0
  32. package/docs/api/get_org_domain_mapping.md +2 -0
  33. package/docs/api/get_pending_decisions.md +2 -0
  34. package/docs/api/get_recent_decisions.md +2 -0
  35. package/docs/api/get_service_boundary_violations.md +2 -0
  36. package/docs/api/get_service_dependency_graph.md +2 -0
  37. package/docs/api/get_session_context.md +2 -0
  38. package/docs/api/get_temporal_context.md +2 -0
  39. package/docs/api/history.md +78 -0
  40. package/docs/api/impact.md +56 -0
  41. package/docs/api/memory.md +70 -0
  42. package/docs/api/org.md +51 -0
  43. package/docs/api/patterns.md +63 -0
  44. package/docs/api/set_intent.md +45 -0
  45. package/docs/api/simulate_change_impact.md +2 -0
  46. package/docs/concepts/domains.md +75 -50
  47. package/docs/concepts/mcp-integration.md +4 -2
  48. package/docs/guides/pre-merge-review.md +2 -2
  49. package/docs/migration/v1-to-v2.md +2 -2
  50. package/docs/screenshots/carto-supabase-blast-radius.png +0 -0
  51. package/package.json +2 -1
  52. package/scripts/gen-api-docs.js +51 -19
  53. package/src/agents/domains.js +72 -24
  54. package/src/ai/tools.js +4 -2
  55. package/src/anci/consumer.js +83 -3
  56. package/src/anci/emit.js +18 -1
  57. package/src/anci/git-meta.js +171 -0
  58. package/src/anci/pack.js +288 -0
  59. package/src/anci/serialize.js +77 -6
  60. package/src/anci/staleness.js +161 -0
  61. package/src/brain/conventions/index.js +134 -20
  62. package/src/brain/invariants/index.js +76 -22
  63. package/src/cli/anci.js +27 -1
  64. package/src/cli/doctor.js +69 -0
  65. package/src/cli/export.js +128 -0
  66. package/src/cli/index.js +19 -1
  67. package/src/cli/init.js +84 -1
  68. package/src/cli/load.js +125 -0
  69. package/src/extractors/imports.js +35 -10
  70. package/src/extractors/languages/javascript.js +8 -4
  71. package/src/extractors/languages/prisma.js +1 -1
  72. package/src/extractors/languages/typescript.js +13 -49
  73. package/src/extractors/schemas.js +60 -0
  74. package/src/extractors/tree-sitter-parser.js +39 -0
  75. package/src/mcp/change-plan.js +131 -12
  76. package/src/mcp/impact.js +119 -0
  77. package/src/mcp/server.js +708 -82
  78. package/src/rules/concepts/auth-middleware.md +111 -0
  79. package/src/rules/concepts/money-as-float.md +80 -0
  80. package/src/rules/engine.js +178 -0
  81. package/src/rules/intent.js +178 -0
  82. package/src/rules/registry.js +24 -0
  83. package/src/rules/rules/auth-missing-on-mutating-route.js +255 -0
  84. package/src/rules/rules/money-as-float.js +166 -0
  85. package/src/store/config-loader.js +77 -3
  86. package/src/store/sqlite-store.js +248 -7
  87. 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)
@@ -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
@@ -1,91 +1,116 @@
1
1
  # Concept: Domains
2
2
 
3
- > Auto-detected clusters of tightly-coupled files. Carto's "AUTH", "PAYMENTS", "DATABASE" without you writing a config.
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
- Files that import each other heavily belong together. Files that don't, don't. Cluster the import graph and you get a set of communitiesthose communities are the codebase's *domains*.
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
- Carto runs **Leiden+CPM** (Constant Potts Model) on the import graph. It's the same family of community-detection algorithms used in social-network analysis. The output: each file gets a domain id; the bitmap engine indexes by it; everything downstream (cross-domain checks, the impact report, the validation API) reads off this.
9
+ There are two ways a file gets its domain, in strict priority order:
10
10
 
11
- ## Why not just regex on paths?
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
- Path-based detection works for greenfield projects with a clean `src/auth/`, `src/payments/` layout. It collapses on:
14
+ ## Source of truth (why the numbers always agree)
14
15
 
15
- - Monorepos where every package has its own `auth/` subdir
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
- Graph clustering is path-agnostic. It looks at who imports whom and produces named communities whose names are derived from the path tokens *most common in that cluster*. That's the magic — the cluster is found by structure, the *name* is found by frequency on the actual file paths in it.
18
+ ## 1. Declared domains (primary)
20
19
 
21
- ## Naming heuristics
20
+ Two override shapes, both under `domains` in `carto.config.json`.
22
21
 
23
- After clustering, Carto picks a name for each cluster:
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": ["editor", "monaco", "text"],
54
- "WORKBENCH": ["workbench", "layout", "panel"]
27
+ "EDITOR": { "globs": ["src/editor/**", "packages/*/editor/**"] },
28
+ "WORKBENCH": { "globs": ["src/workbench/**"] }
55
29
  }
56
30
  }
57
31
  ```
58
32
 
59
- Files whose path tokens match the hints get pulled into the named domain even if the graph clusterer would have put them elsewhere.
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
- **Full keywords + anchors:**
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": ["src/auth/session.ts", "src/auth/middleware.ts"]
42
+ "anchor": ["src/auth/session.ts", "src/auth/middleware.ts"]
69
43
  }
70
44
  }
71
45
  }
72
46
  ```
73
47
 
74
- Anchor files are *pinned*. They always end up in their named domain regardless of clustering. Useful when you've got a deliberately central auth file that the graph clusterer would otherwise lump into CORE.
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 domain assignments. Carto tracks the previous mapping and warns when >5% of files change domain between syncs (`carto check` surfaces it as "unstable clustering"). Anchor files give you a way to force stability where it matters.
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`** has per-domain context the AI reads when working in that area
84
- - **`get_domain(name)`** MCP tool returns the full list of files, routes, models in a domain
85
- - **`validate_diff`** uses cross-domain edges as a violation signal adding `auth/login.ts → payments/billing.ts` is HIGH-risk because it crosses a sensitive boundary
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-commands) — flags new cross-domain violations
91
- - [Leiden algorithm paper](https://www.nature.com/articles/s41598-019-41695-z) — the algorithm Carto uses
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 ~76 tools Carto exposes
27
+ ## The tools Carto exposes
28
28
 
29
- Grouped by what they answer. Categories below match the [`docs/api/`](../api/) layout that directory is the full per-tool reference (auto-generated from `src/mcp/server.js`). The picks below are the high-impact ones; your AI tool picks whichever one fits the question mid-task.
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.9
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.9
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.9
100
+ - uses: theanshsonkar/carto@v2.1.0
101
101
  with:
102
- carto-version: '2.0.9' # pin instead of `latest`
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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "carto-md",
3
- "version": "2.1.0",
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"