codevira 1.6.0__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 (58) hide show
  1. codevira-1.6.0.dist-info/LICENSE +21 -0
  2. codevira-1.6.0.dist-info/METADATA +477 -0
  3. codevira-1.6.0.dist-info/RECORD +58 -0
  4. codevira-1.6.0.dist-info/WHEEL +5 -0
  5. codevira-1.6.0.dist-info/entry_points.txt +2 -0
  6. codevira-1.6.0.dist-info/top_level.txt +2 -0
  7. indexer/__init__.py +1 -0
  8. indexer/chunker.py +428 -0
  9. indexer/global_db.py +197 -0
  10. indexer/graph_generator.py +380 -0
  11. indexer/index_codebase.py +588 -0
  12. indexer/outcome_tracker.py +172 -0
  13. indexer/rule_learner.py +186 -0
  14. indexer/sqlite_graph.py +640 -0
  15. indexer/treesitter_parser.py +423 -0
  16. mcp_server/__init__.py +1 -0
  17. mcp_server/__main__.py +20 -0
  18. mcp_server/auto_init.py +257 -0
  19. mcp_server/cli.py +622 -0
  20. mcp_server/crash_logger.py +236 -0
  21. mcp_server/data/__init__.py +1 -0
  22. mcp_server/data/agents/builder.md +84 -0
  23. mcp_server/data/agents/developer.md +111 -0
  24. mcp_server/data/agents/documenter.md +138 -0
  25. mcp_server/data/agents/orchestrator.md +96 -0
  26. mcp_server/data/agents/planner.md +106 -0
  27. mcp_server/data/agents/reviewer.md +82 -0
  28. mcp_server/data/agents/tester.md +83 -0
  29. mcp_server/data/config.example.yaml +33 -0
  30. mcp_server/data/rules/coding-standards.md +48 -0
  31. mcp_server/data/rules/engineering-excellence.md +28 -0
  32. mcp_server/data/rules/git-cicd-governance.md +32 -0
  33. mcp_server/data/rules/git_commits.md +130 -0
  34. mcp_server/data/rules/incremental-updates.md +5 -0
  35. mcp_server/data/rules/master_rule.md +187 -0
  36. mcp_server/data/rules/multi-language.md +19 -0
  37. mcp_server/data/rules/persistence.md +21 -0
  38. mcp_server/data/rules/resilience-observability.md +17 -0
  39. mcp_server/data/rules/smoke-testing.md +48 -0
  40. mcp_server/data/rules/testing-standards.md +23 -0
  41. mcp_server/detect.py +284 -0
  42. mcp_server/gitignore.py +284 -0
  43. mcp_server/global_sync.py +187 -0
  44. mcp_server/http_server.py +341 -0
  45. mcp_server/ide_inject.py +444 -0
  46. mcp_server/launchd.py +156 -0
  47. mcp_server/migrate.py +215 -0
  48. mcp_server/paths.py +256 -0
  49. mcp_server/prompts.py +136 -0
  50. mcp_server/server.py +1049 -0
  51. mcp_server/tools/__init__.py +0 -0
  52. mcp_server/tools/changesets.py +223 -0
  53. mcp_server/tools/code_reader.py +335 -0
  54. mcp_server/tools/graph.py +637 -0
  55. mcp_server/tools/learning.py +238 -0
  56. mcp_server/tools/playbook.py +89 -0
  57. mcp_server/tools/roadmap.py +599 -0
  58. mcp_server/tools/search.py +145 -0
