tokenguard 0.2.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 (30) hide show
  1. tokenguard-0.2.0/.gitignore +7 -0
  2. tokenguard-0.2.0/LICENSE +21 -0
  3. tokenguard-0.2.0/PKG-INFO +198 -0
  4. tokenguard-0.2.0/README.md +171 -0
  5. tokenguard-0.2.0/pyproject.toml +51 -0
  6. tokenguard-0.2.0/tests/__init__.py +0 -0
  7. tokenguard-0.2.0/tests/test_detector.py +142 -0
  8. tokenguard-0.2.0/tests/test_integration.py +295 -0
  9. tokenguard-0.2.0/tests/test_portable_hook.py +586 -0
  10. tokenguard-0.2.0/tests/test_scaffold.py +120 -0
  11. tokenguard-0.2.0/tokenguard/__init__.py +3 -0
  12. tokenguard-0.2.0/tokenguard/cli.py +251 -0
  13. tokenguard-0.2.0/tokenguard/detector.py +118 -0
  14. tokenguard-0.2.0/tokenguard/scaffold.py +228 -0
  15. tokenguard-0.2.0/tokenguard/templates/claude_md/base.md.j2 +55 -0
  16. tokenguard-0.2.0/tokenguard/templates/claude_md/generic.md.j2 +5 -0
  17. tokenguard-0.2.0/tokenguard/templates/claude_md/go.md.j2 +7 -0
  18. tokenguard-0.2.0/tokenguard/templates/claude_md/javascript.md.j2 +7 -0
  19. tokenguard-0.2.0/tokenguard/templates/claude_md/python.md.j2 +8 -0
  20. tokenguard-0.2.0/tokenguard/templates/claude_md/rust.md.j2 +7 -0
  21. tokenguard-0.2.0/tokenguard/templates/hooks/mirror-hook.py +836 -0
  22. tokenguard-0.2.0/tokenguard/templates/memory/MEMORY.md.j2 +19 -0
  23. tokenguard-0.2.0/tokenguard/templates/rules/bash-safety.md +16 -0
  24. tokenguard-0.2.0/tokenguard/templates/rules/commit-standards.md +16 -0
  25. tokenguard-0.2.0/tokenguard/templates/rules/efficiency-rules.md +25 -0
  26. tokenguard-0.2.0/tokenguard/templates/rules/operational-principles.md +9 -0
  27. tokenguard-0.2.0/tokenguard/templates/rules/path-portability.md +3 -0
  28. tokenguard-0.2.0/tokenguard/templates/rules/python-async-safety.md +8 -0
  29. tokenguard-0.2.0/tokenguard/templates/rules/session-protocol.md +17 -0
  30. tokenguard-0.2.0/tokenguard/updater.py +237 -0
