codevoight 0.1.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.
- codevoight/__init__.py +8 -0
- codevoight/cli.py +219 -0
- codevoight/core/config.py +51 -0
- codevoight/core/diff.py +51 -0
- codevoight/core/models.py +82 -0
- codevoight/core/security.py +68 -0
- codevoight/db/repository.py +221 -0
- codevoight/db/schema.py +40 -0
- codevoight/generators/base.py +20 -0
- codevoight/generators/deterministic.py +216 -0
- codevoight/generators/llm.py +33 -0
- codevoight/git/hook.py +113 -0
- codevoight/git/runner.py +57 -0
- codevoight/server.py +209 -0
- codevoight-0.1.0.dist-info/METADATA +191 -0
- codevoight-0.1.0.dist-info/RECORD +20 -0
- codevoight-0.1.0.dist-info/WHEEL +5 -0
- codevoight-0.1.0.dist-info/entry_points.txt +2 -0
- codevoight-0.1.0.dist-info/licenses/LICENSE +21 -0
- codevoight-0.1.0.dist-info/top_level.txt +1 -0
codevoight/git/runner.py
ADDED
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import subprocess
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import List, Optional, Tuple
|
|
6
|
+
|
|
7
|
+
from codevoight.core.diff import normalize_text
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class GitRunner:
|
|
11
|
+
def __init__(self, repo_path: Optional[Path] = None):
|
|
12
|
+
self.repo_path = repo_path or Path.cwd()
|
|
13
|
+
|
|
14
|
+
def _run(self, args: List[str]) -> Tuple[int, str, str]:
|
|
15
|
+
cmd = ["git"] + args
|
|
16
|
+
proc = subprocess.run(
|
|
17
|
+
cmd,
|
|
18
|
+
cwd=str(self.repo_path),
|
|
19
|
+
capture_output=True,
|
|
20
|
+
text=True,
|
|
21
|
+
encoding="utf-8",
|
|
22
|
+
errors="replace",
|
|
23
|
+
)
|
|
24
|
+
return proc.returncode, proc.stdout, proc.stderr
|
|
25
|
+
|
|
26
|
+
def is_git_repo(self) -> bool:
|
|
27
|
+
code, stdout, _ = self._run(["rev-parse", "--is-inside-work-tree"])
|
|
28
|
+
return code == 0 and "true" in stdout.strip().lower()
|
|
29
|
+
|
|
30
|
+
def get_repo_root(self) -> Optional[Path]:
|
|
31
|
+
code, stdout, _ = self._run(["rev-parse", "--show-toplevel"])
|
|
32
|
+
if code == 0 and stdout.strip():
|
|
33
|
+
return Path(stdout.strip()).resolve()
|
|
34
|
+
return None
|
|
35
|
+
|
|
36
|
+
def get_staged_diff(self) -> str:
|
|
37
|
+
"""Get git diff of cached/staged files normalized."""
|
|
38
|
+
code, stdout, _ = self._run(["diff", "--cached"])
|
|
39
|
+
if code == 0:
|
|
40
|
+
return normalize_text(stdout)
|
|
41
|
+
return ""
|
|
42
|
+
|
|
43
|
+
def get_working_diff(self) -> str:
|
|
44
|
+
"""Get git diff against HEAD."""
|
|
45
|
+
code, stdout, _ = self._run(["diff", "HEAD"])
|
|
46
|
+
if code == 0:
|
|
47
|
+
return normalize_text(stdout)
|
|
48
|
+
return ""
|
|
49
|
+
|
|
50
|
+
def stage_files(self, paths: List[str]) -> bool:
|
|
51
|
+
code, _, _ = self._run(["add"] + paths)
|
|
52
|
+
return code == 0
|
|
53
|
+
|
|
54
|
+
def commit(self, message: str) -> Tuple[bool, str]:
|
|
55
|
+
code, stdout, stderr = self._run(["commit", "-m", message])
|
|
56
|
+
output = stdout if code == 0 else f"{stderr}\n{stdout}".strip()
|
|
57
|
+
return code == 0, output
|
codevoight/server.py
ADDED
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import os
|
|
5
|
+
import time
|
|
6
|
+
import uuid
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Any, Dict, List, Optional
|
|
9
|
+
|
|
10
|
+
try:
|
|
11
|
+
from mcp.server.mcpserver import MCPServer
|
|
12
|
+
except ImportError:
|
|
13
|
+
from mcp.server.fastmcp import FastMCP as MCPServer # type: ignore
|
|
14
|
+
|
|
15
|
+
from codevoight.core.diff import compute_diff_hash, normalize_text
|
|
16
|
+
from codevoight.core.models import CapabilityToken, SessionStatus, StagedSession
|
|
17
|
+
from codevoight.core.security import get_or_create_secret, verify_token
|
|
18
|
+
from codevoight.db.repository import StateRepository
|
|
19
|
+
from codevoight.generators.deterministic import DeterministicQuizGenerator
|
|
20
|
+
from codevoight.git.runner import GitRunner
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def create_server(workspace_root: Optional[Path] = None) -> MCPServer:
|
|
24
|
+
ws_env = (
|
|
25
|
+
os.getenv("CODEVOIGHT_WORKSPACE")
|
|
26
|
+
or os.getenv("QUIZGATE_WORKSPACE")
|
|
27
|
+
or os.getenv("CODEGATE_WORKSPACE")
|
|
28
|
+
or os.getenv("WORKSPACE")
|
|
29
|
+
)
|
|
30
|
+
if ws_env:
|
|
31
|
+
root = Path(ws_env).resolve()
|
|
32
|
+
else:
|
|
33
|
+
root = (workspace_root or GitRunner().get_repo_root() or Path.cwd()).resolve()
|
|
34
|
+
|
|
35
|
+
codevoight_dir = root / ".codevoight"
|
|
36
|
+
if not codevoight_dir.exists():
|
|
37
|
+
if (root / ".quizgate").exists():
|
|
38
|
+
codevoight_dir = root / ".quizgate"
|
|
39
|
+
elif (root / ".codegate").exists():
|
|
40
|
+
codevoight_dir = root / ".codegate"
|
|
41
|
+
|
|
42
|
+
db_path = codevoight_dir / "state.db"
|
|
43
|
+
|
|
44
|
+
repo = StateRepository(db_path)
|
|
45
|
+
git_runner = GitRunner(root)
|
|
46
|
+
generator = DeterministicQuizGenerator()
|
|
47
|
+
|
|
48
|
+
server = MCPServer(
|
|
49
|
+
name="codevoight",
|
|
50
|
+
instructions="CodeVoight: Voight-Kampff human comprehension gate for AI code changes before Git commits.",
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
@server.tool()
|
|
54
|
+
def stage_code_change(files: List[Dict[str, str]], description: str = "") -> str:
|
|
55
|
+
"""Stage one or more file changes into the workspace and register a human comprehension quiz.
|
|
56
|
+
|
|
57
|
+
Args:
|
|
58
|
+
files: List of file dictionaries with keys 'file_path' and 'content'.
|
|
59
|
+
description: Short summary of what this change accomplishes.
|
|
60
|
+
"""
|
|
61
|
+
if not files:
|
|
62
|
+
return "[ERROR] No files provided to stage."
|
|
63
|
+
|
|
64
|
+
staged_paths = []
|
|
65
|
+
for f in files:
|
|
66
|
+
rel_path = f.get("file_path")
|
|
67
|
+
content = f.get("content")
|
|
68
|
+
if not rel_path or content is None:
|
|
69
|
+
continue
|
|
70
|
+
|
|
71
|
+
target_file = root / rel_path
|
|
72
|
+
target_file.parent.mkdir(parents=True, exist_ok=True)
|
|
73
|
+
target_file.write_text(content, encoding="utf-8")
|
|
74
|
+
staged_paths.append(rel_path)
|
|
75
|
+
|
|
76
|
+
if not staged_paths:
|
|
77
|
+
return "[ERROR] No valid file paths provided."
|
|
78
|
+
|
|
79
|
+
git_runner.stage_files(staged_paths)
|
|
80
|
+
staged_diff = git_runner.get_staged_diff()
|
|
81
|
+
if not staged_diff.strip():
|
|
82
|
+
return "[WARNING] Changes were written to disk, but no git diff was produced."
|
|
83
|
+
|
|
84
|
+
diff_hash = compute_diff_hash(staged_diff)
|
|
85
|
+
session_id = str(uuid.uuid4())[:8]
|
|
86
|
+
|
|
87
|
+
questions = generator.generate_quiz(staged_diff, description)
|
|
88
|
+
|
|
89
|
+
now = time.time()
|
|
90
|
+
session = StagedSession(
|
|
91
|
+
session_id=session_id,
|
|
92
|
+
diff_text=staged_diff,
|
|
93
|
+
diff_hash=diff_hash,
|
|
94
|
+
description=description,
|
|
95
|
+
status=SessionStatus.PENDING_QUIZ,
|
|
96
|
+
created_at=now,
|
|
97
|
+
expires_at=now + 1800.0,
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
repo.create_session(session, questions)
|
|
101
|
+
|
|
102
|
+
return (
|
|
103
|
+
f"[CODEVOIGHT: CHANGES STAGED]\n"
|
|
104
|
+
f"Session ID : {session_id}\n"
|
|
105
|
+
f"Diff Hash : {diff_hash[:16]}...\n"
|
|
106
|
+
f"Files : {', '.join(staged_paths)}\n"
|
|
107
|
+
f"Questions : {len(questions)} generated\n\n"
|
|
108
|
+
f"ACTION REQUIRED:\n"
|
|
109
|
+
f"Tell the developer: 'Please run \"codevoight quiz {session_id}\" in your terminal "
|
|
110
|
+
f"to verify your understanding and unlock the commit gate.'"
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
@server.tool()
|
|
114
|
+
def get_gate_status(session_id: str = "") -> str:
|
|
115
|
+
"""Check the authorization status of a CodeVoight staging session.
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
session_id: Optional session identifier. If omitted, checks latest pending or current staged diff.
|
|
119
|
+
"""
|
|
120
|
+
if session_id:
|
|
121
|
+
session = repo.get_session(session_id)
|
|
122
|
+
else:
|
|
123
|
+
session = repo.get_latest_pending_session()
|
|
124
|
+
if not session:
|
|
125
|
+
staged_diff = git_runner.get_staged_diff()
|
|
126
|
+
if staged_diff:
|
|
127
|
+
d_hash = compute_diff_hash(staged_diff)
|
|
128
|
+
session = repo.find_valid_approved_session(d_hash)
|
|
129
|
+
|
|
130
|
+
if not session:
|
|
131
|
+
return json.dumps({
|
|
132
|
+
"status": "NOT_FOUND",
|
|
133
|
+
"can_commit": False,
|
|
134
|
+
"message": "No active or pending staging session found."
|
|
135
|
+
}, indent=2)
|
|
136
|
+
|
|
137
|
+
now = time.time()
|
|
138
|
+
is_expired = now > session.expires_at
|
|
139
|
+
status_val = "EXPIRED" if is_expired else session.status.value
|
|
140
|
+
can_commit = (status_val == SessionStatus.APPROVED.value) and not is_expired
|
|
141
|
+
|
|
142
|
+
return json.dumps({
|
|
143
|
+
"session_id": session.session_id,
|
|
144
|
+
"status": status_val,
|
|
145
|
+
"diff_hash": session.diff_hash,
|
|
146
|
+
"can_commit": can_commit,
|
|
147
|
+
"expires_in_seconds": max(0, int(session.expires_at - now)),
|
|
148
|
+
}, indent=2)
|
|
149
|
+
|
|
150
|
+
@server.tool()
|
|
151
|
+
def commit_staged(session_id: str, commit_message: str) -> str:
|
|
152
|
+
"""Commit staged changes to Git if and only if the human developer has passed the comprehension quiz.
|
|
153
|
+
|
|
154
|
+
Args:
|
|
155
|
+
session_id: The session ID of the approved staging session.
|
|
156
|
+
commit_message: The commit message for git commit.
|
|
157
|
+
"""
|
|
158
|
+
session = repo.get_session(session_id)
|
|
159
|
+
if not session:
|
|
160
|
+
return f"[ACCESS DENIED] Staging session '{session_id}' not found."
|
|
161
|
+
|
|
162
|
+
if session.status != SessionStatus.APPROVED:
|
|
163
|
+
return (
|
|
164
|
+
f"[ACCESS DENIED] Session '{session_id}' status is '{session.status.value}'. "
|
|
165
|
+
f"The human developer must run 'codevoight quiz {session_id}' to pass verification before committing."
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
if not session.capability_token:
|
|
169
|
+
return "[ACCESS DENIED] No capability token found for this session."
|
|
170
|
+
|
|
171
|
+
secret_key = get_or_create_secret(codevoight_dir)
|
|
172
|
+
try:
|
|
173
|
+
token = CapabilityToken.parse(session.capability_token)
|
|
174
|
+
except Exception as e:
|
|
175
|
+
return f"[ACCESS DENIED] Corrupted capability token: {e}"
|
|
176
|
+
|
|
177
|
+
current_staged_diff = git_runner.get_staged_diff()
|
|
178
|
+
if not current_staged_diff:
|
|
179
|
+
return "[ACCESS DENIED] Working tree has no staged git changes to commit."
|
|
180
|
+
|
|
181
|
+
current_diff_hash = compute_diff_hash(current_staged_diff)
|
|
182
|
+
|
|
183
|
+
# Constant time cryptographic verification
|
|
184
|
+
if not verify_token(token, current_diff_hash, secret_key):
|
|
185
|
+
return (
|
|
186
|
+
"[ACCESS DENIED] Capability token invalid, expired, or staged git diff modified after quiz! "
|
|
187
|
+
"You must re-stage and pass a new quiz."
|
|
188
|
+
)
|
|
189
|
+
|
|
190
|
+
# Execute commit
|
|
191
|
+
success, output = git_runner.commit(commit_message)
|
|
192
|
+
if not success:
|
|
193
|
+
return f"[COMMIT FAILED] Git error:\n{output}"
|
|
194
|
+
|
|
195
|
+
# Consume token to prevent replay
|
|
196
|
+
repo.consume_session_token(session_id)
|
|
197
|
+
|
|
198
|
+
return f"[COMMIT SUCCESSFUL] CodeVoight gate passed!\n{output}"
|
|
199
|
+
|
|
200
|
+
return server
|
|
201
|
+
|
|
202
|
+
|
|
203
|
+
def main() -> None:
|
|
204
|
+
server = create_server()
|
|
205
|
+
server.run(transport="stdio")
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
if __name__ == "__main__":
|
|
209
|
+
main()
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: codevoight
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Voight-Kampff Human Comprehension Gate for AI Coding Agents and Git Commits
|
|
5
|
+
Author: CodeVoight Contributors
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/bibinwithab/codevoight
|
|
8
|
+
Project-URL: Repository, https://github.com/bibinwithab/codevoight
|
|
9
|
+
Project-URL: Issues, https://github.com/bibinwithab/codevoight/issues
|
|
10
|
+
Keywords: mcp,git,ai,commit-gate,voight-kampff,comprehension,cursor,antigravity
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
19
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
20
|
+
Classifier: Topic :: Security
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
Requires-Dist: mcp>=1.2.0
|
|
25
|
+
Requires-Dist: pydantic>=2.6.0
|
|
26
|
+
Requires-Dist: click>=8.1.0
|
|
27
|
+
Provides-Extra: dev
|
|
28
|
+
Requires-Dist: pytest>=8.0.0; extra == "dev"
|
|
29
|
+
Requires-Dist: pytest-asyncio>=0.23.0; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=1.0.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=5.0.0; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
# CodeVoight
|
|
35
|
+
|
|
36
|
+
> Voight-Kampff Human Comprehension Gate for AI Coding Agents and Git Commits
|
|
37
|
+
|
|
38
|
+
**CodeVoight** (inspired by the Blade Runner Voight-Kampff interrogation test that distinguishes humans from synthetic AI) guarantees human comprehension before AI coding agents commit code. It intercepts changes into a staged verification buffer and physically blocks Git commits until the developer reviews the diff and passes an interactive comprehension quiz via the terminal.
|
|
39
|
+
|
|
40
|
+
---
|
|
41
|
+
|
|
42
|
+
## 1. Why CodeVoight?
|
|
43
|
+
|
|
44
|
+
Autonomous AI coding agents in modern IDEs (Cursor, Windsurf, Cline, Antigravity) write code directly to disk and create Git commits without verifying whether the human developer understands what changed.
|
|
45
|
+
|
|
46
|
+
CodeVoight enforces a **strict, fail-closed comprehension gate**:
|
|
47
|
+
1. **Agent Stages Changes**: The AI agent calls CodeVoight's MCP tool `stage_code_change`.
|
|
48
|
+
2. **Deterministic Quiz Generated**: CodeVoight analyzes the staged diff (AST changes, modified functions, error handling, touched files) and generates comprehension questions.
|
|
49
|
+
3. **Out-of-Band Human Verification**: To prevent the AI agent from answering its own quiz, the quiz must be completed by the developer via the terminal (`codevoight quiz`).
|
|
50
|
+
4. **Cryptographic Capability Token**: Upon passing the quiz with 100% score, an HMAC-SHA256 capability token is generated and bound to the exact SHA-256 hash of the staged diff.
|
|
51
|
+
5. **Physical Enforcement**: Git commits are gated both at the MCP tool level (`commit_staged`) and at the Git repository level via a `.git/hooks/pre-commit` hook. If the staged diff changes or lacks an approved token, Git aborts the commit.
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## 2. Architecture
|
|
56
|
+
|
|
57
|
+
```mermaid
|
|
58
|
+
flowchart TD
|
|
59
|
+
subgraph Agent["1. AI Agent (Cursor / Antigravity / Cline)"]
|
|
60
|
+
A["Calls MCP Tool: stage_code_change()"]
|
|
61
|
+
AC["Calls MCP Tool: commit_staged()"]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
subgraph Storage["2. Shared State (.codevoight/state.db - SQLite WAL)"]
|
|
65
|
+
DB[("Session & Quiz Store\n- Session ID & Diff Hash\n- Status: PENDING / APPROVED\n- HMAC Capability Token")]
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
subgraph Human["3. Human Developer (Terminal CLI)"]
|
|
69
|
+
H["Runs: codevoight quiz [session_id]"]
|
|
70
|
+
Q["Reviews Diff & Answers Quiz"]
|
|
71
|
+
T["100% Score: Generates Capability Token"]
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
subgraph Gate["4. Dual Enforcement Gate"]
|
|
75
|
+
P1["Path A: MCP commit_staged()"]
|
|
76
|
+
P2["Path B: Git Hook (.git/hooks/pre-commit)"]
|
|
77
|
+
GIT[("Git Repository Commit")]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
A -->|"1. Writes staged diff & quiz"| DB
|
|
81
|
+
DB -->|"2. Fetches questions"| H
|
|
82
|
+
H --> Q --> T
|
|
83
|
+
T -->|"3. Stores APPROVED token"| DB
|
|
84
|
+
|
|
85
|
+
AC -->|"Verify session token"| P1
|
|
86
|
+
DB -.->|"Validate token"| P1
|
|
87
|
+
P1 -->|"Pass"| GIT
|
|
88
|
+
|
|
89
|
+
H -.->|"Direct git commit"| P2
|
|
90
|
+
DB -.->|"Validate diff hash"| P2
|
|
91
|
+
P2 -->|"Pass"| GIT
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
---
|
|
95
|
+
|
|
96
|
+
## 3. Installation & Setup
|
|
97
|
+
|
|
98
|
+
### Prerequisites
|
|
99
|
+
* Python 3.10+
|
|
100
|
+
* Git 2.20+
|
|
101
|
+
|
|
102
|
+
### Install CodeVoight
|
|
103
|
+
```powershell
|
|
104
|
+
# Clone or navigate to the repository
|
|
105
|
+
cd C:\Users\jbibingraceson\projects\codegate
|
|
106
|
+
|
|
107
|
+
# Create virtual environment and install in editable mode
|
|
108
|
+
python -m venv .venv
|
|
109
|
+
.\.venv\Scripts\pip.exe install -e .[dev]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### Install Git Pre-Commit Hook
|
|
113
|
+
In your project repository, install the physical commit verification hook:
|
|
114
|
+
```powershell
|
|
115
|
+
.\.venv\Scripts\codevoight.exe hook install
|
|
116
|
+
```
|
|
117
|
+
This writes `.git/hooks/pre-commit`, ensuring that any `git commit` requires a valid quiz approval token.
|
|
118
|
+
|
|
119
|
+
---
|
|
120
|
+
|
|
121
|
+
## 4. IDE MCP Configuration
|
|
122
|
+
|
|
123
|
+
### Cursor
|
|
124
|
+
Add to `.cursor/mcp.json` or your Cursor user settings:
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"codevoight": {
|
|
129
|
+
"command": "python",
|
|
130
|
+
"args": ["-m", "codevoight.server"]
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
### VS Code (Cline / Roo Code)
|
|
137
|
+
Add to your Cline MCP settings:
|
|
138
|
+
```json
|
|
139
|
+
{
|
|
140
|
+
"mcpServers": {
|
|
141
|
+
"codevoight": {
|
|
142
|
+
"command": "python",
|
|
143
|
+
"args": ["-m", "codevoight.server"],
|
|
144
|
+
"disabled": false,
|
|
145
|
+
"autoApprove": []
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Google Antigravity
|
|
152
|
+
Add to your Antigravity MCP settings:
|
|
153
|
+
```json
|
|
154
|
+
{
|
|
155
|
+
"mcpServers": {
|
|
156
|
+
"codevoight": {
|
|
157
|
+
"command": "python",
|
|
158
|
+
"args": ["-m", "codevoight.server"]
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
---
|
|
165
|
+
|
|
166
|
+
## 5. CLI Commands
|
|
167
|
+
|
|
168
|
+
| Command | Description |
|
|
169
|
+
| :--- | :--- |
|
|
170
|
+
| `codevoight quiz [session_id]` | Interactive terminal quiz to review diff and unlock commit |
|
|
171
|
+
| `codevoight status` | View active and pending staging sessions |
|
|
172
|
+
| `codevoight stage <files...>` | Manually stage files and generate a comprehension quiz |
|
|
173
|
+
| `codevoight hook install` | Install the `.git/hooks/pre-commit` verification gate |
|
|
174
|
+
| `codevoight hook verify` | Test current staged Git changes against CodeVoight state |
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## 6. Verification & Test Suite
|
|
179
|
+
|
|
180
|
+
Run the full automated test suite:
|
|
181
|
+
```powershell
|
|
182
|
+
.\.venv\Scripts\pytest.exe -v
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The test suite covers:
|
|
186
|
+
* HMAC capability token generation, tampering detection, and TTL expiry.
|
|
187
|
+
* Cross-platform diff normalization and SHA-256 hash consistency.
|
|
188
|
+
* SQLite WAL concurrency and transaction rollback.
|
|
189
|
+
* Deterministic AST diff question generation and grading.
|
|
190
|
+
* End-to-end Git pre-commit hook enforcement.
|
|
191
|
+
* MCP stdio tool execution, blocking unapproved commits and passing approved commits.
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
codevoight/__init__.py,sha256=nxhB6FI9nhxgWX4TTFgBGqT5gMSBtfw446xX5twfS4Y,206
|
|
2
|
+
codevoight/cli.py,sha256=0Szw6c15vU1UJS4hIKnQmOCqMTqxNzCSdeXjSa0539M,8016
|
|
3
|
+
codevoight/server.py,sha256=guhCvawxuV1fkxobjU_y_8Cqlxh0iylYSGr0TtDb3S0,7564
|
|
4
|
+
codevoight/core/config.py,sha256=C6Upas5OV8NGmb6Dk99CcaeB0-Rp5Jdqd2pigv1GF3s,1525
|
|
5
|
+
codevoight/core/diff.py,sha256=5PUY_aPQZhavBdLvWt0q7c3dgFat839TD84UUyc8QXg,1594
|
|
6
|
+
codevoight/core/models.py,sha256=S_dM8iGSULXFiSfBNk-yKV825iuoqDJfrl0xAsZbMt0,1862
|
|
7
|
+
codevoight/core/security.py,sha256=lEqgwbumy8wmw97TYO_2x9jE3NHOJw3YWbdDILLnQGo,2197
|
|
8
|
+
codevoight/db/repository.py,sha256=QMiOMikRQzJJAB14qq4ns6NbI94-bF-3MtS-a_p-4AY,8071
|
|
9
|
+
codevoight/db/schema.py,sha256=FZPbOJ_qNejfwBOtSCBOtMSTxoMPoj5ujypUWRgm59g,1133
|
|
10
|
+
codevoight/generators/base.py,sha256=x5Bkov8u3s1OsIs_AIhNZ3AJgVHnviX3gB3IpsYMOzw,661
|
|
11
|
+
codevoight/generators/deterministic.py,sha256=rAfTi4XO-HsKDGN8jJ66y2v2YqxPkOrn4gcK-GBLl-A,9026
|
|
12
|
+
codevoight/generators/llm.py,sha256=9vxqkruV2H_wLpEr3oGUTeXpbYs9eV96OnDdB0bXw6I,1606
|
|
13
|
+
codevoight/git/hook.py,sha256=I_bGPUk3RZKTQShCd6Ifb3DNmvwyp9TqxignwwLlkEY,3699
|
|
14
|
+
codevoight/git/runner.py,sha256=aamIDtVcP03s1L7Hbay2Imz0q2-E9zS-ubjzpIyer0U,1872
|
|
15
|
+
codevoight-0.1.0.dist-info/licenses/LICENSE,sha256=xu7qH6sbLy1Jk7HqTSZqDOZa3ppuTq55C56eAbR_vlA,1071
|
|
16
|
+
codevoight-0.1.0.dist-info/METADATA,sha256=FE_jMFjK3v9klfHkhECSb0DYybUsgDVzVi5NzXkD8uI,6460
|
|
17
|
+
codevoight-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
18
|
+
codevoight-0.1.0.dist-info/entry_points.txt,sha256=FsOcm7my2mymEnNOlN_RY3dVE50FHQG4EnYuEAU0nds,51
|
|
19
|
+
codevoight-0.1.0.dist-info/top_level.txt,sha256=SWALPlQFRcU_ypGQXBGmlGiQPrnRpfBg7oOsnVtSXzQ,11
|
|
20
|
+
codevoight-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Bibin Graceson
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
codevoight
|