Changelog
All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
[Unreleased] - ReleaseDate
Added
- CHANGELOG on docs site — the project changelog is now browseable at the Docusaurus documentation site alongside the rest of the docs. (#258)
Fixed
- README doc links — all relative documentation links in the README
replaced with absolute Docusaurus URLs; a missing
/docs/path segment in several links corrected. (#265, #266)
Changed
- CI path filters — workflow jobs now run only when files relevant to that job change (source, docs, CI configs), avoiding spurious runs on unrelated commits.
[1.0.0] - 2026-06-01
Changed
-
Cache hashing strictness dial (BREAKING) — replaced the two-knob
cache.hashing: strict|version+cache.version_fidelity: major_minor|...config with a singlecache.hashing: loose|normal|strict(defaultnormal). Folder layout follows the mode:loose→<adapter>/<shape>/,normal→<adapter>/<version_mm>/<shape>/,strict→<adapter>/<VERSION_TAG>-<content_hash>/, whereshapeis a 12-hex SHA-256 over the active tags ∪ enabled bundles. The plaintext selection set (active_tags,enabled_bundles) is now recorded in.llmenv-manifest.json, and pruning is mode-aware (state/is never touched). Existing configs usinghashing: version/strictorversion_fidelitymust migrate to the new single key. (#246) -
MCP servers written to
.claude.json(BREAKING) — the Claude Code adapter now merges resolved MCP servers into the top-levelmcpServersobject of.claude.json(the surface Claude Code actually reads) instead of writing a standalonemcp.jsonthat Claude never ingested. The merge is read-modify-write: llmenv servers are upserted by name and every foreign key (oauthAccount,projects,numStartups, hooks, …) is preserved; a corrupt or non-object.claude.jsonis a hard error rather than being overwritten. Remote servers now carry an explicit"type"(http/sse).mcp.jsonis no longer written, andenabledMcpjsonServers(a project.mcp.jsonapproval gate, irrelevant to auto-trusted user-scoped servers) is no longer emitted. (#244)
Fixed
-
pruneno longer reports un-removed symlinks as removed — a symlink whose unlink failed was still counted in theremovedlist, sollmenv pruneclaimed deletions it never made. The failed unlink stays non-fatal (pruning continues) but is now logged and reported under a separatefailedlist, and the CLI surfaces "failed to remove" lines and a count. (#255) -
Corrupt cache manifest now logged — a
.llmenv-manifest.jsonthat fails to parse is still treated as "no prior knowledge" (non-fatal, the documented behavior), but the discard now emits atracing::warn!instead of being swallowed silently, so the degraded re-render is observable. (#247) -
Deep-merge non-idempotence —
util::merge_json/merge_yamlandmerge::capabilities::merge_native_featurecould keep duplicate sequence/array elements on the insert and scalar-overwrite paths that a later merge would dedup away, somerge(merge(x)) != merge(x). All write paths now normalize (dedup at every depth) on insert, making the merge fully idempotent. Surfaced by new property-based tests covering the Claude Code adapter serialization/validation paths and the merge engine. (#107, #108, #109, #110, #111)
Added
-
Third-party license attribution + dual-license texts — added the
LICENSE-MITandLICENSE-APACHEtexts for the project'sMIT OR Apache-2.0license, and generate per-dependency attribution with cargo-about into two outputs:THIRD-PARTY-LICENSES.md(ships with the binary/source dist) andwebsite/docs/third-party-licenses.md(browseable on the docs site).cargo deny checknow gates the license policy in CI and on pre-push, and the allowlist was audited and extended (MIT-0,BSD-3-Clause,ISC,CDLA-Permissive-2.0) — all permissive or weak/file-scoped copyleft, mutually compatible for the dual-licensed binary. (#253) -
Cross-project tag-scoped memory recall — the
turn_starthook now issues, in addition to the existing project-scoped recall, one project-unfiltered recall per active tag keyed on that tag'sllmenv-tag:<tag>keyword. Memory stored under a tag in one project surfaces when the same tag activates in another, completing the cross-project promise of the write side (#81). Tags are validated before expansion so a malformed scope can't inject recall metacharacters. Bundle-scoped recall (llmenv-bundle:<bundle>) remains documented-but-unimplemented (#215). (#197) -
Lifecycle memory hooks — new
hook-runcommand provides engine-neutral lifecycle event dispatching for ICM memory integration. Three events (session_start,turn_start,session_end) trigger corresponding ICM actions (icm_wake_up,icm_memory_recall,icm_memory_store). Hooks auto-activate when a memory backend is configured; failures degrade gracefully with warnings (exit 0) so hooks never block the agent. (#171) -
ICM-aware Claude Code adapter — the adapter now resolves three previously open design questions automatically. (1) It merges every resolved MCP server into the top-level
mcpServersof.claude.json— the surface Claude actually reads — so user-scoped servers are auto-trusted and never prompt (the earlierenabledMcpjsonServersapproach targeted the deadmcp.jsonand was removed; see the #244 entry below). (2) When the resolved manifest includes theicmMCP server, the adapter emitsautoMemoryEnabled: falseso ICM and Claude's native auto memory don't both write (a usernativeoverride still wins). (3) The adapter always registers aSessionStarthook running the newllmenv check-stalesubcommand, which compares the booted config folder against the one llmenv would materialize now and warns the user to restart on drift. (#121, #122, #123, #124) -
Per-feature
nativeoverrides + top-level passthrough — completes the two-layer engine-capabilities model: every modeled feature now has both an engine-neutral generic form and an engine-specificnativeoverride emitted verbatim. New top-levelnative_<feature>sibling maps (native_permissions,native_hooks,native_plugins,native_mcp), each a per-engine fragment; the Claude Code adapter deep-merges each onto its rendered subtree (native_hooks→hooks,native_plugins→ settings top level,native_mcp→mcp.json). The top-levelnative.<engine>catch-all (for keys no modeled feature owns, e.g.alwaysThinkingEnabled) threads throughmerge()and is overlaid ontosettings.jsonlast. A modeled-feature key (permissions,hooks) in the catch-all now hard-errors instead of silently clobbering the security-rendered output (which would bypass the deny-never-weakened invariant) — it belongs in thenative_<feature>sibling. Addsutil::merge_jsonfor the adapter-side overlay. (#96, #97, #102) -
Plugin + marketplace support —
marketplace:andplugin-collection:are now first-class top-level config blocks, selected onto a scope by tag intersection (same model as bundles and MCP servers). Plugins are written asmarketplace:pluginrefs. Git marketplace sources are cloned once into<cache_dir>/marketplaces/<name>/(shared across scopes, fast-forwarded byllmenv plugin sync); local-path sources are used in place. The resolved git HEAD is mixed into the materialized scope hash so a marketplace update re-renders. The Claude Code adapter rendersextraKnownMarketplaces(asdirectorysources pointing at the local clone) andenabledPlugins(plugin@marketplace, all enabled) intosettings.json. New CLI commands:marketplace-ls,plugin-ls,plugin sync;doctorflags orphan collections and unreferenced marketplaces. The internal resolved model is engine-agnostic so a future Codex adapter can reuse it. (#59) -
settings.jsonpermission rendering — the Claude Code adapter now renders engine-neutral permission rules ({tool, pattern}/{tool, paths}) into Claude'sTool(pattern)string grammar, landing in flatpermissions.{allow,ask,deny}arrays alongside verbatimpermissions.native.claude_coderule strings.default_modemaps todefaultMode. Native suppression is directional: a nativedenyoverrides a neutralallow/askof the same string, but a nativeallownever weakens a neutraldeny(deny is authoritative). The two-layer invariant — every major feature gets a generic form plus an engine-specificnativeoverride — is now documented indocs/design/engine-capabilities.md. (#34) -
CLI color support —
--color <auto|always|never>mode withshould_use_color()honoringNO_COLORandCLICOLOR_FORCEenv vars plus TTY detection. Color glyph helpers centralized insrc/cli/style.rs.tag-ls,scope-ls,bundle-ls,doctor, andstatusemit colored markers (active*,(inactive),(orphan), doctor✓/⚠/✗);exportoutput stays plain so its shell-eval'd stdout never carries escape codes. (#62) -
llmenv prunecommand — subcommand with--all,--older-than <duration>, and--dry-runflags (--all/--older-thanmutually exclusive, durations parsed viahumantime).cache::prune()performs the on-disk sweep: deletion is symlink-safe (links are unlinked, never followed), orphaned*.tmpstaging dirs are always cleared, and--older-thanonly ages out current-version cache folders so the staleness and age axes stay orthogonal. (#63) -
Doctor diagnostic command (
llmenv doctor) with full health checks- Validates configuration file parsing
- Checks cache directory writability
- Tests git remote connectivity
- Optional garbage collection with
--gcflag - Configurable cache retention via
cache_retention_hourssetting
-
User documentation
docs/getting-started.md— Installation and quick start guidedocs/configuration.md— Complete configuration schema with examplesdocs/icm-topology.md— MCP server integration guide- Updated README with feature overview and examples
-
Cache retention configuration
- New optional setting
cache_retention_hoursin[settings]section - Defaults to 168 hours (7 days)
- Garbage collection removes stale cache entries and orphaned
.tmpdirectories
- New optional setting
-
Scope matching infrastructure
- Network scope matching via WiFi SSID
- Host scope matching via hostname
- User scope matching via OS user
- Project scope matching via project markers (e.g.,
.llmenvrc)
-
Bundle system
- Environment variable bundles with tag-based activation
- Multiple bundles can be active simultaneously
- Tag filtering for selective bundle activation
-
Shell integration
- Automatic scope evaluation via shell hooks
- Support for zsh and bash
- Throttled configuration sync (respects sync interval)
-
Git sync
- Automatic commit and push of configuration changes
llmenv synccommand for on-demand synchronization- Configurable sync interval
-
MCP server integration
- Scope-aware activation of Model Context Protocol server
- Automatic process lifecycle management
- Server binding configuration
Changed
-
Config format is now YAML — configuration lives at
~/.config/llmenv/config.yaml(wasconfig.toml),llmenv initemits YAML, and project marker files are parsed as YAML. The list-heavy scope/bundle schema reads far more compactly in YAML. Dropped thetomldependency and migrated off the deprecatedserde_yamlcrate to the maintainedserde_yaml_ngfork. (#76) -
Lifecycle hooks run on a current-thread tokio runtime —
hook-runfires on the agent's hot path (session start and every prompt turn) and only does oneblock_onover a short sequential chain of HTTP round-trips, so the multi-threaded runtime's worker pool was pure startup overhead. Swapped toBuilder::new_current_thread().enable_all().build(); the 2s timeout and fail-soft behavior are unchanged. Added integration tests driving the realhook-runbinary to lock in the fail-soft contract (exit 0 + stderr warning on unknown event, no backend, malformed/SSRF-rejected URL, unreachable backend). (#186, #187, #189)
Fixed
- Path traversal detection now parses path components instead of substring
matching, so traversal the old checks missed — e.g. a trailing
foo/..with no slash — is rejected. Newpaths::has_parent_componenthelper backs bothcache_dir(#65) andproject.path_prefixvalidation. (#65) - Improved error messages with context using
anyhow - Fixed shell variable name validation
- Added proper escaping for shell metacharacters in exported variables
Security
- Adapter-returned env var names are validated at the source (in
build_and_materialize) in addition to the final emission loop, so no future emission path can smuggle a name that breaks theexport NAME=...shell contract. (#67)
Hardening
- Documented the cache-key invariant in
materialize::cache::hash_manifest: the key is a function of (relative path, file contents) only and deliberately excludes the absolute source path, so a bundle reached via a symlink or alias reuses the cache rather than missing it. Guarded by a new regression test. (#66)
Documentation
- Complete API documentation for all public modules
- Configuration examples for common use cases
- Troubleshooting guide in MCP documentation
- Comprehensive security/performance/property-test audit
(
docs/superpowers/specs/2026-05-26-comprehensive-audit.md); follow-ups filed as #65–#73. (#56) - Documented
Config::load's path-expansion contract — callers must expand~before calling; adebug_assertenforces this in debug builds. (#68)