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.
Files changed (33) hide show
  1. codevoight-0.1.0/LICENSE +21 -0
  2. codevoight-0.1.0/PKG-INFO +191 -0
  3. codevoight-0.1.0/README.md +158 -0
  4. codevoight-0.1.0/pyproject.toml +56 -0
  5. codevoight-0.1.0/setup.cfg +4 -0
  6. codevoight-0.1.0/src/codevoight/__init__.py +8 -0
  7. codevoight-0.1.0/src/codevoight/cli.py +219 -0
  8. codevoight-0.1.0/src/codevoight/core/config.py +51 -0
  9. codevoight-0.1.0/src/codevoight/core/diff.py +51 -0
  10. codevoight-0.1.0/src/codevoight/core/models.py +82 -0
  11. codevoight-0.1.0/src/codevoight/core/security.py +68 -0
  12. codevoight-0.1.0/src/codevoight/db/repository.py +221 -0
  13. codevoight-0.1.0/src/codevoight/db/schema.py +40 -0
  14. codevoight-0.1.0/src/codevoight/generators/base.py +20 -0
  15. codevoight-0.1.0/src/codevoight/generators/deterministic.py +216 -0
  16. codevoight-0.1.0/src/codevoight/generators/llm.py +33 -0
  17. codevoight-0.1.0/src/codevoight/git/hook.py +113 -0
  18. codevoight-0.1.0/src/codevoight/git/runner.py +57 -0
  19. codevoight-0.1.0/src/codevoight/server.py +209 -0
  20. codevoight-0.1.0/src/codevoight.egg-info/PKG-INFO +191 -0
  21. codevoight-0.1.0/src/codevoight.egg-info/SOURCES.txt +31 -0
  22. codevoight-0.1.0/src/codevoight.egg-info/dependency_links.txt +1 -0
  23. codevoight-0.1.0/src/codevoight.egg-info/entry_points.txt +2 -0
  24. codevoight-0.1.0/src/codevoight.egg-info/requires.txt +9 -0
  25. codevoight-0.1.0/src/codevoight.egg-info/top_level.txt +1 -0
  26. codevoight-0.1.0/tests/test_cli.py +141 -0
  27. codevoight-0.1.0/tests/test_config.py +45 -0
  28. codevoight-0.1.0/tests/test_db.py +65 -0
  29. codevoight-0.1.0/tests/test_diff.py +45 -0
  30. codevoight-0.1.0/tests/test_generator.py +48 -0
  31. codevoight-0.1.0/tests/test_git_hook.py +65 -0
  32. codevoight-0.1.0/tests/test_mcp_server.py +89 -0
  33. codevoight-0.1.0/tests/test_security.py +62 -0
@@ -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,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,8 @@
1
+ """CodeVoight - Voight-Kampff Human Comprehension Gate for AI Commits."""
2
+
3
+ from codevoight.core.config import load_dotenv
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ # Auto-load .env if present in workspace root
8
+ load_dotenv()
@@ -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()