codemesh 0.1.1__py3-none-any.whl

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 (52) hide show
  1. codemesh/__init__.py +5 -0
  2. codemesh/__main__.py +8 -0
  3. codemesh/cli/__init__.py +3 -0
  4. codemesh/cli/init.py +208 -0
  5. codemesh/cli/install_cmd.py +208 -0
  6. codemesh/cli/main.py +469 -0
  7. codemesh/context/__init__.py +3 -0
  8. codemesh/context/builder.py +388 -0
  9. codemesh/db/__init__.py +3 -0
  10. codemesh/db/connection.py +66 -0
  11. codemesh/db/queries.py +696 -0
  12. codemesh/db/schema.py +125 -0
  13. codemesh/embedding/__init__.py +3 -0
  14. codemesh/extraction/__init__.py +7 -0
  15. codemesh/extraction/languages/__init__.py +95 -0
  16. codemesh/extraction/languages/c_family.py +614 -0
  17. codemesh/extraction/languages/go.py +397 -0
  18. codemesh/extraction/languages/java.py +603 -0
  19. codemesh/extraction/languages/python.py +718 -0
  20. codemesh/extraction/languages/rust.py +435 -0
  21. codemesh/extraction/languages/swift.py +464 -0
  22. codemesh/extraction/languages/typescript.py +1222 -0
  23. codemesh/extraction/orchestrator.py +218 -0
  24. codemesh/graph/__init__.py +8 -0
  25. codemesh/graph/query_manager.py +117 -0
  26. codemesh/graph/traverser.py +107 -0
  27. codemesh/indexer.py +240 -0
  28. codemesh/mcp/__init__.py +3 -0
  29. codemesh/mcp/server.py +60 -0
  30. codemesh/mcp/tools.py +605 -0
  31. codemesh/querier.py +269 -0
  32. codemesh/resolution/__init__.py +7 -0
  33. codemesh/resolution/frameworks/__init__.py +15 -0
  34. codemesh/resolution/frameworks/django.py +30 -0
  35. codemesh/resolution/frameworks/fastapi.py +23 -0
  36. codemesh/resolution/import_resolver.py +69 -0
  37. codemesh/resolution/name_matcher.py +30 -0
  38. codemesh/resolution/resolver.py +268 -0
  39. codemesh/retrieval/__init__.py +7 -0
  40. codemesh/search/__init__.py +3 -0
  41. codemesh/sync/__init__.py +3 -0
  42. codemesh/sync/watcher.py +135 -0
  43. codemesh/types.py +148 -0
  44. codemesh/viz/__init__.py +0 -0
  45. codemesh/viz/graph_builder.py +162 -0
  46. codemesh/viz/server.py +122 -0
  47. codemesh/viz/templates/index.html +359 -0
  48. codemesh-0.1.1.dist-info/METADATA +337 -0
  49. codemesh-0.1.1.dist-info/RECORD +52 -0
  50. codemesh-0.1.1.dist-info/WHEEL +4 -0
  51. codemesh-0.1.1.dist-info/entry_points.txt +2 -0
  52. codemesh-0.1.1.dist-info/licenses/LICENSE +21 -0
