Files
contextzip/CHANGELOG.md
T

924 lines
47 KiB
Markdown
Raw Normal View History

# Changelog
All notable changes to contextzip are documented here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.0.0/).
This project uses [Semantic Versioning](https://semver.org/).
---
## [0.1.1] — 2026-05-15
### Fixed
- LICENSE link in README now points to GitHub instead of a relative path that broke on PyPI
- License metadata updated to SPDX string format (`"MIT"`) to comply with setuptools ≥ 77
---
## [0.1.2] — 2026-05-15
### Added
- Smart framework and language detection (Next.js, Node.js, Python, Django, FastAPI, Rust, Go, Ruby)
- Automatic exclusion rules per detected ecosystem
- `.gitignore` patterns respected automatically (`--no-gitignore` to bypass)
- `--include` flag for scoping to specific directories (exact path-boundary matching)
- `--exclude` flag for additional gitignore-style exclusion patterns
- `--dry-run` flag to preview without creating any files
- `--verbose` flag showing every file with its size
- `--output` flag for custom ZIP destination
- `--no-clipboard` flag to skip clipboard/folder-open step
- Tiered clipboard strategy: file-on-clipboard (macOS/Linux), Explorer with file selected (Windows), path fallback
- Warnings for large files (≥ 1 MB), binary files, and skipped files (dangling symlinks, permission errors)
- Rich terminal output with panels, progress bars, and colour-coded detection results
- Compression stats (ratio, size before/after) shown after ZIP creation
---
## [0.2.0] — 2026-05-15
### Added
- `--git-changes` mode for packaging only files reported as changed by git
- Automatic detection of:
- staged files
- unstaged modifications
- untracked files
- Dedicated git-aware packaging pipeline optimised for AI/code review workflows
- Rich git change summary panel with staged/unstaged/untracked breakdown
- Verbose git mode output showing categorized changed files
- Graceful handling for:
- repositories with no changes
- deleted files
- submodules
- repositories outside the selected project tree
- missing git installations
- non-git directories
- Support for monorepo-aware repository root resolution
- Binary and large-file checks now also apply in git-changes mode
### Improved
- File resolution architecture refactored to support multiple packaging strategies cleanly
- Internal file scanning pipeline separated from git-based file selection logic
- CLI output consistency improved between standard scan mode and git-changes mode
### Developer Notes
- Introduced new `contextzip.git` module for git integration and porcelain parsing
- Added `resolve_files_from_git()` pipeline for pre-selected file resolution
- Added structured `GitChanges` and `GitError` models for cleaner error handling and extensibility
---
## [0.2.1] — 2026-05-16
### Fixed
- `--exclude` / `-e` patterns containing Windows-style paths (e.g. `.\CHANGELOG.md`) were silently ignored due to backslash and leading `.\` — patterns are now normalized to forward-slash posix paths before matching
- Files passed to `-e` were not being excluded from the ZIP despite appearing in the exclusion list
### Improved
- `-e` now accepts multiple space-separated patterns in a single flag invocation: `-e file1 file2 folder/` in addition to the existing repeatable form `-e file1 -e file2`
- Folder exclusion via `-e folder/*` is now supported and normalized to `folder/` automatically
## [0.2.2] - 2026-05-16
### Added
- `contextzip exclude PATTERN…` subcommand — exclude multiple files and folders
in a single invocation without repeating `-e` (e.g. `contextzip exclude CHANGELOG.md LICENSE .github/`)
- `contextzip include PATH…` subcommand — symmetric counterpart to `exclude`;
packages only the specified paths (e.g. `contextzip include src/ app/`)
- All modifier flags (`--dry-run`, `--verbose`, `--output`, `--no-clipboard`,
`--git-changes`, `--no-gitignore`) work on both subcommands
### Changed
- `-e` / `--exclude` flag on the main command restored and kept for full
backwards compatibility alongside the new subcommand
- Warning messages for large files now mention both `-e PATTERN` and
`contextzip exclude PATTERN` as remediation options
- `_normalize_pattern` promoted to a named module-level function (testable,
documented); behaviour is unchanged
### Fixed
- Removed the previous positional-argument approach to exclusions on the main
command, which was ambiguous and undiscoverable
---
## [0.2.3] — 2026-05-19
### Added
- Dedicated Ruby ecosystem exclusion rules via new `contextzip.rules.ruby` module
- Automatic exclusion of common Ruby / Rails artifacts:
- Bundler directories and lockfiles
- Rails logs, temp files, assets, storage, and Spring cache
- Built gems and compiled Ruby extensions
- Coverage and generated documentation directories
- Additional package discovery keywords for AI tooling and code packaging:
- `claude`
- `chatgpt`
- `code-packaging`
### Changed
- Ruby project detection now uses the dedicated Ruby rule module instead of the generic base rules
- Git-based packaging now enforces base exclusion safety rules even for tracked files
- Repository governance and metadata files are now excluded by default:
- `CHANGELOG*`
- `LICENSE*`
- `CONTRIBUTING*`
- `SECURITY.md`
- `.github` issue / PR templates
- `.gitignore` is no longer excluded automatically from packaged context archives
### Fixed
- Prevented git-tracked sensitive files (e.g. `.env`, secrets, binaries) from being included in `--git-changes` mode
- Improved ZIP packaging resilience when file stat operations fail during archive creation
- Fixed progress tracking during ZIP generation to avoid repeated filesystem stat calls
- Fixed Ruby framework detection routing so Ruby projects receive ecosystem-specific exclusion behaviour
---
## [0.2.4] — 2026-05-20
### Added
* Persistent `.contextzip/` workspace directory for generated context packages
* Automatic output naming based on packaging mode:
* `codebase.zip` for standard packaging
* `changes.zip` for `--git-changes`
* Automatic `.contextzip/` exclusion from packaged archives and file scanning
* Automatic `.gitignore` integration:
* `.contextzip/` is appended when a git repository is detected
* missing entries are added only once
* Repository-root-aware workspace placement:
* `.contextzip/` is created at the git repository root when `.git/` is detected
* falls back to the current working directory outside git repositories
### Changed
* ZIP outputs are now written to the persistent `.contextzip/` workspace by default instead of temporary system directories
* Generated archives now overwrite previous outputs with the same logical name instead of creating temporary randomized filenames
* `--output` now bypasses all `.contextzip/` workspace logic and writes directly to the user-specified destination
### Improved
* Generated context packages are now easier to rediscover, reuse, and inspect across AI-assisted development workflows
* Workspace output structure now provides a foundation for future features such as:
* timestamped package history
* metadata indexing
* incremental packaging
* AI debug sessions
* package caching
### Fixed
* Prevented `.contextzip/` contents from recursively packaging previous generated archives
* Improved fallback behaviour when workspace creation fails due to filesystem permissions or read-only directories
## [0.3.0] — 2026-05-24
### Added
* `--prompt` / `-p` flag for AI-powered file selection — describe your task in
plain English and contextzip automatically selects the minimum relevant files
(e.g. `contextzip --prompt "Change toast color on failed login"`)
* Gemini 2.5 Flash Lite integration via a thin `httpx` client — no Google SDK
required; a single lightweight POST call to the Gemini API
* First-run onboarding flow for users without an API key:
* Displays a guided panel with a link to [Google AI Studio](https://aistudio.google.com/apikey) (free, no credit card)
* Optionally opens the browser automatically
* Saves the key persistently on confirmation
* Persistent API key storage in the platform-appropriate config directory:
* `~/.config/contextzip/config.json` on Linux / macOS
* `%APPDATA%\contextzip\config.json` on Windows
* `GEMINI_API_KEY` environment variable always takes precedence
* `contextzip config` subcommand for key management:
* `contextzip config` — show current key status and source
* `contextzip config --reset-key` — clear stored key and re-run onboarding
* `contextzip config --show-key-path` — print config file location
* `prompt.txt` is written as the first entry in every AI-generated ZIP,
containing the task description, detected framework, and selected file list —
so any AI tool receiving the archive immediately knows the context
* Keyword-based heuristic file scorer as a rate-limit fallback:
* Activates only on confirmed HTTP 429 from Gemini
* Scores files by token overlap with the prompt, directory semantics, and
extension type
* User is warned clearly that heuristic was used and Gemini results are
preferred
* `--prompt --dry-run` combination — preview AI-selected files without
creating a ZIP
* API key validation on load — keys not starting with `AIza` are treated as
unconfigured and trigger onboarding rather than a cryptic API error
* `diagnose_api_key()` surfaces a human-readable explanation when a key exists
but fails validation, before any API call is attempted
### Changed
* `create_zip()` in `packager.py` now accepts an optional `prompt_txt`
parameter; when provided, `prompt.txt` is written as the first ZIP entry
* xdg-open on Linux now runs with stdout and stderr suppressed to prevent
KDE / DBus / MIME warnings from appearing over the API key prompt
### Developer Notes
* Introduced `contextzip.config` module for cross-platform config path
resolution and key read / write / delete operations
* Introduced `contextzip.ai` package with three modules:
* `gemini.py` — Gemini API client with typed exception hierarchy
(`GeminiError`, `GeminiRateLimitError`, `GeminiUnavailable`)
* `heuristic.py` — keyword + directory scoring fallback scorer
* `selector.py` — project map builder, AI orchestration, `prompt.txt`
generation; returns `(paths, prompt_txt, method)` so callers know
which selection path was taken
* Added `httpx` as a required runtime dependency
## [0.3.1] — 2026-05-25
### Added
* `contextzip watch -- <command>` subcommand — wraps any dev server or build
process, buffers its output, detects errors automatically, and packages a
debug-ready ZIP in a single keypress
* Supports `npm run dev`, `python manage.py runserver`, and any process
that writes errors to stdout/stderr
* On error detection, renders an inline prompt beneath the error output:
`[D] package debug context [S] skip`
* Pressing `D` writes `.contextzip/debug-context.zip` immediately; the
wrapped process continues running uninterrupted
* On `Ctrl+C`, if no errors were packaged during the session, a final
prompt offers to capture the full session output as a fallback
* `debug-context.zip` — flat ZIP structure produced by `watch`:
* `prompt.txt` — auto-generated from detected framework, error type, and
referenced files; no `--prompt` flag required from the user
* `terminal-error.txt` — cleaned, noise-stripped error block and stack trace
* `source-files.zip` — inner ZIP containing source files extracted from
stack frames, with relative paths preserved
* `contextzip/error_parser.py` — full terminal output processing pipeline:
* `strip_ansi()` — removes all CSI, OSC, and Fe escape sequences and bare
carriage returns from raw process output
* `detect_error_block()` — backward scan through the rolling buffer to find
the most recent error; walks back from terminal exception lines to capture
the full block including the `Traceback` header
* `extract_paths()` — runs per-framework path regexes on the error block,
resolves relative and bare filenames, and filters out stdlib, venv, and
`node_modules` paths
* `strip_noise()` — removes known noise lines (request logs, startup
banners, HMR chatter) and collapses blank line runs
* `build_prompt_txt()` — generates structured, AI-ready `prompt.txt` content
* `process_buffer()` — convenience function running the full pipeline in a
single call
* `contextzip/watcher.py` — process management and packaging:
* Spawns child via `subprocess.Popen` with piped stdout/stderr; two daemon
threads drain pipes concurrently into a `deque(maxlen=2000)` rolling buffer
* Detection thread polls the buffer every 300ms using `detect_error_block()`
* Error deduplication: MD5 of the first three non-blank lines of an error
block; same signature within a session suppresses repeat prompts
* Single-keypress D/S prompt via `termios`/`tty` raw mode on Unix and
`msvcrt` on Windows, with an `input()` fallback for unusual environments
* Child terminated cleanly on `Ctrl+C`: SIGTERM → 3-second grace period →
SIGKILL
* `contextzip/rules/errors/python.py` — error detection pattern sets for
Python, Django, and FastAPI:
* 18 error start patterns covering tracebacks, syntax errors, Django/DRF
exceptions, uvicorn/gunicorn ERROR logs, and pytest failures
* 4 stack frame path patterns
* 21 noise patterns for Django startup, request logs, pytest collection
output, and pip install chatter
* `contextzip/rules/errors/node.py` — error detection pattern sets for
Node.js, Next.js, and React:
* 33 error start patterns covering JS runtime errors, Next.js error symbols
(`✗`, `×`, `⨯`), webpack/bundler errors, npm errors, and unhandled
promise rejections
* 7 stack frame and import path patterns
* 32 noise patterns for Next.js/Vite/CRA startup, HMR chatter, and request
logs
### Changed
* `cli.py` refactored from a single 1,100-line file into four focused modules
with no behaviour changes:
* `cli.py` — Click group, four subcommands, and `_run()`; routing only
* `cli_display.py` — all Rich rendering helpers (`print_detection`,
`print_scan_summary`, `print_file_warnings`, `print_package_result`, etc.)
* `cli_ai.py` — AI selection orchestration and `normalize_pattern`
* `cli_onboard.py` — Gemini API key onboarding flow and browser launch
* `pyproject.toml` updated to include `contextzip.rules.errors` in package
discovery
### Developer Notes
* `contextzip/rules/errors/` follows the same pattern as `contextzip/rules/`:
one file per ecosystem, each exporting `ERROR_START_PATTERNS`,
`PATH_PATTERNS`, and `NOISE_PATTERNS` as lists of compiled `re.Pattern`
objects — adding support for a new framework means adding one file
* `_load_patterns()` in `error_parser.py` maps ecosystem names from
`DetectionResult.ecosystems` to rule modules; unrecognised ecosystems fall
back to loading both Python and Node patterns as a best-effort default
* `cli_display.py` functions accept an optional `con: Console` parameter
(defaulting to the module-level console) making every display function
independently testable without monkey-patching
* No new runtime dependencies introduced — `watch` uses only stdlib
(`subprocess`, `threading`, `queue`, `termios`/`msvcrt`, `zipfile`)
## [0.3.2] — 2026-05-25
### Added
* **Python API** — contextzip is now usable as a library, not just a CLI tool:
* `get_git_changes(path?)` — returns a `FileCollection` of git-modified,
added, and untracked files; applies base safety rules (no secrets, no
binaries) automatically
* `get_files(path?, include?, exclude?, use_gitignore?)` — returns a
`FileCollection` of all project files after applying ecosystem rules,
`.gitignore`, and any extra exclusion patterns
* `create_zip(collection, output?)` — writes a `FileCollection` to a ZIP
archive and returns a `PackageResult`; output path is caller-controlled
* `detect_ecosystem(path?)` — returns a `DetectionResult` with ecosystem
names, display string, and confidence level
* `FileCollection` — unified return type for `get_git_changes` and
`get_files`; supports iteration, `len()`, and `bool()` directly; `.files`
is always a plain `list[pathlib.Path]` usable without zipping
* `PackageResult` re-exported from the top-level package
* Exception hierarchy: `ContextzipError` (base), `NotARepositoryError`,
`GitNotFoundError`, `GitCommandError`, `NoFilesError`
* All API functions default `path` to `Path.cwd()`, matching CLI behaviour
* `contextzip/api.py` — new module implementing the public API; no Click, no
Rich output, no `SystemExit`; raises typed exceptions on failure
* `contextzip/packager.py` — added `create_zip_silent()` and
`_workspace_output_path_silent()`: same ZIP logic as the CLI path but
without a `Console` dependency, used internally by the API
### Changed
* `contextzip/__init__.py` — now exports the full public API
(`get_git_changes`, `get_files`, `create_zip`, `detect_ecosystem`,
`FileCollection`, `PackageResult`, and all exception types) so
`from contextzip import ...` works without knowing internal module paths
### Fixed
* `filters.py` — symlink guard inner `try` block repeated the identical
`relative_to` call that already failed in the outer block; the fallback now
correctly uses the pre-resolve (`original_path`) to compute the relative
path for symlinks that point outside the project tree
* `watcher.py` — `_drain_output_queue` silently consumed sentinel values
(`raw=None`) with `continue`, making them invisible to `_check_reader_done`;
sentinels are now put back into the queue so done flags are set correctly
* `watcher.py` — `_read_key_windows` had no timeout and would spin forever in
non-interactive terminals; now uses a 60-second `monotonic` deadline
matching the existing Unix `select` timeout
* `packager.py` — removed unused `_safe_name` helper
* `ai/gemini.py` — corrected stale docstring (`gemini-2.0-flash-lite` →
`gemini-2.5-flash-lite`) to match the actual `DEFAULT_MODEL` constant
## [0.3.3] — 2026-05-29
### Fixed
* `contextzip.ai` and all subpackages were missing from the published wheel
due to an incomplete `include` allowlist in `pyproject.toml` — replaced
with auto-discovery so all current and future subpackages are captured
automatically
## [0.3.4] — 2026-06-21
### Added
* `contextzip eod` — builds a paste-ready end-of-day prompt from today's
Claude/ChatGPT conversation plus whatever code changed, copied straight
to the clipboard. Does no summarizing itself — that's left to whichever
AI tool the prompt gets pasted into
* `contextzip handoff` — same pipeline, aimed at continuing a project in a
fresh chat after hitting a usage limit, rather than at an end-of-day report
* Per-branch commit checkpoints (`.contextzip/markers/eod.json`,
`.contextzip/markers/handoff.json`) so every run only reports what's new
since *that command* last ran on *that branch* — no manual bookkeeping,
no date-based guessing
* Three-case code-change resolution per file, checked in priority order:
1. **Not pushed** — diff against the upstream branch (covers committed-
but-unpushed commits and uncommitted edits in one comparison) + the
complete current file
2. **Diverged from Claude** — diff against the version Claude last
produced, matched by filename against a local artifacts folder + the
complete current codebase file
3. **Since last run** — diff against the stored marker commit, or the
branch's merge-base with the default branch on a branch never tracked
before + the complete current file
Brand-new untracked files skip the diff (no baseline exists) and are
included as full content only
* Best-effort fetching of Claude's artifact files for case 2, using the
user's own Claude.ai session key (`contextzip config --set-session-key`).
Explicitly degrades gracefully — a missing key, an expired cookie, or the
endpoint changing shape just skips case 2 with a warning, never fails the
whole `eod`/`handoff` run
* `contextzip config --set-session-key` / `--reset-session-key` — manage
the Claude session key alongside the existing Gemini key; default
`contextzip config` now shows both keys' status
* Text-clipboard support (`clipboard.handle_text()`) — `eod`/`handoff`
produce a prompt to paste, not a file to upload, so the existing
file-clipboard tiers gained a text-mode sibling (`pbcopy` / `xclip`+`xsel`
/ `clip.exe`)
* `exports/` added to the base exclusion ruleset (next to `.contextzip/`) —
conversation exports and fetched Claude artifacts are contextzip's own
working files, never code to package or report on
### Changed
* `packager._ensure_gitignore()` generalised to accept an arbitrary entry
instead of being hardcoded to `.contextzip/`, so `exports/` can register
itself in `.gitignore` the same way on first use
### Developer Notes
* New modules: `markers.py` (per-branch checkpoint storage), `code_changes.py`
(the three-case decision tree), `claude_export.py` (locating the latest
export, inline-vs-attach sizing), `claude_artifacts.py` (best-effort
artifact fetching, rewritten on `httpx` for consistency with `ai/gemini.py`
rather than introducing a second HTTP stack), `brief.py` (orchestrates
`eod`/`handoff` end to end)
* `git.py` gained branch-aware primitives: `get_current_branch()`,
`get_default_branch()` (origin/HEAD detection with fallback chain),
`get_merge_base()`, `is_ancestor()`, `get_upstream_branch()`,
`diff_against()` (working-tree-aware — captures committed-since-baseline
and uncommitted changes in one diff), `changed_files_against()`
* A stored marker that's no longer an ancestor of `HEAD` (e.g. after a
rebase) is detected via `is_ancestor()` and silently recomputed from the
branch's merge-base, with a warning surfaced to the user rather than
producing a nonsense diff
* `claude_artifacts.py` matches Claude's flat artifact filenames against the
codebase by basename only, since artifacts carry no knowledge of the
repo's directory structure; ambiguous matches (same filename in multiple
directories) pick the most recently modified candidate and surface a
warning rather than guessing silently
## [0.3.5] — 2026-08-09
### Added
- **Monorepo-aware framework detection.** Detection now scans project_dir plus a
shallow, bounded subdirectory tree (2 levels deep), instead of only checking
the project root. A layout like `root/frontend/package.json` +
`root/backend/requirements.txt` now correctly detects both Node.js and
Python and stacks both exclusion rule sets — previously this fell through
to only the universal base rules, leaving `node_modules/`, `.venv/`, and
similar unexcluded except by whatever `.gitignore` happened to catch. The
scan skips dependency/build directories (`node_modules/`, `.venv/`, `.git/`,
etc.) so it never walks into a large dependency tree. Detection output now
shows which subdirectory each ecosystem was found in, e.g.
`Next.js (frontend/) + FastAPI (backend/)`.
- **Configurable `.contextzip/` workspace location**, resolved in priority
order: `CONTEXTZIP_WORKSPACE_LOCATION` env var → project config
(`.contextzip.json`, committed, team-shared) → personal config (per-machine,
via `contextzip config --set-workspace`) → default (`git-root`, unchanged
behavior). Accepts `"git-root"`, `"cwd"`, or an explicit path.
- `contextzip config --set-workspace <location>` / `--reset-workspace` — set
or clear your personal workspace location default.
- `contextzip config` (no flags) now shows the resolved workspace location
and which config tier it came from.
- New `.contextzip.json` project config file (optional, git-committed) for
pinning `workspace_location` for every contributor.
### Fixed
- `.gitignore` registration for the `.contextzip/` entry now correctly
derives the git root independently, rather than assuming it's always the
workspace directory's parent — a latent bug this release's configurable
workspace location would otherwise have triggered (it only skips writing
when the workspace is genuinely outside the repo, e.g. an absolute custom
path).
### Removed
- **`eod` and `handoff` commands**, along with the Claude.ai session-key
config option they depended on. Both relied on an undocumented Claude.ai
endpoint to fetch previously-produced artifacts, which was never a stable
foundation for a shipped feature.
- Removed modules: `brief.py`, `claude_export.py`, `claude_artifacts.py`,
`code_changes.py`, `markers.py`
- Removed CLI options: `contextzip config --set-session-key` /
`--reset-session-key`
- Removed config keys: `claude_session_key`
- Also removed now-unused branch-diffing internals from `git.py`
(`get_current_branch`, `get_upstream_branch`, `diff_against`, and related
helpers) that existed solely to support `eod`/`handoff`
- If you relied on `eod`/`handoff`, there's no replacement in this
release — conversation exports and per-file diff prompts will need to be
built by hand, or via your own script against `contextzip --git-changes`.
2026-08-23 03:07:18 +05:30
## [0.3.6] — 2026-08-23
### Added
* **`cz` CLI alias.** `cz` can now be used as a shorthand for `contextzip`, with identical commands and behavior.
* **`.contextzip/` project workspace.** Contextzip now uses a dedicated workspace containing `config.json` and an `output/` directory for generated context archives.
* **Project configuration via `.contextzip/config.json`.** Added persistent project-level preferences for `always_include`, `always_exclude`, and AI-assisted context selection.
* **AI configuration.** Added configurable AI preferences including whether AI selection is enabled, the provider to use, and the maximum number of files AI can select.
* **Configured file selection.** `always_include` and `always_exclude` preferences are now applied during context generation.
* **Configurable AI file limits.** The configured `ai.max_files` value is now respected by the AI selection pipeline.
* **Legacy `.contextzip.json` deprecation.** Project configuration is moving from `.contextzip.json` to `.contextzip/config.json`.
### Changed
* Generated context archives are now organized under `.contextzip/output/`.
* Contextzip configuration is now resolved through the new project configuration system while preserving the existing personal configuration for machine-specific settings and credentials.
## [0.3.7] — 2026-08-23
### Added
* **Visual config UI.** `contextzip config --ui` opens a local browser tab for setting `always_include`/`always_exclude` by clicking through your actual file tree, with live file counts and packed size as you go.
* **Exclude suggestions.** The config UI automatically flags common non-code file types not already covered by the default exclusion rules — PDFs, office docs, fonts, media, design files, and anything over 1MB — as one-click "exclude all" chips.
* **First-run offer.** A project with no config at all offers to launch the visual config UI on its first run. Declining is remembered — it won't ask again unless you run `contextzip config --ui` yourself.
### Changed
* Declining the visual config UI's first-run offer is now stored in the personal config (`~/.config/contextzip/config.json`) so it's asked at most once per machine.
### Security
* The local config UI binds to `127.0.0.1` only and requires a random per-session token on every request (matching Jupyter Notebook's approach) — nothing about the project's file structure leaves the machine.
## [0.3.8] — 2026-08-24
### Added
* **`apply-zip` command.** Closes the other half of the round trip: takes a ZIP handed back by an AI tool and writes it into the project. Every file is classified — new, modified, unchanged, drifted (changed or removed locally since it was sent), or untracked (no baseline to compare against) — before anything is written.
* **Local sidecar manifest.** Every ZIP `contextzip` creates now also gets a `<name>.manifest.json` written next to it in `.contextzip/output/` — a hash of each included file, taken at zip-time. It's written to disk only, **never inside the ZIP**, so it's never uploaded and never visible to whatever AI tool the ZIP is pasted into.
* **`.contextzip/inbox/`.** Drop an AI-returned ZIP here and `apply-zip` finds it automatically; passing a path explicitly always overrides auto-detection. Multiple ZIPs in the inbox are listed and you're asked to be explicit rather than guessed at.
* **Automatic backups.** Every file `apply-zip` is about to overwrite is copied first into `.contextzip/backups/<timestamp>/`.
* **`apply_zip()`** Python API function, mirroring `create_zip()`'s plain-function, no-Click, no-Rich style.
* New `--dry-run`, `--verbose`, `--yes`, and `--manifest` flags on `apply-zip`.
### Changed
* Consumed ZIPs are moved to `.contextzip/inbox/applied/<timestamp>-name.zip` rather than deleted, so they double as an audit trail alongside the backups.
### Security
* `apply-zip` validates every ZIP entry's resolved path before extracting anything, refusing archives with path-traversal entries (`../`) that would write outside the project tree — no override flag, no exceptions.
### Notes
* v1 of `apply-zip` only adds and modifies files — deletions are never inferred from a ZIP's contents. A file missing from a returned ZIP is left alone.
## [0.3.9] - 2026-08-26
### Security
- Config file permissions (`~/.config/contextzip/config.json`) are now
locked to `0600` on **every** write, not just on creation. Previously a
pre-existing config file from an older install (or one created under a
permissive umask) could stay group/world-readable indefinitely — the
file can hold a Gemini API key in plaintext, so this closes a real local
exposure on shared machines.
- The local config UI (`contextzip config --ui`) now compares the request
token with `hmac.compare_digest` instead of `==`, removing a
timing-based side channel on the single-use session token.
- Substantially expanded the list of secret/credential files that are
always excluded from packaged zips, regardless of framework:
SSH private keys (`id_rsa`, `id_ed25519`, `id_ecdsa`, `id_dsa`),
keystores (`.jks`, `.keystore`, `.p12`, `.pkcs12`, `.ppk`),
CLI/package-manager credential files (`.npmrc`, `.netrc`, `.pypirc`,
`.pgpass`, `.dockercfg`, Docker `config.json`), cloud provider
credentials (`.aws/credentials`, `.aws/config`, `*serviceaccount*.json`,
`kubeconfig`), and Terraform state (`*.tfstate*`, `.terraform/`), which
routinely contains plaintext secrets even for "just infra" resources.
### Fixed
- `apply-zip` now detects when every entry in an AI-returned zip is
nested under one incidental top-level folder — the shape produced by
`zip -r out.zip myfolder` or GitHub's "Download ZIP" — and strips it
automatically when doing so clearly improves the match against the
project's manifest, so files land at their real project-relative paths
instead of recreating a wrapper folder.
- `apply-zip` now warns before applying a zip whose structure barely
matches the project at all (under 10% of paths found in the manifest,
even after wrapper stripping), instead of silently treating every file
as brand new.
### Changed
- `.contextzip/inbox/applied/` now keeps only the most recently applied
zip instead of accumulating one per session.
### Internal
- Removed an unused `GitChanges` import in `api.py`.
- Clarified the `.gitignore` comment describing what lives under
`.contextzip/` and confirming the Gemini API key is never stored there.
2026-08-26 16:16:56 +05:30
## [0.4.0] - 2026-08-26
### Added
- **Config UI redesign.** `contextzip config --ui` is now a five-tab
interface (Files, AI selection, Workspace, Advanced, Raw JSON) instead
of a single always_include/always_exclude pane. The file tree and
pattern list now render as a diff — a solid gutter and `+`/`-` marks on
every row and pattern, matching the git-diff mental model the rest of
the tool already uses. Still a single dependency-free HTML string with
no build step, no Node.js, and no network calls from the page itself.
- **New, persisted project preferences** (`.contextzip/config.json`),
editable from the new UI tabs or by hand:
- `ai.prompt_template` — house conventions prepended to every generated
`prompt.txt`, ahead of the task description.
- `limits.max_file_size_mb` — replaces the fixed 1 MB "large file"
threshold with a per-project value.
- `limits.redact_secrets` — reserved for a future scrub of
secret-shaped values inside otherwise-included files. Persisted and
editable now; not yet enforced during packaging (see Known
limitations below).
- `applied_zip_retention` — how many past `apply-zip` archives to keep
in `.contextzip/inbox/applied/` before pruning (was hardcoded to 1).
- `webui.auto_open` / `webui.port` — skip auto-launching a browser tab
and/or pin the config UI to a fixed local port instead of a random
free one.
### Changed
- `contextzip config --ui`'s local server now exposes the full project
config through `/api/state` and accepts the full config through
`/api/save`, instead of only always_include/always_exclude.
- Large-file warnings in normal CLI output now reflect the project's
configured threshold instead of a fixed 1 MB.
### Known limitations
- `limits.redact_secrets` can be toggled on and is saved correctly, but
nothing reads it yet — no redaction currently happens during
packaging. Flagging this explicitly so it isn't relied on before the
enforcement lands.
2026-08-30 23:28:59 +05:30
## [0.4.1] — 2026-08-30
### Added
- **Per-mode output folders.** `.contextzip/output/` now splits by run
mode instead of one flat folder: `output/codebase/` (plain
`contextzip`), `output/git-changes/` (`--git-changes`),
`output/prompt/` (`--prompt`, filename `vibe.zip`), and
`output/watch/` (`debug-context.zip`). The folder name matches the
flag that produced it, so it's unambiguous which zip is which
without having to remember which command you last ran.
- **Automatic workspace cleanup.** After every successful command
(main run and `apply-zip`), `.contextzip/` is pruned automatically:
old zip/manifest/report sets per mode folder, old `backups/`
folders, and old archived zips in `inbox/applied/` are all deleted
down to the most recent one — no confirmation, no separate command.
New project settings: `cleanup.enabled` (default `true`) and
`cleanup.keep_recent` (default `1`).
- **`limits.redact_secrets` is now enforced.** Previously persisted in
config and editable in the config UI but silently ignored by
packaging (flagged as a known limitation in 0.4.0). Now, text files
already going into the archive are scanned for secret-shaped values
— AWS/Google/GitHub/Slack/Stripe/Anthropic/OpenAI keys, JWTs,
private-key blocks, and a conservative generic
`key`/`secret`/`token`/`password` assignment pattern — and matched
values are replaced with `[REDACTED]` before writing. Only scans
files already known to be text and within `limits.max_file_size_mb`;
binary and oversized files are never touched. Redactions are
surfaced in a terminal warning and itemized in the `.report.txt`
file, never applied silently.
- **`.report.txt` files.** Every packaging run (and `apply-zip` run)
writes a full plain-text report alongside its ZIP —
`codebase.report.txt` / `codebase.apply-report.txt` — containing
everything that used to fill the terminal: the excluded-directory
breakdown, the complete included-file list, per-file warnings, and
(when applicable) which files had secrets redacted.
### Changed
- **Terminal output redesigned.** Every command now prints a compact,
one-line-per-step log (`✓` / `!` / `✗`) instead of boxed Rich panels
and tables — matching the register of tools like `pnpm`, `vite`, and
`ruff`. A full-width terminal is no longer needed to read a run.
- The "Scanned" and "Packed" steps are now combined into a single
line, e.g. `Scanned 1518 files & Packed 42 files · 42 included
(394 KB), 1476 excluded, 394 KB → 126 KB, ↓68% smaller`.
- Removed the `contextzip vX.X.X` / project-path banner from every
command — output now starts directly at the first real step.
- `apply-zip`'s manifest auto-detection now searches recursively
across the new per-mode output subfolders, so it still finds the
right manifest under the new layout.
- Full file listings and excluded-directory breakdowns moved out of
default terminal output — shown under `-v`/`--verbose`, or in the
full report file.
### Removed
- `.gitignore patterns applied` is no longer printed (no signal, dead
weight).
- Opening the output folder no longer prints a confirmation line.
## [0.4.2] — 2026-08-31
### Added
- **`limits.redact_secrets` is now enforced.** Previously persisted in
config and editable in the config UI but silently ignored by
packaging (flagged as a known limitation in 0.4.0). Now, text files
already going into the archive are scanned for secret-shaped values
— AWS/Google/GitHub/Slack/Stripe/Anthropic/OpenAI keys, JWTs,
private-key blocks, and a conservative generic
`key`/`secret`/`token`/`password` assignment pattern — and matched
values are replaced with `[REDACTED]` before writing. Only scans
files already known to be text and within `limits.max_file_size_mb`;
binary and oversized files are never touched. Surfaced in a terminal
warning and itemized in the `.report.txt` file, never applied
silently.
### Changed
- **`.contextzip/` is now fully local by default, no exceptions.**
`config.json` was previously carved out of the workspace's
`.gitignore` so it stayed trackable automatically. It's now ignored
like everything else in the workspace — nothing under `.contextzip/`
is ever pushed unless you explicitly opt in with
`git add -f .contextzip/config.json`.
## [0.4.3] — 2026-09-20
### Added
- **`contextzip update` / `cz update`** — checks PyPI for a newer release
and updates in place, so you no longer have to remember
`pip install --upgrade contextzip` after every one of your own
releases. Detects whether this install came from pip, pipx, or
`uv tool` and re-invokes the matching one against the *currently
running* interpreter — never a possibly-different `pip` off PATH.
`--check` reports the current/latest version without installing;
`--yes` skips the confirmation prompt.
### Fixed
- **`apply-zip` no longer dumps a nested copy of the project when the
returned zip wraps everything in a folder named after the project
itself** (e.g. `project/`, or a folder matching the package's own
name). The wrapper-stripping logic added in an earlier release had a
guard that skipped stripping whenever a real folder with that name
already existed in the project — which is exactly what happens when
an AI tool names the wrapper after the project. Now, whenever a
manifest is available, the decision is made by comparing how well
paths actually match the manifest with vs. without stripping, not by
guessing from what's on disk.
- **`contextzip config --ui` now closes its browser tab automatically
after a successful save** and hands control back to the terminal,
instead of only telling you to close it yourself.
2026-09-22 02:44:07 +05:30
## [0.4.4] — 2026-09-21
### Added
- **`contextzip update` / `cz update`** — checks PyPI for a newer release
and updates in place, so you no longer have to remember
`pip install --upgrade contextzip` after every one of your own
releases. Detects whether this install came from pip, pipx, or
`uv tool` and re-invokes the matching one against the *currently
running* interpreter — never a possibly-different `pip` off PATH.
`--check` reports the current/latest version without installing;
`--yes` skips the confirmation prompt.
- **`.contextzip/inbox/README.txt`** is now created automatically the
moment the `.contextzip/` workspace is first set up (and self-heals if
it's ever missing) — a plain-language explainer of what the inbox is
for, how to drop a zip into it, and what happens afterward, so the
folder isn't a silent, unexplained thing you have to already know
about.
### Fixed
- **`apply-zip` no longer dumps a nested copy of the project** when the
returned zip wraps everything in a folder named after the project
itself (e.g. `project/`, or a folder matching the package's own name).
The wrapper-stripping logic had a guard that skipped stripping
whenever a real folder with that name already existed in the
project — exactly what happens when an AI tool names the wrapper
after the project. The decision is now made by comparing manifest
match-rate with vs. without stripping instead of guessing from what's
on disk.
- **`apply-zip` no longer leaves a zip's wrapper folder in place when the
freshest manifest is scoped to a small subset of the project**
(most commonly `.contextzip/output/git-changes/changes.manifest.json`,
which only ever tracked files touched in one prior git-diff run). A
zip of otherwise-legitimate new files matched that manifest at 0%
whether or not the wrapper was stripped, so the fix above's
manifest-comparison saw a tie and played it safe by leaving the
wrapper in place. It now falls back to comparing the zip's top-level
folders against the project's actual current directory layout
(`app/`, `components/`, `lib/`, etc.) whenever the manifest signal
ties, and strips confidently when that layout lines up.
- **`apply-zip <path>` no longer drops its `.apply-report.txt` next to
wherever the zip happens to be** (e.g. the project root) when given an
explicit path outside `.contextzip/inbox/`. The report now always
writes to `.contextzip/inbox/`, matching where it's always shown up
when a zip is auto-detected from there. The zip itself is still left
in place, unmoved, when given an explicit path.
- **Auto-cleanup now actually removes old `*.apply-report.txt` files
sitting loose in `.contextzip/inbox/`** instead of leaving every one
ever written to pile up indefinitely. Keeps only the most recent
`cleanup.keep_recent` (default 1), same rule as everything else it
prunes.
### Removed
- **The per-apply backup mechanism (`.contextzip/backups/<timestamp>/`)
is gone entirely.** `apply-zip` no longer copies overwritten files
there before writing; the manifest-diff confirmation prompt and the
fact that the applied zip is always archived (not deleted) remain the
safety net. Auto-cleanup now also wipes any leftover
`.contextzip/backups/` folder outright on projects upgrading from an
older contextzip version, rather than just pruning it down over time.
## [0.4.5] — 2026-09-27
### Fixed
- **`--git-changes` was silently ignoring `.contextzip/config.json`.**
`_run()` only built the exclude/force-include rules from
`always_include`/`always_exclude` — and folded in the current run's
`--include`/`--exclude` — inside the standard-scan branch.
`resolve_files_from_git()` had no parameters to receive any of that, so
`--git-changes` packaged exactly what `git status` reported, no more and
no less, regardless of what the config or the flags for that run said.
A project could have `*.snap` in `always_exclude` and still see it land
in every `--git-changes` zip. `resolve_files_from_git()` now accepts
`extra_exclude`, `include_only`, and `force_include` and applies them
with the same precedence `resolve_files()` already used — force-include
first, then the safety floor plus excludes, then include-only — so both
modes resolve a project's files identically.
- **`contextzip watch` could package a file straight out of
`always_exclude`** if a stack trace happened to name it — the debug-zip
path built its file list from `extract_paths()` alone, with no
awareness that a project had already asked for that file to be left out
everywhere else. `process_buffer()` now filters extracted paths through
the project's `always_exclude`/`always_include` before they reach
`prompt.txt` or `source-files.zip`.
- **The Python API (`get_files()`, `get_git_changes()`) never read
`.contextzip/config.json` at all** — library callers got only the
built-in rules plus whatever `include`/`exclude` they passed in
explicitly, even on a project with a committed config the CLI already
honored. Both functions now load project config by default and apply
`always_include`/`always_exclude`/`limits.max_file_size_mb` the same way
the CLI does. This is a behavior change for existing callers relying on
the old, config-blind result — pass the new `use_project_config=False`
to keep it.
### Changed
- **Empty-result messages now read "Nothing to compress in this one"**
instead of "Nothing to package" — shown both when `--git-changes` finds
a clean working tree and when a standard scan excludes every file.
- **Unknown-ecosystem detection now points you to request your language.**
When contextzip can't identify a project's stack, the detection line is
followed by `Don't see your language? Add it here: <link>` instead of
leaving that one line as the only output.
2026-10-07 21:22:26 +05:30
## [0.4.6] — 2026-10-07
### Fixed
- **The ZIP was never actually copied to the clipboard on Windows or
Linux.** On Windows there was no clipboard step at all: contextzip
opened Explorer and left you to copy the file yourself. On Linux it
wrote the ZIP's raw bytes to the clipboard as `application/zip`, which
browsers ignore on paste, and only when `xclip` happened to be
installed. Both platforms now put a real *file reference* on the
clipboard, the same thing Ctrl+C on a file does, so you can paste the
ZIP straight into Claude, ChatGPT or any other upload box.
- **Windows:** sets `CF_HDROP` directly through the Win32 API (plus
"Preferred DropEffect" so Explorer pastes a copy), then reads it back
to confirm. If that fails it falls back to PowerShell
`Set-Clipboard -LiteralPath`, and only then to opening Explorer.
- **Linux:** sets `text/uri-list`, the target browsers read when you
paste a file. Order of attempts: `wl-copy` (only if already installed,
on Wayland), a new built-in X11 clipboard owner, then `xclip` (only if
already installed). Every attempt is read back before contextzip
reports success.
- **A failed clipboard copy was silent.** When contextzip fell back to
opening the folder it printed nothing, so you couldn't tell the copy had
failed. It now says so and, when it knows, how to fix it.
- **Explorer fallback lost the selection on paths with spaces or commas.**
`explorer /select,` is now passed as one correctly quoted command line.
- `xclip` / `wl-copy` are run with their output discarded rather than
captured, so they can no longer block the CLI.
### Added
- **Built-in X11 clipboard owner — nothing to install on Linux.**
`pip install contextzip` is now enough: contextzip speaks the small part
of the X11 protocol needed to own the clipboard itself (standard library
only, no new dependency). It offers `text/uri-list`,
`x-special/gnome-copied-files` and `application/x-kde-cutselection`, so
browsers and GNOME/KDE file managers can all paste the file. A tiny
background process holds the clipboard and exits on its own as soon as
something else is copied.
### Changed
- Headless and SSH Linux sessions (no display) now print the ZIP's path
instead of trying to launch `xdg-open`.
- `ClipboardResult` has a new optional `hint` field explaining why a copy
fell back and how to fix it.
- macOS: quotes and backslashes in the ZIP's path are now escaped in the
Finder script. Behaviour is otherwise unchanged.
### Known limitations
- Pasting on Wayland-native browsers relies on XWayland passing the
clipboard through, which GNOME and KDE do. A Wayland-only setup without
XWayland needs `wl-clipboard` (`wl-copy`) installed; otherwise contextzip
opens the folder and tells you what to install.