@@ -0,0 +1,7 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ *.egg-info/
5
+ dist/
6
+ build/
7
+ .pytest_cache/
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 ENTER Konsult
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,198 @@
1
+ Metadata-Version: 2.4
2
+ Name: tokenguard
3
+ Version: 0.2.0
4
+ Summary: Safety gates, efficiency rules, and persistent memory for Claude Code projects
5
+ Project-URL: Homepage, https://enterkonsult.com
6
+ Project-URL: PyPI, https://pypi.org/project/tokenguard/
7
+ Author: ENTER Konsult
8
+ License-Expression: MIT
9
+ License-File: LICENSE
10
+ Keywords: ai,claude,claude-code,gates,guardrails,safety,tokens
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: License :: OSI Approved :: MIT License
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.9
16
+ Classifier: Programming Language :: Python :: 3.10
17
+ Classifier: Programming Language :: Python :: 3.11
18
+ Classifier: Programming Language :: Python :: 3.12
19
+ Classifier: Programming Language :: Python :: 3.13
20
+ Classifier: Topic :: Software Development :: Quality Assurance
21
+ Requires-Python: >=3.9
22
+ Requires-Dist: jinja2>=3.0
23
+ Provides-Extra: test
24
+ Requires-Dist: pytest-tmp-files>=0.0.2; extra == 'test'
25
+ Requires-Dist: pytest>=7.0; extra == 'test'
26
+ Description-Content-Type: text/markdown
27
+
28
+ # TokenGuard
29
+
30
+ **Stop Claude Code from wasting your tokens and making dangerous mistakes.**
31
+
32
+ Claude Code is powerful, but out of the box it has no guardrails. It will read thousands of lines from `node_modules`, accidentally commit your `.env` file, force-push to main, and burn through your entire context window without warning.
33
+
34
+ TokenGuard fixes this. It installs 11 safety gates that check every single action Claude Code takes before it happens. Bad action? Blocked. Good action? Passes through instantly.
35
+
36
+ ```bash
37
+ pip install tokenguard
38
+ cd your-project
39
+ tokenguard init
40
+ ```
41
+
42
+ That's it. Your project is now protected.
43
+
44
+ ## What does it actually do?
45
+
46
+ Every time Claude Code tries to use a tool (read a file, run a command, write code), TokenGuard checks the action against 11 rules:
47
+
48
+ | Gate | What it prevents |
49
+ |------|-----------------|
50
+ | **Dependency Guardian** | Reading `node_modules`, `venv`, `dist`, `__pycache__` -- folders that burn thousands of tokens for zero value |
51
+ | **Destructive Git** | Force push, `reset --hard`, `clean -fd`, `stash drop` -- actions that destroy your work |
52
+ | **Git Push Gate** | Pushes to protected branches (`main`, `master`, `develop`, `release`, `production`) |
53
+ | **Secrets Detection** | Committing `.env`, `.pem`, `credentials.json`, API keys, SSH keys |
54
+ | **Tool Substitution** | Using `grep` instead of `rg`, `find` instead of `fd`, `cat` pipes instead of Read -- slower tools that waste tokens |
55
+ | **Bash Linting** | Common shell syntax errors (semicolons after `$()`, broken `sed`, bad `[[ ]]` negation) |
56
+ | **Temp Script** | Creating throwaway scripts in `/tmp` that clutter your workflow |
57
+ | **Env Var Protection** | Reading sensitive environment variables (`$API_KEY`, `$SECRET`, `$TOKEN`, `$AWS_*`) |
58
+ | **Network Binding** | Binding to `0.0.0.0` without authentication -- exposing services to the network |
59
+ | **Context Gate** | Warns at 70% context usage, blocks at 85% -- prevents Claude from losing its memory mid-task |
60
+
61
+ If a gate blocks an action, Claude Code sees a clear message explaining why and what to do instead.
62
+
63
+ ## Install
64
+
65
+ ```bash
66
+ pip install tokenguard
67
+ ```
68
+
69
+ Requires Python 3.9+. One dependency: Jinja2 (for template rendering). The generated hook uses Python stdlib only.
70
+
71
+ ## Quick Start
72
+
73
+ ```bash
74
+ cd your-project
75
+ tokenguard init
76
+ ```
77
+
78
+ TokenGuard detects your project type automatically and generates a `.claude/` directory:
79
+
80
+ ```
81
+ your-project/.claude/
82
+ CLAUDE.md # Project instructions tuned to your stack
83
+ hooks/mirror-hook.py # The 11 safety gates
84
+ rules/ # Efficiency and safety rules
85
+ memory/MEMORY.md # Persistent memory across sessions
86
+ settings.json # Registers the hook with Claude Code
87
+ ```
88
+
89
+ ### What is `.claude/`?
90
+
91
+ Claude Code reads a `.claude/` directory in your project for configuration. TokenGuard generates this directory with best-practice defaults so you don't have to write it yourself.
92
+
93
+ ## Commands
94
+
95
+ | Command | What it does |
96
+ |---------|-------------|
97
+ | `tokenguard init` | Set up safety gates in your project |
98
+ | `tokenguard init --type rust` | Override auto-detected project type |
99
+ | `tokenguard init --dry-run` | Preview what would be created (no files written) |
100
+ | `tokenguard init --no-hooks` | Skip hook installation (rules and memory only) |
101
+ | `tokenguard update` | Check for available template updates |
102
+ | `tokenguard update --force` | Apply updates (your customizations are preserved) |
103
+ | `tokenguard status` | Show what's installed and whether updates are available |
104
+ | `tokenguard eject` | Remove TokenGuard tracking but keep all generated files |
105
+
106
+ ## Project Detection
107
+
108
+ TokenGuard looks at your project files to determine the stack and generates tailored instructions:
109
+
110
+ | Files found | Detected as |
111
+ |-------------|-------------|
112
+ | `pyproject.toml`, `setup.py`, `requirements.txt` | Python |
113
+ | `package.json` | JavaScript |
114
+ | `Cargo.toml` | Rust |
115
+ | `go.mod` | Go |
116
+ | None of the above | Generic |
117
+
118
+ Override with `--type`:
119
+
120
+ ```bash
121
+ tokenguard init --type javascript
122
+ ```
123
+
124
+ ## How Updates Work
125
+
126
+ When TokenGuard releases improved templates, `tokenguard update` compares your files:
127
+
128
+ - **Updatable** -- you haven't edited the file, and a newer template is available. Safe to update.
129
+ - **User-modified** -- you customized the file. TokenGuard will never overwrite your changes.
130
+ - **Current** -- already up to date.
131
+
132
+ ```bash
133
+ tokenguard update # See what changed
134
+ tokenguard update --force # Apply the updates
135
+ ```
136
+
137
+ ## Ejecting
138
+
139
+ Want to keep the generated files but stop using TokenGuard?
140
+
141
+ ```bash
142
+ tokenguard eject
143
+ ```
144
+
145
+ This removes the `.tokenguard.json` tracking file and unregisters the hook. All your `.claude/` files stay exactly as they are -- fully yours to edit.
146
+
147
+ ## How It Works Under the Hood
148
+
149
+ 1. `tokenguard init` generates files from Jinja2 templates based on your project type
150
+ 2. It registers a [PreToolUse hook](https://docs.anthropic.com/en/docs/claude-code/hooks) in `.claude/settings.json`
151
+ 3. On every Claude Code tool call, `mirror-hook.py` receives the action as JSON via stdin
152
+ 4. It evaluates each gate in priority order and returns `allow` or `deny`
153
+ 5. File hashes are stored in `.tokenguard.json` so updates know what you've changed
154
+
155
+ The hook is a single Python file using only the standard library. No background processes, no network calls, no dependencies.
156
+
157
+ ## GRIP Users
158
+
159
+ If you already use GRIP globally, TokenGuard detects this and skips hook installation to avoid double-gating. You still get the project-level CLAUDE.md, rules, and memory scaffold.
160
+
161
+ Force full installation with:
162
+
163
+ ```bash
164
+ tokenguard init --standalone
165
+ ```
166
+
167
+ ## Cross-Platform
168
+
169
+ TokenGuard works on Windows, macOS, and Linux. The hook handles platform differences (like `fcntl` on Unix vs `msvcrt` on Windows) automatically.
170
+
171
+ ## TokenGuard vs GRIP
172
+
173
+ TokenGuard is the free, open-source foundation. It comes from **GRIP**, a full AI agent management system built by [ENTER Konsult](https://enterkonsult.com) for professional development teams.
174
+
175
+ | Capability | TokenGuard (free) | GRIP (full) |
176
+ |------------|:-:|:-:|
177
+ | 11 safety gates | Yes | Yes |
178
+ | Efficiency rules | Yes | Yes |
179
+ | Session memory scaffold | Yes | Yes |
180
+ | Project-aware CLAUDE.md | Yes | Yes |
181
+ | 100+ specialized skills | -- | Yes |
182
+ | 19 autonomous agents | -- | Yes |
183
+ | 26 operating modes (code, review, security, strategy...) | -- | Yes |
184
+ | Recursive self-improvement loops | -- | Yes |
185
+ | Adaptive context extension (1M tokens) | -- | Yes |
186
+ | Multi-session state persistence | -- | Yes |
187
+ | Team coordination and pair programming | -- | Yes |
188
+ | Sprint automation (issue to PR) | -- | Yes |
189
+ | PR review automation | -- | Yes |
190
+ | Custom skill and agent creation | -- | Yes |
191
+
192
+ If your team needs more than guardrails -- autonomous workflows, intelligent code review, multi-agent coordination, or custom AI tooling -- reach out to us.
193
+
194
+ **Website**: [enterkonsult.com](https://enterkonsult.com)
195
+
196
+ ## License
197
+
198
+ MIT
@@ -0,0 +1,171 @@
1
+ # TokenGuard
2
+
3
+ **Stop Claude Code from wasting your tokens and making dangerous mistakes.**
4
+
5
+ Claude Code is powerful, but out of the box it has no guardrails. It will read thousands of lines from `node_modules`, accidentally commit your `.env` file, force-push to main, and burn through your entire context window without warning.
6
+
7
+ TokenGuard fixes this. It installs 11 safety gates that check every single action Claude Code takes before it happens. Bad action? Blocked. Good action? Passes through instantly.
8
+
9
+ ```bash
10
+ pip install tokenguard
11
+ cd your-project
12
+ tokenguard init
13
+ ```
14
+
15
+ That's it. Your project is now protected.
16
+
17
+ ## What does it actually do?
18
+
19
+ Every time Claude Code tries to use a tool (read a file, run a command, write code), TokenGuard checks the action against 11 rules:
20
+
21
+ | Gate | What it prevents |
22
+ |------|-----------------|
23
+ | **Dependency Guardian** | Reading `node_modules`, `venv`, `dist`, `__pycache__` -- folders that burn thousands of tokens for zero value |
24
+ | **Destructive Git** | Force push, `reset --hard`, `clean -fd`, `stash drop` -- actions that destroy your work |
25
+ | **Git Push Gate** | Pushes to protected branches (`main`, `master`, `develop`, `release`, `production`) |
26
+ | **Secrets Detection** | Committing `.env`, `.pem`, `credentials.json`, API keys, SSH keys |
27
+ | **Tool Substitution** | Using `grep` instead of `rg`, `find` instead of `fd`, `cat` pipes instead of Read -- slower tools that waste tokens |
28
+ | **Bash Linting** | Common shell syntax errors (semicolons after `$()`, broken `sed`, bad `[[ ]]` negation) |
29
+ | **Temp Script** | Creating throwaway scripts in `/tmp` that clutter your workflow |
30
+ | **Env Var Protection** | Reading sensitive environment variables (`$API_KEY`, `$SECRET`, `$TOKEN`, `$AWS_*`) |
31
+ | **Network Binding** | Binding to `0.0.0.0` without authentication -- exposing services to the network |
32
+ | **Context Gate** | Warns at 70% context usage, blocks at 85% -- prevents Claude from losing its memory mid-task |
33
+
34
+ If a gate blocks an action, Claude Code sees a clear message explaining why and what to do instead.
35
+
36
+ ## Install
37
+
38
+ ```bash
39
+ pip install tokenguard
40
+ ```
41
+
42
+ Requires Python 3.9+. One dependency: Jinja2 (for template rendering). The generated hook uses Python stdlib only.
43
+
44
+ ## Quick Start
45
+
46
+ ```bash
47
+ cd your-project
48
+ tokenguard init
49
+ ```
50
+
51
+ TokenGuard detects your project type automatically and generates a `.claude/` directory:
52
+
53
+ ```
54
+ your-project/.claude/
55
+ CLAUDE.md # Project instructions tuned to your stack
56
+ hooks/mirror-hook.py # The 11 safety gates
57
+ rules/ # Efficiency and safety rules
58
+ memory/MEMORY.md # Persistent memory across sessions
59
+ settings.json # Registers the hook with Claude Code
60
+ ```
61
+
62
+ ### What is `.claude/`?
63
+
64
+ Claude Code reads a `.claude/` directory in your project for configuration. TokenGuard generates this directory with best-practice defaults so you don't have to write it yourself.
65
+
66
+ ## Commands
67
+
68
+ | Command | What it does |
69
+ |---------|-------------|
70
+ | `tokenguard init` | Set up safety gates in your project |
71
+ | `tokenguard init --type rust` | Override auto-detected project type |
72
+ | `tokenguard init --dry-run` | Preview what would be created (no files written) |
73
+ | `tokenguard init --no-hooks` | Skip hook installation (rules and memory only) |
74
+ | `tokenguard update` | Check for available template updates |
75
+ | `tokenguard update --force` | Apply updates (your customizations are preserved) |
76
+ | `tokenguard status` | Show what's installed and whether updates are available |
77
+ | `tokenguard eject` | Remove TokenGuard tracking but keep all generated files |
78
+
79
+ ## Project Detection
80
+
81
+ TokenGuard looks at your project files to determine the stack and generates tailored instructions:
82
+
83
+ | Files found | Detected as |
84
+ |-------------|-------------|
85
+ | `pyproject.toml`, `setup.py`, `requirements.txt` | Python |
86
+ | `package.json` | JavaScript |
87
+ | `Cargo.toml` | Rust |
88
+ | `go.mod` | Go |
89
+ | None of the above | Generic |
90
+
91
+ Override with `--type`:
92
+
93
+ ```bash
94
+ tokenguard init --type javascript
95
+ ```
96
+
97
+ ## How Updates Work
98
+
99
+ When TokenGuard releases improved templates, `tokenguard update` compares your files:
100
+
101
+ - **Updatable** -- you haven't edited the file, and a newer template is available. Safe to update.
102
+ - **User-modified** -- you customized the file. TokenGuard will never overwrite your changes.
103
+ - **Current** -- already up to date.
104
+
105
+ ```bash
106
+ tokenguard update # See what changed
107
+ tokenguard update --force # Apply the updates
108
+ ```
109
+
110
+ ## Ejecting
111
+
112
+ Want to keep the generated files but stop using TokenGuard?
113
+
114
+ ```bash
115
+ tokenguard eject
116
+ ```
117
+
118
+ This removes the `.tokenguard.json` tracking file and unregisters the hook. All your `.claude/` files stay exactly as they are -- fully yours to edit.
119
+
120
+ ## How It Works Under the Hood
121
+
122
+ 1. `tokenguard init` generates files from Jinja2 templates based on your project type
123
+ 2. It registers a [PreToolUse hook](https://docs.anthropic.com/en/docs/claude-code/hooks) in `.claude/settings.json`
124
+ 3. On every Claude Code tool call, `mirror-hook.py` receives the action as JSON via stdin
125
+ 4. It evaluates each gate in priority order and returns `allow` or `deny`
126
+ 5. File hashes are stored in `.tokenguard.json` so updates know what you've changed
127
+
128
+ The hook is a single Python file using only the standard library. No background processes, no network calls, no dependencies.
129
+
130
+ ## GRIP Users
131
+
132
+ If you already use GRIP globally, TokenGuard detects this and skips hook installation to avoid double-gating. You still get the project-level CLAUDE.md, rules, and memory scaffold.
133
+
134
+ Force full installation with:
135
+
136
+ ```bash
137
+ tokenguard init --standalone
138
+ ```
139
+
140
+ ## Cross-Platform
141
+
142
+ TokenGuard works on Windows, macOS, and Linux. The hook handles platform differences (like `fcntl` on Unix vs `msvcrt` on Windows) automatically.
143
+
144
+ ## TokenGuard vs GRIP
145
+
146
+ TokenGuard is the free, open-source foundation. It comes from **GRIP**, a full AI agent management system built by [ENTER Konsult](https://enterkonsult.com) for professional development teams.
147
+
148
+ | Capability | TokenGuard (free) | GRIP (full) |
149
+ |------------|:-:|:-:|
150
+ | 11 safety gates | Yes | Yes |
151
+ | Efficiency rules | Yes | Yes |
152
+ | Session memory scaffold | Yes | Yes |
153
+ | Project-aware CLAUDE.md | Yes | Yes |
154
+ | 100+ specialized skills | -- | Yes |
155
+ | 19 autonomous agents | -- | Yes |
156
+ | 26 operating modes (code, review, security, strategy...) | -- | Yes |
157
+ | Recursive self-improvement loops | -- | Yes |
158
+ | Adaptive context extension (1M tokens) | -- | Yes |
159
+ | Multi-session state persistence | -- | Yes |
160
+ | Team coordination and pair programming | -- | Yes |
161
+ | Sprint automation (issue to PR) | -- | Yes |
162
+ | PR review automation | -- | Yes |
163
+ | Custom skill and agent creation | -- | Yes |
164
+
165
+ If your team needs more than guardrails -- autonomous workflows, intelligent code review, multi-agent coordination, or custom AI tooling -- reach out to us.
166
+
167
+ **Website**: [enterkonsult.com](https://enterkonsult.com)
168
+
169
+ ## License
170
+
171
+ MIT
@@ -0,0 +1,51 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "tokenguard"
7
+ version = "0.2.0"
8
+ description = "Safety gates, efficiency rules, and persistent memory for Claude Code projects"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [
14
+ { name = "ENTER Konsult" },
15
+ ]
16
+ keywords = ["claude", "claude-code", "safety", "gates", "ai", "tokens", "guardrails"]
17
+ classifiers = [
18
+ "Development Status :: 3 - Alpha",
19
+ "Intended Audience :: Developers",
20
+ "License :: OSI Approved :: MIT License",
21
+ "Programming Language :: Python :: 3",
22
+ "Programming Language :: Python :: 3.9",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "Topic :: Software Development :: Quality Assurance",
28
+ ]
29
+
30
+ dependencies = [
31
+ "jinja2>=3.0",
32
+ ]
33
+
34
+ [project.optional-dependencies]
35
+ test = [
36
+ "pytest>=7.0",
37
+ "pytest-tmp-files>=0.0.2",
38
+ ]
39
+
40
+ [project.scripts]
41
+ tokenguard = "tokenguard.cli:main"
42
+
43
+ [project.urls]
44
+ Homepage = "https://enterkonsult.com"
45
+ PyPI = "https://pypi.org/project/tokenguard/"
46
+
47
+ [tool.hatch.build.targets.wheel]
48
+ packages = ["tokenguard"]
49
+
50
+ [tool.pytest.ini_options]
51
+ testpaths = ["tests"]
File without changes
@@ -0,0 +1,142 @@
1
+ """Tests for project type detection."""
2
+
3
+ import json
4
+ from pathlib import Path
5
+
6
+ from tokenguard.detector import (
7
+ detect_existing_hooks,
8
+ detect_existing_mirror,
9
+ detect_project_type,
10
+ has_grip_hook,
11
+ )
12
+
13
+
14
+ def test_detect_python_pyproject(tmp_path):
15
+ (tmp_path / "pyproject.toml").write_text("[project]\nname = 'test'\n")
16
+ assert detect_project_type(tmp_path) == "python"
17
+
18
+
19
+ def test_detect_python_requirements(tmp_path):
20
+ (tmp_path / "requirements.txt").write_text("flask\n")
21
+ assert detect_project_type(tmp_path) == "python"
22
+
23
+
24
+ def test_detect_javascript(tmp_path):
25
+ (tmp_path / "package.json").write_text('{"name": "test"}\n')
26
+ assert detect_project_type(tmp_path) == "javascript"
27
+
28
+
29
+ def test_detect_rust(tmp_path):
30
+ (tmp_path / "Cargo.toml").write_text("[package]\nname = 'test'\n")
31
+ assert detect_project_type(tmp_path) == "rust"
32
+
33
+
34
+ def test_detect_go(tmp_path):
35
+ (tmp_path / "go.mod").write_text("module test\n")
36
+ assert detect_project_type(tmp_path) == "go"
37
+
38
+
39
+ def test_detect_generic_empty(tmp_path):
40
+ assert detect_project_type(tmp_path) == "generic"
41
+
42
+
43
+ def test_detect_generic_makefile_only(tmp_path):
44
+ (tmp_path / "Makefile").write_text("all:\n\techo hi\n")
45
+ assert detect_project_type(tmp_path) == "generic"
46
+
47
+
48
+ def test_python_takes_priority_over_makefile(tmp_path):
49
+ (tmp_path / "pyproject.toml").write_text("[project]\n")
50
+ (tmp_path / "Makefile").write_text("all:\n")
51
+ assert detect_project_type(tmp_path) == "python"
52
+
53
+
54
+ def test_detect_existing_mirror_not_present(tmp_path):
55
+ assert detect_existing_mirror(tmp_path) is None
56
+
57
+
58
+ def test_detect_existing_mirror_present(tmp_path):
59
+ claude_dir = tmp_path / ".claude"
60
+ claude_dir.mkdir()
61
+ data = {"version": "0.1.0", "project_type": "python"}
62
+ (claude_dir / ".tokenguard.json").write_text(json.dumps(data))
63
+ result = detect_existing_mirror(tmp_path)
64
+ assert result is not None
65
+ assert result["version"] == "0.1.0"
66
+
67
+
68
+ def test_detect_existing_mirror_corrupt_json(tmp_path):
69
+ claude_dir = tmp_path / ".claude"
70
+ claude_dir.mkdir()
71
+ (claude_dir / ".tokenguard.json").write_text("not json")
72
+ assert detect_existing_mirror(tmp_path) is None
73
+
74
+
75
+ # =========================================================================
76
+ # Hook detection (GRIP coexistence)
77
+ # =========================================================================
78
+
79
+ def test_detect_existing_hooks_empty(tmp_path):
80
+ """No settings.json -> no hooks."""
81
+ assert detect_existing_hooks(tmp_path) == []
82
+
83
+
84
+ def test_detect_existing_hooks_from_project(tmp_path):
85
+ """Hooks in project settings.json should be detected."""
86
+ claude_dir = tmp_path / ".claude"
87
+ claude_dir.mkdir()
88
+ settings = {
89
+ "hooks": {
90
+ "PreToolUse": [{
91
+ "matcher": ".*",
92
+ "hooks": [{"type": "command", "command": "python3 hooks/my-hook.py"}]
93
+ }]
94
+ }
95
+ }
96
+ (claude_dir / "settings.json").write_text(json.dumps(settings))
97
+ hooks = detect_existing_hooks(tmp_path)
98
+ assert len(hooks) == 1
99
+ assert "my-hook.py" in hooks[0]
100
+
101
+
102
+ def test_has_grip_hook_false(tmp_path):
103
+ """No GRIP hook -> False."""
104
+ assert has_grip_hook(tmp_path) is False
105
+
106
+
107
+ def test_has_grip_hook_true(tmp_path):
108
+ """GRIP context-gate-hook.py registered -> True."""
109
+ claude_dir = tmp_path / ".claude"
110
+ claude_dir.mkdir()
111
+ settings = {
112
+ "hooks": {
113
+ "PreToolUse": [{
114
+ "matcher": ".*",
115
+ "hooks": [{
116
+ "type": "command",
117
+ "command": "python3 ~/.claude/hooks/context-gate-hook.py"
118
+ }]
119
+ }]
120
+ }
121
+ }
122
+ (claude_dir / "settings.json").write_text(json.dumps(settings))
123
+ assert has_grip_hook(tmp_path) is True
124
+
125
+
126
+ def test_has_grip_hook_not_triggered_by_mirror(tmp_path):
127
+ """Mirror hook should not be detected as GRIP hook."""
128
+ claude_dir = tmp_path / ".claude"
129
+ claude_dir.mkdir()
130
+ settings = {
131
+ "hooks": {
132
+ "PreToolUse": [{
133
+ "matcher": ".*",
134
+ "hooks": [{
135
+ "type": "command",
136
+ "command": "python3 .claude/hooks/mirror-hook.py"
137
+ }]
138
+ }]
139
+ }
140
+ }
141
+ (claude_dir / "settings.json").write_text(json.dumps(settings))
142
+ assert has_grip_hook(tmp_path) is False