mirror of
https://github.com/akadeepesh/contextzip.git
synced 2026-10-20 08:36:20 +02:00
1082 lines
56 KiB
Markdown
1082 lines
56 KiB
Markdown
# 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`.
|
||
|
||
|
||
## [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.
|
||
|
||
|
||
## [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.
|
||
|
||
## [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.
|
||
|
||
|
||
## [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.
|
||
|
||
|
||
## [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.
|
||
|
||
---
|
||
|
||
## [0.5.0] — 2026-10-19
|
||
|
||
### Added — versioned snapshots
|
||
- **Every package is a numbered snapshot.** A copy of each ZIP and its
|
||
manifest is kept in `.contextzip/snapshots/`, so `apply-zip` can merge a
|
||
reply against exactly what was sent — even after you've packaged again,
|
||
which used to delete the original. Kept while among the newest
|
||
`snapshots.keep` (10), younger than `snapshots.max_age_days` (14), or
|
||
awaited by a reply in the inbox. `-o` ZIPs are snapshotted too.
|
||
- **`CONTEXTZIP.md` in every ZIP**: the snapshot ID plus short reply rules
|
||
for the AI (complete files, same paths, leave `[REDACTED]` alone, list
|
||
moves and deletions). A reply that echoes it is matched to its exact
|
||
snapshot. Turn off with `"zip_guide": false` or in the config UI.
|
||
- **`contextzip snapshots`** lists snapshots; **`apply-zip --snapshot ID`**
|
||
pins one.
|
||
- **Moves and deletions.** New statuses *moved* (declared in the reply's
|
||
`CONTEXTZIP.md`, or near-identical content at a new path) and
|
||
*delete-requested*. The old file is only removed with an explicit yes,
|
||
`--remove-moved` / `--allow-delete`, or the review page's checkbox —
|
||
never by `--yes` alone and never if you've changed it since.
|
||
- **Non-git monorepos**: when a folder holds a few sibling repos and no
|
||
git of its own (`shop/frontend/.git`, `shop/backend/.git`), contextzip
|
||
asks once whether to use it as the project root and remembers the answer.
|
||
- A notice when you run contextzip inside a git submodule.
|
||
- Monorepo packages declared in npm/yarn/pnpm workspaces, `lerna.json`,
|
||
Cargo workspaces or `go.work` are detected at any depth.
|
||
- `.gitattributes` normalising line endings to LF.
|
||
|
||
### Fixed
|
||
- When Gemini is rate-limited, the summary now says the files were picked
|
||
by keyword match instead of crediting Gemini.
|
||
- The Python API's `create_zip()` now honours the project's
|
||
`limits.redact_secrets` setting, like the CLI.
|
||
- Moved off pathspec's deprecated `gitwildmatch` factory to
|
||
`GitIgnoreSpec` (same matching, pinned by tests).
|
||
- **Monorepos: everything now anchors on the project root, from any
|
||
folder.** Running `cz` inside `frontend/` used to create
|
||
`frontend/.contextzip/`, write manifests relative to `frontend/`, and
|
||
skip changes in sibling folders. Now every command — packaging,
|
||
`--git-changes`, `apply-zip`, `watch` and the config UI — finds the
|
||
project root first and works from there.
|
||
- The root is the **outermost** git repository (not above your home
|
||
folder), so a nested `frontend/.git` left by create-next-app or
|
||
create-vite no longer hijacks it. A `.git` *file* (worktrees,
|
||
submodules) now counts as a repo. Without git, an existing
|
||
`.contextzip/` workspace marks the root. `CONTEXTZIP_ROOT` overrides.
|
||
- Paths you type are relative to where you are, like `git add`:
|
||
`cz include src/` from `frontend/` means `frontend/src/`.
|
||
- `contextzip config` shows which root was picked and why.
|
||
- An old `.contextzip/` left in a subfolder by earlier versions is
|
||
pointed out as safe to delete.
|
||
- **`--git-changes` missed changes inside nested repos.** An embedded repo
|
||
(`frontend/.git`) or submodule appeared as one opaque entry and its
|
||
changes were dropped. Nested repos are now read too.
|
||
- **`--git-changes` mangled unusual file names.** Names with spaces or
|
||
non-ASCII characters came back quoted and escaped; git's NUL-separated
|
||
output is used now.
|
||
- **`watch` from a subfolder** runs your command where you ran it and
|
||
resolves stack-trace paths against that folder first. Bare file names
|
||
are matched without walking `node_modules/`.
|
||
- **`apply-zip` could write `[REDACTED]` over real secrets** if the AI
|
||
returned a redacted file. Redacted files are now always merged against
|
||
the redacted copy that was sent, keeping your real values.
|
||
|
||
### Changed
|
||
- **`apply-zip` rebuilt.**
|
||
- Files are matched to the project by path suffix, so ZIPs wrapped in
|
||
extra folders, relative to a subfolder, flattened, or using backslash
|
||
separators land in the right place; new files follow their siblings.
|
||
Names that fit several files are settled by content, or marked
|
||
*ambiguous* and never guessed.
|
||
- The snapshot (manifest) that best matches the returned ZIP is used,
|
||
not just the newest — no more everything-is-drifted after an unrelated
|
||
run or a ZIP written with `-o` (those manifests are now registered in
|
||
`.contextzip/output/external/`).
|
||
- Three-way merge: when you and the AI changed a file in different
|
||
places, the changes are merged (status *merged*). Only changes to the
|
||
same lines are a *conflict*.
|
||
- A file that already matches what the AI returned is *unchanged*, not
|
||
drifted. Line endings never count as a change, and text is written
|
||
with the local file's line endings and BOM.
|
||
- New statuses: *merged*, *conflict*, *deleted*, *partial* (the AI
|
||
returned `// ... rest of the file unchanged` instead of code) and
|
||
*ambiguous*.
|
||
- When something needs a decision, you choose: apply the safe changes,
|
||
review in the browser, resolve in your editor, apply everything, or
|
||
quit. Undecided files are left alone and the ZIP stays in the inbox.
|
||
- Non-interactive runs (CI) write nothing when a decision is needed,
|
||
unless `--safe-only` or `--yes` is given.
|
||
- `__MACOSX/`, `.DS_Store` and echoed `prompt.txt` are ignored.
|
||
- The Python API's `apply_zip()` now leaves files that need a decision
|
||
alone unless `include_risky=True`. `ApplyResult` gains `skipped` and
|
||
`zip_kept`.
|
||
- API functions called without a `path` now use the project root, like
|
||
the CLI.
|
||
|
||
### Added
|
||
- `--here`: package only the folder you're in.
|
||
- `apply-zip --review`: a local, token-protected review page with
|
||
side-by-side diffs, word-level highlights and in-page conflict editing.
|
||
- `apply-zip --editor`: resolve conflicts in VS Code's three-way merge
|
||
editor (also Cursor, Windsurf, VSCodium; or set `CONTEXTZIP_EDITOR`).
|
||
- `apply-zip --safe-only`.
|
||
- `CONTEXTZIP_ROOT` to pin the project root.
|
||
- Manifest format v2: records the ZIP's location, the project root, the
|
||
redacted files and a line-ending-insensitive hash per text file.
|
||
|
||
---
|
||
|
||
## [0.4.7] — 2026-10-19
|
||
|
||
### Changed
|
||
- **A `.env` you deliberately include now has every value redacted.**
|
||
Secret files (`.env`, private keys, credential files, Terraform state and
|
||
so on) are still left out by default. You can still bring one back with
|
||
`contextzip include .env`, `always_include` in `.contextzip/config.json`
|
||
or `contextzip config --ui`, so the AI can see which variables your app
|
||
expects. It is now never zipped as-is:
|
||
- `.env` / `.env.*` files keep every key, comment and blank line, but
|
||
every value becomes `[REDACTED]`. Multi-line quoted values collapse to
|
||
one redacted line, and any line that isn't `KEY=value` is redacted
|
||
whole.
|
||
- Any other secret file gets the secret-pattern scan (private-key blocks,
|
||
API keys, tokens and so on).
|
||
- A secret file that isn't text (`.p12`, `.jks` and so on) can't be
|
||
redacted, so it is left out and listed in the report.
|
||
- This happens whatever `limits.redact_secrets` says and whatever the
|
||
file's size. It applies to normal runs, `--git-changes`, `--prompt`,
|
||
the `source-files.zip` inside `contextzip watch`'s debug package, and
|
||
the Python API.
|
||
- **`contextzip include` / `--include` now brings back paths you name
|
||
explicitly**, even when the rules exclude them. `contextzip include .env`,
|
||
`include dist/` and `include some-gitignored-file.txt` all work now.
|
||
Previously they quietly matched nothing. Excluded folders nested inside a
|
||
named folder are still skipped (`include dist/` leaves out
|
||
`dist/node_modules/`). Naming an ordinary folder still only narrows the
|
||
run, so `include src/` doesn't pull in `src/.env`. The CLI prints a line
|
||
when this happens, and secret files are redacted as described above.
|
||
This also works with `--git-changes` and the Python API's `get_files()`.
|
||
- Include paths now accept a leading `./` or `.\` (`contextzip include ./src`).
|
||
- The scan report now groups repeated redactions (`.env value x3`).
|
||
|
||
### Fixed
|
||
- The "Don't see your language?" hint shown for undetected projects linked
|
||
to a placeholder (`https://example.com`). It now points to
|
||
<https://www.contextzipcli.com/request-language>.
|
||
- **The report didn't list files that couldn't be written to the ZIP**,
|
||
even though the CLI said "see report". They now appear under "Left out
|
||
of the ZIP" with the reason.
|
||
|
||
### Developer notes
|
||
- `rules/base.py` has a new `SECRET_PATTERNS` list. `PATTERNS` still
|
||
contains it, so the default exclusions are unchanged.
|
||
- New in `contextzip.redact`: `is_sensitive_path()`, `is_env_file()`,
|
||
`redact_env()` and `redact_sensitive_file()`.
|