ai.prompt_template is now prepended to every generated prompt.txt,
ahead of the task description, as a 'Project conventions:' block.
Threaded through ai_select()/build_prompt_only_txt() in selector.py,
run_ai_selection() in cli_ai.py, and the --prompt call site in cli.py.
resolve_files() and resolve_files_from_git() now accept
large_file_warn_bytes instead of a fixed 1 MB constant. cli.py threads
a project's limits.max_file_size_mb through to both the filtering and
the warning text in output.
Add ai.prompt_template, a new LimitsConfig (max_file_size_mb,
redact_secrets), a new WebUIConfig (auto_open, port), and
applied_zip_retention. All new fields are optional with sensible
defaults and defensive parsing — a malformed value falls back to the
default rather than erroring. Foundation for the config UI redesign
and the features that follow in this release.
Drop the unused GitChanges import in api.py. Expand the .gitignore
comment on .contextzip/ to note it's project-local and that the
Gemini API key is never stored there.
Detect when every entry in an AI-returned zip shares one incidental
top-level folder (the shape produced by 'zip -r' or GitHub's Download
ZIP) and strip it automatically when doing so clearly improves the
match against the project manifest, so files land at their real
project-relative paths.
Also warn before applying a zip whose paths barely match the project
at all, instead of silently treating every file as new. The
applied/ folder now keeps only the most recently applied zip.
Route all writes through a single _write_config() helper and re-apply
0600 permissions each time, not just on creation. A pre-existing file
from an older install (or one created under a permissive umask) could
otherwise stay group/world-readable indefinitely, exposing a plaintext
Gemini API key on shared machines.
Adds an 'Applying AI-returned changes' section covering the manifest
mechanism, the new/modified/unchanged/drifted/untracked classification
table, safety guarantees, and inbox/manifest resolution. Also updates
the command table, Python API table, and workspace layout diagram to
include inbox/, backups/, and the manifest sidecar.
Adds apply_zip() alongside get_files/get_git_changes/create_zip,
following the same plain-function, exception-raising style (no Click,
no Rich, no SystemExit). Exposes ApplyResult and ZipNotFoundError
from the package root.
Wires applier.py into the CLI: auto-detects a zip from
.contextzip/inbox/ (or takes an explicit path), prints a plan summary
(new/modified/unchanged/drifted/untracked counts), and only prompts
for confirmation when something looks risky — drift, untracked paths,
or no manifest at all. Clean modifications and new files apply with
no prompt.
Flags: --dry-run/-n, --verbose/-v, --yes/-y, --manifest.
Introduces applier.py: resolves the AI-returned zip (inbox or explicit
path), diffs it against a per-zip manifest, and classifies every file
as new/modified/unchanged/drifted/untracked before writing anything.
Only ever adds or modifies files — no deletions are inferred.
packager.py now writes a <name>.manifest.json sidecar next to every
zip it creates, containing a hash of each included file. It's written
to disk only, never added as a zip entry, so it's never uploaded or
visible to whatever AI tool the zip is pasted into.
Safety: zip entries are validated against path traversal before
extraction; every file about to be overwritten is backed up under
.contextzip/backups/<timestamp>/ first; consumed zips are archived to
.contextzip/inbox/applied/ rather than deleted.
- 'contextzip config --ui' launches the local config UI directly
- First run in a project with no config at all offers to launch it
interactively, gated on TTY + no CI env var + no --prompt/--output
- Declining is remembered per-machine (config.py) so it only asks once
- Accepting and saving mid-run reloads config and continues packaging
immediately in the same invocation
Single-file HTML/CSS/JS, no build step, no CDN calls — colors and
typography matched to the showcase site's design tokens. Live file tree
with checkboxes, one-click exclude suggestions, working-config panel
with raw JSON preview, and a save flow with a success overlay.
Stdlib-only HTTP server bound to 127.0.0.1, with a random per-session
token (same approach as Jupyter Notebook) and no outbound network calls
of its own. Auto-shuts down on save, tab idle, or a hard time ceiling.
Includes:
- suggestions.py: detects PDFs, office docs, fonts, media, and anything
over 1MB that isn't already covered by the default exclusion rules
- persist.py: writes always_include/always_exclude to
.contextzip/config.json, reusing the existing workspace/.gitignore setup
Splits file-tree scanning into a one-time disk walk (scan_all_files) plus
a pure in-memory classifier (classify_scanned_files) that can be run
against different include/exclude patterns without re-touching the
filesystem. resolve_files() is untouched — this is purely additive.
Prunes known-huge, always-irrelevant directories (.venv, node_modules,
.git, __pycache__, build output, etc.) during the walk itself, and
rejects symlinks that resolve outside the project tree (e.g. a venv's
bin/python pointing at the system interpreter).
- README: expand framework detection section, new "Workspace
location" section, drop stale exports/ mention left over from
the eod/handoff removal
- Add CHANGELOG.md
- Bump version 0.3.4 -> 0.3.5 (pyproject.toml, __init__.py)
Adds a precedence chain for where .contextzip/ is created:
env var (CONTEXTZIP_WORKSPACE_LOCATION) > project config
(.contextzip.json, git-committed, team-shared) > personal config
(per-machine, contextzip config --set-workspace) > default
(git-root, unchanged behavior).
Also fixes a latent bug this surfaced: .gitignore registration
derived git_root as workspace.parent, which only held true when
the workspace was always directly under git root. Now derives it
independently via _find_git_root and only registers .gitignore
when the workspace is actually inside the repo.
New: project_config.py (loads .contextzip.json), config.py
get/save/delete_workspace_location, cli.py --set-workspace/
--reset-workspace on `contextzip config`.
Detection previously only checked project_dir itself, so a monorepo
like root/frontend/package.json + root/backend/requirements.txt
detected nothing and fell through to only the universal base
exclusion rules — leaving node_modules/, .venv/, etc. unexcluded
except by whatever .gitignore happened to catch.
Now scans project_dir plus a shallow, bounded subdirectory tree
(depth 2), skipping dependency/build dirs (node_modules, .venv,
.git, etc.) so it never walks into a large dependency tree. Runs
unconditionally, not just when root comes up empty, since a
root-level marker doesn't imply there's nothing else to find in
sibling directories.
DetectionResult now tracks which subdirectory each ecosystem was
found in (`sources`), surfaced in the CLI panel, e.g.
"Next.js (frontend/) + FastAPI (backend/)".