contextzip 0.4.7__tar.gz → 0.5.1__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 (77) hide show
  1. {contextzip-0.4.7 → contextzip-0.5.1}/PKG-INFO +45 -17
  2. {contextzip-0.4.7 → contextzip-0.5.1}/README.md +42 -15
  3. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/__init__.py +1 -1
  4. contextzip-0.5.1/contextzip/__main__.py +37 -0
  5. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/api.py +39 -20
  6. contextzip-0.5.1/contextzip/applier.py +1357 -0
  7. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/cleanup.py +16 -1
  8. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/cli.py +477 -44
  9. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/cli_ai.py +2 -2
  10. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/cli_display.py +133 -50
  11. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/config.py +28 -0
  12. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/detector.py +12 -1
  13. contextzip-0.5.1/contextzip/editor.py +124 -0
  14. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/error_parser.py +54 -12
  15. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/filters.py +17 -2
  16. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/git.py +102 -36
  17. contextzip-0.5.1/contextzip/guide.py +134 -0
  18. contextzip-0.5.1/contextzip/merge3.py +176 -0
  19. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/packager.py +89 -35
  20. contextzip-0.5.1/contextzip/pathmatch.py +265 -0
  21. contextzip-0.5.1/contextzip/pathsetup.py +208 -0
  22. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/project_config.py +57 -5
  23. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/redact.py +2 -3
  24. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/report.py +43 -3
  25. contextzip-0.5.1/contextzip/root.py +331 -0
  26. contextzip-0.5.1/contextzip/snapshots.py +259 -0
  27. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/watcher.py +11 -1
  28. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/webui/assets.py +17 -2
  29. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/webui/persist.py +3 -0
  30. contextzip-0.5.1/contextzip/webui/review.py +360 -0
  31. contextzip-0.5.1/contextzip/webui/review_assets.py +407 -0
  32. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/webui/server.py +7 -0
  33. contextzip-0.5.1/contextzip/workspaces.py +138 -0
  34. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/PKG-INFO +45 -17
  35. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/SOURCES.txt +26 -1
  36. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/top_level.txt +1 -0
  37. {contextzip-0.4.7 → contextzip-0.5.1}/pyproject.toml +3 -2
  38. contextzip-0.5.1/tests/__init__.py +0 -0
  39. contextzip-0.5.1/tests/helpers.py +77 -0
  40. contextzip-0.5.1/tests/test_applier.py +283 -0
  41. contextzip-0.5.1/tests/test_filters.py +84 -0
  42. contextzip-0.5.1/tests/test_git.py +47 -0
  43. contextzip-0.5.1/tests/test_guide.py +31 -0
  44. contextzip-0.5.1/tests/test_merge3.py +114 -0
  45. contextzip-0.5.1/tests/test_pathmatch.py +85 -0
  46. contextzip-0.5.1/tests/test_pathsetup.py +92 -0
  47. contextzip-0.5.1/tests/test_review.py +30 -0
  48. contextzip-0.5.1/tests/test_root.py +148 -0
  49. contextzip-0.5.1/tests/test_snapshots.py +82 -0
  50. contextzip-0.5.1/tests/test_watch_paths.py +28 -0
  51. contextzip-0.5.1/tests/test_workspaces.py +48 -0
  52. contextzip-0.4.7/contextzip/applier.py +0 -808
  53. {contextzip-0.4.7 → contextzip-0.5.1}/LICENSE +0 -0
  54. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/_x11_clip.py +0 -0
  55. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/ai/__init__.py +0 -0
  56. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/ai/gemini.py +0 -0
  57. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/ai/heuristic.py +0 -0
  58. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/ai/selector.py +0 -0
  59. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/cli_onboard.py +0 -0
  60. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/clipboard.py +0 -0
  61. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/__init__.py +0 -0
  62. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/base.py +0 -0
  63. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/errors/__init__.py +0 -0
  64. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/errors/node.py +0 -0
  65. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/errors/python.py +0 -0
  66. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/go.py +0 -0
  67. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/node.py +0 -0
  68. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/python.py +0 -0
  69. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/ruby.py +0 -0
  70. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/rules/rust.py +0 -0
  71. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/updater.py +0 -0
  72. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/webui/__init__.py +0 -0
  73. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip/webui/suggestions.py +0 -0
  74. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/dependency_links.txt +0 -0
  75. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/entry_points.txt +0 -0
  76. {contextzip-0.4.7 → contextzip-0.5.1}/contextzip.egg-info/requires.txt +0 -0
  77. {contextzip-0.4.7 → contextzip-0.5.1}/setup.cfg +0 -0