mcp_server/migrate.py ADDED
@@ -0,0 +1,215 @@
1
+ """
2
+ migrate.py — Legacy → Centralized storage migration for Codevira v1.6.
3
+
4
+ Migrates per-project .codevira/ directories into ~/.codevira/projects/<key>/
5
+ so that data lives centrally and no longer pollutes the project tree.
6
+
7
+ Key behaviors:
8
+ - Idempotent: safe to call multiple times; second call is a no-op.
9
+ - Non-destructive: renames old .codevira/ to .codevira.migrated/ (not deleted).
10
+ - SQLite-safe: uses sqlite3.Connection.backup() to copy WAL-mode databases.
11
+ - Partial-migration recovery: if metadata.json is missing from centralized dir,
12
+ migration is re-run from scratch.
13
+ """
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ import logging
18
+ import shutil
19
+ import sqlite3
20
+ from datetime import datetime, timezone
21
+ from pathlib import Path
22
+
23
+ logger = logging.getLogger(__name__)
24
+
25
+ _CODEVIRA_VERSION = "1.6.0"
26
+
27
+
28
+ def detect_migration_needed(project_root: Path) -> bool:
29
+ """Return True if the project has a legacy .codevira/ that needs migration.
30
+
31
+ Conditions:
32
+ - <project_root>/.codevira/config.yaml exists (legacy initialized project)
33
+ - No corresponding centralized dir with metadata.json exists yet
34
+ """
35
+ from mcp_server.paths import _sanitize_path_key, get_global_home
36
+
37
+ legacy = project_root / ".codevira"
38
+ if not (legacy / "config.yaml").is_file():
39
+ return False
40
+
41
+ key = _sanitize_path_key(project_root)
42
+ centralized = get_global_home() / "projects" / key
43
+ # Migration complete only if metadata.json is present
44
+ if (centralized / "metadata.json").is_file():
45
+ return False
46
+
47
+ return True
48
+
49
+
50
+ def migrate_to_centralized(project_root: Path) -> dict:
51
+ """Migrate <project_root>/.codevira/ to ~/.codevira/projects/<key>/.
52
+
53
+ Returns a summary dict:
54
+ {migrated: True, files_copied: N, old_path: str, new_path: str}
55
+ or
56
+ {migrated: False, reason: str}
57
+
58
+ Migration steps:
59
+ 1. Create centralized directory structure
60
+ 2. Copy config.yaml and roadmap.yaml
61
+ 3. Copy graph.db via sqlite3 backup API (WAL-safe)
62
+ 4. Copy codeindex/ directory (ChromaDB persistent storage)
63
+ 5. Write metadata.json
64
+ 6. Update global.db project registry
65
+ 7. Rename old .codevira/ → .codevira.migrated/ (safety net)
66
+ """
67
+ from mcp_server.paths import _sanitize_path_key, _get_git_remote_url, get_global_home, get_global_db_path
68
+
69
+ legacy = project_root / ".codevira"
70
+ if not (legacy / "config.yaml").is_file():
71
+ return {"migrated": False, "reason": "No legacy .codevira/config.yaml found"}
72
+
73
+ key = _sanitize_path_key(project_root)
74
+ centralized = get_global_home() / "projects" / key
75
+
76
+ # Already migrated?
77
+ if (centralized / "metadata.json").is_file():
78
+ return {"migrated": False, "reason": "Already migrated"}
79
+
80
+ logger.info("Migrating %s → %s", legacy, centralized)
81
+
82
+ # Create directory structure
83
+ (centralized / "graph" / "changesets").mkdir(parents=True, exist_ok=True)
84
+ (centralized / "codeindex").mkdir(parents=True, exist_ok=True)
85
+ (centralized / "logs").mkdir(parents=True, exist_ok=True)
86
+
87
+ files_copied = 0
88
+
89
+ # 1. Copy config.yaml
90
+ src_config = legacy / "config.yaml"
91
+ dst_config = centralized / "config.yaml"
92
+ shutil.copy2(src_config, dst_config)
93
+ files_copied += 1
94
+
95
+ # 2. Copy roadmap.yaml (may not exist)
96
+ src_roadmap = legacy / "roadmap.yaml"
97
+ if src_roadmap.exists():
98
+ shutil.copy2(src_roadmap, centralized / "roadmap.yaml")
99
+ files_copied += 1
100
+
101
+ # 3. Copy graph.db via sqlite3 backup API (safe under WAL mode)
102
+ src_db = legacy / "graph" / "graph.db"
103
+ dst_db = centralized / "graph" / "graph.db"
104
+ if src_db.exists():
105
+ src_conn = None
106
+ dst_conn = None
107
+ try:
108
+ src_conn = sqlite3.connect(str(src_db))
109
+ dst_conn = sqlite3.connect(str(dst_db))
110
+ src_conn.backup(dst_conn)
111
+ files_copied += 1
112
+ except Exception as e:
113
+ logger.warning("Could not backup graph.db via API, falling back to copy: %s", e)
114
+ # Fallback: copy main db + WAL/SHM files if present
115
+ shutil.copy2(src_db, dst_db)
116
+ for suffix in ("-wal", "-shm"):
117
+ wal_file = src_db.parent / (src_db.name + suffix)
118
+ if wal_file.exists():
119
+ shutil.copy2(wal_file, dst_db.parent / (dst_db.name + suffix))
120
+ files_copied += 1
121
+ finally:
122
+ if src_conn:
123
+ src_conn.close()
124
+ if dst_conn:
125
+ dst_conn.close()
126
+
127
+ # 4. Copy codeindex/ (ChromaDB directory)
128
+ src_index = legacy / "codeindex"
129
+ dst_index = centralized / "codeindex"
130
+ if src_index.exists() and src_index.is_dir():
131
+ if dst_index.exists():
132
+ shutil.rmtree(dst_index)
133
+ shutil.copytree(src_index, dst_index)
134
+ files_copied += 1
135
+
136
+ # 5. Write metadata.json
137
+ git_remote = _get_git_remote_url(project_root)
138
+ metadata = {
139
+ "path_key": key,
140
+ "git_remote": git_remote,
141
+ "original_path": str(project_root),
142
+ "migrated_at": datetime.now(timezone.utc).isoformat(),
143
+ "version": _CODEVIRA_VERSION,
144
+ }
145
+ (centralized / "metadata.json").write_text(json.dumps(metadata, indent=2))
146
+
147
+ # 6. Update global.db project registry
148
+ gdb = None
149
+ try:
150
+ from indexer.global_db import GlobalDB
151
+ gdb = GlobalDB(get_global_db_path())
152
+ _ensure_git_remote_column(gdb)
153
+ gdb.register_project(
154
+ path=str(centralized),
155
+ name=project_root.name,
156
+ language="unknown",
157
+ git_remote=git_remote,
158
+ )
159
+ except Exception as e:
160
+ logger.warning("Could not update global.db during migration: %s", e)
161
+ finally:
162
+ if gdb is not None:
163
+ gdb.close()
164
+
165
+ # Invalidate the data-dir cache so get_data_dir() now returns the newly
166
+ # populated centralized directory, not the old legacy path.
167
+ try:
168
+ from mcp_server.paths import invalidate_data_dir_cache
169
+ invalidate_data_dir_cache(project_root)
170
+ except Exception:
171
+ pass # Cache invalidation is best-effort; migration proceeds regardless
172
+
173
+ # 7. Rename old .codevira/ → .codevira.migrated/ (safety net, not deleted)
174
+ migrated_backup = project_root / ".codevira.migrated"
175
+ if migrated_backup.exists():
176
+ shutil.rmtree(migrated_backup)
177
+ legacy.rename(migrated_backup)
178
+
179
+ logger.info(
180
+ "Migration complete: %d files copied. Legacy dir renamed to %s",
181
+ files_copied,
182
+ migrated_backup,
183
+ )
184
+
185
+ return {
186
+ "migrated": True,
187
+ "files_copied": files_copied,
188
+ "old_path": str(legacy),
189
+ "new_path": str(centralized),
190
+ }
191
+
192
+
193
+ def cleanup_legacy_dir(project_root: Path) -> bool:
194
+ """Remove the .codevira.migrated/ safety-net directory after confirmation.
195
+
196
+ Returns True if the directory was removed, False if it didn't exist.
197
+ Call only after verifying the migration was successful.
198
+ """
199
+ backup = project_root / ".codevira.migrated"
200
+ if backup.exists():
201
+ shutil.rmtree(backup)
202
+ logger.info("Removed legacy backup dir: %s", backup)
203
+ return True
204
+ return False
205
+
206
+
207
+ def _ensure_git_remote_column(gdb) -> None:
208
+ """Add git_remote column to global_db.projects if not present (v1.6 schema upgrade)."""
209
+ try:
210
+ cols = [row[1] for row in gdb.conn.execute("PRAGMA table_info(projects)").fetchall()]
211
+ if "git_remote" not in cols:
212
+ gdb.conn.execute("ALTER TABLE projects ADD COLUMN git_remote TEXT")
213
+ gdb.conn.commit()
214
+ except Exception:
215
+ pass
mcp_server/paths.py ADDED
@@ -0,0 +1,256 @@
1
+ """
2
+ paths.py — Centralized path resolution for Codevira.
3
+
4
+ Resolution priority for get_data_dir():
5
+ 1. Centralized ~/.codevira/projects/<key>/ — new in v1.6 (keyed by project path)
6
+ 2. Git remote lookup — survives directory renames
7
+ 3. Legacy <project_root>/.codevira/ — backward compat for existing projects
8
+ 4. Default to centralized path for brand-new projects
9
+
10
+ Project root discovery (get_project_root()):
11
+ - Uses --project-dir CLI override if set
12
+ - Walks upward from cwd looking for project markers:
13
+ .git, pyproject.toml, package.json, go.mod, Cargo.toml, .codevira/
14
+ - Falls back to cwd if no marker found
15
+
16
+ Performance notes:
17
+ - get_data_dir() result is cached per project root (_data_dir_cache).
18
+ First call may spawn one `git remote` subprocess and scan metadata files;
19
+ every subsequent call for the same root is a dict lookup (~0µs).
20
+ - Call invalidate_data_dir_cache() after init/migration to force re-resolution.
21
+ """
22
+ from __future__ import annotations
23
+
24
+ import json
25
+ import re
26
+ import subprocess
27
+ from pathlib import Path
28
+
29
+ # Allow overriding project directory via CLI flag (e.g. for Google Antigravity
30
+ # which doesn't support the `cwd` option in its MCP config).
31
+ _project_dir_override: Path | None = None
32
+
33
+
34
+ def set_project_dir(path: str | Path) -> None:
35
+ """Override the project directory (called by CLI when --project-dir is passed).
36
+
37
+ Also clears the data-dir cache so subsequent get_data_dir() calls
38
+ resolve against the new project root, not a stale cached entry.
39
+ """
40
+ global _project_dir_override
41
+ _project_dir_override = Path(path).resolve()
42
+ invalidate_data_dir_cache()
43
+
44
+
45
+ # ---------------------------------------------------------------------------
46
+ # Path-key helpers (for centralized storage)
47
+ # ---------------------------------------------------------------------------
48
+
49
+ #: Markers that identify a project root when walking upward.
50
+ _PROJECT_MARKERS = frozenset({
51
+ ".git",
52
+ "pyproject.toml",
53
+ "package.json",
54
+ "go.mod",
55
+ "Cargo.toml",
56
+ ".codevira",
57
+ })
58
+
59
+
60
+ def _sanitize_path_key(abs_path: str | Path) -> str:
61
+ """Convert an absolute path to a filesystem-safe key string.
62
+
63
+ Uses a short hash suffix to prevent collisions between paths that
64
+ differ only in separator characters (e.g. /foo-bar vs /foo/bar) or
65
+ drive letters (D:\\Projects\\Foo vs C:\\Projects\\Foo).
66
+
67
+ Examples:
68
+ /Users/sachin/Projects/Foo → Users_sachin_Projects_Foo_a1b2c3d4
69
+ C:\\Users\\sachin\\Projects → Users_sachin_Projects_a1b2c3d4
70
+ """
71
+ import hashlib
72
+ resolved = str(Path(abs_path).resolve())
73
+ # Hash the FULL resolved path (before any lossy transforms) for uniqueness
74
+ path_hash = hashlib.sha256(resolved.encode()).hexdigest()[:8]
75
+ # Strip drive letter and leading separators for the human-readable part
76
+ stripped = re.sub(r"^[A-Za-z]:", "", resolved) # Windows drive letter
77
+ stripped = stripped.lstrip("/\\")
78
+ # Replace path separators with underscores (not hyphens — preserves
79
+ # literal hyphens in directory names as distinct from separators)
80
+ key = re.sub(r"[/\\]", "_", stripped)
81
+ # Replace any remaining non-safe chars with hyphens
82
+ key = re.sub(r"[^a-zA-Z0-9._-]", "-", key)
83
+ # Collapse consecutive underscores/hyphens
84
+ key = re.sub(r"[_-]{2,}", "_", key)
85
+ key = key.strip("_-")
86
+ return f"{key}_{path_hash}"
87
+
88
+
89
+ def _get_git_remote_url(project_root: Path) -> str | None:
90
+ """Return the git remote 'origin' URL for project_root, or None."""
91
+ try:
92
+ result = subprocess.run(
93
+ ["git", "-C", str(project_root), "remote", "get-url", "origin"],
94
+ capture_output=True,
95
+ text=True,
96
+ timeout=3,
97
+ )
98
+ if result.returncode == 0:
99
+ url = result.stdout.strip()
100
+ return url if url else None
101
+ except (FileNotFoundError, subprocess.TimeoutExpired, OSError):
102
+ pass
103
+ return None
104
+
105
+
106
+ def _find_project_by_git_remote(remote_url: str) -> Path | None:
107
+ """Scan ~/.codevira/projects/ for a project whose metadata.json matches remote_url."""
108
+ projects_dir = get_global_home() / "projects"
109
+ if not projects_dir.exists():
110
+ return None
111
+ for meta_file in projects_dir.glob("*/metadata.json"):
112
+ try:
113
+ meta = json.loads(meta_file.read_text())
114
+ if meta.get("git_remote") == remote_url:
115
+ # Return the centralized data dir (the directory containing metadata.json)
116
+ return meta_file.parent
117
+ except (json.JSONDecodeError, OSError):
118
+ continue
119
+ return None
120
+
121
+
122
+ # ---------------------------------------------------------------------------
123
+ # Project root discovery
124
+ # ---------------------------------------------------------------------------
125
+
126
+ def _discover_project_root(start: Path) -> Path:
127
+ """Walk upward from *start* to find the nearest project root.
128
+
129
+ A project root is any ancestor directory that contains at least one
130
+ of: .git, pyproject.toml, package.json, go.mod, Cargo.toml, .codevira/
131
+
132
+ Stops at the first match so that nested repos return the inner root.
133
+ Falls back to *start* if no marker is found.
134
+ """
135
+ start = start.resolve()
136
+ for candidate in (start, *start.parents):
137
+ for marker in _PROJECT_MARKERS:
138
+ if (candidate / marker).exists():
139
+ return candidate
140
+ return start
141
+
142
+
143
+ def get_project_root() -> Path:
144
+ """Return the project root directory.
145
+
146
+ Uses --project-dir override if set (for Google Antigravity),
147
+ otherwise falls back to the current working directory.
148
+ """
149
+ if _project_dir_override is not None:
150
+ return _discover_project_root(_project_dir_override)
151
+ return _discover_project_root(Path.cwd())
152
+
153
+
154
+ # ---------------------------------------------------------------------------
155
+ # Data directory resolution (v1.6 centralized + legacy fallback)
156
+ # ---------------------------------------------------------------------------
157
+
158
+ # ---------------------------------------------------------------------------
159
+ # Data directory cache — avoids re-running subprocess + glob on every tool call.
160
+ # Keyed by resolved project root Path. Invalidated after init/migration.
161
+ # ---------------------------------------------------------------------------
162
+ _data_dir_cache: dict[Path, Path] = {}
163
+
164
+
165
+ def invalidate_data_dir_cache(project_root: Path | None = None) -> None:
166
+ """Clear the data-dir cache so the next call re-resolves from disk.
167
+
168
+ Call this after codevira init or after a migration completes, when the
169
+ centralized directory has just been created and the cache entry would
170
+ still point to the old (non-existent) default path.
171
+
172
+ Args:
173
+ project_root: If given, only invalidate that project's entry.
174
+ If None, clear the entire cache.
175
+ """
176
+ if project_root is None:
177
+ _data_dir_cache.clear()
178
+ else:
179
+ _data_dir_cache.pop(Path(project_root).resolve(), None)
180
+
181
+
182
+ def get_data_dir() -> Path:
183
+ """Return the Codevira data directory for the current project.
184
+
185
+ Resolution chain (run once per project root, then cached):
186
+ 1. Centralized ~/.codevira/projects/<key>/ if config.yaml exists there
187
+ 2. Git remote lookup — finds centralized dir even after directory rename
188
+ 3. Legacy <project_root>/.codevira/ if config.yaml exists there
189
+ 4. Default to centralized path (new projects land here automatically)
190
+
191
+ After the first call for a given project root the result is cached in
192
+ _data_dir_cache. Subsequent calls are O(1) dict lookups with no I/O.
193
+ Call invalidate_data_dir_cache() after init or migration to force refresh.
194
+ """
195
+ project_root = get_project_root()
196
+
197
+ # Fast path — already resolved for this root
198
+ if project_root in _data_dir_cache:
199
+ return _data_dir_cache[project_root]
200
+
201
+ result = _resolve_data_dir(project_root)
202
+ _data_dir_cache[project_root] = result
203
+ return result
204
+
205
+
206
+ def _resolve_data_dir(project_root: Path) -> Path:
207
+ """Perform the actual (potentially slow) data-dir resolution.
208
+
209
+ This is the only place that spawns subprocesses or reads metadata files.
210
+ Always call get_data_dir() in production code — it caches this result.
211
+ """
212
+ key = _sanitize_path_key(project_root)
213
+ centralized = get_global_home() / "projects" / key
214
+
215
+ # 1. Centralized dir already initialized?
216
+ if (centralized / "config.yaml").is_file():
217
+ return centralized
218
+
219
+ # 2. Try git remote lookup (survives directory renames).
220
+ # _get_git_remote_url() and _find_project_by_git_remote() are the
221
+ # potentially expensive operations — subprocess + metadata file scan.
222
+ # They only run once per project root thanks to the cache above.
223
+ remote_url = _get_git_remote_url(project_root)
224
+ if remote_url:
225
+ found = _find_project_by_git_remote(remote_url)
226
+ if found is not None:
227
+ return found
228
+
229
+ # 3. Legacy in-project .codevira/ (backward compat for v1.5 and earlier)
230
+ legacy = project_root / ".codevira"
231
+ if (legacy / "config.yaml").is_file():
232
+ return legacy
233
+
234
+ # 4. Default to centralized path — new project, will be created on init
235
+ return centralized
236
+
237
+
238
+ def get_package_data_dir() -> Path:
239
+ """Return the bundled data directory that ships with the pip package.
240
+
241
+ Contains: rules/, agents/, config.example.yaml
242
+ These are read-only assets installed alongside the package.
243
+ """
244
+ return Path(__file__).parent / "data"
245
+
246
+
247
+ def get_global_home() -> Path:
248
+ """Return ~/.codevira/ global data directory. Creates it if needed."""
249
+ home = Path.home() / ".codevira"
250
+ home.mkdir(parents=True, exist_ok=True)
251
+ return home
252
+
253
+
254
+ def get_global_db_path() -> Path:
255
+ """Return path to the global SQLite database for cross-project intelligence."""
256
+ return get_global_home() / "global.db"
mcp_server/prompts.py ADDED
@@ -0,0 +1,136 @@
1
+ """
2
+ prompts.py — MCP workflow prompt templates.
3
+
4
+ Five pre-built prompts that give AI agents structured starting points
5
+ for common workflows: review, debug, onboard, pre-commit, and architecture.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ PROMPTS = {
10
+ "review_changes": {
11
+ "name": "review_changes",
12
+ "description": "Review current changes with risk analysis, test coverage gaps, and learned rules.",
13
+ "arguments": [
14
+ {"name": "base_ref", "description": "Base git ref to diff against (default: main)", "required": False},
15
+ ],
16
+ "template": (
17
+ "Review the current code changes for quality, risks, and missed tests.\n\n"
18
+ "Steps:\n"
19
+ "1. Call analyze_changes() to get function-level risk scores and test coverage gaps.\n"
20
+ "2. Call get_learned_rules() to check if any changes violate learned patterns.\n"
21
+ "3. Call get_preferences() to verify the changes match the developer's coding style.\n"
22
+ "4. For each HIGH risk change, call query_graph() to understand callers and downstream impact.\n\n"
23
+ "Present a structured review with:\n"
24
+ "- Risk summary (high/medium/low changes)\n"
25
+ "- Test coverage gaps\n"
26
+ "- Style violations\n"
27
+ "- Suggested improvements"
28
+ ),
29
+ },
30
+ "debug_issue": {
31
+ "name": "debug_issue",
32
+ "description": "Debug an issue by tracing code paths, searching past decisions, and checking confidence.",
33
+ "arguments": [
34
+ {"name": "description", "description": "Description of the issue to debug", "required": True},
35
+ ],
36
+ "template": (
37
+ "Debug the following issue: {description}\n\n"
38
+ "Steps:\n"
39
+ "1. Call search_codebase() with the issue description to find relevant code.\n"
40
+ "2. Call query_graph() on the most relevant files to trace callers and callees.\n"
41
+ "3. Call search_decisions() to check if similar issues were addressed before.\n"
42
+ "4. Call get_decision_confidence() on affected files to check if this is an ambiguous area.\n"
43
+ "5. Call get_impact() on the likely source file to understand blast radius.\n\n"
44
+ "Present:\n"
45
+ "- Most likely root cause with evidence\n"
46
+ "- Affected code paths\n"
47
+ "- Past decisions that relate to this area\n"
48
+ "- Recommended fix approach"
49
+ ),
50
+ },
51
+ "onboard_session": {
52
+ "name": "onboard_session",
53
+ "description": "Start a new coding session with full context — roadmap, open work, rules, and recent history.",
54
+ "arguments": [],
55
+ "template": (
56
+ "Orient to this project and prepare for a productive coding session.\n\n"
57
+ "Steps:\n"
58
+ "1. Call get_session_context() for a complete catch-up: roadmap phase, open changesets, "
59
+ "recent decisions, confidence scores, preferences, and learned rules.\n"
60
+ "2. Call list_open_changesets() to check for interrupted multi-file work.\n"
61
+ "3. Call get_project_maturity() to understand overall project intelligence level.\n\n"
62
+ "Present a concise session briefing:\n"
63
+ "- Current project phase and next action\n"
64
+ "- Any open changesets that need resuming\n"
65
+ "- Key rules and preferences to follow\n"
66
+ "- Areas of low confidence that need extra care"
67
+ ),
68
+ },
69
+ "pre_commit_check": {
70
+ "name": "pre_commit_check",
71
+ "description": "Pre-commit review — check staged changes for risks and missing tests.",
72
+ "arguments": [],
73
+ "template": (
74
+ "Review the staged changes before committing.\n\n"
75
+ "Steps:\n"
76
+ "1. Call analyze_changes(base_ref='HEAD') to review staged changes at function level.\n"
77
+ "2. Call find_hotspots() to check if any modified functions are complexity hotspots.\n"
78
+ "3. Call get_learned_rules() to verify no patterns are violated.\n\n"
79
+ "Present:\n"
80
+ "- Changes summary with risk scores\n"
81
+ "- Missing test coverage\n"
82
+ "- Hotspot warnings (large functions, high fan-in)\n"
83
+ "- Go/no-go recommendation"
84
+ ),
85
+ },
86
+ "architecture_overview": {
87
+ "name": "architecture_overview",
88
+ "description": "Show project architecture — dependency graph, layers, hotspots, and maturity.",
89
+ "arguments": [],
90
+ "template": (
91
+ "Generate an architecture overview of this project.\n\n"
92
+ "Steps:\n"
93
+ "1. Call export_graph(format='mermaid') to generate a dependency diagram.\n"
94
+ "2. Call list_nodes() to see all files organized by layer.\n"
95
+ "3. Call find_hotspots() to identify complexity and risk areas.\n"
96
+ "4. Call get_project_maturity() for overall intelligence metrics.\n\n"
97
+ "Present:\n"
98
+ "- Dependency diagram (Mermaid)\n"
99
+ "- Layer breakdown (API, service, database, utility, test)\n"
100
+ "- Hotspots and risk areas\n"
101
+ "- Maturity score and what it means"
102
+ ),
103
+ },
104
+ }
105
+
106
+
107
+ def list_prompts() -> list[dict]:
108
+ """Return all available prompts for MCP list_prompts()."""
109
+ return [
110
+ {
111
+ "name": p["name"],
112
+ "description": p["description"],
113
+ "arguments": p.get("arguments", []),
114
+ }
115
+ for p in PROMPTS.values()
116
+ ]
117
+
118
+
119
+ def get_prompt(name: str, arguments: dict | None = None) -> dict | None:
120
+ """Return a prompt template with arguments substituted."""
121
+ prompt = PROMPTS.get(name)
122
+ if not prompt:
123
+ return None
124
+
125
+ template = prompt["template"]
126
+ if arguments:
127
+ for key, value in arguments.items():
128
+ template = template.replace(f"{{{key}}}", str(value))
129
+
130
+ return {
131
+ "name": prompt["name"],
132
+ "description": prompt["description"],
133
+ "messages": [
134
+ {"role": "user", "content": {"type": "text", "text": template}},
135
+ ],
136
+ }