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.
- codevira-1.6.0.dist-info/LICENSE +21 -0
- codevira-1.6.0.dist-info/METADATA +477 -0
- codevira-1.6.0.dist-info/RECORD +58 -0
- codevira-1.6.0.dist-info/WHEEL +5 -0
- codevira-1.6.0.dist-info/entry_points.txt +2 -0
- codevira-1.6.0.dist-info/top_level.txt +2 -0
- indexer/__init__.py +1 -0
- indexer/chunker.py +428 -0
- indexer/global_db.py +197 -0
- indexer/graph_generator.py +380 -0
- indexer/index_codebase.py +588 -0
- indexer/outcome_tracker.py +172 -0
- indexer/rule_learner.py +186 -0
- indexer/sqlite_graph.py +640 -0
- indexer/treesitter_parser.py +423 -0
- mcp_server/__init__.py +1 -0
- mcp_server/__main__.py +20 -0
- mcp_server/auto_init.py +257 -0
- mcp_server/cli.py +622 -0
- mcp_server/crash_logger.py +236 -0
- mcp_server/data/__init__.py +1 -0
- mcp_server/data/agents/builder.md +84 -0
- mcp_server/data/agents/developer.md +111 -0
- mcp_server/data/agents/documenter.md +138 -0
- mcp_server/data/agents/orchestrator.md +96 -0
- mcp_server/data/agents/planner.md +106 -0
- mcp_server/data/agents/reviewer.md +82 -0
- mcp_server/data/agents/tester.md +83 -0
- mcp_server/data/config.example.yaml +33 -0
- mcp_server/data/rules/coding-standards.md +48 -0
- mcp_server/data/rules/engineering-excellence.md +28 -0
- mcp_server/data/rules/git-cicd-governance.md +32 -0
- mcp_server/data/rules/git_commits.md +130 -0
- mcp_server/data/rules/incremental-updates.md +5 -0
- mcp_server/data/rules/master_rule.md +187 -0
- mcp_server/data/rules/multi-language.md +19 -0
- mcp_server/data/rules/persistence.md +21 -0
- mcp_server/data/rules/resilience-observability.md +17 -0
- mcp_server/data/rules/smoke-testing.md +48 -0
- mcp_server/data/rules/testing-standards.md +23 -0
- mcp_server/detect.py +284 -0
- mcp_server/gitignore.py +284 -0
- mcp_server/global_sync.py +187 -0
- mcp_server/http_server.py +341 -0
- mcp_server/ide_inject.py +444 -0
- mcp_server/launchd.py +156 -0
- mcp_server/migrate.py +215 -0
- mcp_server/paths.py +256 -0
- mcp_server/prompts.py +136 -0
- mcp_server/server.py +1049 -0
- mcp_server/tools/__init__.py +0 -0
- mcp_server/tools/changesets.py +223 -0
- mcp_server/tools/code_reader.py +335 -0
- mcp_server/tools/graph.py +637 -0
- mcp_server/tools/learning.py +238 -0
- mcp_server/tools/playbook.py +89 -0
- mcp_server/tools/roadmap.py +599 -0
- mcp_server/tools/search.py +145 -0
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
"""
|
|
2
|
+
crash_logger.py — Crash-only logging with automatic secret sanitization.
|
|
3
|
+
|
|
4
|
+
Captures unhandled exceptions and tool-level errors to a rotating log file.
|
|
5
|
+
All log entries are sanitized to strip personal information, secrets, and
|
|
6
|
+
credentials before writing to disk.
|
|
7
|
+
|
|
8
|
+
Log location: ~/.codevira/logs/crashes.log
|
|
9
|
+
Max size: 5 MB, rotated with 3 backups (20 MB total)
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import logging
|
|
14
|
+
import re
|
|
15
|
+
import sys
|
|
16
|
+
import threading
|
|
17
|
+
import traceback
|
|
18
|
+
from datetime import datetime, timezone
|
|
19
|
+
from logging.handlers import RotatingFileHandler
|
|
20
|
+
from pathlib import Path
|
|
21
|
+
|
|
22
|
+
# ---------------------------------------------------------------------------
|
|
23
|
+
# Secret patterns — matched and replaced BEFORE writing to disk.
|
|
24
|
+
#
|
|
25
|
+
# CodeVira is a code-indexing/memory tool. Crash tracebacks may contain
|
|
26
|
+
# file paths, config values, or connection strings from the user's project.
|
|
27
|
+
# We sanitize structural patterns (connection strings, key=value, PEM blocks)
|
|
28
|
+
# rather than vendor-specific token formats — CodeVira never handles those.
|
|
29
|
+
# ---------------------------------------------------------------------------
|
|
30
|
+
_SECRET_PATTERNS: list[tuple[re.Pattern, str]] = [
|
|
31
|
+
# Private key PEM blocks
|
|
32
|
+
(re.compile(r'-----BEGIN\s+\w+\s+PRIVATE\s+KEY-----[\s\S]*?-----END\s+\w+\s+PRIVATE\s+KEY-----'), '***PRIVATE_KEY***'),
|
|
33
|
+
# Connection strings with embedded passwords (postgres://, mongodb://, redis://, amqp://, etc.)
|
|
34
|
+
(re.compile(r'(?i)(://[^:]*:)[^@]+(@)'), r'\1***@'),
|
|
35
|
+
# Private/internal IP addresses (RFC 1918)
|
|
36
|
+
(re.compile(r'\b(?:10\.\d{1,3}\.\d{1,3}\.\d{1,3}|172\.(?:1[6-9]|2\d|3[01])\.\d{1,3}\.\d{1,3}|192\.168\.\d{1,3}\.\d{1,3})\b'), '***INTERNAL_IP***'),
|
|
37
|
+
|
|
38
|
+
# ---- Generic keyword-value patterns (catch-all) ----
|
|
39
|
+
|
|
40
|
+
# key=value / key: value / key = value (in text and logs)
|
|
41
|
+
(re.compile(r'(?i)(api[_-]?key|token|secret|password|authorization|bearer|credential)\s*[:=]\s*\S+'), r'\1=***REDACTED***'),
|
|
42
|
+
# JSON-style: "password": "value" or 'password': 'value'
|
|
43
|
+
(re.compile(r"""(?i)(["'](?:password|secret|token|api_key|api-key|auth|credential)["'])\s*:\s*["'][^"']+["']"""), r'\1: "***REDACTED***"'),
|
|
44
|
+
# .env file values for known secret variable names
|
|
45
|
+
(re.compile(r'(?i)((?:DATABASE_URL|REDIS_URL|MONGO_URL|SECRET_KEY|PRIVATE_KEY|ACCESS_TOKEN|REFRESH_TOKEN|API_KEY|AUTH_TOKEN|ENCRYPTION_KEY)\s*=\s*)\S+'), r'\1***REDACTED***'),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
# Home directory — replaced with ~ in all paths
|
|
49
|
+
_HOME = str(Path.home())
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _sanitize(text: str) -> str:
|
|
53
|
+
"""Remove secrets, PII, and sensitive data from log text."""
|
|
54
|
+
# Replace home directory with ~ (hides username from paths)
|
|
55
|
+
text = text.replace(_HOME, "~")
|
|
56
|
+
# Apply all secret patterns
|
|
57
|
+
for pattern, replacement in _SECRET_PATTERNS:
|
|
58
|
+
text = pattern.sub(replacement, text)
|
|
59
|
+
# Strip environment variable dumps that might contain secrets
|
|
60
|
+
text = re.sub(
|
|
61
|
+
r'(?i)environ\s*\{[^}]{200,}\}',
|
|
62
|
+
'environ{***REDACTED_ENV***}',
|
|
63
|
+
text,
|
|
64
|
+
)
|
|
65
|
+
return text
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def _get_log_dir() -> Path:
|
|
69
|
+
"""Return ~/.codevira/logs/, creating it if needed."""
|
|
70
|
+
log_dir = Path.home() / ".codevira" / "logs"
|
|
71
|
+
log_dir.mkdir(parents=True, exist_ok=True)
|
|
72
|
+
return log_dir
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
# ---------------------------------------------------------------------------
|
|
76
|
+
# Module-level logger — initialized once, thread-safe via lock.
|
|
77
|
+
# ---------------------------------------------------------------------------
|
|
78
|
+
_logger: logging.Logger | None = None
|
|
79
|
+
_logger_lock = threading.Lock()
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def _get_logger() -> logging.Logger:
|
|
83
|
+
global _logger
|
|
84
|
+
if _logger is not None:
|
|
85
|
+
return _logger
|
|
86
|
+
|
|
87
|
+
with _logger_lock:
|
|
88
|
+
# Double-check after acquiring lock (another thread may have initialized)
|
|
89
|
+
if _logger is not None:
|
|
90
|
+
return _logger
|
|
91
|
+
|
|
92
|
+
logger = logging.getLogger("codevira.crash")
|
|
93
|
+
logger.setLevel(logging.ERROR)
|
|
94
|
+
logger.propagate = False # Don't pollute stdout/stderr (MCP protocol)
|
|
95
|
+
|
|
96
|
+
log_path = _get_log_dir() / "crashes.log"
|
|
97
|
+
handler = RotatingFileHandler(
|
|
98
|
+
log_path,
|
|
99
|
+
maxBytes=5 * 1024 * 1024, # 5 MB
|
|
100
|
+
backupCount=3, # 20 MB total history
|
|
101
|
+
encoding="utf-8",
|
|
102
|
+
)
|
|
103
|
+
handler.setLevel(logging.ERROR)
|
|
104
|
+
handler.setFormatter(logging.Formatter("%(message)s"))
|
|
105
|
+
logger.addHandler(handler)
|
|
106
|
+
|
|
107
|
+
_logger = logger
|
|
108
|
+
return _logger
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---------------------------------------------------------------------------
|
|
112
|
+
# Public API
|
|
113
|
+
# ---------------------------------------------------------------------------
|
|
114
|
+
|
|
115
|
+
def log_crash(
|
|
116
|
+
error: BaseException,
|
|
117
|
+
*,
|
|
118
|
+
context: str = "",
|
|
119
|
+
tool_name: str = "",
|
|
120
|
+
project_path: str = "",
|
|
121
|
+
) -> None:
|
|
122
|
+
"""
|
|
123
|
+
Log a crash to ~/.codevira/logs/crashes.log.
|
|
124
|
+
|
|
125
|
+
Only writes ERROR-level entries. All content is sanitized before writing.
|
|
126
|
+
|
|
127
|
+
Args:
|
|
128
|
+
error: The exception that was raised.
|
|
129
|
+
context: What was happening when the crash occurred (e.g. "server startup").
|
|
130
|
+
tool_name: MCP tool name if the crash happened during a tool call.
|
|
131
|
+
project_path: Project directory, if known.
|
|
132
|
+
"""
|
|
133
|
+
try:
|
|
134
|
+
logger = _get_logger()
|
|
135
|
+
tb = traceback.format_exception(type(error), error, error.__traceback__)
|
|
136
|
+
tb_text = "".join(tb)
|
|
137
|
+
|
|
138
|
+
# Build structured log entry
|
|
139
|
+
lines = [
|
|
140
|
+
f"{'=' * 72}",
|
|
141
|
+
f"CRASH: {type(error).__name__}: {error}",
|
|
142
|
+
f"TIME: {datetime.now(timezone.utc).isoformat()}",
|
|
143
|
+
]
|
|
144
|
+
if context:
|
|
145
|
+
lines.append(f"WHERE: {context}")
|
|
146
|
+
if tool_name:
|
|
147
|
+
lines.append(f"TOOL: {tool_name}")
|
|
148
|
+
if project_path:
|
|
149
|
+
lines.append(f"PROJECT: {project_path}")
|
|
150
|
+
|
|
151
|
+
# System info (non-sensitive)
|
|
152
|
+
lines.append(f"PYTHON: {sys.version.split()[0]}")
|
|
153
|
+
try:
|
|
154
|
+
from importlib.metadata import version as pkg_version
|
|
155
|
+
lines.append(f"CODEVIRA: {pkg_version('codevira')}")
|
|
156
|
+
except Exception:
|
|
157
|
+
pass
|
|
158
|
+
|
|
159
|
+
lines.append(f"TRACEBACK:\n{tb_text}")
|
|
160
|
+
lines.append("") # blank line separator
|
|
161
|
+
|
|
162
|
+
entry = "\n".join(lines)
|
|
163
|
+
entry = _sanitize(entry)
|
|
164
|
+
|
|
165
|
+
logger.error(entry)
|
|
166
|
+
except Exception:
|
|
167
|
+
# Crash logger must never itself crash the server.
|
|
168
|
+
# Last resort: try stderr (won't break MCP protocol since
|
|
169
|
+
# MCP uses stdout only; stderr is for diagnostics).
|
|
170
|
+
try:
|
|
171
|
+
print(f"[codevira] crash logger failed: {error}", file=sys.stderr)
|
|
172
|
+
except Exception:
|
|
173
|
+
pass
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
def get_crash_log_path() -> Path:
|
|
177
|
+
"""Return the path to the crash log file."""
|
|
178
|
+
return _get_log_dir() / "crashes.log"
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def read_recent_crashes(limit: int = 20) -> str:
|
|
182
|
+
"""
|
|
183
|
+
Read the most recent crash entries from the log file.
|
|
184
|
+
|
|
185
|
+
Returns a formatted string with up to `limit` most recent crashes.
|
|
186
|
+
Content is already sanitized (written sanitized to disk).
|
|
187
|
+
"""
|
|
188
|
+
if limit < 1:
|
|
189
|
+
limit = 1
|
|
190
|
+
|
|
191
|
+
log_path = get_crash_log_path()
|
|
192
|
+
if not log_path.exists():
|
|
193
|
+
return "No crash log found. No crashes have been recorded."
|
|
194
|
+
|
|
195
|
+
text = log_path.read_text(encoding="utf-8", errors="replace")
|
|
196
|
+
if not text.strip():
|
|
197
|
+
return "Crash log is empty. No crashes have been recorded."
|
|
198
|
+
|
|
199
|
+
# Split on separator line and take the most recent entries
|
|
200
|
+
entries = text.split("=" * 72)
|
|
201
|
+
entries = [e.strip() for e in entries if e.strip()]
|
|
202
|
+
|
|
203
|
+
if not entries:
|
|
204
|
+
return "No crash entries found."
|
|
205
|
+
|
|
206
|
+
recent = entries[-limit:]
|
|
207
|
+
count_total = len(entries)
|
|
208
|
+
|
|
209
|
+
# Sanitize the header too (log_path contains home dir)
|
|
210
|
+
safe_path = str(log_path).replace(_HOME, "~")
|
|
211
|
+
header = f"Showing {len(recent)} of {count_total} total crashes"
|
|
212
|
+
header += f"\nLog file: {safe_path}\n"
|
|
213
|
+
header += f"Log size: {log_path.stat().st_size / 1024:.1f} KB\n"
|
|
214
|
+
|
|
215
|
+
body = ("\n" + "=" * 72 + "\n").join(recent)
|
|
216
|
+
return f"{header}\n{'=' * 72}\n{body}"
|
|
217
|
+
|
|
218
|
+
|
|
219
|
+
def install_global_handler() -> None:
|
|
220
|
+
"""
|
|
221
|
+
Install a global exception handler that logs unhandled exceptions.
|
|
222
|
+
|
|
223
|
+
Call this once at MCP server startup. The original excepthook is
|
|
224
|
+
preserved — this only adds logging, it doesn't suppress errors.
|
|
225
|
+
"""
|
|
226
|
+
original_hook = sys.excepthook
|
|
227
|
+
|
|
228
|
+
def _crash_hook(exc_type, exc_value, exc_tb):
|
|
229
|
+
if exc_type is not KeyboardInterrupt:
|
|
230
|
+
log_crash(
|
|
231
|
+
exc_value,
|
|
232
|
+
context="unhandled exception (global handler)",
|
|
233
|
+
)
|
|
234
|
+
original_hook(exc_type, exc_value, exc_tb)
|
|
235
|
+
|
|
236
|
+
sys.excepthook = _crash_hook
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
# This file makes mcp_server/data a Python package so setuptools includes it.
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
# Builder Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Run static analysis and architecture verification. Zero AI tokens for the checks themselves.
|
|
5
|
+
AI tokens used only if violations are found and need explaining.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When You Are Invoked
|
|
10
|
+
|
|
11
|
+
After Tester passes (or in parallel with Tester for medium/large changes).
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## Command Sequence
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# 1. Lint — fast, always run
|
|
19
|
+
ruff check src/ # or: eslint src/, go vet ./..., cargo clippy
|
|
20
|
+
|
|
21
|
+
# 2. Type check — run on changed files/modules
|
|
22
|
+
mypy src/services/ src/schemas/ # or: tsc --noEmit, go build ./...
|
|
23
|
+
|
|
24
|
+
# 3. Architecture verification — if your project has an architecture checker
|
|
25
|
+
python scripts/verify_architecture.py # optional: only if this script exists
|
|
26
|
+
|
|
27
|
+
# 4. Syntax check for any new files
|
|
28
|
+
python -m py_compile <new_file.py> # or equivalent for your language
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Adapt the above commands to your project's toolchain.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Failure Handling
|
|
36
|
+
|
|
37
|
+
### Lint violations
|
|
38
|
+
- Auto-fixable: note them, suggest `ruff check --fix` (or equivalent)
|
|
39
|
+
- Non-auto-fixable: report with line number and rule ID
|
|
40
|
+
|
|
41
|
+
### Type errors
|
|
42
|
+
- Type mismatch — check if a field needs `Optional` or a union type
|
|
43
|
+
- Missing stub — acceptable if third-party, just note it
|
|
44
|
+
- Cannot find module — check import path
|
|
45
|
+
|
|
46
|
+
### Architecture violations (if checker exists)
|
|
47
|
+
- **Always block on these** — import violations are CI failures
|
|
48
|
+
- Report the exact import, the rule it violates, and the correct pattern
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## New Files
|
|
53
|
+
|
|
54
|
+
When a new file was created in this session, check if it was registered in the graph:
|
|
55
|
+
```
|
|
56
|
+
get_node(new_file_path)
|
|
57
|
+
```
|
|
58
|
+
If not found → remind the Developer agent to call `add_node()` before closing the changeset.
|
|
59
|
+
|
|
60
|
+
---
|
|
61
|
+
|
|
62
|
+
## Output Format
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
BUILD: <files checked>
|
|
66
|
+
STATUS: CLEAN | WARNINGS | VIOLATIONS
|
|
67
|
+
|
|
68
|
+
Violations (if any):
|
|
69
|
+
- LINT: <file>:<line> <rule_id> — <message>
|
|
70
|
+
- TYPE: <file>:<line> — <message>
|
|
71
|
+
- ARCH: <file> imports <other_file> — violates <rule>
|
|
72
|
+
|
|
73
|
+
Auto-fixable: <yes/no>
|
|
74
|
+
Block merge: <yes if ARCH violations or type errors; no for lint warnings>
|
|
75
|
+
New files registered in graph: <yes/no — remind developer if no>
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## What You Do NOT Do
|
|
81
|
+
|
|
82
|
+
- Do NOT refactor code to fix style issues (that's over-engineering)
|
|
83
|
+
- Do NOT add type annotations to code you didn't change
|
|
84
|
+
- Do NOT run the full type check on the entire codebase — only changed modules
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
# Developer Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Write code changes. You are the execution engine.
|
|
5
|
+
You receive a task, orient via MCP tools, then write precise targeted changes.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Session Start Protocol (MANDATORY — do not skip)
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
1. list_open_changesets()
|
|
13
|
+
→ If any open: review them first. Pick up unfinished work before starting new.
|
|
14
|
+
|
|
15
|
+
2. get_roadmap()
|
|
16
|
+
→ Confirm current phase and next_action align with the task.
|
|
17
|
+
|
|
18
|
+
3. search_decisions(task_keywords)
|
|
19
|
+
→ Check if this has been decided before. E.g. search_decisions("threshold") before
|
|
20
|
+
changing any threshold values.
|
|
21
|
+
|
|
22
|
+
4. For EACH file mentioned in the task:
|
|
23
|
+
get_node(file_path)
|
|
24
|
+
→ Read: role, rules, do_not_revert, stability, index_status
|
|
25
|
+
→ If index_status.stale: refresh_index([file_path])
|
|
26
|
+
|
|
27
|
+
5. For EACH file you will MODIFY:
|
|
28
|
+
get_impact(file_path)
|
|
29
|
+
→ Read: blast_radius, affected_files, high_risk_files, tests_to_run
|
|
30
|
+
|
|
31
|
+
6. search_codebase(task_description)
|
|
32
|
+
→ Find existing patterns. NEVER rewrite what already exists.
|
|
33
|
+
|
|
34
|
+
7. IF modifying 2+ files:
|
|
35
|
+
start_changeset(id, description, files)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## Coding Rules
|
|
41
|
+
|
|
42
|
+
- Follow `rules/` standards — especially `coding-standards.md`
|
|
43
|
+
- **Never** modify files with `do_not_revert: true` without explicit permission
|
|
44
|
+
- If a file has `rules` in its graph node — read them before writing a single line
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
## Efficiency Rules
|
|
49
|
+
|
|
50
|
+
- Read only the files you NEED, not everything you CAN
|
|
51
|
+
- `get_node()` replaces reading the file for orientation
|
|
52
|
+
- `search_codebase()` replaces grepping for patterns
|
|
53
|
+
- `get_impact()` replaces manual import tracing
|
|
54
|
+
- `search_decisions()` replaces re-reading past sessions to recall a decision
|
|
55
|
+
- If you catch yourself reading a file just to understand it — use MCP first
|
|
56
|
+
|
|
57
|
+
---
|
|
58
|
+
|
|
59
|
+
## Adding New Files
|
|
60
|
+
|
|
61
|
+
When creating a new file, register it in the graph immediately:
|
|
62
|
+
```
|
|
63
|
+
add_node(
|
|
64
|
+
file_path="src/services/new_service.py",
|
|
65
|
+
role="Service that handles X",
|
|
66
|
+
layer="services",
|
|
67
|
+
node_type="file",
|
|
68
|
+
stability="low",
|
|
69
|
+
key_functions=["process", "validate"],
|
|
70
|
+
connects_to=[{"target": "src/core/event_bus.py", "edge": "depends_on"}]
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
This ensures future agents can find it via `get_node()` and `get_impact()`.
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Session End Protocol (MANDATORY)
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
1. IF changeset active:
|
|
81
|
+
- For each file completed: update_changeset_progress(id, file)
|
|
82
|
+
- If all files done: complete_changeset(id, decisions=[...])
|
|
83
|
+
- If session ending early: update_changeset_progress(id, last_file, blocker="reason")
|
|
84
|
+
|
|
85
|
+
2. For each file modified:
|
|
86
|
+
update_node(file_path, {
|
|
87
|
+
"last_changed_by": "Phase N — brief description of what changed",
|
|
88
|
+
"new_rules": ["any new invariants discovered"],
|
|
89
|
+
"new_connections": [{"target": "other/file.py", "edge": "depends_on"}],
|
|
90
|
+
"key_functions": ["new_public_fn"],
|
|
91
|
+
"stability": "high",
|
|
92
|
+
"new_tests": ["tests/unit/test_new.py"],
|
|
93
|
+
})
|
|
94
|
+
|
|
95
|
+
3. update_next_action("exact description of what needs to happen next")
|
|
96
|
+
|
|
97
|
+
4. If new work was discovered:
|
|
98
|
+
add_phase(phase=N, name="...", description="...", priority="medium")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
## Playbook References
|
|
104
|
+
|
|
105
|
+
| Task type | Read this rule file |
|
|
106
|
+
|---|---|
|
|
107
|
+
| Adding an MCP tool | `rules/coding-standards.md` + `rules/testing-standards.md` |
|
|
108
|
+
| Adding a service | `rules/resilience-observability.md` |
|
|
109
|
+
| Modifying imports | Review your project's layer/import rules |
|
|
110
|
+
| Writing tests | `rules/testing-standards.md` |
|
|
111
|
+
| Committing | `rules/git_commits.md` |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# Documenter Agent
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
Write session state back to the project memory. Runs at the END of every session.
|
|
5
|
+
Minimal AI tokens — mostly structured YAML writes via MCP tools.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## When You Are Invoked
|
|
10
|
+
|
|
11
|
+
Always. Last agent in every pipeline, regardless of task type.
|
|
12
|
+
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
## MCP Tools Used
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
complete_changeset(id, decisions) → if a changeset was active
|
|
19
|
+
update_node(file_path, changes) → for each file modified
|
|
20
|
+
update_next_action(next_action) → always
|
|
21
|
+
update_phase_status(status) → if phase status changed
|
|
22
|
+
add_phase(phase, name, description) → if new follow-up work was discovered
|
|
23
|
+
complete_phase(phase_number, decisions) → if the current phase is fully done
|
|
24
|
+
write_session_log(...) → always, at the very end
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Session End Protocol
|
|
30
|
+
|
|
31
|
+
### 1. Complete Active Changeset (if any)
|
|
32
|
+
```
|
|
33
|
+
complete_changeset(
|
|
34
|
+
changeset_id="<id>",
|
|
35
|
+
decisions=[
|
|
36
|
+
"brief statement of each key decision made",
|
|
37
|
+
"e.g. 'threshold set to 0.85 based on empirical testing'"
|
|
38
|
+
]
|
|
39
|
+
)
|
|
40
|
+
```
|
|
41
|
+
Only call if ALL files in the changeset are done.
|
|
42
|
+
If session ending early with unfinished files:
|
|
43
|
+
```
|
|
44
|
+
update_changeset_progress(id, last_file_done, blocker="reason session ended")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
### 2. Update Each Modified Graph Node
|
|
48
|
+
```
|
|
49
|
+
update_node(
|
|
50
|
+
file_path="<relative path>",
|
|
51
|
+
changes={
|
|
52
|
+
"last_changed_by": "Phase N — brief description",
|
|
53
|
+
"new_rules": ["any NEW invariant discovered during this session"],
|
|
54
|
+
"new_connections": [{"target": "other/file.py", "edge": "depends_on"}],
|
|
55
|
+
"key_functions": ["new_function_added"],
|
|
56
|
+
"stability": "high", # only if stability changed
|
|
57
|
+
"new_tests": ["tests/unit/test_new.py"],
|
|
58
|
+
"do_not_revert": True # only if a critical decision was made
|
|
59
|
+
}
|
|
60
|
+
)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### 3. Update Roadmap
|
|
64
|
+
|
|
65
|
+
Always update next action:
|
|
66
|
+
```
|
|
67
|
+
update_next_action(
|
|
68
|
+
"Exact description of what needs to happen next — specific enough for a fresh agent"
|
|
69
|
+
)
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
If this session unblocked or started the current phase:
|
|
73
|
+
```
|
|
74
|
+
update_phase_status(status="in_progress")
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
If new work was discovered during this session:
|
|
78
|
+
```
|
|
79
|
+
add_phase(
|
|
80
|
+
phase=N,
|
|
81
|
+
name="Discovered work item",
|
|
82
|
+
description="Why this is needed and what it involves",
|
|
83
|
+
priority="medium"
|
|
84
|
+
)
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
If the current phase is fully complete:
|
|
88
|
+
```
|
|
89
|
+
complete_phase(
|
|
90
|
+
phase_number=N,
|
|
91
|
+
key_decisions=["decision 1", "decision 2"]
|
|
92
|
+
)
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### 4. Write Session Log via MCP
|
|
96
|
+
```
|
|
97
|
+
write_session_log(
|
|
98
|
+
session_id="<first 8 chars of a UUID>",
|
|
99
|
+
task="<developer's original prompt>",
|
|
100
|
+
phase="<current phase number>",
|
|
101
|
+
files_changed=["src/services/generator.py"],
|
|
102
|
+
decisions=[
|
|
103
|
+
{
|
|
104
|
+
"file_path": "src/services/generator.py",
|
|
105
|
+
"decision": "key decision 1",
|
|
106
|
+
"context": "Why we made this decision"
|
|
107
|
+
}
|
|
108
|
+
],
|
|
109
|
+
next_steps=["<what the next agent should do>"]
|
|
110
|
+
)
|
|
111
|
+
```
|
|
112
|
+
|
|
113
|
+
This writes to SQLite Memory and feeds `search_decisions()`.
|
|
114
|
+
|
|
115
|
+
---
|
|
116
|
+
|
|
117
|
+
## Log Directory Convention
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
.codevira/logs/
|
|
121
|
+
2025-01-15/
|
|
122
|
+
session-a1b2c3d4.yaml
|
|
123
|
+
session-e5f6g7h8.yaml
|
|
124
|
+
2025-01-16/
|
|
125
|
+
session-...yaml
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
One directory per day, one file per session.
|
|
129
|
+
|
|
130
|
+
---
|
|
131
|
+
|
|
132
|
+
## What You Do NOT Do
|
|
133
|
+
|
|
134
|
+
- Do NOT rewrite or summarize code
|
|
135
|
+
- Do NOT add comments or docstrings
|
|
136
|
+
- Do NOT update files not in the changeset
|
|
137
|
+
- Do NOT write verbose prose — keep decisions concise (1 sentence each)
|
|
138
|
+
- Do NOT write the session log manually — always use `write_session_log()` MCP tool
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
# Orchestrator — Agent Routing Logic
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
Determine which agents to invoke based on the nature of the developer's prompt.
|
|
5
|
+
This is not a separate process — it is the decision logic that any agent (Claude Code, Cursor, Windsurf)
|
|
6
|
+
should follow at the start of a session before doing any work.
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Step 1: Classify the Task
|
|
11
|
+
|
|
12
|
+
Read the developer prompt and classify it:
|
|
13
|
+
|
|
14
|
+
| Signal | Task Type |
|
|
15
|
+
|---|---|
|
|
16
|
+
| "fix", "bug", "broken", "error" | `small_fix` or `medium_change` |
|
|
17
|
+
| "add", "implement", "new feature" | `medium_change` or `large_change` |
|
|
18
|
+
| "refactor", "restructure", "redesign" | `large_change` |
|
|
19
|
+
| "review", "audit", "check" | `parallel_review` |
|
|
20
|
+
| "explain", "what does", "how does" | `research` (no code changes) |
|
|
21
|
+
| Affects 1 file, stability=high | `small_fix` |
|
|
22
|
+
| Affects 2–4 files | `medium_change` |
|
|
23
|
+
| Affects 5+ files or crosses service boundaries | `large_change` |
|
|
24
|
+
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
## Step 2: Select Agent Pipeline
|
|
28
|
+
|
|
29
|
+
### `small_fix` (1 file, low blast radius)
|
|
30
|
+
```
|
|
31
|
+
Developer → Tester → Documenter
|
|
32
|
+
```
|
|
33
|
+
Token budget: ~900 overhead
|
|
34
|
+
|
|
35
|
+
### `medium_change` (2–4 files, known blast radius)
|
|
36
|
+
```
|
|
37
|
+
Developer → Reviewer → Tester → Builder → Documenter
|
|
38
|
+
```
|
|
39
|
+
Token budget: ~1,400 overhead
|
|
40
|
+
|
|
41
|
+
### `large_change` (phase-level, cross-service, or uncertain scope)
|
|
42
|
+
```
|
|
43
|
+
Planner → Developer → Reviewer → Tester → Builder → Documenter
|
|
44
|
+
```
|
|
45
|
+
Token budget: ~2,000 overhead
|
|
46
|
+
|
|
47
|
+
### `parallel_review` (audit a module or multiple files)
|
|
48
|
+
```
|
|
49
|
+
[Agent A: file group 1] + [Agent B: file group 2] ← parallel
|
|
50
|
+
→ Documenter
|
|
51
|
+
```
|
|
52
|
+
Token budget: ~800 per parallel agent
|
|
53
|
+
|
|
54
|
+
### `research` (no code changes)
|
|
55
|
+
```
|
|
56
|
+
No agents — just MCP tool calls:
|
|
57
|
+
get_node() + get_impact() + search_codebase() + search_decisions()
|
|
58
|
+
```
|
|
59
|
+
Token budget: ~400
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Step 3: MCP Tools Per Stage
|
|
64
|
+
|
|
65
|
+
Every agent in the pipeline calls only the MCP tools it needs:
|
|
66
|
+
|
|
67
|
+
| Agent | MCP Tools | Shell Commands |
|
|
68
|
+
|---|---|---|
|
|
69
|
+
| Planner | get_full_roadmap, get_impact, list_nodes, search_codebase, search_decisions, add_phase | — |
|
|
70
|
+
| Developer | list_open_changesets, get_roadmap, get_node, get_impact, search_codebase, search_decisions, start_changeset, refresh_index, add_node | — |
|
|
71
|
+
| Reviewer | get_node, get_playbook | — |
|
|
72
|
+
| Tester | get_node (for tests field), list_nodes | project test command |
|
|
73
|
+
| Builder | — | linter, type checker, optional architecture verifier |
|
|
74
|
+
| Documenter | complete_changeset, update_node, update_next_action, update_phase_status, add_phase, write_session_log | — |
|
|
75
|
+
|
|
76
|
+
---
|
|
77
|
+
|
|
78
|
+
## Step 4: Reviewer Trigger Conditions
|
|
79
|
+
|
|
80
|
+
Reviewer is NOT always needed. Trigger it when ANY of:
|
|
81
|
+
- The file's graph node has `stability: high`
|
|
82
|
+
- The file's graph node has `do_not_revert: true`
|
|
83
|
+
- The file's graph node has non-empty `rules`
|
|
84
|
+
- The change affects a schema or event payload
|
|
85
|
+
- The change touches `.codevira/graph/` or `.codevira/roadmap.yaml`
|
|
86
|
+
|
|
87
|
+
Skip reviewer for: scaffolding, new test files, config-only changes, docs.
|
|
88
|
+
|
|
89
|
+
---
|
|
90
|
+
|
|
91
|
+
## Escalation Rule
|
|
92
|
+
|
|
93
|
+
If at any point the blast radius (`get_impact` result) returns more files than expected for the task type, escalate:
|
|
94
|
+
- `small_fix` → blast_radius > 3 → escalate to `medium_change`
|
|
95
|
+
- `medium_change` → blast_radius > 7 → escalate to `large_change`
|
|
96
|
+
- `large_change` → blast_radius > 15 → stop, call `get_roadmap` and document the scope before proceeding
|