@@ -1,10 +1,11 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: contextzip
3
- Version: 0.4.7
3
+ Version: 0.5.1
4
4
  Summary: Intelligently package your codebase for AI tools
5
5
  Author-email: Deepesh <akadeepesh@gmail.com>
6
6
  License-Expression: MIT
7
- Project-URL: Homepage, https://github.com/akadeepesh/contextzip
7
+ Project-URL: Homepage, https://www.contextzipcli.com
8
+ Project-URL: Documentation, https://www.contextzipcli.com/getting-started
8
9
  Project-URL: Repository, https://github.com/akadeepesh/contextzip
9
10
  Project-URL: Issues, https://github.com/akadeepesh/contextzip/issues
10
11
  Keywords: ai,cli,developer-tools,context,zip,llm,claude,chatgpt,code-packaging
@@ -38,7 +39,7 @@ Dynamic: license-file
38
39
  [![Downloads](https://static.pepy.tech/badge/contextzip/month)](https://pepy.tech/project/contextzip)
39
40
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/akadeepesh/contextzip/blob/main/LICENSE)
40
41
 
41
- [Documentation](https://contextzip.vercel.app) ·
42
+ [Documentation](https://www.contextzipcli.com) ·
42
43
  [CLI reference](https://github.com/akadeepesh/contextzip/blob/main/CLI.md) ·
43
44
  [Changelog](https://github.com/akadeepesh/contextzip/blob/main/CHANGELOG.md) ·
44
45
  [Report a bug](https://github.com/akadeepesh/contextzip/issues/new/choose)
@@ -65,6 +66,7 @@ When the AI sends changed files back, `contextzip apply-zip` writes them into yo
65
66
  ## Table of contents
66
67
 
67
68
  - [Installation](#installation)
69
+ - ["`cz` is not recognized"](#cz-is-not-recognized--command-not-found)
68
70
  - [Quick start](#quick-start)
69
71
  - [Features](#features)
70
72
  - [Smart packaging](#smart-packaging)
@@ -103,6 +105,20 @@ This installs two commands that do the same thing: `contextzip` and the shorter
103
105
 
104
106
  To update later, run `contextzip update`. It works out whether you installed with pip, pipx or uv and upgrades the right one.
105
107
 
108
+ ### "`cz` is not recognized" / "command not found"
109
+
110
+ pip installed contextzip, but put the `cz` command in a folder your terminal doesn't search. This is common with Python from the Microsoft Store or the Python install manager on Windows, and with `pip install --user` on macOS and Linux. Look for pip's `WARNING: The scripts cz.exe and contextzip.exe are installed in '…' which is not on PATH.`
111
+
112
+ Python itself always works, so run contextzip through it and let it fix PATH for you:
113
+
114
+ ```bash
115
+ python -m contextzip setup-path # macOS / Linux: python3 -m contextzip setup-path
116
+ ```
117
+
118
+ On Windows this adds the folder to your user PATH (no admin needed). On macOS and Linux it adds one line to your shell's startup file. Open a new terminal and `cz` works. Until then, `python -m contextzip` does everything `cz` does.
119
+
120
+ If you installed with pipx, run `pipx ensurepath`. With uv, run `uv tool update-shell`.
121
+
106
122
  ## Quick start
107
123
 
108
124
  ```bash
@@ -120,6 +136,7 @@ contextzip --git-changes # only what you've changed
120
136
  contextzip --prompt "fix the login redirect bug" # let AI pick the relevant files
121
137
  contextzip include src/ app/ # only these folders
122
138
  contextzip include src/ .env # .env too, values redacted
139
+ contextzip --here # only the folder you're in (monorepos)
123
140
  contextzip exclude tests/ '*.snap' # everything except these
124
141
  contextzip apply-zip # write the AI's changes back
125
142
  ```
@@ -163,7 +180,7 @@ Describe your task in plain language and [Gemini](https://ai.google.dev/) picks
163
180
 
164
181
  - **Setup:** you need a free Gemini API key. contextzip walks you through it the first time you use `--prompt`, or you can set the `GEMINI_API_KEY` environment variable.
165
182
  - **What is sent:** only the list of file **paths and sizes** that already passed the exclusion rules, plus your prompt. File contents never leave your machine.
166
- - **Fallback:** if Gemini is rate-limited or unreachable, a local keyword-based selector takes over, so `--prompt` keeps working.
183
+ - **Fallback:** if Gemini is rate-limited, a local keyword-based selector takes over, so `--prompt` keeps working. Other errors (a bad key, no network) stop with a clear message.
167
184
  - **Model:** `gemini-2.5-flash-lite` by default.
168
185
 
169
186
  ### Applying AI changes back
@@ -174,17 +191,25 @@ contextzip apply-zip fix.zip # or point at one directly
174
191
  contextzip apply-zip --dry-run # preview without writing anything
175
192
  ```
176
193
 
177
- Every ZIP contextzip creates gets a **local manifest** containing a hash of each file. It's stored next to the ZIP and never inside it, so the AI tool never sees it. When a ZIP comes back, `apply-zip` compares every file against that manifest:
194
+ AI tools return ZIPs in every shape: wrapped in an extra folder, relative to a subfolder, flattened, with Windows separators. `apply-zip` matches each file to its real place in your project anyway, and new files follow their siblings.
195
+
196
+ Every ZIP contextzip makes gets a **local snapshot** (a hash of each file, never uploaded). `apply-zip` picks the snapshot that best matches the returned ZIP and compares three versions of each file: what you sent, what's on disk now, and what came back.
178
197
 
179
- | Status | Meaning | Action |
198
+ | Status | Meaning | Default |
180
199
  |---|---|---|
181
- | **New** | File didn't exist before | Written |
182
- | **Modified** | AI changed it, and you haven't touched it locally | Written |
183
- | **Unchanged** | Identical to what's on disk | Skipped |
184
- | **Drifted** | *You* edited it locally since zipping | ⚠️ Asks before overwriting |
185
- | **Untracked** | Exists locally but wasn't in the original ZIP | ⚠️ Asks before overwriting |
200
+ | **New** / **Modified** | New file, or only the AI changed it | Written |
201
+ | **Merged** | You both changed it, in different places | Written (three-way merge) |
202
+ | **Unchanged** | Already matches — including changes you already applied | Skipped |
203
+ | **Conflict** | You both changed the same or neighbouring lines | Needs your call |
204
+ | **Partial** | The AI returned a cut-down file (`// ... rest unchanged`) | Needs your call |
205
+ | **Moved** | The AI moved or renamed it | Written at the new path; the old file is kept unless you say otherwise |
206
+ | **Drifted** / **Deleted** / **Untracked** / **Ambiguous** / **Delete requested** | Can't be applied safely without you | Needs your call |
207
+
208
+ When something needs a decision you choose: apply the safe changes now, **review side by side in your browser** (`--review`), **resolve in VS Code's merge editor** (`--editor`), or apply everything. Line endings never count as a change, redacted secrets are never overwritten with placeholders, and **files are only removed with your explicit OK** — never one you've changed since.
209
+
210
+ ### Snapshots and CONTEXTZIP.md
186
211
 
187
- `apply-zip` only adds and modifies files. **It never deletes anything.** It also unwraps the extra top-level folder that AI tools and "Download ZIP" buttons often add.
212
+ Every package is kept locally as a numbered **snapshot** (`#14`), so a reply can be merged against exactly what you sent, even after you've packaged again. Each ZIP also carries a short **`CONTEXTZIP.md`** for the AI: the snapshot ID, and how to reply so the answer applies cleanly (complete files, same paths, secrets left as `[REDACTED]`, moves and deletions listed). When the reply echoes it back, `apply-zip` knows the exact snapshot. `contextzip snapshots` lists them; turn the file off with `"zip_guide": false`.
188
213
 
189
214
  ### Terminal error watcher
190
215
 
@@ -240,7 +265,7 @@ This opens a local browser tab where you can tick files and folders to always in
240
265
 
241
266
  Every command supports `-h` / `--help`, and `contextzip --version` prints the installed version.
242
267
 
243
- For every flag of every subcommand (`apply-zip`, `watch`, `config`, `update`), see **[CLI.md](https://github.com/akadeepesh/contextzip/blob/main/CLI.md)** or the [online CLI reference](https://contextzip.vercel.app/cli).
268
+ For every flag of every subcommand (`apply-zip`, `watch`, `config`, `update`), see **[CLI.md](https://github.com/akadeepesh/contextzip/blob/main/CLI.md)** or the [online CLI reference](https://www.contextzipcli.com/cli).
244
269
 
245
270
  ## Configuration
246
271
 
@@ -266,6 +291,8 @@ This holds per-project settings. Edit it by hand or with `contextzip config --ui
266
291
  },
267
292
  "applied_zip_retention": 1, // past apply-zip archives to keep
268
293
  "cleanup": { "enabled": true, "keep_recent": 1 },
294
+ "snapshots": { "keep": 10, "max_age_days": 14 }, // history apply-zip merges against
295
+ "zip_guide": true, // add CONTEXTZIP.md to every ZIP
269
296
  "webui": { "auto_open": true, "port": null }
270
297
  }
271
298
  ```
@@ -288,7 +315,7 @@ contextzip config --set-workspace cwd # put .contextzip/ in the current dir
288
315
  contextzip config --show-key-path # print the config file location
289
316
  ```
290
317
 
291
- The full option-by-option guide is at [contextzip.vercel.app/configuration](https://contextzip.vercel.app/configuration).
318
+ The full option-by-option guide is at [contextzipcli.com/configuration](https://www.contextzipcli.com/configuration).
292
319
 
293
320
  ## What is always excluded
294
321
 
@@ -328,11 +355,12 @@ contextzip keeps everything in a `.contextzip/` folder at your git root (change
328
355
  │ ├── git-changes/ changes.zip + manifest + report (--git-changes)
329
356
  │ ├── prompt/ vibe.zip + manifest + report (--prompt)
330
357
  │ └── watch/ debug-context.zip (contextzip watch)
358
+ ├── snapshots/ numbered copies of what you sent (contextzip snapshots)
331
359
  ├── inbox/ drop AI-returned ZIPs here for apply-zip
332
360
  └── config.json project config (optional)
333
361
  ```
334
362
 
335
- Old outputs are cleaned up automatically, and only the most recent set is kept. You can change this with the `cleanup` setting.
363
+ Old outputs are cleaned up automatically: `output/` keeps the latest ZIP per mode (`cleanup`), and `snapshots/` keeps the newest 10 or anything under 14 days old (`snapshots`) — never one a reply in the inbox is waiting on.
336
364
 
337
365
  ## Python API
338
366
 
@@ -365,7 +393,7 @@ print(detect_ecosystem("."))
365
393
 
366
394
  All exceptions inherit from `ContextzipError`: `NotARepositoryError`, `GitNotFoundError`, `GitCommandError`, `NoFilesError` and `ZipNotFoundError`.
367
395
 
368
- For full signatures, see [contextzip.vercel.app/cli#python-api](https://contextzip.vercel.app/cli#python-api).
396
+ For full signatures, see [contextzipcli.com/cli#python-api](https://www.contextzipcli.com/cli#python-api).
369
397
 
370
398
  ## Privacy & security
371
399
 
@@ -391,7 +419,7 @@ If the clipboard can't be used, contextzip opens the output folder with the ZIP
391
419
 
392
420
  ## Contributing
393
421
 
394
- Contributions are welcome, especially **new framework rule sets** and edge-case fixes. Not up for writing code? [Request a feature](https://contextzip.vercel.app/request-feature) or [request your language](https://contextzip.vercel.app/request-language). Both turn into GitHub issues ranked by 👍. To set up a local copy:
422
+ Contributions are welcome, especially **new framework rule sets** and edge-case fixes. Not up for writing code? [Request a feature](https://www.contextzipcli.com/request-feature) or [request your language](https://www.contextzipcli.com/request-language). Both turn into GitHub issues ranked by 👍. To set up a local copy:
395
423
 
396
424
  ```bash
397
425
  git clone https://github.com/akadeepesh/contextzip
@@ -9,7 +9,7 @@
9
9
  [![Downloads](https://static.pepy.tech/badge/contextzip/month)](https://pepy.tech/project/contextzip)
10
10
  [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/akadeepesh/contextzip/blob/main/LICENSE)
11
11
 
12
- [Documentation](https://contextzip.vercel.app) ·
12
+ [Documentation](https://www.contextzipcli.com) ·
13
13
  [CLI reference](https://github.com/akadeepesh/contextzip/blob/main/CLI.md) ·
14
14
  [Changelog](https://github.com/akadeepesh/contextzip/blob/main/CHANGELOG.md) ·
15
15
  [Report a bug](https://github.com/akadeepesh/contextzip/issues/new/choose)
@@ -36,6 +36,7 @@ When the AI sends changed files back, `contextzip apply-zip` writes them into yo
36
36
  ## Table of contents
37
37
 
38
38
  - [Installation](#installation)
39
+ - ["`cz` is not recognized"](#cz-is-not-recognized--command-not-found)
39
40
  - [Quick start](#quick-start)
40
41
  - [Features](#features)
41
42
  - [Smart packaging](#smart-packaging)
@@ -74,6 +75,20 @@ This installs two commands that do the same thing: `contextzip` and the shorter
74
75
 
75
76
  To update later, run `contextzip update`. It works out whether you installed with pip, pipx or uv and upgrades the right one.
76
77
 
78
+ ### "`cz` is not recognized" / "command not found"
79
+
80
+ pip installed contextzip, but put the `cz` command in a folder your terminal doesn't search. This is common with Python from the Microsoft Store or the Python install manager on Windows, and with `pip install --user` on macOS and Linux. Look for pip's `WARNING: The scripts cz.exe and contextzip.exe are installed in '…' which is not on PATH.`
81
+
82
+ Python itself always works, so run contextzip through it and let it fix PATH for you:
83
+
84
+ ```bash
85
+ python -m contextzip setup-path # macOS / Linux: python3 -m contextzip setup-path
86
+ ```
87
+
88
+ On Windows this adds the folder to your user PATH (no admin needed). On macOS and Linux it adds one line to your shell's startup file. Open a new terminal and `cz` works. Until then, `python -m contextzip` does everything `cz` does.
89
+
90
+ If you installed with pipx, run `pipx ensurepath`. With uv, run `uv tool update-shell`.
91
+
77
92
  ## Quick start
78
93
 
79
94
  ```bash
@@ -91,6 +106,7 @@ contextzip --git-changes # only what you've changed
91
106
  contextzip --prompt "fix the login redirect bug" # let AI pick the relevant files
92
107
  contextzip include src/ app/ # only these folders
93
108
  contextzip include src/ .env # .env too, values redacted
109
+ contextzip --here # only the folder you're in (monorepos)
94
110
  contextzip exclude tests/ '*.snap' # everything except these
95
111
  contextzip apply-zip # write the AI's changes back
96
112
  ```
@@ -134,7 +150,7 @@ Describe your task in plain language and [Gemini](https://ai.google.dev/) picks
134
150
 
135
151
  - **Setup:** you need a free Gemini API key. contextzip walks you through it the first time you use `--prompt`, or you can set the `GEMINI_API_KEY` environment variable.
136
152
  - **What is sent:** only the list of file **paths and sizes** that already passed the exclusion rules, plus your prompt. File contents never leave your machine.
137
- - **Fallback:** if Gemini is rate-limited or unreachable, a local keyword-based selector takes over, so `--prompt` keeps working.
153
+ - **Fallback:** if Gemini is rate-limited, a local keyword-based selector takes over, so `--prompt` keeps working. Other errors (a bad key, no network) stop with a clear message.
138
154
  - **Model:** `gemini-2.5-flash-lite` by default.
139
155
 
140
156
  ### Applying AI changes back
@@ -145,17 +161,25 @@ contextzip apply-zip fix.zip # or point at one directly
145
161
  contextzip apply-zip --dry-run # preview without writing anything
146
162
  ```
147
163
 
148
- Every ZIP contextzip creates gets a **local manifest** containing a hash of each file. It's stored next to the ZIP and never inside it, so the AI tool never sees it. When a ZIP comes back, `apply-zip` compares every file against that manifest:
164
+ AI tools return ZIPs in every shape: wrapped in an extra folder, relative to a subfolder, flattened, with Windows separators. `apply-zip` matches each file to its real place in your project anyway, and new files follow their siblings.
165
+
166
+ Every ZIP contextzip makes gets a **local snapshot** (a hash of each file, never uploaded). `apply-zip` picks the snapshot that best matches the returned ZIP and compares three versions of each file: what you sent, what's on disk now, and what came back.
149
167
 
150
- | Status | Meaning | Action |
168
+ | Status | Meaning | Default |
151
169
  |---|---|---|
152
- | **New** | File didn't exist before | Written |
153
- | **Modified** | AI changed it, and you haven't touched it locally | Written |
154
- | **Unchanged** | Identical to what's on disk | Skipped |
155
- | **Drifted** | *You* edited it locally since zipping | ⚠️ Asks before overwriting |
156
- | **Untracked** | Exists locally but wasn't in the original ZIP | ⚠️ Asks before overwriting |
170
+ | **New** / **Modified** | New file, or only the AI changed it | Written |
171
+ | **Merged** | You both changed it, in different places | Written (three-way merge) |
172
+ | **Unchanged** | Already matches — including changes you already applied | Skipped |
173
+ | **Conflict** | You both changed the same or neighbouring lines | Needs your call |
174
+ | **Partial** | The AI returned a cut-down file (`// ... rest unchanged`) | Needs your call |
175
+ | **Moved** | The AI moved or renamed it | Written at the new path; the old file is kept unless you say otherwise |
176
+ | **Drifted** / **Deleted** / **Untracked** / **Ambiguous** / **Delete requested** | Can't be applied safely without you | Needs your call |
177
+
178
+ When something needs a decision you choose: apply the safe changes now, **review side by side in your browser** (`--review`), **resolve in VS Code's merge editor** (`--editor`), or apply everything. Line endings never count as a change, redacted secrets are never overwritten with placeholders, and **files are only removed with your explicit OK** — never one you've changed since.
179
+
180
+ ### Snapshots and CONTEXTZIP.md
157
181
 
158
- `apply-zip` only adds and modifies files. **It never deletes anything.** It also unwraps the extra top-level folder that AI tools and "Download ZIP" buttons often add.
182
+ Every package is kept locally as a numbered **snapshot** (`#14`), so a reply can be merged against exactly what you sent, even after you've packaged again. Each ZIP also carries a short **`CONTEXTZIP.md`** for the AI: the snapshot ID, and how to reply so the answer applies cleanly (complete files, same paths, secrets left as `[REDACTED]`, moves and deletions listed). When the reply echoes it back, `apply-zip` knows the exact snapshot. `contextzip snapshots` lists them; turn the file off with `"zip_guide": false`.
159
183
 
160
184
  ### Terminal error watcher
161
185
 
@@ -211,7 +235,7 @@ This opens a local browser tab where you can tick files and folders to always in
211
235
 
212
236
  Every command supports `-h` / `--help`, and `contextzip --version` prints the installed version.
213
237
 
214
- For every flag of every subcommand (`apply-zip`, `watch`, `config`, `update`), see **[CLI.md](https://github.com/akadeepesh/contextzip/blob/main/CLI.md)** or the [online CLI reference](https://contextzip.vercel.app/cli).
238
+ For every flag of every subcommand (`apply-zip`, `watch`, `config`, `update`), see **[CLI.md](https://github.com/akadeepesh/contextzip/blob/main/CLI.md)** or the [online CLI reference](https://www.contextzipcli.com/cli).
215
239
 
216
240
  ## Configuration
217
241
 
@@ -237,6 +261,8 @@ This holds per-project settings. Edit it by hand or with `contextzip config --ui
237
261
  },
238
262
  "applied_zip_retention": 1, // past apply-zip archives to keep
239
263
  "cleanup": { "enabled": true, "keep_recent": 1 },
264
+ "snapshots": { "keep": 10, "max_age_days": 14 }, // history apply-zip merges against
265
+ "zip_guide": true, // add CONTEXTZIP.md to every ZIP
240
266
  "webui": { "auto_open": true, "port": null }
241
267
  }
242
268
  ```
@@ -259,7 +285,7 @@ contextzip config --set-workspace cwd # put .contextzip/ in the current dir
259
285
  contextzip config --show-key-path # print the config file location
260
286
  ```
261
287
 
262
- The full option-by-option guide is at [contextzip.vercel.app/configuration](https://contextzip.vercel.app/configuration).
288
+ The full option-by-option guide is at [contextzipcli.com/configuration](https://www.contextzipcli.com/configuration).
263
289
 
264
290
  ## What is always excluded
265
291
 
@@ -299,11 +325,12 @@ contextzip keeps everything in a `.contextzip/` folder at your git root (change
299
325
  │ ├── git-changes/ changes.zip + manifest + report (--git-changes)
300
326
  │ ├── prompt/ vibe.zip + manifest + report (--prompt)
301
327
  │ └── watch/ debug-context.zip (contextzip watch)
328
+ ├── snapshots/ numbered copies of what you sent (contextzip snapshots)
302
329
  ├── inbox/ drop AI-returned ZIPs here for apply-zip
303
330
  └── config.json project config (optional)
304
331
  ```
305
332
 
306
- Old outputs are cleaned up automatically, and only the most recent set is kept. You can change this with the `cleanup` setting.
333
+ Old outputs are cleaned up automatically: `output/` keeps the latest ZIP per mode (`cleanup`), and `snapshots/` keeps the newest 10 or anything under 14 days old (`snapshots`) — never one a reply in the inbox is waiting on.
307
334
 
308
335
  ## Python API
309
336
 
@@ -336,7 +363,7 @@ print(detect_ecosystem("."))
336
363
 
337
364
  All exceptions inherit from `ContextzipError`: `NotARepositoryError`, `GitNotFoundError`, `GitCommandError`, `NoFilesError` and `ZipNotFoundError`.
338
365
 
339
- For full signatures, see [contextzip.vercel.app/cli#python-api](https://contextzip.vercel.app/cli#python-api).
366
+ For full signatures, see [contextzipcli.com/cli#python-api](https://www.contextzipcli.com/cli#python-api).
340
367
 
341
368
  ## Privacy & security
342
369
 
@@ -362,7 +389,7 @@ If the clipboard can't be used, contextzip opens the output folder with the ZIP
362
389
 
363
390
  ## Contributing
364
391
 
365
- Contributions are welcome, especially **new framework rule sets** and edge-case fixes. Not up for writing code? [Request a feature](https://contextzip.vercel.app/request-feature) or [request your language](https://contextzip.vercel.app/request-language). Both turn into GitHub issues ranked by 👍. To set up a local copy:
392
+ Contributions are welcome, especially **new framework rule sets** and edge-case fixes. Not up for writing code? [Request a feature](https://www.contextzipcli.com/request-feature) or [request your language](https://www.contextzipcli.com/request-language). Both turn into GitHub issues ranked by 👍. To set up a local copy:
366
393
 
367
394
  ```bash
368
395
  git clone https://github.com/akadeepesh/contextzip
@@ -1,6 +1,6 @@
1
1
  """contextzip — intelligent codebase packager for AI tools."""
2
2
 
3
- __version__ = "0.4.7"
3
+ __version__ = "0.5.1"
4
4
 
5
5
  from contextzip.api import (
6
6
  FileCollection,
@@ -0,0 +1,37 @@
1
+ """
2
+ `python -m contextzip` — works even when the `cz` command isn't on PATH.
3
+
4
+ When that's why it's being used, a one-line tip points to `setup-path`.
5
+ """
6
+
7
+ from __future__ import annotations
8
+
9
+ import sys
10
+
11
+
12
+ def _path_tip() -> None:
13
+ if len(sys.argv) > 1 and sys.argv[1] == "setup-path":
14
+ return
15
+ if not sys.stderr.isatty():
16
+ return
17
+ try:
18
+ from contextzip import pathsetup
19
+
20
+ st = pathsetup.status()
21
+ if st.directory is None or st.on_path or st.manager or st.in_venv:
22
+ return
23
+ except Exception: # noqa: BLE001 — a tip must never break the run
24
+ return
25
+ from rich.console import Console
26
+
27
+ Console(stderr=True).print(
28
+ "[dim]Tip: the cz command isn't on your PATH yet — run "
29
+ "[bold]python -m contextzip setup-path[/] to fix that.[/]"
30
+ )
31
+
32
+
33
+ if __name__ == "__main__":
34
+ from contextzip.cli import main
35
+
36
+ _path_tip()
37
+ main()
@@ -35,7 +35,6 @@ Quickstart
35
35
 
36
36
  from __future__ import annotations
37
37
 
38
- import os
39
38
  from dataclasses import dataclass, field
40
39
  from pathlib import Path
41
40
 
@@ -158,7 +157,7 @@ def get_git_changes(
158
157
  Parameters
159
158
  ----------
160
159
  path:
161
- Directory to start from. Defaults to ``Path.cwd()``. The actual
160
+ Directory to start from. Defaults to the project root found from the current folder. The actual
162
161
  git root may be a parent of this directory.
163
162
  use_project_config:
164
163
  Apply the project's ``.contextzip/config.json``. Defaults to
@@ -234,7 +233,7 @@ def get_files(
234
233
  Parameters
235
234
  ----------
236
235
  path:
237
- Project root to scan. Defaults to ``Path.cwd()``.
236
+ Project root to scan. Defaults to the project root found from the current folder.
238
237
  include:
239
238
  If given, only files under these paths are returned (e.g.
240
239
  ``["src/", "app/"]``). Matched as exact path prefixes.
@@ -358,10 +357,14 @@ def create_zip(
358
357
 
359
358
  output_path = Path(output).resolve() if output is not None else None
360
359
 
360
+ project_cfg = load_project_config(collection.project_dir)
361
361
  return create_zip_silent(
362
362
  resolve_result=resolve_result,
363
363
  project_dir=collection.project_dir,
364
364
  output_path=output_path,
365
+ redact_secrets_enabled=project_cfg.limits.redact_secrets,
366
+ ecosystem=collection.ecosystem,
367
+ guide=project_cfg.zip_guide,
365
368
  )
366
369
 
367
370
 
@@ -370,20 +373,23 @@ def apply_zip(
370
373
  *,
371
374
  project_dir: str | Path | None = None,
372
375
  manifest: str | Path | None = None,
376
+ include_risky: bool = False,
373
377
  ) -> ApplyResult:
374
378
  """
375
379
  Apply an AI-returned ZIP back into *project_dir* (defaults to cwd).
376
380
 
377
- Diffs the zip against the local sidecar manifest most recently written
378
- to ``.contextzip/output/`` (or the one at *manifest*, if given) to
379
- classify every file as new, modified, unchanged, or one with no safe
380
- baseline (locally edited since zipping, or never part of the original
381
- manifest). Only adds and modifies files — nothing is ever deleted.
381
+ Each file is matched to its place in the project (even when the zip is
382
+ wrapped, relative to a subfolder, or flattened), then compared with the
383
+ snapshot of what was sent and with the file on disk. New, modified and
384
+ cleanly merged files are written. Files that need a decision —
385
+ conflicts, partial files, ones you deleted or that weren't sent, and
386
+ ambiguous ones — are left alone unless *include_risky* is True, in which
387
+ case the AI's version wins. Nothing is removed — moved files keep their
388
+ old copy and requested deletions are skipped; use
389
+ ``execute_plan(..., remove_moved=True, allow_delete=True)`` to opt in.
382
390
 
383
- Unlike the CLI, this applies immediately and does not prompt — check
384
- ``contextzip.applier.build_plan()`` yourself first if you want to
385
- inspect risk before writing (see ``ApplyPlan.is_risky`` /
386
- ``.risky_entries``).
391
+ Doesn't prompt. To inspect first, use ``contextzip.applier.build_plan()``
392
+ and ``execute_plan(..., decisions=...)`` directly.
387
393
 
388
394
  Parameters
389
395
  ----------
@@ -391,15 +397,20 @@ def apply_zip(
391
397
  Path to the returned zip. If omitted, auto-detects from
392
398
  ``.contextzip/inbox/`` — exactly one zip must be present there.
393
399
  project_dir:
394
- Project root to apply into. Defaults to ``Path.cwd()``.
400
+ Project root to apply into. Defaults to the project root found from the current folder.
395
401
  manifest:
396
- Explicit manifest path to diff against, overriding auto-detection.
402
+ Snapshot (manifest) to compare against. By default the one that
403
+ best matches the zip is picked automatically.
404
+ include_risky:
405
+ Also write files that need a decision, with the AI's version
406
+ winning. Defaults to False.
397
407
 
398
408
  Returns
399
409
  -------
400
410
  ApplyResult
401
- ``written`` (list of relative paths written) and
402
- ``applied_zip_path`` (where the consumed zip ended up).
411
+ ``written`` (relative paths written), ``skipped`` (paths left alone)
412
+ and ``applied_zip_path`` (where the zip ended up — it stays in the
413
+ inbox while something still needs a decision).
403
414
 
404
415
  Raises
405
416
  ------
@@ -431,9 +442,9 @@ def apply_zip(
431
442
  except ApplyError as exc:
432
443
  raise ZipNotFoundError(str(exc)) from exc
433
444
 
434
- manifest_path = find_latest_manifest(pdir, manifest)
445
+ manifest_path = find_latest_manifest(pdir, manifest) if manifest is not None else None
435
446
  plan = build_plan(resolved_zip, pdir, manifest_path)
436
- return execute_plan(plan, pdir)
447
+ return execute_plan(plan, pdir, include_risky=include_risky)
437
448
 
438
449
 
439
450
  def detect_ecosystem(
@@ -445,7 +456,7 @@ def detect_ecosystem(
445
456
  Parameters
446
457
  ----------
447
458
  path:
448
- Directory to inspect. Defaults to ``Path.cwd()``.
459
+ Directory to inspect. Defaults to the project root found from the current folder.
449
460
 
450
461
  Returns
451
462
  -------
@@ -498,8 +509,16 @@ def _config_kwargs(cfg, extra_exclude: list[str] | None = None) -> dict:
498
509
 
499
510
 
500
511
  def _resolve_dir(path: str | Path | None) -> Path:
512
+ """
513
+ An explicit *path* is used exactly as given. Without one, resolve the
514
+ project root from the current folder — the same root the CLI uses (see
515
+ root.find_project_root) — so a script run from a subfolder of a
516
+ monorepo still works on the whole project with root-relative paths.
517
+ """
501
518
  if path is None:
502
- return Path(os.getcwd()).resolve()
519
+ from contextzip.root import find_project_root
520
+
521
+ return find_project_root().root
503
522
  return Path(path).resolve()
504
523
 
505
524