codemesh/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ """Graph-enhanced retrieval-augmented generation for code."""
2
+
3
+ from __future__ import annotations
4
+
5
+ __version__ = "0.1.1"
codemesh/__main__.py ADDED
@@ -0,0 +1,8 @@
1
+ """Entry point: python -m codemesh."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from codemesh.cli.main import app
6
+
7
+ if __name__ == "__main__":
8
+ app()
@@ -0,0 +1,3 @@
1
+ """CLI application."""
2
+
3
+ from __future__ import annotations
codemesh/cli/init.py ADDED
@@ -0,0 +1,208 @@
1
+ # mypy: ignore-errors
2
+ """Project initialization — creates .codemesh/ directory and agent instructions."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import logging
7
+ from pathlib import Path
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+ _CLAUDE_MD_TEMPLATE = """## CodeMesh
12
+
13
+ CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
14
+
15
+ ### If `.codemesh/` exists in the project
16
+
17
+ **NEVER call `codemesh_explore` or `codemesh_context` directly in the main session.** These tools return large amounts of source code that fills up main session context. Instead, ALWAYS spawn an Explore agent for any exploration question (e.g., "how does X work?", "explain the Y system", "where is Z implemented?").
18
+
19
+ **When spawning Explore agents**, include this instruction in the prompt:
20
+
21
+ > This project has CodeMesh initialized (.codemesh/ exists). Use `codemesh_explore` as your PRIMARY tool — it returns full source code sections from all relevant files in one call.
22
+ >
23
+ > **Rules:**
24
+ > 1. Follow the explore call budget in the `codemesh_explore` tool description — it scales automatically based on project size.
25
+ > 2. Do NOT re-read files that codemesh_explore already returned source code for. The source sections are complete and authoritative.
26
+ > 3. Only fall back to grep/glob/read for files listed under "Additional relevant files" if you need more detail, or if codemesh returned no results.
27
+
28
+ **The main session may only use these lightweight tools directly** (for targeted lookups before making edits, not for exploration):
29
+
30
+ | Tool | Use For |
31
+ |------|---------|
32
+ | `codemesh_search` | Find symbols by name |
33
+ | `codemesh_callers` / `codemesh_callees` | Trace call flow |
34
+ | `codemesh_impact` | Check what's affected before editing |
35
+ | `codemesh_node` | Get a single symbol's details |
36
+
37
+ ### If `.codemesh/` does NOT exist
38
+
39
+ At the start of a session, ask the user if they'd like to initialize CodeMesh:
40
+
41
+ "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
42
+ """
43
+
44
+ _CURSOR_RULES_TEMPLATE = """---
45
+ description: CodeMesh code intelligence
46
+ globs: *
47
+ alwaysApply: true
48
+ ---
49
+
50
+ ## CodeMesh
51
+
52
+ CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
53
+
54
+ ### If `.codemesh/` exists in the project
55
+
56
+ Use CodeMesh MCP tools as your PRIMARY exploration method:
57
+
58
+ 1. `codemesh_explore` — Return source for several related symbols grouped by file, plus a relationship map, in one call. Use this as your main exploration tool.
59
+ 2. `codemesh_callers` / `codemesh_callees` — Trace call flow
60
+ 3. `codemesh_impact` — Check what's affected before editing
61
+ 4. `codemesh_search` — Find symbols by name
62
+
63
+ ### If `.codemesh/` does NOT exist
64
+
65
+ At the start of a session, ask the user if they'd like to initialize CodeMesh:
66
+
67
+ "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
68
+ """
69
+
70
+ _CODEX_TEMPLATE = """## CodeMesh
71
+
72
+ CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
73
+
74
+ ### If `.codemesh/` exists in the project
75
+
76
+ Use CodeMesh MCP tools as your PRIMARY exploration method:
77
+ - `codemesh_explore` — Return source for related symbols grouped by file (main exploration tool)
78
+ - `codemesh_callers` / `codemesh_callees` — Trace call flow
79
+ - `codemesh_impact` — Check what's affected before editing
80
+ - `codemesh_search` — Find symbols by name
81
+
82
+ ### If `.codemesh/` does NOT exist
83
+
84
+ At the start of a session, ask: "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
85
+ """
86
+
87
+ _HERMES_TEMPLATE = """## CodeMesh
88
+
89
+ CodeMesh builds a semantic knowledge graph of codebases for faster, smarter code exploration.
90
+
91
+ ### If `.codemesh/` exists in the project
92
+
93
+ Use CodeMesh MCP tools as your PRIMARY exploration method:
94
+ - `codemesh_explore` — Return source for related symbols grouped by file (main exploration tool)
95
+ - `codemesh_callers` / `codemesh_callees` — Trace call flow
96
+ - `codemesh_impact` — Check what's affected before editing
97
+ - `codemesh_search` — Find symbols by name
98
+
99
+ ### If `.codemesh/` does NOT exist
100
+
101
+ At the start of a session, ask: "I notice this project doesn't have CodeMesh initialized. Would you like me to run `codemesh init -i` to build a code knowledge graph?"
102
+ """
103
+
104
+
105
+ def init_project(root: Path, interactive: bool = False) -> dict:
106
+ """Initialize CodeMesh in a project.
107
+
108
+ Creates .codemesh/ directory and writes agent instruction files.
109
+ Returns a dict with paths created.
110
+ """
111
+ root = root.resolve()
112
+ codemesh_dir = root / ".codemesh"
113
+
114
+ created = {}
115
+
116
+ # Create .codemesh directory
117
+ codemesh_dir.mkdir(parents=True, exist_ok=True)
118
+ created["codemesh_dir"] = str(codemesh_dir)
119
+
120
+ # Write CLAUDE.md (project-level instructions for Claude Code)
121
+ claude_md = root / "CLAUDE.md"
122
+ if interactive and claude_md.exists() and not _confirm_overwrite("CLAUDE.md"):
123
+ # Write to .codemesh/CLAUDE.md instead
124
+ claude_md = codemesh_dir / "CLAUDE.md"
125
+ claude_md.write_text(_CLAUDE_MD_TEMPLATE)
126
+ created["claude_md"] = str(claude_md)
127
+
128
+ # Write Cursor rules
129
+ cursor_dir = root / ".cursor" / "rules"
130
+ cursor_dir.mkdir(parents=True, exist_ok=True)
131
+ cursor_rules = cursor_dir / "codemesh.mdc"
132
+ cursor_rules.write_text(_CURSOR_RULES_TEMPLATE)
133
+ created["cursor_rules"] = str(cursor_rules)
134
+
135
+ # Write AGENTS.md (global instructions for Codex/opencode)
136
+ agents_md = root / "AGENTS.md"
137
+ if interactive and agents_md.exists() and not _confirm_overwrite("AGENTS.md"):
138
+ agents_md = codemesh_dir / "AGENTS.md"
139
+ agents_md.write_text(_CODEX_TEMPLATE)
140
+ created["agents_md"] = str(agents_md)
141
+
142
+ # Write Hermes instructions
143
+ hermes_md = codemesh_dir / "HERMES.md"
144
+ hermes_md.write_text(_HERMES_TEMPLATE)
145
+ created["hermes_md"] = str(hermes_md)
146
+
147
+ # Write config file
148
+ config_file = codemesh_dir / "config.json"
149
+ if not config_file.exists():
150
+ import json
151
+
152
+ config = {
153
+ "version": 1,
154
+ "root": str(root),
155
+ "ignore": [".git", "node_modules", "__pycache__", ".venv", "venv"],
156
+ }
157
+ config_file.write_text(json.dumps(config, indent=2))
158
+ created["config"] = str(config_file)
159
+
160
+ return created
161
+
162
+
163
+ def _confirm_overwrite(name: str) -> bool:
164
+ """Ask user to confirm overwriting an existing file."""
165
+ response = input(f"{name} already exists. Overwrite? [y/N] ").strip().lower()
166
+ return response in ("y", "yes")
167
+
168
+
169
+ def uninit_project(root: Path, force: bool = False) -> dict:
170
+ """Remove CodeMesh from a project.
171
+
172
+ Removes .codemesh/ directory and agent instruction files.
173
+ Returns a dict with paths removed.
174
+ """
175
+ root = root.resolve()
176
+ codemesh_dir = root / ".codemesh"
177
+ removed = []
178
+
179
+ # Remove instruction files
180
+ for f in ["CLAUDE.md", "AGENTS.md"]:
181
+ p = root / f
182
+ if p.exists():
183
+ content = p.read_text()
184
+ if "CodeMesh" in content and (force or _confirm_remove(f)):
185
+ p.unlink()
186
+ removed.append(str(p))
187
+
188
+ # Remove Cursor rules
189
+ cursor_rules = root / ".cursor" / "rules" / "codemesh.mdc"
190
+ if cursor_rules.exists() and (force or _confirm_remove("cursor rules")):
191
+ cursor_rules.unlink()
192
+ removed.append(str(cursor_rules))
193
+
194
+ # Remove .codemesh directory
195
+ if codemesh_dir.exists():
196
+ import shutil
197
+
198
+ if force or _confirm_remove(".codemesh/"):
199
+ shutil.rmtree(codemesh_dir)
200
+ removed.append(str(codemesh_dir))
201
+
202
+ return {"removed": removed}
203
+
204
+
205
+ def _confirm_remove(name: str) -> bool:
206
+ """Ask user to confirm removing a file."""
207
+ response = input(f"Remove {name}? [y/N] ").strip().lower()
208
+ return response in ("y", "yes")
@@ -0,0 +1,208 @@
1
+ # mypy: ignore-errors
2
+ """Install command — auto-configures CodeMesh MCP server for AI coding agents."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import json
7
+ import logging
8
+ from pathlib import Path
9
+
10
+ logger = logging.getLogger(__name__)
11
+
12
+ _CLAUDE_MCP_CONFIG = {
13
+ "mcpServers": {
14
+ "codemesh": {
15
+ "type": "stdio",
16
+ "command": "codemesh",
17
+ "args": ["serve", "--transport", "stdio"],
18
+ }
19
+ }
20
+ }
21
+
22
+ _CLAUDE_PERMISSIONS = {
23
+ "permissions": {
24
+ "allow": [
25
+ "mcp__codemesh__codemesh_search",
26
+ "mcp__codemesh__codemesh_context",
27
+ "mcp__codemesh__codemesh_callers",
28
+ "mcp__codemesh__codemesh_callees",
29
+ "mcp__codemesh__codemesh_impact",
30
+ "mcp__codemesh__codemesh_node",
31
+ "mcp__codemesh__codemesh_status",
32
+ "mcp__codemesh__codemesh_files",
33
+ "mcp__codemesh__codemesh_explore",
34
+ ]
35
+ }
36
+ }
37
+
38
+
39
+ def _find_claude_json_dir() -> Path | None:
40
+ """Find the Claude Code configuration directory."""
41
+ candidates = [
42
+ Path.home() / ".claude",
43
+ Path.home() / ".config" / "claude",
44
+ ]
45
+ for c in candidates:
46
+ if c.exists():
47
+ return c
48
+ # Create the default location
49
+ claude_dir = Path.home() / ".claude"
50
+ claude_dir.mkdir(parents=True, exist_ok=True)
51
+ return claude_dir
52
+
53
+
54
+ def _merge_json_file(path: Path, new_data: dict) -> dict:
55
+ """Merge new data into an existing JSON file."""
56
+ existing: dict = {}
57
+ if path.exists():
58
+ try:
59
+ existing = json.loads(path.read_text())
60
+ except (json.JSONDecodeError, OSError):
61
+ existing = {}
62
+
63
+ # Merge MCP servers
64
+ if "mcpServers" in new_data:
65
+ existing.setdefault("mcpServers", {})
66
+ existing["mcpServers"].update(new_data["mcpServers"])
67
+
68
+ # Merge permissions
69
+ if "permissions" in new_data and "allow" in new_data["permissions"]:
70
+ existing.setdefault("permissions", {"allow": []})
71
+ perms_allow = existing["permissions"].get("allow", [])
72
+ for item in new_data["permissions"]["allow"]:
73
+ if item not in perms_allow:
74
+ perms_allow.append(item)
75
+ existing["permissions"]["allow"] = perms_allow
76
+
77
+ return existing
78
+
79
+
80
+ def install_claude(root: Path, global_config: bool = True) -> dict:
81
+ """Configure Claude Code to use CodeMesh MCP server.
82
+
83
+ Args:
84
+ root: Project root (for project-local config)
85
+ global_config: If True, write to ~/.claude.json (global).
86
+ If False, write to .claude.json in project root.
87
+ """
88
+ result = {"claude_json": None, "claude_settings": None, "claude_md": None}
89
+
90
+ if global_config:
91
+ claude_dir = _find_claude_json_dir()
92
+ if claude_dir is None:
93
+ return result
94
+ claude_json = claude_dir / "claude.json"
95
+ claude_settings = claude_dir / "settings.json"
96
+ else:
97
+ claude_json = root / ".claude.json"
98
+ claude_settings = root / ".claude_settings.json"
99
+
100
+ # Merge MCP config
101
+ if claude_json.exists():
102
+ existing = json.loads(claude_json.read_text())
103
+ existing_mcp = existing.get("mcpServers", {})
104
+ if "codemesh" in existing_mcp:
105
+ result["claude_json"] = str(claude_json) + " (already configured)"
106
+ return result
107
+
108
+ merged = _merge_json_file(claude_json, _CLAUDE_MCP_CONFIG)
109
+ claude_json.write_text(json.dumps(merged, indent=2))
110
+ result["claude_json"] = str(claude_json)
111
+
112
+ # Merge permissions
113
+ merged_settings = _merge_json_file(claude_settings, _CLAUDE_PERMISSIONS)
114
+ claude_settings.write_text(json.dumps(merged_settings, indent=2))
115
+ result["claude_settings"] = str(claude_settings)
116
+
117
+ return result
118
+
119
+
120
+ def install_cursor(root: Path) -> dict:
121
+ """Configure Cursor to use CodeMesh MCP server.
122
+
123
+ Cursor uses .cursor/mcp.json in the project directory.
124
+ """
125
+ result = {"cursor_mcp": None}
126
+
127
+ cursor_dir = root / ".cursor"
128
+ cursor_dir.mkdir(parents=True, exist_ok=True)
129
+ mcp_json = cursor_dir / "mcp.json"
130
+
131
+ config = {}
132
+ if mcp_json.exists():
133
+ try:
134
+ config = json.loads(mcp_json.read_text())
135
+ except (json.JSONDecodeError, OSError):
136
+ config = {}
137
+
138
+ config.setdefault("mcpServers", {})
139
+ if "codemesh" in config.get("mcpServers", {}):
140
+ result["cursor_mcp"] = str(mcp_json) + " (already configured)"
141
+ return result
142
+
143
+ config["mcpServers"]["codemesh"] = {
144
+ "type": "stdio",
145
+ "command": "codemesh",
146
+ "args": ["serve", "--transport", "stdio"],
147
+ }
148
+ mcp_json.write_text(json.dumps(config, indent=2))
149
+ result["cursor_mcp"] = str(mcp_json)
150
+
151
+ return result
152
+
153
+
154
+ def install_codex(root: Path) -> dict:
155
+ """Configure Codex CLI to use CodeMesh MCP server.
156
+
157
+ Codex uses ~/.codex/config.json.
158
+ """
159
+ result = {"codex_config": None}
160
+
161
+ codex_dir = Path.home() / ".codex"
162
+ codex_dir.mkdir(parents=True, exist_ok=True)
163
+ config_file = codex_dir / "config.json"
164
+
165
+ config: dict = {}
166
+ if config_file.exists():
167
+ try:
168
+ config = json.loads(config_file.read_text())
169
+ except (json.JSONDecodeError, OSError):
170
+ config = {}
171
+
172
+ config.setdefault("mcpServers", {})
173
+ if "codemesh" in config.get("mcpServers", {}):
174
+ result["codex_config"] = str(config_file) + " (already configured)"
175
+ return result
176
+
177
+ config["mcpServers"]["codemesh"] = {
178
+ "type": "stdio",
179
+ "command": "codemesh",
180
+ "args": ["serve", "--transport", "stdio"],
181
+ }
182
+ config_file.write_text(json.dumps(config, indent=2))
183
+ result["codex_config"] = str(config_file)
184
+
185
+ return result
186
+
187
+
188
+ def detect_agents() -> list[str]:
189
+ """Detect which AI coding agents are installed."""
190
+ agents = []
191
+
192
+ # Check for Claude Code
193
+ claude_dir = _find_claude_json_dir()
194
+ if claude_dir and claude_dir.exists():
195
+ agents.append("claude")
196
+
197
+ # Check for Cursor
198
+ cursor_check = Path.home() / ".cursor"
199
+ if cursor_check.exists():
200
+ agents.append("cursor")
201
+
202
+ # Check for Codex CLI
203
+ import shutil
204
+
205
+ if shutil.which("codex"):
206
+ agents.append("codex")
207
+
208
+ return agents