grace-context-manager 4.0.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.
- grace_context_manager-4.0.0/PKG-INFO +170 -0
- grace_context_manager-4.0.0/README.md +138 -0
- grace_context_manager-4.0.0/grace_app/__init__.py +3 -0
- grace_context_manager-4.0.0/grace_app/assets/icon.ico +0 -0
- grace_context_manager-4.0.0/grace_app/assets/icon.png +0 -0
- grace_context_manager-4.0.0/grace_app/config.py +40 -0
- grace_context_manager-4.0.0/grace_app/engine.py +127 -0
- grace_context_manager-4.0.0/grace_app/main.py +1118 -0
- grace_context_manager-4.0.0/grace_app/models.py +54 -0
- grace_context_manager-4.0.0/grace_app/tree_model.py +320 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/PKG-INFO +170 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/SOURCES.txt +19 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/dependency_links.txt +1 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/entry_points.txt +2 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/requires.txt +9 -0
- grace_context_manager-4.0.0/grace_context_manager.egg-info/top_level.txt +1 -0
- grace_context_manager-4.0.0/pyproject.toml +62 -0
- grace_context_manager-4.0.0/setup.cfg +4 -0
- grace_context_manager-4.0.0/tests/test_config.py +46 -0
- grace_context_manager-4.0.0/tests/test_engine.py +183 -0
- grace_context_manager-4.0.0/tests/test_tree_model.py +270 -0
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: grace-context-manager
|
|
3
|
+
Version: 4.0.0
|
|
4
|
+
Summary: GRACE Context Manager — Assemble high-quality context for LLM coding assistants
|
|
5
|
+
Author: GRACE Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/VanUST/GRACE
|
|
8
|
+
Project-URL: Source, https://github.com/VanUST/GRACE
|
|
9
|
+
Project-URL: Tracker, https://github.com/VanUST/GRACE/issues
|
|
10
|
+
Keywords: llm,context,prompt,ai,coding,assistant
|
|
11
|
+
Classifier: Development Status :: 4 - Beta
|
|
12
|
+
Classifier: Environment :: X11 Applications :: Qt
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.9
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Software Development :: Code Generators
|
|
22
|
+
Classifier: Topic :: Utilities
|
|
23
|
+
Requires-Python: >=3.9
|
|
24
|
+
Description-Content-Type: text/markdown
|
|
25
|
+
Requires-Dist: PyQt6>=6.5.0
|
|
26
|
+
Provides-Extra: test
|
|
27
|
+
Requires-Dist: pytest>=7.0; extra == "test"
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=1.0; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=4.0; extra == "dev"
|
|
32
|
+
|
|
33
|
+
# GRACE Context Manager
|
|
34
|
+
|
|
35
|
+
A modern GUI tool for assembling high-quality context for LLM coding assistants. Built with PyQt6.
|
|
36
|
+
|
|
37
|
+
## Key Features
|
|
38
|
+
|
|
39
|
+
- **Context Blocks**: Reusable text snippets organized by category (tech stack, constraints, rules). Toggle on/off as needed.
|
|
40
|
+
- **Smart File Selection**: Tree view with checkboxes, file size display, and quick select/deselect.
|
|
41
|
+
- **Advanced Filtering**: Filter by extension (typed manually or by checkboxes with two-way binding), keyword filename filter, and built-in ignore rules (dot-files, `node_modules`, `__pycache__`, `venv`).
|
|
42
|
+
- **Dynamic Extension Detection**: Auto-detect all file extensions in your project with select/deselect all for quick filtering.
|
|
43
|
+
- **Typed Extension Filter**: Extensions can be typed directly as comma-separated values (`.py, .json, .yaml`) or selected via checkboxes — both stay in sync.
|
|
44
|
+
- **Extension Presets**: Save frequently used extension combinations (e.g., `python_ml`, `web_dev`) and reload them across sessions.
|
|
45
|
+
- **Live Preview**: See assembled context with token count estimation before copying.
|
|
46
|
+
- **Preview Modes**: Full context, structure only, or files only.
|
|
47
|
+
- **Dynamic Context Refresh**: Refresh selected file contents when files change on disk without re-selecting.
|
|
48
|
+
- **Scalable UI**: Adjustable interface scaling (75%-200%) for different screen sizes and devices.
|
|
49
|
+
- **Manual Directory Entry**: Type the project root path directly or use Browse/Recent selectors.
|
|
50
|
+
- **Keyboard Shortcuts**: Ctrl+C copy, Ctrl+N new block, Ctrl+R refresh, Ctrl+O browse, Ctrl+A/D select all/none.
|
|
51
|
+
|
|
52
|
+
## Quick Install
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
pip install grace-context-manager
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Then run:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
grace
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Upgrading
|
|
65
|
+
|
|
66
|
+
```bash
|
|
67
|
+
pip install --upgrade grace-context-manager
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Your data is safe.** All settings, context blocks, extension presets, and recent projects are stored in `~/.grace_manager/` — they persist across upgrades and are never overwritten.
|
|
71
|
+
|
|
72
|
+
### One-Click Installers (no Python required)
|
|
73
|
+
|
|
74
|
+
For users without Python, download and run the platform installer which bundles everything:
|
|
75
|
+
|
|
76
|
+
| Platform | Installer |
|
|
77
|
+
|----------|-----------|
|
|
78
|
+
| Windows | **[install.bat](install.bat)** (right-click → "Run") |
|
|
79
|
+
| Linux / macOS | `./install.sh` |
|
|
80
|
+
|
|
81
|
+
### Manual Install (from source)
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
git clone https://github.com/vibechat/grace-context-manager.git
|
|
85
|
+
cd grace-context-manager
|
|
86
|
+
pip install -e .
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Workflow
|
|
90
|
+
|
|
91
|
+
1. **Select Project**: Click **Browse** to choose your project directory, select from **Recent**, or type the path directly.
|
|
92
|
+
2. **Add Context Blocks**: Create reusable blocks for tech stack, coding rules, etc.
|
|
93
|
+
3. **Filter Extensions**: Click **Detect** to auto-discover all file extensions, then:
|
|
94
|
+
- Click checkboxes to toggle individual extensions
|
|
95
|
+
- Type extensions directly in the text field (`.py, .json, .yaml`)
|
|
96
|
+
- Use **All**/**None** for quick toggle
|
|
97
|
+
- Save/Load **presets** for reuse across projects
|
|
98
|
+
4. **Keyword Filter**: Set a keyword to only show files matching that keyword in their filename.
|
|
99
|
+
5. **Select Files**: Check files/directories to include in context.
|
|
100
|
+
6. **Add Instruction**: Write your task in the instruction box.
|
|
101
|
+
7. **Refresh**: Click **Refresh Context** to reload changed files from disk.
|
|
102
|
+
8. **Copy**: Click **Copy Context** and paste into your LLM.
|
|
103
|
+
|
|
104
|
+
## Extension Presets
|
|
105
|
+
|
|
106
|
+
Save and load extension filter combinations:
|
|
107
|
+
|
|
108
|
+
1. Select your desired extensions (via checkboxes or typing)
|
|
109
|
+
2. Click **Save** to store as a named preset (e.g., `python_ml`)
|
|
110
|
+
3. Click **Load▾** to apply a previously saved preset
|
|
111
|
+
4. Presets persist globally in `~/.grace_manager/extension_presets.json`
|
|
112
|
+
|
|
113
|
+
Built-in defaults: `python_ml`, `web_dev`, `data_science`, `rust`, `golang`, `python`, `all`.
|
|
114
|
+
|
|
115
|
+
## Keyboard Shortcuts
|
|
116
|
+
|
|
117
|
+
| Shortcut | Action |
|
|
118
|
+
|----------|--------|
|
|
119
|
+
| `Ctrl+C` | Copy context to clipboard |
|
|
120
|
+
| `Ctrl+N` | New context block |
|
|
121
|
+
| `Ctrl+R` | Refresh context |
|
|
122
|
+
| `Ctrl+O` | Browse directory |
|
|
123
|
+
| `Ctrl+A` | Select all files |
|
|
124
|
+
| `Ctrl+D` | Deselect all files |
|
|
125
|
+
|
|
126
|
+
## Running Tests
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Install test dependencies
|
|
130
|
+
pip install -e ".[test]"
|
|
131
|
+
|
|
132
|
+
# Run all tests
|
|
133
|
+
pytest tests/ -v
|
|
134
|
+
|
|
135
|
+
# Run specific test file
|
|
136
|
+
pytest tests/test_engine.py -v
|
|
137
|
+
pytest tests/test_tree_model.py -v
|
|
138
|
+
pytest tests/test_config.py -v
|
|
139
|
+
|
|
140
|
+
# Quick smoke test (no pytest required)
|
|
141
|
+
python -m pytest tests/
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Tests run headless (virtual display) and cover:
|
|
145
|
+
- **Engine**: Token estimation, file scanning with extension/keyword filters, context assembly across all modes
|
|
146
|
+
- **Tree model**: Check/uncheck propagation, partial uncheck (Bug 1+2 fix), eager loading, extension & keyword filters, nested directory handling, dot-file exclusion
|
|
147
|
+
- **Config**: Built-in constants, ignore rules, extension presets
|
|
148
|
+
|
|
149
|
+
## Context Blocks
|
|
150
|
+
|
|
151
|
+
Context blocks are saved in `~/.grace_manager/profiles.json`. They persist across sessions.
|
|
152
|
+
|
|
153
|
+
Organize blocks by category:
|
|
154
|
+
- `tech` - Technology stack info
|
|
155
|
+
- `constraints` - Coding rules and constraints
|
|
156
|
+
- `context` - Project-specific context
|
|
157
|
+
- Any custom category
|
|
158
|
+
|
|
159
|
+
## UI Scaling
|
|
160
|
+
|
|
161
|
+
Use the scale buttons in the sidebar to adjust the UI. Scaling preference is saved in `~/.grace_manager/settings.json` and persists across sessions.
|
|
162
|
+
|
|
163
|
+
## Configuration Files
|
|
164
|
+
|
|
165
|
+
| File | Purpose |
|
|
166
|
+
|------|---------|
|
|
167
|
+
| `~/.grace_manager/profiles.json` | Context blocks |
|
|
168
|
+
| `~/.grace_manager/recent_projects.json` | Recently opened projects |
|
|
169
|
+
| `~/.grace_manager/settings.json` | UI preferences (scaling, etc.) |
|
|
170
|
+
| `~/.grace_manager/extension_presets.json` | Saved extension presets |
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
# GRACE Context Manager
|
|
2
|
+
|
|
3
|
+
A modern GUI tool for assembling high-quality context for LLM coding assistants. Built with PyQt6.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Context Blocks**: Reusable text snippets organized by category (tech stack, constraints, rules). Toggle on/off as needed.
|
|
8
|
+
- **Smart File Selection**: Tree view with checkboxes, file size display, and quick select/deselect.
|
|
9
|
+
- **Advanced Filtering**: Filter by extension (typed manually or by checkboxes with two-way binding), keyword filename filter, and built-in ignore rules (dot-files, `node_modules`, `__pycache__`, `venv`).
|
|
10
|
+
- **Dynamic Extension Detection**: Auto-detect all file extensions in your project with select/deselect all for quick filtering.
|
|
11
|
+
- **Typed Extension Filter**: Extensions can be typed directly as comma-separated values (`.py, .json, .yaml`) or selected via checkboxes — both stay in sync.
|
|
12
|
+
- **Extension Presets**: Save frequently used extension combinations (e.g., `python_ml`, `web_dev`) and reload them across sessions.
|
|
13
|
+
- **Live Preview**: See assembled context with token count estimation before copying.
|
|
14
|
+
- **Preview Modes**: Full context, structure only, or files only.
|
|
15
|
+
- **Dynamic Context Refresh**: Refresh selected file contents when files change on disk without re-selecting.
|
|
16
|
+
- **Scalable UI**: Adjustable interface scaling (75%-200%) for different screen sizes and devices.
|
|
17
|
+
- **Manual Directory Entry**: Type the project root path directly or use Browse/Recent selectors.
|
|
18
|
+
- **Keyboard Shortcuts**: Ctrl+C copy, Ctrl+N new block, Ctrl+R refresh, Ctrl+O browse, Ctrl+A/D select all/none.
|
|
19
|
+
|
|
20
|
+
## Quick Install
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
pip install grace-context-manager
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
Then run:
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
grace
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
### Upgrading
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install --upgrade grace-context-manager
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
**Your data is safe.** All settings, context blocks, extension presets, and recent projects are stored in `~/.grace_manager/` — they persist across upgrades and are never overwritten.
|
|
39
|
+
|
|
40
|
+
### One-Click Installers (no Python required)
|
|
41
|
+
|
|
42
|
+
For users without Python, download and run the platform installer which bundles everything:
|
|
43
|
+
|
|
44
|
+
| Platform | Installer |
|
|
45
|
+
|----------|-----------|
|
|
46
|
+
| Windows | **[install.bat](install.bat)** (right-click → "Run") |
|
|
47
|
+
| Linux / macOS | `./install.sh` |
|
|
48
|
+
|
|
49
|
+
### Manual Install (from source)
|
|
50
|
+
|
|
51
|
+
```bash
|
|
52
|
+
git clone https://github.com/vibechat/grace-context-manager.git
|
|
53
|
+
cd grace-context-manager
|
|
54
|
+
pip install -e .
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Workflow
|
|
58
|
+
|
|
59
|
+
1. **Select Project**: Click **Browse** to choose your project directory, select from **Recent**, or type the path directly.
|
|
60
|
+
2. **Add Context Blocks**: Create reusable blocks for tech stack, coding rules, etc.
|
|
61
|
+
3. **Filter Extensions**: Click **Detect** to auto-discover all file extensions, then:
|
|
62
|
+
- Click checkboxes to toggle individual extensions
|
|
63
|
+
- Type extensions directly in the text field (`.py, .json, .yaml`)
|
|
64
|
+
- Use **All**/**None** for quick toggle
|
|
65
|
+
- Save/Load **presets** for reuse across projects
|
|
66
|
+
4. **Keyword Filter**: Set a keyword to only show files matching that keyword in their filename.
|
|
67
|
+
5. **Select Files**: Check files/directories to include in context.
|
|
68
|
+
6. **Add Instruction**: Write your task in the instruction box.
|
|
69
|
+
7. **Refresh**: Click **Refresh Context** to reload changed files from disk.
|
|
70
|
+
8. **Copy**: Click **Copy Context** and paste into your LLM.
|
|
71
|
+
|
|
72
|
+
## Extension Presets
|
|
73
|
+
|
|
74
|
+
Save and load extension filter combinations:
|
|
75
|
+
|
|
76
|
+
1. Select your desired extensions (via checkboxes or typing)
|
|
77
|
+
2. Click **Save** to store as a named preset (e.g., `python_ml`)
|
|
78
|
+
3. Click **Load▾** to apply a previously saved preset
|
|
79
|
+
4. Presets persist globally in `~/.grace_manager/extension_presets.json`
|
|
80
|
+
|
|
81
|
+
Built-in defaults: `python_ml`, `web_dev`, `data_science`, `rust`, `golang`, `python`, `all`.
|
|
82
|
+
|
|
83
|
+
## Keyboard Shortcuts
|
|
84
|
+
|
|
85
|
+
| Shortcut | Action |
|
|
86
|
+
|----------|--------|
|
|
87
|
+
| `Ctrl+C` | Copy context to clipboard |
|
|
88
|
+
| `Ctrl+N` | New context block |
|
|
89
|
+
| `Ctrl+R` | Refresh context |
|
|
90
|
+
| `Ctrl+O` | Browse directory |
|
|
91
|
+
| `Ctrl+A` | Select all files |
|
|
92
|
+
| `Ctrl+D` | Deselect all files |
|
|
93
|
+
|
|
94
|
+
## Running Tests
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
# Install test dependencies
|
|
98
|
+
pip install -e ".[test]"
|
|
99
|
+
|
|
100
|
+
# Run all tests
|
|
101
|
+
pytest tests/ -v
|
|
102
|
+
|
|
103
|
+
# Run specific test file
|
|
104
|
+
pytest tests/test_engine.py -v
|
|
105
|
+
pytest tests/test_tree_model.py -v
|
|
106
|
+
pytest tests/test_config.py -v
|
|
107
|
+
|
|
108
|
+
# Quick smoke test (no pytest required)
|
|
109
|
+
python -m pytest tests/
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
Tests run headless (virtual display) and cover:
|
|
113
|
+
- **Engine**: Token estimation, file scanning with extension/keyword filters, context assembly across all modes
|
|
114
|
+
- **Tree model**: Check/uncheck propagation, partial uncheck (Bug 1+2 fix), eager loading, extension & keyword filters, nested directory handling, dot-file exclusion
|
|
115
|
+
- **Config**: Built-in constants, ignore rules, extension presets
|
|
116
|
+
|
|
117
|
+
## Context Blocks
|
|
118
|
+
|
|
119
|
+
Context blocks are saved in `~/.grace_manager/profiles.json`. They persist across sessions.
|
|
120
|
+
|
|
121
|
+
Organize blocks by category:
|
|
122
|
+
- `tech` - Technology stack info
|
|
123
|
+
- `constraints` - Coding rules and constraints
|
|
124
|
+
- `context` - Project-specific context
|
|
125
|
+
- Any custom category
|
|
126
|
+
|
|
127
|
+
## UI Scaling
|
|
128
|
+
|
|
129
|
+
Use the scale buttons in the sidebar to adjust the UI. Scaling preference is saved in `~/.grace_manager/settings.json` and persists across sessions.
|
|
130
|
+
|
|
131
|
+
## Configuration Files
|
|
132
|
+
|
|
133
|
+
| File | Purpose |
|
|
134
|
+
|------|---------|
|
|
135
|
+
| `~/.grace_manager/profiles.json` | Context blocks |
|
|
136
|
+
| `~/.grace_manager/recent_projects.json` | Recently opened projects |
|
|
137
|
+
| `~/.grace_manager/settings.json` | UI preferences (scaling, etc.) |
|
|
138
|
+
| `~/.grace_manager/extension_presets.json` | Saved extension presets |
|
|
Binary file
|
|
Binary file
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from pathlib import Path
|
|
2
|
+
|
|
3
|
+
CONFIG_DIR = Path.home() / ".grace_manager"
|
|
4
|
+
PROFILES_FILE = CONFIG_DIR / "profiles.json"
|
|
5
|
+
RECENT_FILE = CONFIG_DIR / "recent_projects.json"
|
|
6
|
+
SETTINGS_FILE = CONFIG_DIR / "settings.json"
|
|
7
|
+
EXTENSION_PRESETS_FILE = CONFIG_DIR / "extension_presets.json"
|
|
8
|
+
|
|
9
|
+
DEFAULT_EXTENSIONS = ".py, .md, .txt, .json, .yaml, .toml, .cfg, .ini"
|
|
10
|
+
|
|
11
|
+
BUILTIN_IGNORE_DIRS = {'__pycache__', 'venv', 'node_modules', 'egg-info'}
|
|
12
|
+
BUILTIN_IGNORE_FILES = {'Thumbs.db'}
|
|
13
|
+
|
|
14
|
+
SCALE_STEPS = [75, 90, 100, 110, 125, 150, 175, 200]
|
|
15
|
+
|
|
16
|
+
MAX_FILE_SIZE = 1024 * 1024
|
|
17
|
+
|
|
18
|
+
DEFAULT_EXTENSION_PRESETS = {
|
|
19
|
+
"python_ml": [".py", ".json", ".yaml", ".yml", ".toml", ".md", ".cfg"],
|
|
20
|
+
"web_dev": [".js", ".ts", ".jsx", ".tsx", ".json", ".html", ".css", ".scss", ".md"],
|
|
21
|
+
"data_science": [".py", ".ipynb", ".json", ".csv", ".sql", ".r", ".md"],
|
|
22
|
+
"rust": [".rs", ".toml", ".md", ".json"],
|
|
23
|
+
"golang": [".go", ".mod", ".sum", ".md", ".json", ".yaml"],
|
|
24
|
+
"python": [".py", ".md", ".txt", ".json", ".yaml", ".toml"],
|
|
25
|
+
"all": [],
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
EXT_ICONS = {
|
|
29
|
+
".py": "\ue606", ".js": "JS", ".ts": "TS", ".jsx": "JS",
|
|
30
|
+
".tsx": "TS", ".md": "MD", ".json": "{}", ".yaml": "Y",
|
|
31
|
+
".yml": "Y", ".xml": "X", ".html": "H", ".css": "#",
|
|
32
|
+
".toml": "T", ".cfg": "C", ".ini": "I", ".rs": "RS",
|
|
33
|
+
".go": "Go", ".java": "Jv", ".c": "C", ".cpp": "C+",
|
|
34
|
+
".h": "H", ".rb": "Rb", ".php": "P", ".sql": "SQ",
|
|
35
|
+
".sh": "Sh", ".bash": "Sh", ".zsh": "Sh", ".fish": "Fi",
|
|
36
|
+
".ps1": "PS", ".bat": "B", ".cmake": "CM", ".make": "Mk",
|
|
37
|
+
".dockerfile": "D", ".kt": "Kt", ".swift": "Sw",
|
|
38
|
+
".r": "R", ".m": "M", ".scala": "Sc", ".lua": "Lu",
|
|
39
|
+
".vim": "Vi", ".tex": "Tx", ".gitignore": "Gi",
|
|
40
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
from typing import List, Set, Optional
|
|
4
|
+
from .models import ContextBlock
|
|
5
|
+
from .config import MAX_FILE_SIZE
|
|
6
|
+
|
|
7
|
+
class TokenEstimator:
|
|
8
|
+
@staticmethod
|
|
9
|
+
def estimate(text: str) -> int:
|
|
10
|
+
return max(1, len(text) // 4)
|
|
11
|
+
|
|
12
|
+
@staticmethod
|
|
13
|
+
def format_count(count: int) -> str:
|
|
14
|
+
if count < 1000:
|
|
15
|
+
return f"{count} tokens"
|
|
16
|
+
return f"{count/1000:.1f}k tokens"
|
|
17
|
+
|
|
18
|
+
class FileScanner:
|
|
19
|
+
@staticmethod
|
|
20
|
+
def scan_extensions(root_path: str, ignore_dirs: Set[str], max_exts: int = 60) -> List[str]:
|
|
21
|
+
extensions: Set[str] = set()
|
|
22
|
+
try:
|
|
23
|
+
for dirpath, dirnames, filenames in os.walk(root_path):
|
|
24
|
+
dirnames[:] = [d for d in dirnames if d not in ignore_dirs and not d.startswith('.')]
|
|
25
|
+
for fname in filenames:
|
|
26
|
+
if fname.startswith('.'):
|
|
27
|
+
continue
|
|
28
|
+
ext = os.path.splitext(fname)[1].lower()
|
|
29
|
+
if ext:
|
|
30
|
+
extensions.add(ext)
|
|
31
|
+
if len(extensions) > max_exts:
|
|
32
|
+
break
|
|
33
|
+
if len(extensions) > max_exts:
|
|
34
|
+
break
|
|
35
|
+
except (PermissionError, OSError):
|
|
36
|
+
pass
|
|
37
|
+
return sorted(extensions)
|
|
38
|
+
|
|
39
|
+
@staticmethod
|
|
40
|
+
def list_entries(base_path: str, extensions: List[str], ignore_dirs: Set[str],
|
|
41
|
+
ignore_files: Set[str], keyword_filter: str = "") -> List[str]:
|
|
42
|
+
exts = {e.strip().lower() for e in extensions if e.strip()}
|
|
43
|
+
kw = keyword_filter.strip().lower()
|
|
44
|
+
valid: List[str] = []
|
|
45
|
+
try:
|
|
46
|
+
for dirpath, dirnames, filenames in os.walk(base_path):
|
|
47
|
+
dirnames[:] = [d for d in dirnames if d not in ignore_dirs and not d.startswith('.')]
|
|
48
|
+
for fname in filenames:
|
|
49
|
+
if fname in ignore_files or fname.startswith('.'):
|
|
50
|
+
continue
|
|
51
|
+
ext = os.path.splitext(fname)[1].lower()
|
|
52
|
+
if exts and ext not in exts:
|
|
53
|
+
continue
|
|
54
|
+
if kw and kw not in fname.lower():
|
|
55
|
+
continue
|
|
56
|
+
valid.append(os.path.join(dirpath, fname))
|
|
57
|
+
except (PermissionError, OSError):
|
|
58
|
+
pass
|
|
59
|
+
return valid
|
|
60
|
+
|
|
61
|
+
class ContextBuilder:
|
|
62
|
+
@staticmethod
|
|
63
|
+
def build_tree_string(tree: dict, prefix: str = "") -> List[str]:
|
|
64
|
+
lines: List[str] = []
|
|
65
|
+
keys = sorted(tree.keys())
|
|
66
|
+
for i, key in enumerate(keys):
|
|
67
|
+
is_last = i == len(keys) - 1
|
|
68
|
+
connector = "\u2514\u2500\u2500 " if is_last else "\u251c\u2500\u2500 "
|
|
69
|
+
if tree[key]:
|
|
70
|
+
lines.append(f"{prefix}{connector}{key}/")
|
|
71
|
+
ext = " " if is_last else "\u2502 "
|
|
72
|
+
lines.extend(ContextBuilder.build_tree_string(tree[key], prefix + ext))
|
|
73
|
+
else:
|
|
74
|
+
lines.append(f"{prefix}{connector}{key}")
|
|
75
|
+
return lines
|
|
76
|
+
|
|
77
|
+
@staticmethod
|
|
78
|
+
def build(blocks: List[ContextBlock], selected_files: List[str],
|
|
79
|
+
base_dir: str, mode: str, instruction: str,
|
|
80
|
+
max_file_size: int = MAX_FILE_SIZE) -> str:
|
|
81
|
+
parts: List[str] = []
|
|
82
|
+
|
|
83
|
+
if blocks:
|
|
84
|
+
parts.append("=== CONTEXT BLOCKS ===\n")
|
|
85
|
+
for block in sorted(blocks, key=lambda b: b.order):
|
|
86
|
+
parts.append(f"--- {block.name} ({block.category}) ---")
|
|
87
|
+
parts.append(block.content)
|
|
88
|
+
parts.append("")
|
|
89
|
+
|
|
90
|
+
if selected_files and mode != "Files Only":
|
|
91
|
+
tree_dict = {}
|
|
92
|
+
for fp in sorted(selected_files):
|
|
93
|
+
rel = os.path.relpath(fp, base_dir)
|
|
94
|
+
curr = tree_dict
|
|
95
|
+
for part in rel.split(os.sep):
|
|
96
|
+
curr = curr.setdefault(part, {})
|
|
97
|
+
parts.append("=== PROJECT STRUCTURE ===")
|
|
98
|
+
parts.append(".")
|
|
99
|
+
parts.extend(ContextBuilder.build_tree_string(tree_dict))
|
|
100
|
+
parts.append("")
|
|
101
|
+
|
|
102
|
+
if mode != "Structure Only":
|
|
103
|
+
parts.append("=== FILE CONTENTS ===\n")
|
|
104
|
+
for fp in sorted(selected_files):
|
|
105
|
+
rel = os.path.relpath(fp, base_dir)
|
|
106
|
+
try:
|
|
107
|
+
size = os.path.getsize(fp)
|
|
108
|
+
if size > max_file_size:
|
|
109
|
+
parts.append(f"--- {rel} ---")
|
|
110
|
+
parts.append(f"[Omitted: {size/1024/1024:.1f}MB exceeds 1MB limit]\n")
|
|
111
|
+
continue
|
|
112
|
+
with open(fp, 'r', encoding='utf-8', errors='replace') as fh:
|
|
113
|
+
content = fh.read()
|
|
114
|
+
ext = os.path.splitext(rel)[1].lstrip('.') or 'text'
|
|
115
|
+
parts.append(f"--- {rel} ---")
|
|
116
|
+
parts.append(f"```{ext}")
|
|
117
|
+
parts.append(content)
|
|
118
|
+
parts.append("```\n")
|
|
119
|
+
except Exception as e:
|
|
120
|
+
parts.append(f"--- {rel} ---")
|
|
121
|
+
parts.append(f"[Error: {e}]\n")
|
|
122
|
+
|
|
123
|
+
if instruction:
|
|
124
|
+
parts.append("=== YOUR TASK ===")
|
|
125
|
+
parts.append(instruction)
|
|
126
|
+
|
|
127
|
+
return "\n".join(parts)
|