47 KiB
Changelog
All notable changes to contextzip are documented here.
The format follows Keep a Changelog. This project uses Semantic Versioning.
[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
.gitignorepatterns respected automatically (--no-gitignoreto bypass)--includeflag for scoping to specific directories (exact path-boundary matching)--excludeflag for additional gitignore-style exclusion patterns--dry-runflag to preview without creating any files--verboseflag showing every file with its size--outputflag for custom ZIP destination--no-clipboardflag 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-changesmode 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.gitmodule for git integration and porcelain parsing - Added
resolve_files_from_git()pipeline for pre-selected file resolution - Added structured
GitChangesandGitErrormodels for cleaner error handling and extensibility
[0.2.1] — 2026-05-16
Fixed
--exclude/-epatterns 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
-ewere not being excluded from the ZIP despite appearing in the exclusion list
Improved
-enow 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 tofolder/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 toexclude; 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/--excludeflag on the main command restored and kept for full backwards compatibility alongside the new subcommand- Warning messages for large files now mention both
-e PATTERNandcontextzip exclude PATTERNas remediation options _normalize_patternpromoted 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.rubymodule - 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:
claudechatgptcode-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.githubissue / PR templates
.gitignoreis no longer excluded automatically from packaged context archives
Fixed
- Prevented git-tracked sensitive files (e.g.
.env, secrets, binaries) from being included in--git-changesmode - 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.zipfor standard packagingchanges.zipfor--git-changes
-
Automatic
.contextzip/exclusion from packaged archives and file scanning -
Automatic
.gitignoreintegration:.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
--outputnow 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/-pflag 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
httpxclient — 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 (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.jsonon Linux / macOS%APPDATA%\contextzip\config.jsonon WindowsGEMINI_API_KEYenvironment variable always takes precedence
contextzip configsubcommand for key management:contextzip config— show current key status and sourcecontextzip config --reset-key— clear stored key and re-run onboardingcontextzip config --show-key-path— print config file location
prompt.txtis 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-runcombination — preview AI-selected files without creating a ZIP- API key validation on load — keys not starting with
AIzaare 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()inpackager.pynow accepts an optionalprompt_txtparameter; when provided,prompt.txtis 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.configmodule for cross-platform config path resolution and key read / write / delete operations - Introduced
contextzip.aipackage with three modules:gemini.py— Gemini API client with typed exception hierarchy (GeminiError,GeminiRateLimitError,GeminiUnavailable)heuristic.py— keyword + directory scoring fallback scorerselector.py— project map builder, AI orchestration,prompt.txtgeneration; returns(paths, prompt_txt, method)so callers know which selection path was taken
- Added
httpxas 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
Dwrites.contextzip/debug-context.zipimmediately; 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
- Supports
debug-context.zip— flat ZIP structure produced bywatch:prompt.txt— auto-generated from detected framework, error type, and referenced files; no--promptflag required from the userterminal-error.txt— cleaned, noise-stripped error block and stack tracesource-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 outputdetect_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 theTracebackheaderextract_paths()— runs per-framework path regexes on the error block, resolves relative and bare filenames, and filters out stdlib, venv, andnode_modulespathsstrip_noise()— removes known noise lines (request logs, startup banners, HMR chatter) and collapses blank line runsbuild_prompt_txt()— generates structured, AI-readyprompt.txtcontentprocess_buffer()— convenience function running the full pipeline in a single call
contextzip/watcher.py— process management and packaging:- Spawns child via
subprocess.Popenwith piped stdout/stderr; two daemon threads drain pipes concurrently into adeque(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/ttyraw mode on Unix andmsvcrton Windows, with aninput()fallback for unusual environments - Child terminated cleanly on
Ctrl+C: SIGTERM → 3-second grace period → SIGKILL
- Spawns child via
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
- 33 error start patterns covering JS runtime errors, Next.js error symbols
(
Changed
cli.pyrefactored from a single 1,100-line file into four focused modules with no behaviour changes:cli.py— Click group, four subcommands, and_run(); routing onlycli_display.py— all Rich rendering helpers (print_detection,print_scan_summary,print_file_warnings,print_package_result, etc.)cli_ai.py— AI selection orchestration andnormalize_patterncli_onboard.py— Gemini API key onboarding flow and browser launch
pyproject.tomlupdated to includecontextzip.rules.errorsin package discovery
Developer Notes
contextzip/rules/errors/follows the same pattern ascontextzip/rules/: one file per ecosystem, each exportingERROR_START_PATTERNS,PATH_PATTERNS, andNOISE_PATTERNSas lists of compiledre.Patternobjects — adding support for a new framework means adding one file_load_patterns()inerror_parser.pymaps ecosystem names fromDetectionResult.ecosystemsto rule modules; unrecognised ecosystems fall back to loading both Python and Node patterns as a best-effort defaultcli_display.pyfunctions accept an optionalcon: Consoleparameter (defaulting to the module-level console) making every display function independently testable without monkey-patching- No new runtime dependencies introduced —
watchuses 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 aFileCollectionof git-modified, added, and untracked files; applies base safety rules (no secrets, no binaries) automaticallyget_files(path?, include?, exclude?, use_gitignore?)— returns aFileCollectionof all project files after applying ecosystem rules,.gitignore, and any extra exclusion patternscreate_zip(collection, output?)— writes aFileCollectionto a ZIP archive and returns aPackageResult; output path is caller-controlleddetect_ecosystem(path?)— returns aDetectionResultwith ecosystem names, display string, and confidence levelFileCollection— unified return type forget_git_changesandget_files; supports iteration,len(), andbool()directly;.filesis always a plainlist[pathlib.Path]usable without zippingPackageResultre-exported from the top-level package- Exception hierarchy:
ContextzipError(base),NotARepositoryError,GitNotFoundError,GitCommandError,NoFilesError - All API functions default
pathtoPath.cwd(), matching CLI behaviour
-
contextzip/api.py— new module implementing the public API; no Click, no Rich output, noSystemExit; raises typed exceptions on failure -
contextzip/packager.py— addedcreate_zip_silent()and_workspace_output_path_silent(): same ZIP logic as the CLI path but without aConsoledependency, 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) sofrom contextzip import ...works without knowing internal module paths
Fixed
filters.py— symlink guard innertryblock repeated the identicalrelative_tocall 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 treewatcher.py—_drain_output_queuesilently consumed sentinel values (raw=None) withcontinue, making them invisible to_check_reader_done; sentinels are now put back into the queue so done flags are set correctlywatcher.py—_read_key_windowshad no timeout and would spin forever in non-interactive terminals; now uses a 60-secondmonotonicdeadline matching the existing Unixselecttimeoutpackager.py— removed unused_safe_namehelperai/gemini.py— corrected stale docstring (gemini-2.0-flash-lite→gemini-2.5-flash-lite) to match the actualDEFAULT_MODELconstant
[0.3.3] — 2026-05-29
Fixed
contextzip.aiand all subpackages were missing from the published wheel due to an incompleteincludeallowlist inpyproject.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:
- Not pushed — diff against the upstream branch (covers committed- but-unpushed commits and uncommitted edits in one comparison) + the complete current file
- Diverged from Claude — diff against the version Claude last produced, matched by filename against a local artifacts folder + the complete current codebase file
- 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 wholeeod/handoffrun -
contextzip config --set-session-key/--reset-session-key— manage the Claude session key alongside the existing Gemini key; defaultcontextzip confignow shows both keys' status -
Text-clipboard support (
clipboard.handle_text()) —eod/handoffproduce 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/, soexports/can register itself in.gitignorethe 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 onhttpxfor consistency withai/gemini.pyrather than introducing a second HTTP stack),brief.py(orchestrateseod/handoffend to end) git.pygained 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 viais_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.pymatches 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.txtnow correctly detects both Node.js and Python and stacks both exclusion rule sets — previously this fell through to only the universal base rules, leavingnode_modules/,.venv/, and similar unexcluded except by whatever.gitignorehappened 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_LOCATIONenv var → project config (.contextzip.json, committed, team-shared) → personal config (per-machine, viacontextzip 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.jsonproject config file (optional, git-committed) for pinningworkspace_locationfor every contributor.
Fixed
.gitignoreregistration 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
eodandhandoffcommands, 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 supporteod/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 againstcontextzip --git-changes.
- Removed modules:
[0.3.6] — 2026-08-23
Added
czCLI alias.czcan now be used as a shorthand forcontextzip, with identical commands and behavior..contextzip/project workspace. Contextzip now uses a dedicated workspace containingconfig.jsonand anoutput/directory for generated context archives.- Project configuration via
.contextzip/config.json. Added persistent project-level preferences foralways_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_includeandalways_excludepreferences are now applied during context generation. - Configurable AI file limits. The configured
ai.max_filesvalue is now respected by the AI selection pipeline. - Legacy
.contextzip.jsondeprecation. Project configuration is moving from.contextzip.jsonto.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 --uiopens a local browser tab for settingalways_include/always_excludeby 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 --uiyourself.
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.1only 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-zipcommand. 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
contextzipcreates now also gets a<name>.manifest.jsonwritten 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 andapply-zipfinds 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-zipis about to overwrite is copied first into.contextzip/backups/<timestamp>/. apply_zip()Python API function, mirroringcreate_zip()'s plain-function, no-Click, no-Rich style.- New
--dry-run,--verbose,--yes, and--manifestflags onapply-zip.
Changed
- Consumed ZIPs are moved to
.contextzip/inbox/applied/<timestamp>-name.ziprather than deleted, so they double as an audit trail alongside the backups.
Security
apply-zipvalidates 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-ziponly 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 to0600on 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 withhmac.compare_digestinstead 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, Dockerconfig.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-zipnow detects when every entry in an AI-returned zip is nested under one incidental top-level folder — the shape produced byzip -r out.zip myfolderor 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-zipnow 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
GitChangesimport inapi.py. - Clarified the
.gitignorecomment 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 --uiis 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 generatedprompt.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 pastapply-ziparchives 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/stateand 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_secretscan 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/(plaincontextzip),output/git-changes/(--git-changes),output/prompt/(--prompt, filenamevibe.zip), andoutput/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, oldbackups/folders, and old archived zips ininbox/applied/are all deleted down to the most recent one — no confirmation, no separate command. New project settings:cleanup.enabled(defaulttrue) andcleanup.keep_recent(default1). limits.redact_secretsis 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 generickey/secret/token/passwordassignment pattern — and matched values are replaced with[REDACTED]before writing. Only scans files already known to be text and withinlimits.max_file_size_mb; binary and oversized files are never touched. Redactions are surfaced in a terminal warning and itemized in the.report.txtfile, never applied silently..report.txtfiles. Every packaging run (andapply-ziprun) 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 likepnpm,vite, andruff. 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 appliedis 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_secretsis 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 generickey/secret/token/passwordassignment pattern — and matched values are replaced with[REDACTED]before writing. Only scans files already known to be text and withinlimits.max_file_size_mb; binary and oversized files are never touched. Surfaced in a terminal warning and itemized in the.report.txtfile, never applied silently.
Changed
.contextzip/is now fully local by default, no exceptions.config.jsonwas previously carved out of the workspace's.gitignoreso 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 withgit 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 rememberpip install --upgrade contextzipafter every one of your own releases. Detects whether this install came from pip, pipx, oruv tooland re-invokes the matching one against the currently running interpreter — never a possibly-differentpipoff PATH.--checkreports the current/latest version without installing;--yesskips the confirmation prompt.
Fixed
apply-zipno 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 --uinow 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 rememberpip install --upgrade contextzipafter every one of your own releases. Detects whether this install came from pip, pipx, oruv tooland re-invokes the matching one against the currently running interpreter — never a possibly-differentpipoff PATH.--checkreports the current/latest version without installing;--yesskips the confirmation prompt..contextzip/inbox/README.txtis 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-zipno 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-zipno 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.txtnext 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.txtfiles sitting loose in.contextzip/inbox/instead of leaving every one ever written to pile up indefinitely. Keeps only the most recentcleanup.keep_recent(default 1), same rule as everything else it prunes.
Removed
- The per-apply backup mechanism (
.contextzip/backups/<timestamp>/) is gone entirely.apply-zipno 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-changeswas silently ignoring.contextzip/config.json._run()only built the exclude/force-include rules fromalways_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-changespackaged exactly whatgit statusreported, no more and no less, regardless of what the config or the flags for that run said. A project could have*.snapinalways_excludeand still see it land in every--git-changeszip.resolve_files_from_git()now acceptsextra_exclude,include_only, andforce_includeand applies them with the same precedenceresolve_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 watchcould package a file straight out ofalways_excludeif a stack trace happened to name it — the debug-zip path built its file list fromextract_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'salways_exclude/always_includebefore they reachprompt.txtorsource-files.zip.- The Python API (
get_files(),get_git_changes()) never read.contextzip/config.jsonat all — library callers got only the built-in rules plus whateverinclude/excludethey passed in explicitly, even on a project with a committed config the CLI already honored. Both functions now load project config by default and applyalways_include/always_exclude/limits.max_file_size_mbthe same way the CLI does. This is a behavior change for existing callers relying on the old, config-blind result — pass the newuse_project_config=Falseto keep it.
Changed
- Empty-result messages now read "Nothing to compress in this one"
instead of "Nothing to package" — shown both when
--git-changesfinds 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 whenxcliphappened 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_HDROPdirectly 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 PowerShellSet-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, thenxclip(only if already installed). Every attempt is read back before contextzip reports success.
- Windows: sets
- 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-copyare 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 contextzipis now enough: contextzip speaks the small part of the X11 protocol needed to own the clipboard itself (standard library only, no new dependency). It offerstext/uri-list,x-special/gnome-copied-filesandapplication/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. ClipboardResulthas a new optionalhintfield 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.