contextzip 0.3.4__tar.gz → 0.3.6__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.
- {contextzip-0.3.4 → contextzip-0.3.6}/PKG-INFO +93 -42
- {contextzip-0.3.4 → contextzip-0.3.6}/README.md +92 -41
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/__init__.py +1 -1
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/ai/gemini.py +15 -7
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/ai/heuristic.py +9 -2
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/ai/selector.py +7 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/cli.py +151 -213
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/cli_ai.py +8 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/cli_display.py +8 -56
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/cli_onboard.py +0 -59
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/clipboard.py +0 -86
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/config.py +24 -19
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/detector.py +122 -12
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/filters.py +44 -8
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/git.py +0 -131
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/packager.py +186 -35
- contextzip-0.3.6/contextzip/project_config.py +231 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/watcher.py +6 -6
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/PKG-INFO +93 -42
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/SOURCES.txt +1 -5
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/entry_points.txt +1 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/pyproject.toml +2 -1
- contextzip-0.3.4/contextzip/brief.py +0 -280
- contextzip-0.3.4/contextzip/claude_artifacts.py +0 -150
- contextzip-0.3.4/contextzip/claude_export.py +0 -73
- contextzip-0.3.4/contextzip/code_changes.py +0 -257
- contextzip-0.3.4/contextzip/markers.py +0 -61
- {contextzip-0.3.4 → contextzip-0.3.6}/LICENSE +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/ai/__init__.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/api.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/error_parser.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/__init__.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/base.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/errors/__init__.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/errors/node.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/errors/python.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/go.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/node.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/python.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/ruby.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip/rules/rust.py +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/dependency_links.txt +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/requires.txt +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/contextzip.egg-info/top_level.txt +0 -0
- {contextzip-0.3.4 → contextzip-0.3.6}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: contextzip
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.6
|
|
4
4
|
Summary: Intelligently package your codebase for AI tools
|
|
5
5
|
Author-email: Deepesh <akadeepesh@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -47,13 +47,13 @@ contextzip eliminates that entirely. Run it from your project root — it detect
|
|
|
47
47
|
|
|
48
48
|
## Features
|
|
49
49
|
|
|
50
|
-
- **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each
|
|
50
|
+
- **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each. Detection isn't limited to the project root: a shallow, bounded scan of subdirectories means a monorepo (`frontend/` + `backend/`, etc.) gets every ecosystem it contains detected and excluded correctly, not just whatever sits at the top level
|
|
51
51
|
- **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
|
|
52
52
|
- **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
|
|
53
53
|
- **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
|
|
54
54
|
- **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
|
|
55
|
-
- **
|
|
56
|
-
- **Persistent workspace** — all generated ZIPs land in `.contextzip
|
|
55
|
+
- **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip/config.json`
|
|
56
|
+
- **Persistent workspace** — all generated ZIPs land in `.contextzip/output/`, discoverable, reusable, and git-ignored automatically
|
|
57
57
|
- **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
|
|
58
58
|
- **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
|
|
59
59
|
- **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
|
|
@@ -95,7 +95,7 @@ contextzip will:
|
|
|
95
95
|
|
|
96
96
|
1. Detect your framework (e.g. `Next.js + Node.js`)
|
|
97
97
|
2. Apply the appropriate exclusion rules
|
|
98
|
-
3. Create a compressed ZIP in `.contextzip/` at your project root
|
|
98
|
+
3. Create a compressed ZIP in `.contextzip/output/` at your project root
|
|
99
99
|
4. Open your file manager with the ZIP selected and ready to copy
|
|
100
100
|
|
|
101
101
|
---
|
|
@@ -106,6 +106,8 @@ contextzip will:
|
|
|
106
106
|
contextzip [OPTIONS]
|
|
107
107
|
```
|
|
108
108
|
|
|
109
|
+
> `cz` is a shorthand alias for `contextzip` — both commands are identical and support every option and subcommand shown below (e.g. `cz --git-changes`, `cz exclude node_modules`).
|
|
110
|
+
|
|
109
111
|
| Option | Description |
|
|
110
112
|
|---|---|
|
|
111
113
|
| `-p`, `--prompt TEXT` | Describe your task in plain English — Gemini selects only the relevant files |
|
|
@@ -118,7 +120,7 @@ contextzip [OPTIONS]
|
|
|
118
120
|
| `--no-clipboard` | Skip the clipboard / folder-open step |
|
|
119
121
|
| `--no-gitignore` | Ignore the project's `.gitignore` |
|
|
120
122
|
|
|
121
|
-
**Subcommands:** `exclude`, `include`, `watch`, `config
|
|
123
|
+
**Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
|
|
122
124
|
|
|
123
125
|
---
|
|
124
126
|
|
|
@@ -226,7 +228,7 @@ contextzip starts your process normally. You see output exactly as you would wit
|
|
|
226
228
|
╰───────────────────────────────────────────────────╯
|
|
227
229
|
```
|
|
228
230
|
|
|
229
|
-
Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Your server keeps running — no restart, no interruption.
|
|
231
|
+
Press **D** and contextzip immediately writes `.contextzip/output/debug-context.zip`. Your server keeps running — no restart, no interruption.
|
|
230
232
|
|
|
231
233
|
**What's in the ZIP:**
|
|
232
234
|
|
|
@@ -244,60 +246,109 @@ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Y
|
|
|
244
246
|
|
|
245
247
|
---
|
|
246
248
|
|
|
247
|
-
##
|
|
249
|
+
## What gets excluded
|
|
248
250
|
|
|
249
|
-
|
|
251
|
+
contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
|
|
252
|
+
|
|
253
|
+
**Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` working folder.
|
|
254
|
+
|
|
255
|
+
**By framework:**
|
|
256
|
+
|
|
257
|
+
| Stack | Additional exclusions |
|
|
258
|
+
|---|---|
|
|
259
|
+
| Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
|
|
260
|
+
| Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
|
|
261
|
+
| Rust | `target/`, `Cargo.lock`, `*.rlib` |
|
|
262
|
+
| Go | `vendor/`, `go.sum`, `bin/` |
|
|
263
|
+
|
|
264
|
+
Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied. Marker files don't have to sit at the project root: contextzip also does a shallow, bounded scan of subdirectories (2 levels deep, skipping `node_modules/`, `.venv/`, `.git/`, and other dependency/build dirs it would never want to look inside anyway), so a layout like
|
|
250
265
|
|
|
251
|
-
```
|
|
252
|
-
|
|
253
|
-
|
|
266
|
+
```
|
|
267
|
+
root/
|
|
268
|
+
frontend/package.json
|
|
269
|
+
backend/requirements.txt
|
|
254
270
|
```
|
|
255
271
|
|
|
256
|
-
|
|
272
|
+
detects both Node.js and Python from `root/`, without either marker existing at the top level. Run with default output (not `--dry-run --verbose`) and you'll see which subdirectory each ecosystem came from, e.g. `Next.js (frontend/) + FastAPI (backend/)`.
|
|
257
273
|
|
|
258
|
-
|
|
274
|
+
---
|
|
259
275
|
|
|
260
|
-
|
|
261
|
-
- Whatever code changed, resolved per file in priority order:
|
|
262
|
-
1. **Not pushed** — diffed against your upstream branch, plus the complete current file
|
|
263
|
-
2. **Diverged from Claude** — diffed against the version Claude last produced (if you've set up a session key — see below), plus the complete current codebase file
|
|
264
|
-
3. **Since last run** — diffed against a per-branch checkpoint that `eod`/`handoff` remember automatically, plus the complete current file
|
|
265
|
-
- New, untracked files are included as full content (there's nothing to diff them against)
|
|
266
|
-
- `eod` ends the prompt with a flat instruction to produce a work-log table; `handoff` frames it as continuing the project in a new chat
|
|
276
|
+
## Workspace location
|
|
267
277
|
|
|
268
|
-
|
|
278
|
+
By default, `.contextzip/` is created at your git root — that's `_find_git_root()` walking up from the current directory until it finds a `.git` folder; outside a repo, it falls back to the current directory. This can be overridden two ways, in order of priority:
|
|
269
279
|
|
|
270
|
-
|
|
280
|
+
1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
|
|
281
|
+
2. **A committed `.contextzip/config.json` at the project root** — team-shared, applies to everyone who clones the repo:
|
|
282
|
+
```json
|
|
283
|
+
{ "workspace_location": "git-root" }
|
|
284
|
+
```
|
|
285
|
+
3. **Your personal config** — a per-machine default that doesn't get committed:
|
|
286
|
+
```bash
|
|
287
|
+
contextzip config --set-workspace cwd # always use ./.contextzip wherever you run it
|
|
288
|
+
contextzip config --set-workspace git-root # back to the default
|
|
289
|
+
contextzip config --set-workspace ~/zips # a fixed custom location, anywhere
|
|
290
|
+
contextzip config --reset-workspace # clear your personal override
|
|
291
|
+
```
|
|
271
292
|
|
|
272
|
-
|
|
273
|
-
contextzip config --set-session-key
|
|
274
|
-
```
|
|
293
|
+
Accepted values are `"git-root"`, `"cwd"`, or any path (absolute, or relative to the git root). Run `contextzip config` with no flags to see which value is currently active and where it came from.
|
|
275
294
|
|
|
276
|
-
This
|
|
295
|
+
This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip/config.json` instead if you'd rather each subproject keep its own.
|
|
296
|
+
|
|
297
|
+
---
|
|
298
|
+
|
|
299
|
+
## Project configuration
|
|
300
|
+
|
|
301
|
+
Every project gets a `.contextzip/` workspace at the project root:
|
|
277
302
|
|
|
278
|
-
```
|
|
279
|
-
contextzip
|
|
280
|
-
|
|
303
|
+
```
|
|
304
|
+
.contextzip/
|
|
305
|
+
├── config.json # project preferences — commit this
|
|
306
|
+
├── .gitignore # keeps output/ untracked, config.json trackable
|
|
307
|
+
└── output/ # generated ZIPs — never committed
|
|
308
|
+
├── codebase.zip
|
|
309
|
+
├── changes.zip (--git-changes)
|
|
310
|
+
└── debug-context.zip (contextzip watch)
|
|
281
311
|
```
|
|
282
312
|
|
|
283
|
-
|
|
313
|
+
`.contextzip/` is **not** committed to Git by default — `.contextzip/.gitignore` is written automatically the first time you run contextzip in a repo, and it ignores everything in the folder *except* `config.json` and itself. That's deliberate: `output/` is per-machine, disposable scratch space, while `config.json` holds team-shared preferences you'll usually want everyone on the same page about.
|
|
314
|
+
|
|
315
|
+
`config.json` currently supports:
|
|
316
|
+
|
|
317
|
+
```json
|
|
318
|
+
{
|
|
319
|
+
"workspace_location": "git-root",
|
|
320
|
+
"scan_depth": null,
|
|
321
|
+
"always_include": [],
|
|
322
|
+
"always_exclude": [],
|
|
323
|
+
"ai": {
|
|
324
|
+
"enabled": true,
|
|
325
|
+
"provider": "gemini",
|
|
326
|
+
"max_files": 10
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
```
|
|
284
330
|
|
|
285
|
-
|
|
331
|
+
All keys are optional — start with just the ones you need.
|
|
286
332
|
|
|
287
|
-
|
|
333
|
+
| Key | What it does |
|
|
334
|
+
| --- | --- |
|
|
335
|
+
| `workspace_location` | Same as `contextzip config --set-workspace`, but team-shared. See [Workspace location](#workspace-location). |
|
|
336
|
+
| `scan_depth` | Reserved for a future bounded-depth scan mode. |
|
|
337
|
+
| `always_include` | A standing "force include" list (gitwildmatch patterns). Files matching it are packaged even if an auto-rule or `.gitignore` would otherwise exclude them — e.g. `["docs/architecture.md"]` to always pull in a doc that lives in an otherwise-excluded folder. |
|
|
338
|
+
| `always_exclude` | A standing exclusion list, applied on every run without retyping `-e`/`--exclude`. |
|
|
339
|
+
| `ai.enabled` | Set to `false` to disable `--prompt` entirely for this project; contextzip will refuse with a clear message instead of silently ignoring it. |
|
|
340
|
+
| `ai.provider` | Reserved for future AI providers. Only `"gemini"` is currently supported. |
|
|
341
|
+
| `ai.max_files` | Caps how many files `--prompt` mode may return, for both the Gemini and keyword-heuristic paths. |
|
|
288
342
|
|
|
289
|
-
|
|
343
|
+
`always_include`/`always_exclude` are additive on top of `--include`/`--exclude` for that run — an explicit `contextzip include PATH` still has the final say over what's actually packaged. Persistent preferences belong in `config.json` rather than an ever-growing list of CLI flags; CLI flags stay for one-off, explicit actions (`--dry-run`, `--prompt "…"`, `--output FILE`, etc.).
|
|
290
344
|
|
|
291
|
-
**
|
|
345
|
+
A **web-based configuration UI** that generates `config.json` for you is on the roadmap — the schema above is designed to grow additively, so future preferences (including ones set visually) won't require another migration.
|
|
292
346
|
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
| Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
|
|
297
|
-
| Rust | `target/`, `Cargo.lock`, `*.rlib` |
|
|
298
|
-
| Go | `vendor/`, `go.sum`, `bin/` |
|
|
347
|
+
### Deprecation: `.contextzip.json`
|
|
348
|
+
|
|
349
|
+
Earlier versions of contextzip read a `.contextzip.json` file at the project root for `workspace_location`/`scan_depth`. That file is now **deprecated** in favor of `.contextzip/config.json` — contextzip still reads it automatically if `.contextzip/config.json` doesn't exist yet (so nothing breaks), and prints a one-time reminder to migrate. To migrate, just move its contents into the `"workspace_location"`/`"scan_depth"` keys of a new `.contextzip/config.json` and delete the old file.
|
|
299
350
|
|
|
300
|
-
|
|
351
|
+
`~/.config/contextzip/config.json` (your personal, per-machine config — API key, personal workspace override) is unrelated and unaffected by any of this.
|
|
301
352
|
|
|
302
353
|
---
|
|
303
354
|
|
|
@@ -18,13 +18,13 @@ contextzip eliminates that entirely. Run it from your project root — it detect
|
|
|
18
18
|
|
|
19
19
|
## Features
|
|
20
20
|
|
|
21
|
-
- **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each
|
|
21
|
+
- **Smart framework detection** — automatically identifies Node.js, Next.js, Python, Django, FastAPI, Rust, Go, and Ruby, applying the right exclusion rules for each. Detection isn't limited to the project root: a shallow, bounded scan of subdirectories means a monorepo (`frontend/` + `backend/`, etc.) gets every ecosystem it contains detected and excluded correctly, not just whatever sits at the top level
|
|
22
22
|
- **Respects `.gitignore`** — your existing ignore patterns are honoured automatically
|
|
23
23
|
- **Git-aware packaging** — use `--git-changes` to package only modified, staged, and untracked files; perfect for incremental debugging and PR review sessions
|
|
24
24
|
- **AI-powered file selection** — describe your task in plain English with `--prompt` and Gemini selects the minimum relevant files automatically, no manual hunting required
|
|
25
25
|
- **Terminal error watcher** — wrap any dev server with `contextzip watch` to auto-detect errors and package a ready-to-upload debug context in one keypress
|
|
26
|
-
- **
|
|
27
|
-
- **Persistent workspace** — all generated ZIPs land in `.contextzip
|
|
26
|
+
- **Configurable workspace location** — `.contextzip/` lives at the git root by default, but you can pin it elsewhere per-machine (`contextzip config --set-workspace`) or for the whole team via a committed `.contextzip/config.json`
|
|
27
|
+
- **Persistent workspace** — all generated ZIPs land in `.contextzip/output/`, discoverable, reusable, and git-ignored automatically
|
|
28
28
|
- **Warns before it's a problem** — flags large (≥ 1 MB) and binary files that AI tools can't read, before you waste an upload
|
|
29
29
|
- **Handles edge cases** — dangling symlinks, unreadable files, and paths outside the project tree are caught and reported, never silently dropped
|
|
30
30
|
- **Full CLI control** — `--include`, `--exclude`, `--dry-run`, `--output`, all composable
|
|
@@ -66,7 +66,7 @@ contextzip will:
|
|
|
66
66
|
|
|
67
67
|
1. Detect your framework (e.g. `Next.js + Node.js`)
|
|
68
68
|
2. Apply the appropriate exclusion rules
|
|
69
|
-
3. Create a compressed ZIP in `.contextzip/` at your project root
|
|
69
|
+
3. Create a compressed ZIP in `.contextzip/output/` at your project root
|
|
70
70
|
4. Open your file manager with the ZIP selected and ready to copy
|
|
71
71
|
|
|
72
72
|
---
|
|
@@ -77,6 +77,8 @@ contextzip will:
|
|
|
77
77
|
contextzip [OPTIONS]
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
+
> `cz` is a shorthand alias for `contextzip` — both commands are identical and support every option and subcommand shown below (e.g. `cz --git-changes`, `cz exclude node_modules`).
|
|
81
|
+
|
|
80
82
|
| Option | Description |
|
|
81
83
|
|---|---|
|
|
82
84
|
| `-p`, `--prompt TEXT` | Describe your task in plain English — Gemini selects only the relevant files |
|
|
@@ -89,7 +91,7 @@ contextzip [OPTIONS]
|
|
|
89
91
|
| `--no-clipboard` | Skip the clipboard / folder-open step |
|
|
90
92
|
| `--no-gitignore` | Ignore the project's `.gitignore` |
|
|
91
93
|
|
|
92
|
-
**Subcommands:** `exclude`, `include`, `watch`, `config
|
|
94
|
+
**Subcommands:** `exclude`, `include`, `watch`, `config` — run `contextzip --help` for full details.
|
|
93
95
|
|
|
94
96
|
---
|
|
95
97
|
|
|
@@ -197,7 +199,7 @@ contextzip starts your process normally. You see output exactly as you would wit
|
|
|
197
199
|
╰───────────────────────────────────────────────────╯
|
|
198
200
|
```
|
|
199
201
|
|
|
200
|
-
Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Your server keeps running — no restart, no interruption.
|
|
202
|
+
Press **D** and contextzip immediately writes `.contextzip/output/debug-context.zip`. Your server keeps running — no restart, no interruption.
|
|
201
203
|
|
|
202
204
|
**What's in the ZIP:**
|
|
203
205
|
|
|
@@ -215,60 +217,109 @@ Press **D** and contextzip immediately writes `.contextzip/debug-context.zip`. Y
|
|
|
215
217
|
|
|
216
218
|
---
|
|
217
219
|
|
|
218
|
-
##
|
|
220
|
+
## What gets excluded
|
|
219
221
|
|
|
220
|
-
|
|
222
|
+
contextzip stacks exclusion rules based on your detected stack, on top of your `.gitignore`.
|
|
223
|
+
|
|
224
|
+
**Always excluded:** `.git/`, `.env` files, logs, caches, editor config (`.vscode/`, `.idea/`), OS files (`.DS_Store`, `Thumbs.db`), common binary formats, and contextzip's own `.contextzip/` working folder.
|
|
225
|
+
|
|
226
|
+
**By framework:**
|
|
227
|
+
|
|
228
|
+
| Stack | Additional exclusions |
|
|
229
|
+
|---|---|
|
|
230
|
+
| Node.js / Next.js | `node_modules/`, `.next/`, `dist/`, `build/`, lock files, `*.min.js`, `*.d.ts` |
|
|
231
|
+
| Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
|
|
232
|
+
| Rust | `target/`, `Cargo.lock`, `*.rlib` |
|
|
233
|
+
| Go | `vendor/`, `go.sum`, `bin/` |
|
|
234
|
+
|
|
235
|
+
Detection is additive — a monorepo with both `package.json` and `pyproject.toml` gets both rule sets applied. Marker files don't have to sit at the project root: contextzip also does a shallow, bounded scan of subdirectories (2 levels deep, skipping `node_modules/`, `.venv/`, `.git/`, and other dependency/build dirs it would never want to look inside anyway), so a layout like
|
|
221
236
|
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
|
|
237
|
+
```
|
|
238
|
+
root/
|
|
239
|
+
frontend/package.json
|
|
240
|
+
backend/requirements.txt
|
|
225
241
|
```
|
|
226
242
|
|
|
227
|
-
|
|
243
|
+
detects both Node.js and Python from `root/`, without either marker existing at the top level. Run with default output (not `--dry-run --verbose`) and you'll see which subdirectory each ecosystem came from, e.g. `Next.js (frontend/) + FastAPI (backend/)`.
|
|
228
244
|
|
|
229
|
-
|
|
245
|
+
---
|
|
230
246
|
|
|
231
|
-
|
|
232
|
-
- Whatever code changed, resolved per file in priority order:
|
|
233
|
-
1. **Not pushed** — diffed against your upstream branch, plus the complete current file
|
|
234
|
-
2. **Diverged from Claude** — diffed against the version Claude last produced (if you've set up a session key — see below), plus the complete current codebase file
|
|
235
|
-
3. **Since last run** — diffed against a per-branch checkpoint that `eod`/`handoff` remember automatically, plus the complete current file
|
|
236
|
-
- New, untracked files are included as full content (there's nothing to diff them against)
|
|
237
|
-
- `eod` ends the prompt with a flat instruction to produce a work-log table; `handoff` frames it as continuing the project in a new chat
|
|
247
|
+
## Workspace location
|
|
238
248
|
|
|
239
|
-
|
|
249
|
+
By default, `.contextzip/` is created at your git root — that's `_find_git_root()` walking up from the current directory until it finds a `.git` folder; outside a repo, it falls back to the current directory. This can be overridden two ways, in order of priority:
|
|
240
250
|
|
|
241
|
-
|
|
251
|
+
1. **`CONTEXTZIP_WORKSPACE_LOCATION` environment variable** — for a one-off override on a single run
|
|
252
|
+
2. **A committed `.contextzip/config.json` at the project root** — team-shared, applies to everyone who clones the repo:
|
|
253
|
+
```json
|
|
254
|
+
{ "workspace_location": "git-root" }
|
|
255
|
+
```
|
|
256
|
+
3. **Your personal config** — a per-machine default that doesn't get committed:
|
|
257
|
+
```bash
|
|
258
|
+
contextzip config --set-workspace cwd # always use ./.contextzip wherever you run it
|
|
259
|
+
contextzip config --set-workspace git-root # back to the default
|
|
260
|
+
contextzip config --set-workspace ~/zips # a fixed custom location, anywhere
|
|
261
|
+
contextzip config --reset-workspace # clear your personal override
|
|
262
|
+
```
|
|
242
263
|
|
|
243
|
-
|
|
244
|
-
contextzip config --set-session-key
|
|
245
|
-
```
|
|
264
|
+
Accepted values are `"git-root"`, `"cwd"`, or any path (absolute, or relative to the git root). Run `contextzip config` with no flags to see which value is currently active and where it came from.
|
|
246
265
|
|
|
247
|
-
This
|
|
266
|
+
This matters most for monorepos where you sometimes run contextzip from a subdirectory (`cd frontend && contextzip`) — with the default `git-root` setting, the workspace still lands at the repo root no matter where you ran it from, so you don't end up with scattered `.contextzip/` folders across `frontend/`, `backend/`, etc. Set `workspace_location: "cwd"` in a project's `.contextzip/config.json` instead if you'd rather each subproject keep its own.
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Project configuration
|
|
271
|
+
|
|
272
|
+
Every project gets a `.contextzip/` workspace at the project root:
|
|
248
273
|
|
|
249
|
-
```
|
|
250
|
-
contextzip
|
|
251
|
-
|
|
274
|
+
```
|
|
275
|
+
.contextzip/
|
|
276
|
+
├── config.json # project preferences — commit this
|
|
277
|
+
├── .gitignore # keeps output/ untracked, config.json trackable
|
|
278
|
+
└── output/ # generated ZIPs — never committed
|
|
279
|
+
├── codebase.zip
|
|
280
|
+
├── changes.zip (--git-changes)
|
|
281
|
+
└── debug-context.zip (contextzip watch)
|
|
252
282
|
```
|
|
253
283
|
|
|
254
|
-
|
|
284
|
+
`.contextzip/` is **not** committed to Git by default — `.contextzip/.gitignore` is written automatically the first time you run contextzip in a repo, and it ignores everything in the folder *except* `config.json` and itself. That's deliberate: `output/` is per-machine, disposable scratch space, while `config.json` holds team-shared preferences you'll usually want everyone on the same page about.
|
|
285
|
+
|
|
286
|
+
`config.json` currently supports:
|
|
287
|
+
|
|
288
|
+
```json
|
|
289
|
+
{
|
|
290
|
+
"workspace_location": "git-root",
|
|
291
|
+
"scan_depth": null,
|
|
292
|
+
"always_include": [],
|
|
293
|
+
"always_exclude": [],
|
|
294
|
+
"ai": {
|
|
295
|
+
"enabled": true,
|
|
296
|
+
"provider": "gemini",
|
|
297
|
+
"max_files": 10
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
```
|
|
255
301
|
|
|
256
|
-
|
|
302
|
+
All keys are optional — start with just the ones you need.
|
|
257
303
|
|
|
258
|
-
|
|
304
|
+
| Key | What it does |
|
|
305
|
+
| --- | --- |
|
|
306
|
+
| `workspace_location` | Same as `contextzip config --set-workspace`, but team-shared. See [Workspace location](#workspace-location). |
|
|
307
|
+
| `scan_depth` | Reserved for a future bounded-depth scan mode. |
|
|
308
|
+
| `always_include` | A standing "force include" list (gitwildmatch patterns). Files matching it are packaged even if an auto-rule or `.gitignore` would otherwise exclude them — e.g. `["docs/architecture.md"]` to always pull in a doc that lives in an otherwise-excluded folder. |
|
|
309
|
+
| `always_exclude` | A standing exclusion list, applied on every run without retyping `-e`/`--exclude`. |
|
|
310
|
+
| `ai.enabled` | Set to `false` to disable `--prompt` entirely for this project; contextzip will refuse with a clear message instead of silently ignoring it. |
|
|
311
|
+
| `ai.provider` | Reserved for future AI providers. Only `"gemini"` is currently supported. |
|
|
312
|
+
| `ai.max_files` | Caps how many files `--prompt` mode may return, for both the Gemini and keyword-heuristic paths. |
|
|
259
313
|
|
|
260
|
-
|
|
314
|
+
`always_include`/`always_exclude` are additive on top of `--include`/`--exclude` for that run — an explicit `contextzip include PATH` still has the final say over what's actually packaged. Persistent preferences belong in `config.json` rather than an ever-growing list of CLI flags; CLI flags stay for one-off, explicit actions (`--dry-run`, `--prompt "…"`, `--output FILE`, etc.).
|
|
261
315
|
|
|
262
|
-
**
|
|
316
|
+
A **web-based configuration UI** that generates `config.json` for you is on the roadmap — the schema above is designed to grow additively, so future preferences (including ones set visually) won't require another migration.
|
|
263
317
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
| Python / Django / FastAPI | `__pycache__/`, `.venv/`, `*.pyc`, `migrations/`, `.pytest_cache/`, lock files |
|
|
268
|
-
| Rust | `target/`, `Cargo.lock`, `*.rlib` |
|
|
269
|
-
| Go | `vendor/`, `go.sum`, `bin/` |
|
|
318
|
+
### Deprecation: `.contextzip.json`
|
|
319
|
+
|
|
320
|
+
Earlier versions of contextzip read a `.contextzip.json` file at the project root for `workspace_location`/`scan_depth`. That file is now **deprecated** in favor of `.contextzip/config.json` — contextzip still reads it automatically if `.contextzip/config.json` doesn't exist yet (so nothing breaks), and prints a one-time reminder to migrate. To migrate, just move its contents into the `"workspace_location"`/`"scan_depth"` keys of a new `.contextzip/config.json` and delete the old file.
|
|
270
321
|
|
|
271
|
-
|
|
322
|
+
`~/.config/contextzip/config.json` (your personal, per-machine config — API key, personal workspace override) is unrelated and unaffected by any of this.
|
|
272
323
|
|
|
273
324
|
---
|
|
274
325
|
|
|
@@ -70,6 +70,7 @@ def select_files(
|
|
|
70
70
|
file_tree: list[tuple[str, int]], # [(rel_path, size_bytes), ...]
|
|
71
71
|
ecosystem: str,
|
|
72
72
|
model: str = DEFAULT_MODEL,
|
|
73
|
+
max_files: int | None = None,
|
|
73
74
|
) -> list[str]:
|
|
74
75
|
"""
|
|
75
76
|
Ask Gemini which files are relevant to *prompt* and return their paths.
|
|
@@ -89,6 +90,11 @@ def select_files(
|
|
|
89
90
|
Gives the model important context for relevance scoring.
|
|
90
91
|
model:
|
|
91
92
|
Gemini model identifier. Defaults to gemini-2.5-flash-lite.
|
|
93
|
+
max_files:
|
|
94
|
+
Hard cap on how many files may be returned, regardless of what the
|
|
95
|
+
model outputs. Defaults to _MAX_FILES (12) when omitted — typically
|
|
96
|
+
overridden by a project's `ai.max_files` preference
|
|
97
|
+
(.contextzip/config.json).
|
|
92
98
|
|
|
93
99
|
Returns
|
|
94
100
|
-------
|
|
@@ -110,7 +116,8 @@ def select_files(
|
|
|
110
116
|
"Install it with: pip install httpx"
|
|
111
117
|
)
|
|
112
118
|
|
|
113
|
-
|
|
119
|
+
effective_max_files = max_files if max_files and max_files > 0 else _MAX_FILES
|
|
120
|
+
system_prompt = _build_system_prompt(effective_max_files)
|
|
114
121
|
user_message = _build_user_message(prompt, file_tree, ecosystem)
|
|
115
122
|
|
|
116
123
|
url = _API_URL.format(model=model, key=api_key)
|
|
@@ -153,7 +160,7 @@ def select_files(
|
|
|
153
160
|
f"Gemini API returned HTTP {response.status_code}: {response.text[:200]}"
|
|
154
161
|
)
|
|
155
162
|
|
|
156
|
-
return _parse_response(response.json(), file_tree)
|
|
163
|
+
return _parse_response(response.json(), file_tree, effective_max_files)
|
|
157
164
|
|
|
158
165
|
|
|
159
166
|
# ---------------------------------------------------------------------------
|
|
@@ -161,8 +168,8 @@ def select_files(
|
|
|
161
168
|
# ---------------------------------------------------------------------------
|
|
162
169
|
|
|
163
170
|
|
|
164
|
-
def _build_system_prompt() -> str:
|
|
165
|
-
return """\
|
|
171
|
+
def _build_system_prompt(max_files: int = _MAX_FILES) -> str:
|
|
172
|
+
return f"""\
|
|
166
173
|
You are a precise file relevance assistant for software projects.
|
|
167
174
|
|
|
168
175
|
Your only job: given a developer's task description and a project file tree,
|
|
@@ -178,7 +185,7 @@ Rules you must follow:
|
|
|
178
185
|
- Prefer files that will be MODIFIED over files that are merely referenced.
|
|
179
186
|
- Config files, test files, and documentation should only appear if the
|
|
180
187
|
task explicitly concerns them.
|
|
181
|
-
- Never return more than
|
|
188
|
+
- Never return more than {max_files} files. For most tasks 2–5 files is correct.
|
|
182
189
|
- Order by relevance: most directly relevant file first.\
|
|
183
190
|
"""
|
|
184
191
|
|
|
@@ -209,13 +216,14 @@ Return only a JSON array of the most relevant file paths for this task.\
|
|
|
209
216
|
def _parse_response(
|
|
210
217
|
data: dict,
|
|
211
218
|
file_tree: list[tuple[str, int]],
|
|
219
|
+
max_files: int = _MAX_FILES,
|
|
212
220
|
) -> list[str]:
|
|
213
221
|
"""
|
|
214
222
|
Extract and validate the file list from the Gemini API response.
|
|
215
223
|
|
|
216
224
|
- Parses the JSON array from the model's text output
|
|
217
225
|
- Drops any path the model hallucinated (not in the real file tree)
|
|
218
|
-
- Enforces the
|
|
226
|
+
- Enforces the *max_files* hard cap
|
|
219
227
|
- Warns (via exception) if the model returned mostly invalid paths
|
|
220
228
|
"""
|
|
221
229
|
# Navigate the Gemini response structure
|
|
@@ -266,7 +274,7 @@ def _parse_response(
|
|
|
266
274
|
)
|
|
267
275
|
|
|
268
276
|
# Enforce hard cap
|
|
269
|
-
return validated[:
|
|
277
|
+
return validated[:max_files]
|
|
270
278
|
|
|
271
279
|
|
|
272
280
|
# ---------------------------------------------------------------------------
|
|
@@ -201,6 +201,7 @@ def select_files(
|
|
|
201
201
|
*,
|
|
202
202
|
prompt: str,
|
|
203
203
|
file_tree: list[tuple[str, int]],
|
|
204
|
+
max_files: int | None = None,
|
|
204
205
|
) -> list[str]:
|
|
205
206
|
"""
|
|
206
207
|
Score and rank *file_tree* entries by relevance to *prompt*.
|
|
@@ -212,18 +213,24 @@ def select_files(
|
|
|
212
213
|
file_tree:
|
|
213
214
|
Candidate files as (relative_posix_path, size_bytes) tuples.
|
|
214
215
|
Standard exclusions must already have been applied by the caller.
|
|
216
|
+
max_files:
|
|
217
|
+
Cap on how many files may be returned. Defaults to MAX_FILES (8)
|
|
218
|
+
when omitted — typically overridden by a project's `ai.max_files`
|
|
219
|
+
preference (.contextzip/config.json).
|
|
215
220
|
|
|
216
221
|
Returns
|
|
217
222
|
-------
|
|
218
223
|
list[str]
|
|
219
224
|
Relative POSIX paths of the top-scoring files, best first.
|
|
220
|
-
Files scoring zero are excluded. Result is capped at
|
|
225
|
+
Files scoring zero are excluded. Result is capped at *max_files*.
|
|
221
226
|
"""
|
|
222
227
|
tokens = _tokenize(prompt)
|
|
223
228
|
if not tokens:
|
|
224
229
|
# Degenerate prompt — return nothing rather than random files
|
|
225
230
|
return []
|
|
226
231
|
|
|
232
|
+
effective_max_files = max_files if max_files and max_files > 0 else MAX_FILES
|
|
233
|
+
|
|
227
234
|
scored: list[tuple[float, str]] = []
|
|
228
235
|
|
|
229
236
|
for rel_path, size_bytes in file_tree:
|
|
@@ -234,7 +241,7 @@ def select_files(
|
|
|
234
241
|
# Sort descending by score, then alphabetically for determinism on ties
|
|
235
242
|
scored.sort(key=lambda x: (-x[0], x[1]))
|
|
236
243
|
|
|
237
|
-
return [path for _, path in scored[:
|
|
244
|
+
return [path for _, path in scored[:effective_max_files]]
|
|
238
245
|
|
|
239
246
|
|
|
240
247
|
# ---------------------------------------------------------------------------
|
|
@@ -32,10 +32,15 @@ def ai_select(
|
|
|
32
32
|
prompt: str,
|
|
33
33
|
ecosystem: str,
|
|
34
34
|
api_key: str,
|
|
35
|
+
max_files: int | None = None,
|
|
35
36
|
) -> tuple[list[Path], str, str]:
|
|
36
37
|
"""
|
|
37
38
|
Select the minimum relevant files for *prompt* from *resolved.included*.
|
|
38
39
|
|
|
40
|
+
*max_files* caps how many files either backend may return — typically a
|
|
41
|
+
project's `ai.max_files` preference (.contextzip/config.json). None
|
|
42
|
+
falls back to each backend's own built-in default cap.
|
|
43
|
+
|
|
39
44
|
Returns
|
|
40
45
|
-------
|
|
41
46
|
(selected_paths, prompt_txt, method)
|
|
@@ -54,12 +59,14 @@ def ai_select(
|
|
|
54
59
|
prompt=prompt,
|
|
55
60
|
file_tree=file_tree,
|
|
56
61
|
ecosystem=ecosystem,
|
|
62
|
+
max_files=max_files,
|
|
57
63
|
)
|
|
58
64
|
except GeminiRateLimitError:
|
|
59
65
|
# Confirmed HTTP 429 only — fall back to keyword heuristic
|
|
60
66
|
selected_rel = _heuristic.select_files(
|
|
61
67
|
prompt=prompt,
|
|
62
68
|
file_tree=file_tree,
|
|
69
|
+
max_files=max_files,
|
|
63
70
|
)
|
|
64
71
|
method = USED_HEURISTIC
|
|
65
72
|
# All other GeminiErrors (bad key, network, unexpected status) propagate
|