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.
@@ -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,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (84.0.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ codevoight = codevoight.cli:main
@@ -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