codevoight 0.1.0__tar.gz
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-0.1.0/LICENSE +21 -0
- codevoight-0.1.0/PKG-INFO +191 -0
- codevoight-0.1.0/README.md +158 -0
- codevoight-0.1.0/pyproject.toml +56 -0
- codevoight-0.1.0/setup.cfg +4 -0
- codevoight-0.1.0/src/codevoight/__init__.py +8 -0
- codevoight-0.1.0/src/codevoight/cli.py +219 -0
- codevoight-0.1.0/src/codevoight/core/config.py +51 -0
- codevoight-0.1.0/src/codevoight/core/diff.py +51 -0
- codevoight-0.1.0/src/codevoight/core/models.py +82 -0
- codevoight-0.1.0/src/codevoight/core/security.py +68 -0
- codevoight-0.1.0/src/codevoight/db/repository.py +221 -0
- codevoight-0.1.0/src/codevoight/db/schema.py +40 -0
- codevoight-0.1.0/src/codevoight/generators/base.py +20 -0
- codevoight-0.1.0/src/codevoight/generators/deterministic.py +216 -0
- codevoight-0.1.0/src/codevoight/generators/llm.py +33 -0
- codevoight-0.1.0/src/codevoight/git/hook.py +113 -0
- codevoight-0.1.0/src/codevoight/git/runner.py +57 -0
- codevoight-0.1.0/src/codevoight/server.py +209 -0
- codevoight-0.1.0/src/codevoight.egg-info/PKG-INFO +191 -0
- codevoight-0.1.0/src/codevoight.egg-info/SOURCES.txt +31 -0
- codevoight-0.1.0/src/codevoight.egg-info/dependency_links.txt +1 -0
- codevoight-0.1.0/src/codevoight.egg-info/entry_points.txt +2 -0
- codevoight-0.1.0/src/codevoight.egg-info/requires.txt +9 -0
- codevoight-0.1.0/src/codevoight.egg-info/top_level.txt +1 -0
- codevoight-0.1.0/tests/test_cli.py +141 -0
- codevoight-0.1.0/tests/test_config.py +45 -0
- codevoight-0.1.0/tests/test_db.py +65 -0
- codevoight-0.1.0/tests/test_diff.py +45 -0
- codevoight-0.1.0/tests/test_generator.py +48 -0
- codevoight-0.1.0/tests/test_git_hook.py +65 -0
- codevoight-0.1.0/tests/test_mcp_server.py +89 -0
- codevoight-0.1.0/tests/test_security.py +62 -0
codevoight-0.1.0/LICENSE
ADDED
|
@@ -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,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,158 @@
|
|
|
1
|
+
# CodeVoight
|
|
2
|
+
|
|
3
|
+
> Voight-Kampff Human Comprehension Gate for AI Coding Agents and Git Commits
|
|
4
|
+
|
|
5
|
+
**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.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## 1. Why CodeVoight?
|
|
10
|
+
|
|
11
|
+
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.
|
|
12
|
+
|
|
13
|
+
CodeVoight enforces a **strict, fail-closed comprehension gate**:
|
|
14
|
+
1. **Agent Stages Changes**: The AI agent calls CodeVoight's MCP tool `stage_code_change`.
|
|
15
|
+
2. **Deterministic Quiz Generated**: CodeVoight analyzes the staged diff (AST changes, modified functions, error handling, touched files) and generates comprehension questions.
|
|
16
|
+
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`).
|
|
17
|
+
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.
|
|
18
|
+
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.
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## 2. Architecture
|
|
23
|
+
|
|
24
|
+
```mermaid
|
|
25
|
+
flowchart TD
|
|
26
|
+
subgraph Agent["1. AI Agent (Cursor / Antigravity / Cline)"]
|
|
27
|
+
A["Calls MCP Tool: stage_code_change()"]
|
|
28
|
+
AC["Calls MCP Tool: commit_staged()"]
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
subgraph Storage["2. Shared State (.codevoight/state.db - SQLite WAL)"]
|
|
32
|
+
DB[("Session & Quiz Store\n- Session ID & Diff Hash\n- Status: PENDING / APPROVED\n- HMAC Capability Token")]
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
subgraph Human["3. Human Developer (Terminal CLI)"]
|
|
36
|
+
H["Runs: codevoight quiz [session_id]"]
|
|
37
|
+
Q["Reviews Diff & Answers Quiz"]
|
|
38
|
+
T["100% Score: Generates Capability Token"]
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
subgraph Gate["4. Dual Enforcement Gate"]
|
|
42
|
+
P1["Path A: MCP commit_staged()"]
|
|
43
|
+
P2["Path B: Git Hook (.git/hooks/pre-commit)"]
|
|
44
|
+
GIT[("Git Repository Commit")]
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
A -->|"1. Writes staged diff & quiz"| DB
|
|
48
|
+
DB -->|"2. Fetches questions"| H
|
|
49
|
+
H --> Q --> T
|
|
50
|
+
T -->|"3. Stores APPROVED token"| DB
|
|
51
|
+
|
|
52
|
+
AC -->|"Verify session token"| P1
|
|
53
|
+
DB -.->|"Validate token"| P1
|
|
54
|
+
P1 -->|"Pass"| GIT
|
|
55
|
+
|
|
56
|
+
H -.->|"Direct git commit"| P2
|
|
57
|
+
DB -.->|"Validate diff hash"| P2
|
|
58
|
+
P2 -->|"Pass"| GIT
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## 3. Installation & Setup
|
|
64
|
+
|
|
65
|
+
### Prerequisites
|
|
66
|
+
* Python 3.10+
|
|
67
|
+
* Git 2.20+
|
|
68
|
+
|
|
69
|
+
### Install CodeVoight
|
|
70
|
+
```powershell
|
|
71
|
+
# Clone or navigate to the repository
|
|
72
|
+
cd C:\Users\jbibingraceson\projects\codegate
|
|
73
|
+
|
|
74
|
+
# Create virtual environment and install in editable mode
|
|
75
|
+
python -m venv .venv
|
|
76
|
+
.\.venv\Scripts\pip.exe install -e .[dev]
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Install Git Pre-Commit Hook
|
|
80
|
+
In your project repository, install the physical commit verification hook:
|
|
81
|
+
```powershell
|
|
82
|
+
.\.venv\Scripts\codevoight.exe hook install
|
|
83
|
+
```
|
|
84
|
+
This writes `.git/hooks/pre-commit`, ensuring that any `git commit` requires a valid quiz approval token.
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## 4. IDE MCP Configuration
|
|
89
|
+
|
|
90
|
+
### Cursor
|
|
91
|
+
Add to `.cursor/mcp.json` or your Cursor user settings:
|
|
92
|
+
```json
|
|
93
|
+
{
|
|
94
|
+
"mcpServers": {
|
|
95
|
+
"codevoight": {
|
|
96
|
+
"command": "python",
|
|
97
|
+
"args": ["-m", "codevoight.server"]
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### VS Code (Cline / Roo Code)
|
|
104
|
+
Add to your Cline MCP settings:
|
|
105
|
+
```json
|
|
106
|
+
{
|
|
107
|
+
"mcpServers": {
|
|
108
|
+
"codevoight": {
|
|
109
|
+
"command": "python",
|
|
110
|
+
"args": ["-m", "codevoight.server"],
|
|
111
|
+
"disabled": false,
|
|
112
|
+
"autoApprove": []
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Google Antigravity
|
|
119
|
+
Add to your Antigravity MCP settings:
|
|
120
|
+
```json
|
|
121
|
+
{
|
|
122
|
+
"mcpServers": {
|
|
123
|
+
"codevoight": {
|
|
124
|
+
"command": "python",
|
|
125
|
+
"args": ["-m", "codevoight.server"]
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## 5. CLI Commands
|
|
134
|
+
|
|
135
|
+
| Command | Description |
|
|
136
|
+
| :--- | :--- |
|
|
137
|
+
| `codevoight quiz [session_id]` | Interactive terminal quiz to review diff and unlock commit |
|
|
138
|
+
| `codevoight status` | View active and pending staging sessions |
|
|
139
|
+
| `codevoight stage <files...>` | Manually stage files and generate a comprehension quiz |
|
|
140
|
+
| `codevoight hook install` | Install the `.git/hooks/pre-commit` verification gate |
|
|
141
|
+
| `codevoight hook verify` | Test current staged Git changes against CodeVoight state |
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## 6. Verification & Test Suite
|
|
146
|
+
|
|
147
|
+
Run the full automated test suite:
|
|
148
|
+
```powershell
|
|
149
|
+
.\.venv\Scripts\pytest.exe -v
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The test suite covers:
|
|
153
|
+
* HMAC capability token generation, tampering detection, and TTL expiry.
|
|
154
|
+
* Cross-platform diff normalization and SHA-256 hash consistency.
|
|
155
|
+
* SQLite WAL concurrency and transaction rollback.
|
|
156
|
+
* Deterministic AST diff question generation and grading.
|
|
157
|
+
* End-to-end Git pre-commit hook enforcement.
|
|
158
|
+
* MCP stdio tool execution, blocking unapproved commits and passing approved commits.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "codevoight"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Voight-Kampff Human Comprehension Gate for AI Coding Agents and Git Commits"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
authors = [
|
|
12
|
+
{ name = "CodeVoight Contributors" }
|
|
13
|
+
]
|
|
14
|
+
license = "MIT"
|
|
15
|
+
keywords = ["mcp", "git", "ai", "commit-gate", "voight-kampff", "comprehension", "cursor", "antigravity"]
|
|
16
|
+
classifiers = [
|
|
17
|
+
"Development Status :: 4 - Beta",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Programming Language :: Python :: 3.10",
|
|
21
|
+
"Programming Language :: Python :: 3.11",
|
|
22
|
+
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
24
|
+
"Programming Language :: Python :: 3.14",
|
|
25
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
26
|
+
"Topic :: Security",
|
|
27
|
+
]
|
|
28
|
+
dependencies = [
|
|
29
|
+
"mcp>=1.2.0",
|
|
30
|
+
"pydantic>=2.6.0",
|
|
31
|
+
"click>=8.1.0",
|
|
32
|
+
]
|
|
33
|
+
|
|
34
|
+
[project.urls]
|
|
35
|
+
Homepage = "https://github.com/bibinwithab/codevoight"
|
|
36
|
+
Repository = "https://github.com/bibinwithab/codevoight"
|
|
37
|
+
Issues = "https://github.com/bibinwithab/codevoight/issues"
|
|
38
|
+
|
|
39
|
+
[project.optional-dependencies]
|
|
40
|
+
dev = [
|
|
41
|
+
"pytest>=8.0.0",
|
|
42
|
+
"pytest-asyncio>=0.23.0",
|
|
43
|
+
"build>=1.0.0",
|
|
44
|
+
"twine>=5.0.0",
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
[project.scripts]
|
|
48
|
+
codevoight = "codevoight.cli:main"
|
|
49
|
+
|
|
50
|
+
[tool.setuptools.packages.find]
|
|
51
|
+
where = ["src"]
|
|
52
|
+
|
|
53
|
+
[tool.pytest.ini_options]
|
|
54
|
+
testpaths = ["tests"]
|
|
55
|
+
pythonpath = ["src"]
|
|
56
|
+
asyncio_mode = "auto"
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
import time
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
import click
|
|
9
|
+
|
|
10
|
+
from codevoight.core.diff import compute_diff_hash, normalize_text
|
|
11
|
+
from codevoight.core.models import (
|
|
12
|
+
QuizSubmission,
|
|
13
|
+
SessionStatus,
|
|
14
|
+
StagedSession,
|
|
15
|
+
)
|
|
16
|
+
from codevoight.core.security import get_or_create_secret, sign_token
|
|
17
|
+
from codevoight.db.repository import StateRepository
|
|
18
|
+
from codevoight.generators.deterministic import (
|
|
19
|
+
DeterministicQuizEvaluator,
|
|
20
|
+
DeterministicQuizGenerator,
|
|
21
|
+
)
|
|
22
|
+
from codevoight.git.hook import install_pre_commit_hook, verify_staged_commit
|
|
23
|
+
from codevoight.git.runner import GitRunner
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
def get_repo_and_db_paths() -> tuple[Path, Path]:
|
|
27
|
+
runner = GitRunner()
|
|
28
|
+
root = (runner.get_repo_root() or Path.cwd()).resolve()
|
|
29
|
+
codevoight_dir = root / ".codevoight"
|
|
30
|
+
if not codevoight_dir.exists():
|
|
31
|
+
if (root / ".quizgate").exists():
|
|
32
|
+
codevoight_dir = root / ".quizgate"
|
|
33
|
+
elif (root / ".codegate").exists():
|
|
34
|
+
codevoight_dir = root / ".codegate"
|
|
35
|
+
db_path = codevoight_dir / "state.db"
|
|
36
|
+
return codevoight_dir, db_path
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@click.group()
|
|
40
|
+
def main() -> None:
|
|
41
|
+
"""CodeVoight: Voight-Kampff Comprehension Gate for AI Commits."""
|
|
42
|
+
pass
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@main.command("quiz")
|
|
46
|
+
@click.argument("session_id", required=False)
|
|
47
|
+
def run_quiz(session_id: Optional[str] = None) -> None:
|
|
48
|
+
"""Take comprehension quiz for a staged change to unlock git commit."""
|
|
49
|
+
codevoight_dir, db_path = get_repo_and_db_paths()
|
|
50
|
+
repo = StateRepository(db_path)
|
|
51
|
+
|
|
52
|
+
session = None
|
|
53
|
+
if session_id:
|
|
54
|
+
session = repo.get_session(session_id)
|
|
55
|
+
if not session:
|
|
56
|
+
click.echo(f"[ERROR] Session '{session_id}' not found.", err=True)
|
|
57
|
+
sys.exit(1)
|
|
58
|
+
else:
|
|
59
|
+
session = repo.get_latest_pending_session()
|
|
60
|
+
if not session:
|
|
61
|
+
click.echo("[INFO] No pending quiz sessions found.", err=True)
|
|
62
|
+
click.echo("Stage code changes via MCP or 'codevoight stage' first.")
|
|
63
|
+
sys.exit(0)
|
|
64
|
+
|
|
65
|
+
click.echo("=" * 60)
|
|
66
|
+
click.echo(f"CODEVOIGHT COMPREHENSION QUIZ")
|
|
67
|
+
click.echo(f"Session ID : {session.session_id}")
|
|
68
|
+
click.echo(f"Diff Hash : {session.diff_hash[:16]}...")
|
|
69
|
+
if session.description:
|
|
70
|
+
click.echo(f"Description: {session.description}")
|
|
71
|
+
click.echo("=" * 60)
|
|
72
|
+
|
|
73
|
+
# Show Diff Summary
|
|
74
|
+
click.echo("\n--- STAGED DIFF PREVIEW ---")
|
|
75
|
+
diff_lines = session.diff_text.strip().splitlines()
|
|
76
|
+
for line in diff_lines[:30]:
|
|
77
|
+
click.echo(line)
|
|
78
|
+
if len(diff_lines) > 30:
|
|
79
|
+
click.echo(f"... ({len(diff_lines) - 30} more lines omitted) ...")
|
|
80
|
+
click.echo("---------------------------\n")
|
|
81
|
+
|
|
82
|
+
questions = repo.get_questions_for_session(session.session_id)
|
|
83
|
+
if not questions:
|
|
84
|
+
click.echo("[WARNING] No questions found for this session. Generating on the fly...")
|
|
85
|
+
gen = DeterministicQuizGenerator()
|
|
86
|
+
questions = gen.generate_quiz(session.diff_text, session.description)
|
|
87
|
+
|
|
88
|
+
answers = {}
|
|
89
|
+
for idx, q in enumerate(questions, 1):
|
|
90
|
+
click.echo(f"\n[Question {idx}/{len(questions)}] {q.prompt}")
|
|
91
|
+
for opt in q.options:
|
|
92
|
+
click.echo(f" [{opt.id}] {opt.text}")
|
|
93
|
+
|
|
94
|
+
valid_ids = [opt.id.upper() for opt in q.options]
|
|
95
|
+
chosen = ""
|
|
96
|
+
while chosen not in valid_ids:
|
|
97
|
+
chosen = click.prompt("Your answer", type=str).strip().upper()
|
|
98
|
+
answers[q.id] = chosen
|
|
99
|
+
|
|
100
|
+
# Evaluate
|
|
101
|
+
submission = QuizSubmission(session_id=session.session_id, answers=answers)
|
|
102
|
+
evaluator = DeterministicQuizEvaluator(passing_score=1.0)
|
|
103
|
+
result = evaluator.evaluate(submission, questions)
|
|
104
|
+
|
|
105
|
+
click.echo("\n" + "=" * 60)
|
|
106
|
+
if result.passed:
|
|
107
|
+
secret_key = get_or_create_secret(codevoight_dir)
|
|
108
|
+
token = sign_token(session.session_id, session.diff_hash, secret_key)
|
|
109
|
+
repo.update_session_status(session.session_id, SessionStatus.APPROVED, token.serialize())
|
|
110
|
+
|
|
111
|
+
click.echo(f"[PASSED] Score: {int(result.score * 100)}% ({result.correct}/{result.total})")
|
|
112
|
+
click.echo(f"[UNLOCKED] Capability token generated for diff hash {session.diff_hash[:16]}.")
|
|
113
|
+
click.echo("You may now proceed with 'git commit' or call the MCP commit tool.")
|
|
114
|
+
else:
|
|
115
|
+
repo.update_session_status(session.session_id, SessionStatus.REJECTED)
|
|
116
|
+
click.echo(f"[FAILED] Score: {int(result.score * 100)}% ({result.correct}/{result.total})")
|
|
117
|
+
click.echo("[LOCKED] Commit permission DENIED. Review the following feedback:")
|
|
118
|
+
for fb in result.feedback:
|
|
119
|
+
click.echo(f" * {fb}")
|
|
120
|
+
click.echo("\nReview the diff and run 'codevoight quiz' to try again.")
|
|
121
|
+
sys.exit(1)
|
|
122
|
+
click.echo("=" * 60)
|
|
123
|
+
|
|
124
|
+
|
|
125
|
+
@main.command("status")
|
|
126
|
+
def show_status() -> None:
|
|
127
|
+
"""View active and recent CodeVoight staging sessions."""
|
|
128
|
+
_, db_path = get_repo_and_db_paths()
|
|
129
|
+
if not db_path.exists():
|
|
130
|
+
click.echo("[INFO] No CodeVoight database found in current repository.")
|
|
131
|
+
return
|
|
132
|
+
|
|
133
|
+
repo = StateRepository(db_path)
|
|
134
|
+
sessions = repo.list_sessions(limit=10)
|
|
135
|
+
if not sessions:
|
|
136
|
+
click.echo("[INFO] No staging sessions found.")
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
click.echo(f"{'SESSION ID':<12} {'STATUS':<15} {'EXPIRES IN':<12} {'DIFF HASH':<16} {'DESCRIPTION'}")
|
|
140
|
+
click.echo("-" * 75)
|
|
141
|
+
now = time.time()
|
|
142
|
+
for s in sessions:
|
|
143
|
+
remaining = max(0, int(s.expires_at - now))
|
|
144
|
+
rem_str = f"{remaining}s" if remaining > 0 else "EXPIRED"
|
|
145
|
+
click.echo(f"{s.session_id[:10]:<12} {s.status.value:<15} {rem_str:<12} {s.diff_hash[:14]:<16} {s.description[:20]}")
|
|
146
|
+
|
|
147
|
+
|
|
148
|
+
@main.command("hook")
|
|
149
|
+
@click.argument("action", type=click.Choice(["install", "verify"]))
|
|
150
|
+
def manage_hook(action: str) -> None:
|
|
151
|
+
"""Install or test the Git pre-commit verification hook."""
|
|
152
|
+
if action == "install":
|
|
153
|
+
success, msg = install_pre_commit_hook()
|
|
154
|
+
if success:
|
|
155
|
+
click.echo(f"[SUCCESS] CodeVoight pre-commit hook installed to: {msg}")
|
|
156
|
+
else:
|
|
157
|
+
click.echo(f"[ERROR] Failed to install hook: {msg}", err=True)
|
|
158
|
+
sys.exit(1)
|
|
159
|
+
elif action == "verify":
|
|
160
|
+
success, msg = verify_staged_commit()
|
|
161
|
+
if success:
|
|
162
|
+
click.echo(f"[SUCCESS] {msg}")
|
|
163
|
+
else:
|
|
164
|
+
click.echo(f"[DENIED] {msg}", err=True)
|
|
165
|
+
sys.exit(1)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
@main.command("verify-commit", hidden=True)
|
|
169
|
+
def run_verify_commit() -> None:
|
|
170
|
+
"""Internal entrypoint invoked directly by .git/hooks/pre-commit."""
|
|
171
|
+
success, msg = verify_staged_commit()
|
|
172
|
+
if not success:
|
|
173
|
+
click.echo(f"[CODEVOIGHT] {msg}", err=True)
|
|
174
|
+
sys.exit(1)
|
|
175
|
+
sys.exit(0)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
@main.command("stage")
|
|
179
|
+
@click.argument("files", nargs=-1, required=True)
|
|
180
|
+
@click.option("--description", "-m", default="Manual CLI staging", help="Description of staged change")
|
|
181
|
+
def stage_from_cli(files: tuple[str, ...], description: str) -> None:
|
|
182
|
+
"""Stage files and create a comprehension quiz session."""
|
|
183
|
+
runner = GitRunner()
|
|
184
|
+
if not runner.is_git_repo():
|
|
185
|
+
click.echo("[ERROR] Not in a git repository.", err=True)
|
|
186
|
+
sys.exit(1)
|
|
187
|
+
|
|
188
|
+
runner.stage_files(list(files))
|
|
189
|
+
staged_diff = runner.get_staged_diff()
|
|
190
|
+
if not staged_diff.strip():
|
|
191
|
+
click.echo("[WARNING] No staged changes found for specified files.")
|
|
192
|
+
return
|
|
193
|
+
|
|
194
|
+
import uuid
|
|
195
|
+
codevoight_dir, db_path = get_repo_and_db_paths()
|
|
196
|
+
repo = StateRepository(db_path)
|
|
197
|
+
diff_hash = compute_diff_hash(staged_diff)
|
|
198
|
+
|
|
199
|
+
gen = DeterministicQuizGenerator()
|
|
200
|
+
questions = gen.generate_quiz(staged_diff, description)
|
|
201
|
+
|
|
202
|
+
now = time.time()
|
|
203
|
+
session = StagedSession(
|
|
204
|
+
session_id=str(uuid.uuid4())[:8],
|
|
205
|
+
diff_text=staged_diff,
|
|
206
|
+
diff_hash=diff_hash,
|
|
207
|
+
description=description,
|
|
208
|
+
status=SessionStatus.PENDING_QUIZ,
|
|
209
|
+
created_at=now,
|
|
210
|
+
expires_at=now + 1800.0,
|
|
211
|
+
)
|
|
212
|
+
|
|
213
|
+
repo.create_session(session, questions)
|
|
214
|
+
click.echo(f"[SUCCESS] Staged session created: {session.session_id}")
|
|
215
|
+
click.echo(f"Run 'codevoight quiz {session.session_id}' to take the quiz and unlock commit.")
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
if __name__ == "__main__":
|
|
219
|
+
main()
|