Changelog¶
Changelog¶
All notable changes to Jarvy will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Policy¶
- Stable releases (
vX.Y.Z) get a curated entry below before the tag is pushed. The release workflow'sBuild release notesstep awk-extracts the matching## [vX.Y.Z]section into the GitHub release body, then appends a**Full Changelog**compare link plus Jarvy's standing install/security footer. Forgetting this entry causes the workflow to fall through to a rawgit loglisting — technically valid, but reads like a commit dump rather than a curated narrative. Update CHANGELOG before tagging. - Pre-releases (
vX.Y.Z-rc.N,-beta.N,-alpha.N) do not get a CHANGELOG entry. The awk extraction returns empty, the workflow falls through togit log <prev-tag>..<tag>notes, and that fallback is the intended pre-release path. The curated stable entry below is written once when the corresponding stable cuts. - Entry headers must match the awk pattern:
## [vX.Y.Z]or## [vX.Y.Z] — Title(em-dash optional). Other shapes won't be matched.
See docs/release-testing.md
for the full release process and
docs/release-quirks-jarvy.md
for divergences from generic release skills.
[v0.6.4] — task-runner lifecycle hooks + jr one-command setup (2026-07-17)¶
Features:
- npm-style lifecycle hooks for
jarvy run— ifpre<name>orpost<name>keys exist in[commands],jarvy run <name>runs them around the main command with npm's exact semantics: a failing pre aborts the run (its exit code propagates), post only runs after a successful main, a failing post fails the run, and extra--args go to the main command only. The more readable colon spelling works too ("pre:build"/"post:build", quoted — TOML bare keys can't contain:); when both spellings are defined the colon form wins and a note is printed. Hooks are ordinary entries: listed, directly runnable, same safety guards. jarvy shell-init --apply— wires thejr(=jarvy run) shorthand into your shell rc file with one command, whatever way you installed jarvy (previously nothing did this automatically —cargo installusers never gotjrwithout hand-editing their rc). Idempotent; supports bash/zsh/sh/fish/PowerShell, and nushell via a materialized~/.jarvy/init.nu(re-run--applyafter upgrading).- Bare
jarvy runwith no[commands]table now prints a real getting-started guide (worked example, the three usage forms, cwd rule,jrtip) instead of a one-line hint. - New docs page: Task runner — npm-run-style documentation including a "Coming from npm?" mapping table.
Fixes:
- Every jarvy invocation warned
plugin directory has insecure permissionsfor users who never created~/.jarvy/tools.d— the permission probe conflated "directory doesn't exist" with "unsafe permissions". An absent plugin directory is now a silent no-op; the security gate still fires for directories that exist with loose permissions.
Internal:
verify-release-assets.shaccepts the bare-basenameSHA256SUMS.txtentries introduced in v0.6.3 (its listing regex previously required a path separator before the name).- This repo's own
jarvy.tomlgained a[commands]table — jarvy's development tasks now dogfoodjarvy run.
[v0.6.3] — updater reliability + installer integrity (2026-07-16)¶
First release under the gate model (see docs/release-testing.md):
soaked as v0.6.3-rc.1 for the 24h patch window because it changes the
update chain.
Security:
install.sh/install.ps1checksum verification actually fires now. Two stacked bugs meantcurl | bashand PowerShell installs were silently skipping integrity verification:SHA256SUMS.txtentries carry build paths that the lookup never matched, and under PowerShell 7 the sums file arrived as raw bytes that never split into lines. Both installers now match entries by basename and decode the manifest explicitly — a tampered download aborts the install on every platform. Caught by the new installer end-to-end suite on its first run.SHA256SUMS.txtentries are also generated as bare asset names going forward (the pathed entries likewise broke Chocolatey's publish step on v0.6.1).
Fixes:
jarvy update --rollbackno longer fails withText file busyon Linux — the restore path wrote over the running executable; it now uses the same atomic temp-rename as the forward update. The consumed backup also no longer leaves an orphaned file in~/.jarvy/backup/.- Windows binary updates retry the final swap (3× with backoff) when
Defender or an indexer briefly holds the freshly extracted binary
(
Access is deniedflake). - Release binaries embed the exact release tag: an rc build reports
jarvy 0.6.3-rc.1from--versioninstead of the bare crate version, and the update checker compares true prerelease versions — beta-channel users were previously never offered the next rc. - Unknown subcommands (
jarvy rollback, typos) exit2in non-interactive contexts instead of printing a menu prompt and exiting0— scripts and CI can no longer mistake a typo for success. Humans at a TTY still get the interactive menu.
Changed:
- Hooks run under
bash(orsh) on Unix instead of the user's$SHELL. zsh's lack of word splitting silently broke POSIX-style hooks depending on who ran them — the samejarvy.tomlnow behaves identically for every developer. Opt back into a specific shell with[hooks.config] shell = "zsh". - A pinned
nodeversion installs through nvm when nvm is configured (nvm install <ver> && nvm alias default <ver>), sonode = "24"is honored and a second package-manager node no longer shadows nvm's. Ranges andlatestkeep the platform installer.
Known issues:
- Upgrading from v0.6.2 on Windows with
jarvy update --method binarycan intermittently fail withAccess is denied— the retry fix ships in this release but the binary performing that upgrade is v0.6.2's. Re-run the update or use the install script. Upgrades from v0.6.3 onward retry automatically.
Internal:
- Release pipeline hardening after the v0.6.1 incident: a
draft-verification gate (checksums, cosign, SBOMs, and a binary
--versionsmoke) must pass before any release publishes; the Cargo.toml↔tag guard refuses to build a mislabeled tag; and the installer/package e2e suites now actually run on every release. Soak-window automation is bump-kind aware and no longer labels force-dispatched validation runs (#53).
[v0.6.2] — nvm detection fix (2026-07-14)¶
Corrected re-release of the withdrawn v0.6.1 — identical code plus the version bump v0.6.1 was missing and a release-workflow guard that makes the mistake impossible to repeat (the tag now refuses to build unless Cargo.toml matches it).
Fixes:
- nvm is now detected via its filesystem marker
(
${NVM_DIR:-$HOME/.nvm}/nvm.sh) instead of shell probes. nvm is a shell function, not a binary, so the previous PATH lookup andbash -lcprobes could never see it — everyjarvy setupwithnvmin the config re-downloaded and re-ran the installer, and the version check reported nvm as needing install forever. Detection now converges after one install, andjarvy upgrade nodecorrectly suggestsnvm installwhen node is nvm-managed. (Windows is unaffected: nvm-windows is a real binary.)
Internal:
- CI action bumps (Dependabot):
actions/checkout4→7,actions/download-artifact7→8,actions/setup-java4→5,azure/setup-helm4.3.0→5.0.1,docker/setup-buildx-action3→4. The release-critical ones were smoke-validated by dispatch runs before this release.
This is also the first release upgradable to via
jarvy update --method binary from v0.6.0 — the redirect fix shipped in
v0.6.0 makes the updater work going forward (upgrading from v0.5.x
still requires the install script or a package manager).
[v0.6.1] — WITHDRAWN (2026-07-14)¶
Withdrawn ~40 minutes after publication: the version-bump commit was
missed before tagging, so every binary and package asset self-identified
as 0.6.0, breaking jarvy update with a perpetual update loop. No
downstream channel consumed it (crates.io skipped, Chocolatey failed on
the name mismatch). Use [v0.6.2] — the corrected re-release of the same
code.
[v0.6.0] — jarvy run, editor integrations, git config hardening (2026-07-13)¶
Known issues:
- Upgrading from v0.5.x with
jarvy update --method binaryfails withChecksum verification failed: the v0.5.x updater does not follow the HTTP redirects GitHub now serves on release-asset downloads (fixed in this release, but the fix cannot reach the old binary doing the upgrade). Workaround: reinstall via the install script (curl -fsSL https://raw.githubusercontent.com/Cliftonz/jarvy/main/dist/scripts/install.sh | bash) or your package manager. Upgrades from v0.6.0 onward work.
Features:
jarvy run [name] [-- args]— npm-run-style command runner for a[commands]table injarvy.toml. An explicitly named command is treated as consent (shell chaining allowed; the command is printed ANSI-stripped before exec), the child's exit code is propagated, and the working directory is the config file's directory. Barejarvy runlists the available commands; there is no implicitcargo run/cargo testfallback, and NUL-bearing (on Windows, also%-bearing)-- argsare refused.jarvy shell-initemits a snippet definingjras shorthand, and shell-init/completions gain nushell support alongside bash/zsh/fish/sh/powershell. Telemetry:run.command.*,shell_init.generated,completions.generated.jarvy doctor --check <path,tools,hooks>— limit the health run to specific categories (system info is always shown as context). Skips the unselected probes entirely; an unknown category exits2with the valid set. Composes with--toolsand--extended. Closes PRD-027.jarvy skills update [<name>]— re-fetch skills fromlibrary_sources, compare the advertised version/sha against the installed sidecar per agent, and reinstall only on divergence (no-op when unchanged; a pinned[skills.install]version that no longer matches the library refuses rather than silently drifting). PRD-049 phase 2.jarvy skills remove <name>— uninstallSKILL.md+ the.jarvy-skill.jsonsidecar from every targeted agent. Idempotent: removing an absent skill is a clean no-op. User-added companion files next toSKILL.mdare preserved.- Ad-hoc
jarvy skills install <name>— a name absent from[skills.install]now resolves from the configuredlibrary_sourcesatlatestinstead of erroring (the--nameflag is replaced by a positional argument). - All three subcommands accept
--format json(PRD-051), and emitskills.updated/skills.removedtelemetry events (gated behind the standard telemetry opt-in). jarvy setupobservability flags are now wired into the tracing subscriber:-v/-vv/-vvvraise console verbosity,-qsuppresses all but errors (without starvingjarvy.log/ OTLP),--log-format json,--log-file <path>,--debug-filter <module>, and--profile/--profile-output(phase-level timing report). These flags previously parsed but were silently dropped.- 15 new tools:
allure,aws-sam-cli,cfn-lint,cypress,goaccess,k3s,linkerd,locust,microk8s,playwright,putty,structurizr,task,tmux,todoist.structurizris the 2026 consolidated "software architecture models as code" tool (C4 model) — installs via Homebrew / Linuxbrew (the old, now-deprecatedstructurizr-cliformula is not used); no first-party winget/choco, so Windows uses the.waror Docker image.k3sandmicrok8scover single-node local Kubernetes: k3s is Linux-only and installs from a commit-pinned, sha256-verified copy of the upstream installer (no floatingget.k3s.iofetch); microk8s installs via snap on Linux or Canonical's Multipass-backed Homebrew tap on macOS. [git]gains three capabilities (seedocs/git-config.md):[git.extra]— a free-form escape hatch for git config keys Jarvy doesn't model as typed fields (core.fsmonitor,feature.manyFiles, …), applied last so they override modeled fields. Each entry runs a layered guard: dotted-grammar/flag-injection key validation, a leading--value refusal (argv option-injection), an outright refusal of keys whose value git executes (core.pager/editor/sshCommand/askPass/hooksPath,pager.<cmd>,filter.*.clean,*.textconv,merge.*.driver,remote.*.uploadpack,init.templateDir, … — RCE the!-filter alone missed;core.fsmonitor=true|falsestays allowed) overridable withJARVY_ALLOW_GIT_EXEC_KEYS=1, a security-guardrail-downgrade check, and a!-shell-value refusal (leading whitespace included). The typededitor/credential_helperfields (which legitimately set such keys) are value-guarded instead: a bare command/helper + flags is allowed, a shell metacharacter /!/ program-path value is refused. The dry-run preview runs the same gauntlet, so it shows refusals rather than claiming a blocked key "would be set."os_defaults(default on;os_defaults = falseopts out) — writes host-aware defaults for unset keys:core.autocrlf(Windowstrue/ elseinput), Windowscore.longpaths, macOScore.precomposeunicode, plus a cross-platform recommended set (fetch.prune,rerere.enabled,merge.conflictStyle = zdiff3). Explicit fields and[git.extra]always win; keys already matching are skipped so re-runs don't re-write.- Security guardrail —
[git.extra]values that weaken a git defense are refused (core.protectNTFS/protectHFS = false,safe.directory = *(CVE-2022-24765),safe.bareRepository = all,fsck.*/fetch.fsck.*/receive.fsck.* = ignore,*.fsckObjects = false,http[.<url>].sslVerify = false) unlessJARVY_ALLOW_GIT_PROTECT_DOWNGRADE=1. - Trust boundary — a remote-origin config (
--from <url>) cannot apply[git]unless it setsallow_remote = true, and even then writes are forced to--localscope (never~/.gitconfig).jarvy setup --dry-runpreviews every OS-default and[git.extra]write. Fullgit_config.*telemetry — lifecycle, adoption (extra_applied,os_defaults_applied), and security refusals/overrides — all gated behind the telemetry opt-out. - Default post-install hooks for
rust(clippy/rustfmt + cargo env),tmux(TPM plugin manager),kubectx(kctx/kns aliases), andnvim(starterinit.lua). Unix-only where the tool also ships on Windows.
Added:
- Library manifests can now reference item bodies by URL instead of
inlining them (PRD-054 companion-fetch phase):
ai_hookitems honorbash_url/powershell_url(each requiring a matching*_sha256pin), andskillitems install theircompanion_files(templates, helper scripts) alongsideSKILL.md. Every companion fetch is HTTPS-bounded, sha256-verified against the manifest pin (no unverified fetch path), cached content-addressed under~/.jarvy/library.d/companions/, and skill companion filenames are path-safety validated so a hostile manifest cannot escape the skill directory. New telemetry:library.companion.{fetched,sha_mismatch,fetch_failed,refused_filename}. - npm wrapper (
packages/npm/, PRD-021):jarvy-mcpnpm package that downloads the platform-native jarvy release binary on install (SHA256-verified against the releaseSHA256SUMS.txt, mirroringinstall.sh) and exposesjarvy-mcp(runsjarvy mcp) andjarvybins.npx -y jarvy-mcpis now a zero-install MCP client command. Not yet published to npm — maintainer action. - Docker image (
dist/docker/Dockerfile, PRD-021): multi-stage Alpine image that builds jarvy from source, runs as a non-root user, pinsJARVY_MCP_WORKSPACE=/workspace, and defaults to the MCP server entrypoint..github/workflows/docker-publish.ymlbuilds linux/amd64 + linux/arm64 toghcr.io/cliftonz/jarvy, gated on release tags / manual dispatch only. - MCP registry submission prep (
dist/mcp-registry/, PRD-021):server.jsonfor the official MCP registry (io.github.cliftonz/jarvy, validated against the2025-12-11schema) plus maintainer submission runbook. Ownership proofs are pre-wired:mcpNamein the npm package.json and theio.modelcontextprotocol.server.nameOCI label in the Dockerfile. Not submitted — requires the maintainer to publish npm + ghcr first. - CI e2e coverage for Fedora, Arch, and Alpine via distro containers on the free ubuntu runner (PRD-014).
- Published GitHub Action (top-level
action.yml, PRD-017 phase 2): installs jarvy (checksum-verified) and runs validate/setup/doctor, with caching, outputs, and a problem matcher. Self-tested on ubuntu/macos/windows. Referenceable asCliftonz/jarvy@v1once tagged. - VS Code extension (
editors/vscode/, PRD-017 phase 3): livejarvy.tomldiagnostics, setup/doctor/validate commands, status bar, and an unknown-tool quick fix. Compiles clean under stricttsc. - JetBrains/IntelliJ plugin (
editors/jetbrains/, PRD-017 phase 6): Tools ▸ Jarvy actions + a TOML ExternalAnnotator driven byjarvy validate. Builds via./gradlew buildPlugin(IntelliJ Platform Gradle Plugin 2.x, JDK 21). - Consumable pre-commit hook (
.pre-commit-hooks.yaml) and devcontainer feature (dist/devcontainer/jarvy/), PRD-017 phases 4–5.
Changed:
rustmigrated from a bespoke installer to thedefine_tool!macro (so it can carry a default hook); removed a duplicaterustentry from the tool index.- Removed ~1.3k LOC of unwired diagnostic surface: the
observability::{network_trace, bundle}modules (the latter duplicated the wiredjarvy ticketbundler) and the per-tool/per-network arms of the profiler.--profile-outputdurations now serialize as integer*_ms, matching theduration_mstelemetry contract.
Fixes:
jarvy update --method binarynow follows HTTP redirects when downloading release assets, fixingChecksum verification failedon every binary self-update (GitHub serves release-asset downloads via redirect). This was the sev-1 that v0.6.0-rc.2 was cut for; see the known-issues note above for upgrading from v0.5.x.cargo jarvy new-tool <name>scaffolding no longer generates uncompilable module wiring.- The ghcr.io Docker image build no longer fails with
linker `musl-gcc` not found: the x86_64-musl linker pin in.cargo/config.toml(needed by the Ubuntu cross-build) is overridden back toccinside the Alpine builder stage. - The JetBrains plugin ID was renamed
com.jarvy.intellij→com.jarvybefore first Marketplace publication — the Plugin Verifier hard-fails IDs containing template words like "intellij", which broke the weeklyverifyPluginCI job. install.shno longer exits1after a successful install. TheEXIT-trap cleanup referenced alocal tmp_dirthat was out of scope when the trap fired at script exit; underset -uthat threwtmp_dir: unbound variableand failed every install right after it reported success. The trap now bakes the resolved path in at registration time. (Caught by the new GitHub Action self-test.)install.shnow retries transient GitHub failures (3× with backoff) and honorsGITHUB_TOKEN/GH_TOKENfor the authenticated 5000/hr API rate limit, so a single rate-limited request no longer hard-failsget_latest_versionor silently skips the checksum fetch whenSHA256SUMS.txtis actually present.- The
curl … | bashinstaller (install.sh) now verifies the downloaded archive against the releaseSHA256SUMS.txtbefore extracting it. The checksum routine existed but was never called, so installs ran with no integrity check at all; the PowerShell installer (install.ps1) had the same dead-code gap and is fixed too. A mismatch aborts the install; setJARVY_SKIP_CHECKSUM=1to opt out. - Fix
curl … | bashfailing with a download 404 on 64-bit glibc Linux — the most common platform.install.shrequested anx86_64-unknown-linux-gnuasset, but Jarvy ships only the staticx86_64-unknown-linux-muslbuild for x86_64, so nothing matched. The installer now requests the triple that actually ships (x86_64 → musl, aarch64 → gnu, armv7 → gnueabihf).
Internal:
- Log reading and stats (
jarvy logs view/logs stats) no longer allocate per line on the no-sanitization fast path. - New searchable tool directory in the docs site with per-OS install commands for every registered tool.
- Add end-to-end coverage for the download → install → upgrade path, which
previously had none: unit tests for
install.sh(checksum accept/reject, channel filter, platform-triple mapping), ashellcheck/bash -n/ PowerShell-parse lint gate, an installer e2e workflow that runsinstall.shandinstall.ps1against a real published release, and a package-install matrix (.deb→ apt,.rpm→ dnf, crate → cargo,.msi→ Windows) that also assertsjarvy updateauto-detects the install method.
v0.5.2 — Fix jarvy update --method binary (2026-07-06)¶
Patch release. jarvy update --method binary could not find a release
asset on any platform, so binary self-update failed for everyone; this
fixes it.
Fixes:
- Fix
jarvy update --method binaryfailing with "No binary for this platform" (Windows) or a download error (macOS/Linux). The updater matched assets by an ad-hoc<os>-<arch>string (linux-x86_64,darwin-aarch64), but release assets are named by Rust target triple (x86_64-unknown-linux-musl,aarch64-apple-darwin,x86_64-pc-windows-msvc) since the binary-tarball change in v0.5.0, so nothing matched. The updater now requests the exact triple each platform ships. Other update methods (Homebrew, Cargo, apt/dnf/…) were unaffected.
Internal:
- Add a
package-verifyfollow-through and make the telemetry disclosure tests hermetic against an ambientJARVY_TEST_MODE, so the Coverage and cross-platform E2E jobs are green again. crates.io publishing moved to Trusted Publishing (OIDC) with the API token kept as a fallback.
v0.5.1 — Fix cargo install jarvy + auto-publish to package managers (2026-07-05)¶
Patch release. The v0.5.0 crate published to crates.io was incomplete
and would not compile, so cargo install jarvy and the cargo path of
jarvy update both failed. This fixes that and hardens the release
pipeline so package managers actually receive each release.
Fixes:
- Fix
cargo install jarvyfailing to compile withcouldn't read .../assets/wizard-skill/SKILL.md. The wizard embeds that file viainclude_str!, but it fell outside theCargo.tomlincludeallowlist, socargo publishstripped it from the tarball. Local and CI builds passed because the file exists in the working tree — only the published crate was broken. If you hit this on v0.5.0, install v0.5.1 (or a prebuilt binary / Homebrew / the install script). - Add a
package-verifyCI job that compiles the packaged crate viacargo publish --dry-run, so a missing embedded asset now fails at pull-request time instead of after publishing an immutable release.
Release automation:
- Auto-publish to package managers on every stable release. crates.io,
Homebrew, and Chocolatey were being skipped because the
release: publishedevent does not trigger downstream workflows when the release is cut by CI's built-in token; the release workflow now dispatches the publish workflow explicitly (stable tags only). - Fix the release job's downstream dispatches, which had been failing with a permissions error and silently skipping the post-release validation and publish workflows.
- Raise the release test-gate poll budget from 15 to 40 minutes so a slow Windows test leg no longer times the release out before it goes green.
[v0.5.0] — Agent-driven wizard + polyglot detection + two review-plan sweeps (2026-07-04)¶
The wizard graduates from "spawn an agent and hope" to a hardened trust
boundary — per-invocation session UUID, Drop-guarded marker file at
~/.jarvy/state/wizard-session-<uuid>.active, single-turn playbook
that actually completes end-to-end. Alongside: a big polyglot
detection sweep (Node + PHP + Rust + Go all first-class, 13 niche
languages, 3 build systems, ~20 new rules, 10 new tools) and the
back-to-back execution of two full parallel-code-review enhancement
plans (32 + 38 items) that hardened almost every corner of the
diff.
Distribution — prebuilt binaries on macOS + Windows:
- Release assets now ship prebuilt binary tarballs (macOS) and zips (Windows) alongside the existing Linux builds (closes #30). Install without a Rust toolchain via the install script or by downloading the target archive directly.
Wizard runtime — from "hangs on MCP prompt" to production-ready:
- Single-turn agent execution. Prompt rewritten for
claude -p/codex exec --— no interactive follow-up gates, no "await user approval" loops that never resolve. The agent runs the playbook top-to-bottom and prints a summary. - MCP mutation-gate bypass (
JARVY_WIZARD_SESSION_ID=<uuid>+ marker file). Previous behaviour: agents piping stdin would fail closed onprompt_mutation_confirmation's TTY read. New behaviour: descendantjarvy mcpserver processes verify both the env AND a paired Drop-guarded marker file AND freshness (≤10 min) viawizard::session::is_active(). Orphaned env carriers (language servers,nohup-detached daemons that outlive the wizard) fail the check and fall through to the normal confirmation gate. - Trust boundary hardening.
~/.jarvy/state/created 0700 (was 0755 — leaked session UUIDs to local users). Marker chmod'd 0600 with chmod-failed / chmod-ignored telemetry. Bypass now refuses unexpected MCP clients (falls through to confirmation gate for anything outside{claude-code, codex}) instead of just warn-logging. Future-mtime capped at 5s clock skew. --allowedTools "mcp__jarvy"pre-approval onclaude -pso the wizard doesn't hang on the first MCP call. Full argv pinned by test.- Silent-success debuggability.
wizard.headless_exitnow carriesjarvy_toml_before / _after / terminal_state(playbook_completed | noop_already_configured | early_exit | unknown) so on-call can distinguish "agent honored step-2 no-op" from "agent crashed" from "agent misread plan" without reading source.
Polyglot detection — from "Rust + Node + Docker + Make" to full first-class support for four ecosystems + 13 niche languages:
- Full 4-lang polyglot. New rules for PHP (
composer.json/composer.lock/artisan/symfony.lock), Go companions wired (golangci-lint,air,delve— were registered but had empty suggests), Rust companions wired (bacon,cargo-nextest— registered, previously silently dropped by the unknown-tool filter), lockfile-precise Node PMs (pnpm-lock.yaml → pnpm,yarn.lock → yarn,bun.lockb → bunbecome required, not blanket suggestions). - 13 niche languages —
deno,elixir(+ erlang companion),erlang,haskell,crystal,gleam(+ erlang),lua(+ luarocks),luarocks,nim,ocaml,scala(+ Mill viabuild.sc),zig,julia(careful to only matchManifest.toml/JuliaProject.toml, not the ambiguous bareProject.toml). - Build systems + infrastructure —
cmake(CMakeLists.txt),skaffold(skaffold.yaml),bazelisk(Bazel launcher, catchesMODULE.bazel/WORKSPACE*/BUILD.bazel/.bazelrc),release-plz(release-plz.toml+cargo install --lockedinstall path),git(.git/),gh(.github/),vscode(.vscode/),infisical(.infisical.json/.env.infisical). - 10 new tools total:
bacon,cargo-nextest,release-plz(cargo-install via newinstall_via_cargo_installhelper),composer,pnpm,yarn,cmake,skaffold,bazelisk,infisical. Publisher-pin comments on the cargo-install trio document canonical crates.io owners for supply-chain traceability.
Discover trust + perf hardening:
atomic_writesensitive-key policy. Refuses to persist anyjarvy.tomlcontaining top-level[secrets] / [credentials] / [tokens] / [api_keys] / [auth]— case-insensitive.jarvy.tomlis chmod'd 0644 (world-readable) on the invariant that discover writes only sanitized tool names + versions; the refusal is belt-and-braces against a future contributor adding adiscover-can-also-cache-Xcodepath. Emitsdiscover.sensitive_key_refusedat ERROR level.atomic_writeperms telemetry.jarvy.tomlnow explicitly chmod'd 0644 (was inheritingNamedTempFile's 0600 default — fresh clones + CI couldn't read the file).discover.jarvy_toml_ perms_unsafefires on chmod failure OR chmod-ignored (NFS / drvfs / exFAT silently drop).- Duplicate
[provisioner]key fix. A tool that was BOTH required (own marker) AND recommended (companion of another detection) — concrete caserelease-plz.toml+.github/— would land twice in[provisioner], producing a duplicate-key TOML parse error on the nextjarvy discover/jarvy validate. Dedup lives inanalyze_withpost-loop;recommended_dropped_dupcount surfaces ondiscover.applied. - Dash / underscore name normalization. Detection rules
conventionally use dash (
release-plz) — matches TOML keys under[provisioner]— while handlers register underscore (RELEASE_PLZ → release_plz, since Rust identifiers can't hold dashes).known_tool_set()now populates both forms soknown_tools.contains(...)inanalyze_withresolves either spelling. Startup-panic on collision. RootIndexbatches rootread_dironce per pass — wasNsyscalls +O(dir_size)allocations across 11 glob rules; now one syscall +HashMap<OsString, PathBuf>for exact lookups- pre-sorted
Vec<PathBuf>for globs. On a 200-file root with 20+ rules, ~35× syscall reduction. LazyLock<Regex>forrust-toolchain.tomlandgo.modversion patterns. WasRegex::newper version extraction — a 30-100 µs compile paid per discover pass. Custom-rule patterns cache in aRwLock<HashMap>. Fast path returnsCow<'static, Regex>(borrowed) so even the LazyLock arms skip atomic RMWs.DetectionRule/DetectionCow refactor.nameandsuggestsland asCow::Borrowed(&'static str)for built-in rules constructed via.into()from string literals — zero allocations per matched rule. Custom rules deserialize asCow::Ownedvia serde with no source changes. Regression guard test asserts every default-rule name + suggest is Borrowed.known_tool_setmemoization. OnceLock cache — was rebuilding ~300 Strings per discover pass (register_all + name clones + dash / underscore aliasing). Startup-panic on alias collision so two tools normalising to the same form fail loudly at boot, not silently mis-dispatch.
Wizard e2e infrastructure:
wizard_e2e_repo_matrix.rs— data-driven 12-shape matrix covering polyglot Node+PHP+Rust+Go, Laravel PHP, Bazel monorepo, K8s + skaffold, Elixir/Gleam BEAM, niche-lang clusters, Python + Infisical + VSCode, empty-repo negative shape. Each row asserts: preview JSON → apply JSON →jarvy.tomlround-trips → 0644 perms →jarvy validateaccepts → byte-identical second apply. Adding a shape = one row.polyglot_node_php_rust_goshape pinned atmin_warnings: 1(node-without-nvm advisory) so a refactor stopping the emission trips instead of silently regressing the operator signal.- New niche-language + dir-marker table tests in
discover::mod::tests— 13 language markers, 3 dir markers, plus adiscover_does_not_walk_into_dot_gitregression guard that plants hostileCargo.toml/mix.exs/package.jsoninside.git/and asserts they DON'T trigger detection.
Observability contract:
- 9 new events (all telemetry-gated):
mcp.mutation.wizard_bypass,mcp.mutation.wizard_bypass_unexpected_client,wizard.session_token_activate_failed,wizard.session_token_perms_unsafe,wizard.session.bypass_refused,discover.recommended_dropped,discover.rules_loaded,discover.jarvy_toml_perms_unsafe,discover.sensitive_key_refused. - 6+ new fields on existing events:
wizard.headless_spawnedgetsmcp_preapproval,wizard_session_env,wizard_session_id;wizard.headless_exitgetsjarvy_toml_before / _after / terminal_state / wizard_session_id;discover.appliedgetsrecommended_dropped_dup / detections_by_rule. terminal_statepinned viapub const— four constants (TERMINAL_STATE_PLAYBOOK_COMPLETED / _NOOP_ALREADY_CONFIGURED / _EARLY_EXIT / _UNKNOWN) so alerting has a stable contract.mcp.auto_approve.*events now honourtelemetry_gate:: is_enabled()— closes a documented opt-in contract hole.- CLAUDE.md event taxonomy fully updated with every new event + field addition. Stable contract for on-call queries.
MCP audit trail correctness:
AuditAction::McpMutationRequested— pre-flight audit is now a distinct action fromMcpMutation(which is emitted only on completed mutations). Pre-fix,gate_mutationwrotemcp_mutation success=trueBEFORE rate-limit / confirmation / execution, meaning failed mutations appeared successful in audit-log queries. Success entries now land at the three actual completion sites (Yes / Always / wizard-bypass).- Wizard-bypass double-audit fixed. Pre-fix, the bypass arm
wrote
log_mcp_mutationtwice per grant (once pre-flight, once in the arm), producing indistinguishable duplicate rows. The arm's log removed; pre-flight covers the request; the tracing event on grant carries forensic detail. effect_summaryANSI-sanitized before flowing to audit + tracing. Workspace-root paths are attacker-controllable via hostile repo directory names ongit clone; a path containing ESC or CR/LF could inject fake log entries or clear the terminal when an operator pages the audit log.
Perf sweep (execution of both review plans):
BufWriteraroundChildStdininsend_prompt_to_child_stdin— 20 KiB prompts now go out in one vectored write instead of PIPE_BUF chunks.RootIndex::find_first_matchreturnsOption<&Path>— index outlives the loop by construction. Pre-fix clonedPathBufper hit.read_boundedusesRead::take + read_to_end— skips theresize(cap, 0)memset. ~16 KiB per polyglot pass.sanitize_sourcebyte-level — skips UTF-8 decode for an ASCII-only check.known_tool_setcollision check uses a byte-fold single-walk.- Toml parsed exactly ONCE per
discoverpass — was two fulltoml::Tableparses (parse_provisioner_pins+parse_discover_blockeach parsed the same source). detections_by_rulefold-into-String — no throwawayVec<&str>betweencollectandjoin.- Session-check span (
debug_span!("wizard.session.check")) — makes slow home directories (network mounts, macOS synced drives) visible to on-call.
Test hardening:
- Two full parallel-code-review sweeps (5 personas each: Rust perf, security, QA, observability, maintainability/DRY) — 32 items in the first, 38 in the second. Every P0 and P1 closed across both.
- Wizard-bypass code path — was untested (a security-relevant
trust hole). Now 5 unit tests + one integration test cover
env-set → bypass fires, non-exact env values ignored,
rate-limit still enforced, single-audit-row invariant,
session::is_active helper. Plus a
filetime-driven test that actually forces old mtime so the 10-minute staleness window is covered (pre-fix the test admitted it couldn't and asserted the opposite). RootIndexdirect coverage — 6 new tests: exact lookup, dir filter, glob determinism, empty dir, only-dirs, missing project dir.- Cow perf regression guard —
default_rules_name_and_suggests_are_all_borrowedrefuses to let a futureString::from(...).into()construction silently reintroduce the ~15-20 heap allocations per polyglot pass. cargo_install_argv_pins_locked_flag— supply-chain contract for bacon / cargo-nextest / release-plz.classify_headless_outcome— table-driven exhaustive coverage of the 4-way terminal_state match.
Breaking-shape additions (all opt-in, no existing config breaks):
- New
~/.jarvy/state/directory created 0700 on first wizard invocation. Nothing at 0.4.0 wrote here; nothing else reads it. Users downgrading to 0.4.0 after using 0.5.0's wizard will leave harmless orphan.activemarker files behind. - New env vars:
JARVY_WIZARD_SESSION(bool),JARVY_WIZARD_SESSION_ID(UUID) set on wizard-spawned agents. Both benign if inherited by unrelated processes. - New optional dev dep:
filetime = "0.2".
Files (~40 touched):
- New:
src/wizard/session.rs,src/tools/{bacon,bazelisk, cargo_nextest,cmake,composer,infisical,pnpm,release_plz,skaffold, yarn}/{mod,definition}.rs,tests/wizard_e2e_repo_matrix.rs. - Enhanced significantly:
src/mcp/extended_tools.rs,src/wizard/{headless,prompt}.rs,src/commands/wizard_cmd.rs,src/discover/{commands,mod,rules,scanner,version}.rs,src/tools/{common,mod}.rs,src/paths.rs,src/mcp/audit.rs,tests/{tools_matrix,wizard_e2e_repo_matrix}.rs,CLAUDE.md.
[v0.4.0] — Library registry / monorepo / discover / git hooks / 25-item review sweep (2026-06-29)¶
The biggest single milestone since v0.2.0 — eleven PRDs closed across a multi-day push, plus the full close-out of the parallel- code-review enhancement plan that ran against the new surface.
Highlights:
- PRD-044 auto-discovery (full) —
jarvy discoverscans the project tree, suggests tools, optionally--applys intojarvy.toml.[discover]config block supports custom rule files;--rules <path>CLI flag overrides per-run.--watchmode re-runs on filesystem events.FileContainingdetection pattern. Version-range narrowing suppresses re-suggestions when the pinned range already covers the detected version.uninstallablebucket surfaces ecosystems jarvy can't install (maven/gradle/dotnet) instead of silently dropping them. Continuous discovery injarvy setupemits an advisory when new markers appear. - PRD-047 monorepo (full) —
[workspace] members = [...]with glob expansion (apps/*) +exclude = [...].jarvy setup --project <name>runs setup against one member;name = "current"auto-detects. Implicit auto-context when cwd sits inside a declared member. Newjarvy contextdiagnostic command.jarvy driftandjarvy doctorhonor the same auto-context. - PRD-051 universal
--format json— every command that prints to stdout accepts--format json. Per-subcommand placement on drift/logs/ticket/services/workspace; exit codes match human path. - PRD-054 library registry (phase 5 + phase 6) — cosign signature
enforcement (real, not scaffolded).
jarvy library list/show/clean/syncCLI + matching MCP tools. - PRD-048 git hook frameworks (full) — pre-commit, husky,
lefthook, native all ship as first-class handlers behind the
unified
HookFrameworkenum.docs/replace-husky.mdwalks through three migration paths. - PRD-055 git skill sources —
git+https://...@<ref>[#<subpath>] github:owner/repo@<ref>URL schemes for[skills] library_sources. Argv-injection refused, symlink walks refused,file://scoped to the cache root.jarvy diagnose --apply+jarvy migrate --apply— previously "(Fix application not yet implemented)" / "--apply is not yet implemented" placeholders now actually do the work.- 25-item parallel-review hardening — strict version allowlists,
filename sanitizer, workspace path-traversal refusal, atomic-write
for discover --apply,
MergeOutcomeenum, telemetry-gate consistency, perf wins (Arcsnapshot, cmd_satisfies cache), QA + observability rounds. - Half-baked surface close-out — every dead-coded
#[allow(dead_code)] // Reserved for ...either shipped or got an explicit deferral note in CHANGELOG.
Breaking-shape additions (all opt-in, no existing config breaks):
[discover]block injarvy.toml— optional, drives custom detection rules.[git_hooks.native]block — only consumed whenframework = "native".[[ai_hooks/mcp_register/skills.library_sources]]entries now acceptmanifest_sha256 = "<hex>".- Cargo feature
test-bypass— gates theJARVY_{LIBRARY,REGISTRY}_ALLOW_INSECURE_FETCH+JARVY_TEST_HOMEtest escape hatches out of release builds. Release builds are inert against those env vars.
New events (CLAUDE.md taxonomy stable contract):
- discover.applied, discover.setup_advisory
- workspace.validate_completed, workspace.member_invalid
- library.sync.failed, library.signature.verified
- git_hooks.{install,update}_{started,completed}
Test counts: 974 lib + 1442 bin + 54 integration suites green.
Behavior changes:
- jarvy discover --watch now exits CONFIG_ERROR (2) instead of
0 when the filesystem watcher backend dies (inotify exhausted,
permission revoked, channel closed). CI wrappers and cargo-watch-
style shells chaining on $? will now correctly see the failure
instead of treating a silent watcher death as success.
- jarvy setup --project <name> now stages synthesized member
configs under ~/.jarvy/cache/synthesized/<sha>.toml (content-
addressed, overwritten per run) instead of leaking persistent
jarvy-setup-project-*.toml files into /tmp. CI bots that
previously accumulated these in their tmpfs can clear them.
- [discover] rules = "<path>" now refuses absolute paths and ..
traversal components. Parse-error advisories also redact source
bytes from the target file so a hostile jarvy.toml cannot
exfiltrate /etc/shadow content via the error message.
Migration notes:
- jarvy migrate --apply will auto-rewrite legacy [tools] blocks
to [provisioner] for users still on the pre-v0.2 schema.
- setup --project current is the natural one-liner for monorepo
contributors who cd into their working subdir.
- Workspace members without a per-member jarvy.toml will see their
synthesized config moved from /tmp/jarvy-setup-project-*.toml to
~/.jarvy/cache/synthesized/. The contents are equivalent; only
the storage path changed.
PRD-056 agent-driven wizard — original 2026-06-30 note (folded into v0.5.0 above)¶
jarvy wizard — hands the current project to your local AI coding
agent (Claude Code, Codex, Cursor, Windsurf, Cline, Continue) to
analyze + configure. Uses your subscription (no vendor LLM gateway,
no API keys handled by Jarvy).
Two modes, auto-picked:
- Headless CLI for Claude Code (claude -p) and Codex
(codex exec --) — Jarvy spawns the agent with a system prompt +
project-context envelope on stdin; the agent calls Jarvy's MCP
tools inline.
- Skill drop for the other four agents — writes
~/.{agent}/skills/jarvy-setup/SKILL.md; user opens their agent
and types "set up jarvy for this project".
Greenfield supported. With no jarvy.toml, the wizard prompt
explicitly instructs the agent to call jarvy_discover_apply with
apply=true to bootstrap a starter file.
Quickstart fallback. If no AI agent is installed, the wizard
delegates to the existing jarvy quickstart flow — users without an
agent aren't blocked.
Trust boundaries. Refuses in sandbox / CI / non-TTY (headless) /
against remote configs by default; JARVY_WIZARD=1 overrides.
New MCP tool: jarvy_wizard_plan — read-only proposal endpoint
the agent calls before mutating. No new mutating MCP tools; existing
gates (MutationCtx rate limit + TTY confirm + audit log) cover the
change surface.
New events: wizard.started, wizard.skill_dropped,
wizard.headless_spawned, wizard.headless_exit, wizard.refused.
All gated on telemetry_gate::is_enabled().
Files:
- New: src/wizard/{mod,context,prompt,headless,skill_drop}.rs,
src/commands/wizard_cmd.rs, assets/wizard-skill/SKILL.md,
prd/056-agent-driven-wizard.md, docs/wizard.md.
- Modified: src/cli/args.rs (adds Wizard subcommand),
src/commands/dispatch.rs (routing), src/mcp/extended_tools.rs
(jarvy_wizard_plan definition + handler),
src/mcp/server.rs (dispatch arm), mkdocs.yml, CLAUDE.md.
v0.4.0 detail expansion — original 2026-06-28 note (already summarized in v0.4.0)¶
A documentation + maintainability + ecosystem-breadth pass that closes
eleven long-open PRDs across five commits. The headliner is PRD-054
library registry + PRD-055 git skill sources — a shared
HTTPS-fetched manifest format that lets a team publish reusable AI
hooks, MCP servers, and AI agent skills at any URL, with skills
additionally supported via plain Git repos (no manifest.json
required). [ai_hooks], [mcp_register], and [skills] all consume
the same format. PRD-049 (skills) rides on it; PRD-048 / 052 (git
hooks, spinners) shipped earlier in the day. No user-visible behavior
changes for existing configs — all new surface is additive.
Added — PRD-044 / 047 phase 2 (everything except interactive mode)¶
Closes the remaining deferred work on the two PRDs plus several quality-of-life follow-ups that fell out of the dependency graph.
PRD-044 — auto-discovery phase 2:
- [discover] config block in jarvy.toml carrying rules = "<path>"
for custom rule files + ignore_dirs. Custom rules append to (never
replace) the built-in set so a user tree can't silence detection of
a real ecosystem.
- --rules <path> CLI flag overrides the config-file path for one-off
runs.
- FileContaining { file, containing } detection pattern — bounded
4 KiB scan, used today by the kubectl rule to catch
kind: Deployment / apiVersion: apps/v1 in bare *.yaml files at
the repo root.
- Per-language version-range narrowing: when the user has already
pinned a semver range that COVERS the detected version (e.g.
node = "^20" + .nvmrc says 20), discover treats it as
already-configured instead of re-suggesting an exact-version pin.
Falls back to the prior "list as required" behavior when we can't
parse either side.
- uninstallable bucket in DiscoverReport for tools jarvy detected
but doesn't have a first-party installer for (maven, gradle, dotnet
…). New CLI section + JSON field — users see what jarvy noticed and
can't help with, instead of silent drops.
- --watch mode — notify filesystem watcher re-runs discover on
every relevant event with a 750ms debounce so editor saves /
bulk rewrites only re-emit once.
- Continuous discovery on jarvy setup — emits a stderr advisory
+ discover.setup_advisory event when project files imply tools
not pinned in [provisioner]. Read-only; never mutates jarvy.toml.
PRD-047 — monorepo phase 2:
- [workspace] members = ["apps/*"] glob expansion. *-only
patterns supported; subdirectory members under the parent are
enumerated and skipped if they start with ..
- [workspace] exclude = [...] glob patterns applied AFTER
expansion. Pairs with globs to handle "everything except
apps/legacy."
- jarvy setup --project <name> runs setup against ONE workspace
member. name = "current" auto-detects from cwd. Members without a
per-member jarvy.toml get a synthesized merged config written to a
tempfile so the existing single-project setup loop doesn't need to
know about workspaces.
- Auto-context detection: when jarvy setup is invoked WITHOUT
--project AND cwd sits inside a declared member, setup scopes to
that member implicitly + prints an advisory line.
- jarvy context standalone command — read-only diagnostic showing
cwd, detected workspace, members (current marked with →),
auto-detected project, and resolved setup file. Supports
--format json.
- jarvy drift and jarvy doctor honor the same auto-context. cwd
inside a member → drift reads that member's merged config and
.jarvy/state.json lives next to the member; doctor checks the
merged inherited toolset. No new flags — the behavior emerges from
the shared setup_cmd::auto_detect_project helper.
Skipped on purpose: --interactive confirm-each-suggestion mode
for discover — user signal said it wasn't worth the friction; the
existing dry-run-then---apply two-step covers the same need.
Verification: cargo fmt + clippy --all-features --all-targets -D warnings clean. 974 lib + 1442 bin tests green (+11 lib, +16 bin vs the prior baseline). New tests cover version-narrowing, uninstallable bucket, glob expansion, exclude patterns, glob_matches table, workspace-project resolution (4 cases including current / synthesize / unknown / no-workspace), and context-cmd in a non-workspace.
Added — close-out of every half-baked surface flagged in the audit¶
A single bundled commit retires the partial-feature list surfaced by the codebase audit. Net: ~3,100 LOC added, ~210 removed, 7 commits worth of work folded into one.
PRD-054 phase 6 — jarvy library cache CLI (src/commands/library_cmd.rs).
New top-level command with four subcommands:
- library list — every cached library (URL, publisher, item counts)
- library show <url> — items inside one cached library
- library clean [--dry-run] — wipe ~/.jarvy/library.d/ and the
process cache (atomic via the existing clear_disk_cache helper)
- library sync --file <jarvy.toml> — force-refresh every declared
[<subsystem>.library_sources] entry, honoring the
remote-config trust gate
All four support --format json. New library_registry::list_cached,
get_cached, and clear_disk_cache helpers (the dead-coded
internals from earlier phases are now wired). 4 unit tests.
Husky framework handler (src/git_hooks/husky.rs). PRD-048's
declared-but-unwired Husky variant now ships a full handler:
npm install --save-dev husky + npx husky install for install,
re-run for update, walks .husky/<name> for run and list. 5
unit tests covering the missing-package.json / missing-.husky/ /
unknown-hook-id error paths.
Lefthook framework handler (src/git_hooks/lefthook.rs). Single
Go-binary alternative to pre-commit / husky. Permissive YAML parse
(top-level BTreeMap<String, Value>) so non-stage keys like
skip_output: don't break the lister. lefthook self-update is
treated as advisory — non-zero exit doesn't fail the update path
(brew users update via brew upgrade). 5 unit tests.
Native git hooks handler (src/git_hooks/native.rs). No framework
process between git and your script: hook bodies live in
[git_hooks.native.hooks.<stage>] and get written straight into
.git/hooks/<stage> with a # managed by jarvy marker. Refuses to
overwrite hand-rolled hooks (any .git/hooks/<name> without the
marker is preserved). Atomic tmp+rename + chmod +x so a mid-write
crash leaves the prior hook intact. User-supplied shebangs are
honored; a missing #! line gets #!/bin/sh injected. 7 unit tests.
Husky migration docs (docs/replace-husky.md). Three paths laid
out — wrap (zero migration), switch to pre-commit, switch to
lefthook — with a decision tree, sample configs for each, and the
caveats per path. Linked from the git-hooks doc.
jarvy diagnose --apply fixes the "not yet implemented" placeholder.
Auto-applicable fixes now actually shell out via sh -c and surface
the exit status. Manual-fix items still print the suggestion — they
need human review. Trust posture: Fix::command comes from the
tool's own ToolSpec (first-party data, not remote-fetched), same
trust we already extend to the printed-for-copy variant.
jarvy migrate --apply rewrites [tools] → [provisioner].
Atomic tmp+rename + post-write TOML round-trip check (refuses to
write garbage). Unknown-tool advisories still report-only — human
decision required. 4 unit tests pinning the rewrite, no-op, and
report-only paths.
jarvy_discover_* / jarvy_workspace_* / jarvy_library_* MCP
tools. Seven new first-class MCP tools so AI agents don't have to
shell out to the CLI for these surfaces:
jarvy_discover_scan, jarvy_discover_apply (mutating — runs
through the standard rate-limit + audit + confirmation gate),
jarvy_workspace_list, jarvy_workspace_show, jarvy_workspace_validate,
jarvy_library_list, jarvy_library_show.
PRD-054 phase 5 — cosign signature enforcement
(src/library_registry/signature.rs). require_signature = true
now actually verifies. We fetch <url>.sig + <url>.pem, feed
them to the existing crate::update::signature::verify_sigstore_signature_with_identity,
and refuse on failure with structured errors:
SignatureRejected, SignatureCompanionsMissing, or
CosignMissing. The previous "scaffolded but not enforced" warning
is gone — replaced by a per-fetch library.signature.verified
success event so operators can graph the verified fraction.
Cached hits don't re-verify (the cache only ever holds previously-
verified manifests). require_signature = false short-circuits as
documented — library.signature_disabled warning still fires.
CLAUDE.md event taxonomy table updated for the new
library.signature.verified event.
Added — parallel-review hardening for PRD-044 / 047 / 051 (25 review items)¶
Follow-up sweep against the parallel-code-review enhancement plan that ran against the three preceding PRDs. All 25 items addressed across 7 batched commits. Highlights:
- Security (P0 #1, #2, #3; P2 #17, #18, #20, #21): Strict allowlist
on extracted versions (
[A-Za-z0-9._+~:\-], ≤64 chars, BOM stripped) closes TOML injection via.python-version/rust-toolchain.toml/.ruby-version. Strict allowlist on*.tf-glob filename source attribution closes the same threat via attacker-controllable filenames.jarvy workspacerefuses traversal members (members = ["../../etc"]) — existence-oracle leak and external jarvy.toml reads no longer possible.jarvy discover --applynow uses atomic tmp+rename writes (no torn files on crash). Ticket-show TTY path runs zip entry names through the existing ANSI sanitizer. - UX / data safety (P0 #4; P1 #8, #14): Empty
[workspace] inherit = []is treated as["provisioner"]by BOTH the CLI display and the production setup resolver via a newWorkspaceConfig::effective_inherit()helper — the previous CLI-only widening was a "show advertises what setup won't do" bug.jarvy discover --applyrefuses to overwrite an existing jarvy.toml that fails to parse as TOML (data-loss prevention). The[provisioner]section detector in the merger now uses a TOML parse instead of substring match, so a comment containing the literal[provisioner]no longer confuses the splicer. - Robustness (P1 #5): The string-level TOML edit in
discover/generator.rs::merge_into_existingnow re-parses its output and falls back to a freshrender_freshon any edge case ([provisioner.linux]subtables, indented headers,[[provisioner]]arrays-of-tables) — guarantee: NEVER write invalid TOML. Return type widened toMergeOutcome { Merged, Noop, BailedToFresh, ExistingUnparseable }so the CLI can produce a clear diagnostic. - Observability (P1 #6, #7, #15): New events
workspace.validate_completed(info!/warn!by error count) +workspace.member_invalid(error_kindonly — no member-name leak),discover.applied(tools added, recommended, already configured, target = "merged" | "noop" | "bailed_to_fresh", duration_ms). All gated throughtelemetry_gate::is_enabled(). CLAUDE.md taxonomy table updated. New CLI dispatch test pins the stderr-pure contract when--format json+JARVY_TELEMETRY=0. - Performance (P1 #9, #10, #11; P2 #22, #23, #24, #25): Workspace
list/show parses root jarvy.toml once instead of per-member. Key
membership now uses
HashSet<&str>borrowed from the parse instead of cloning every value into aBTreeMap. drift_cmd's version regex cached inOnceLock. discover::default_rules() cached inOnceLock(no per-call rebuild of 12-rule vec). recommended_seen checks before clone. detections moved into report instead of cloned. Generator splicer usesString::with_capacity+push_strinstead ofVec<String>. - QA (P1 #12, #13; P2 #19): 13 new tests covering — extension-glob
determinism, hostile filename refusal, version sanitizer, BOM
strip, length cap, merge
[provisioner]-in-comment edge case, merge bail-to-fresh on broken splice, merge refuse-when-unparseable, atomic-write completeness, traversal-member refusal,effective_inherit()round-trips, ci-info/ticket/logs/discover/ workspace JSON-shape contracts, and stdout-pure JSON regression guard. Test totals: 945 lib + 1400 bin (up from 907 / 1353).
CHANGELOG note also includes docs/discover.md and docs/workspace.md
in the doc set that surfaces these PRDs to end users.
Added — PRD-044 / 047 / 051 (auto-discovery + monorepo + JSON output)¶
Three PRDs closed in a single session, all additive (no existing config changes).
PRD-051: --format json on every command. Subcommands that
previously only emitted human text now accept --format json and emit
a structured envelope: jarvy ci-info, jarvy drift {status,accept,
fix}, jarvy logs {stats,clean,config}, jarvy ticket {create,show,
list,clean}, jarvy services {start,stop,status,restart}. CLI exit
codes are identical between human and JSON paths so $? based
control flow keeps working. See docs/cli-reference.md and the new
"Structured output" section in CLAUDE.md.
PRD-044: jarvy discover project tool auto-discovery. New
top-level command that scans the project root for marker files
(Cargo.toml, package.json, go.mod, Dockerfile, k8s/, *.tf, Makefile,
Justfile, …), infers versions from rust-toolchain.toml / .nvmrc
/ .python-version / go.mod, and either prints suggestions or
merges them into jarvy.toml (--apply). The merge is append-only:
hand-pinned tools survive unchanged. New module src/discover/ with
built-in default_rules() covering rust, node, python, go, ruby,
docker, kubectl, helm, terraform, pre-commit, make, just. Custom rule
files deferred. See docs/discover.md.
PRD-047: jarvy workspace monorepo inspection. New CLI surface
over the existing workspace foundation (crate::workspace::
find_workspace_root + merge_configs). Three read-only subcommands
— list, show <member>, validate — that resolve per-member
configs via inheritance and surface the result with (inherited) /
(overridden) provenance. Empty [workspace] inherit = [] is
treated as ["provisioner"] for display so the common case works
without explicit config. Workspace-aware jarvy setup --project <name>
orchestration deferred. See docs/workspace.md.
Added — parallel-review enhancement plan (25 P0/P1 items + sweep)¶
A multi-batch sweep against the parallel-code-review enhancement plan
shipped as 12 commits (6155056..HEAD). Highlights:
- Security (items 1, 2, 3, 12, 13, 14, 15): argv-injection refusal on
git refs, symlink-escape refusal on the clone walker,
file://scoping to the library cache root,manifest_sha256pin on library_sources (refuses re-published manifests), loudlibrary.signature_unenforcedwarning whenrequire_signature = true(cosign not yet enforced in v1),GitHooksConfig::default()matches serde defaults instead of silently disabling hooks, and a newtest-bypassCargo feature that compilesJARVY_{LIBRARY,REGISTRY}_ALLOW_INSECURE_FETCH+JARVY_TEST_HOMEout of release builds (env vars are inert in shipped binaries). - Trust gates (items 4, 5):
[skills]and[git_hooks]now propagateConfigOrigin::RemotefromConfig::mark_remoteand enforce theallow_remoteopt-in on both subsystems. - Observability (items 6, 7, 8, 22, 23, 24): every
library.*/library.git.*/skills.*/git_hooks.*/package.*event readstelemetry_gate::is_enabled()so opt-out users don't ship breadcrumbs;library.sync.failedemit on every error path (was silent);library.synctracing span wraps each per-source fetch;git_hooks.{install,update}_{started,completed}envelopes carrystatus,applied,framework,auto_update,run_after_install,duration_ms— same shape asai_hook.phase_*/mcp_register.phase_*. - Maintainability (items 16, 17, 18, 19):
library_registry::sync_allconsolidates the three identicalprepare_library_sourcescopies;packages::common::run_install_loopconsolidates the gem/go/cargo/nuget install + telemetry loops behind a closure-based helper;net::bounded_fetchcollapses the two copies of HTTPS-only refusal + bounded read + loopback-bypass parser (with per-consumer env-var names preserved for test isolation);agents::Agentis the canonical enum shared byai_hooks,mcp_register, andskills(the three former per-subsystem enums are nowpub usealiases). Net 450 LOC removed. - Performance (items 20, 21): library_registry caches
Arc<Manifest>so resolvers snapshot the cache and drop the mutex before walking items;cmd_satisfiescaches<cmd> --versionstdout so per-tool version probes don't refork the package manager.detect_linux_pm/detect_bsd_pmdropped their localhasclosures that bypassed the cachedhas(). - QA (items 9, 10, 11, 25): coverage tests for ai_hooks
library_sourcesresolution,mcp_register::use_libraryoverrides,skillssha-mismatch refusal, andhooks_cmdaction exit-code contract. Plus#[serial(jarvy_telemetry_disclosure)]on the telemetry disclosure tests to prevent parallel-test flakes.
User-visible config additions: [[<subsystem>.library_sources]] accepts
an optional manifest_sha256 = "<hex>" pin. CLI exit codes and
existing event names are unchanged.
Added — git-shorthand for skill sources (PRD-055)¶
git+https://...@<ref>[#<subpath>]URL scheme on[skills] library_sources. Jarvy clones the repo at the pinned ref, walks the optional subpath forSKILL.mdfiles, parses each file's YAML frontmatter, and synthesizes a manifest in-memory. Publishers don't need to maintainmanifest.json— the SKILL.md files are self-describing.
[[skills.library_sources]]
url = "git+https://github.com/myorg/[email protected]#skills/"
github:owner/repo@<ref>shorthand for the common GitHub case:
[[skills.library_sources]]
url = "github:anthropics/[email protected]"
-
@<ref>pin is mandatory. Unpinned URLs refused at parse time with a clear message — silent floating refs would let a publisher rev skills without a visible pin bump. -
Trust hierarchy: commit SHA (tamper-evident) > tag (mutable but conventional) > branch (freely mutable, emits
library.git.mutable_refwarning every fetch). Documented indocs/library-registry.md. -
SKILL.md frontmatter convention:
name:+version:required,description:+supported_agents:optional. Files missing required fields are skipped with alibrary.git_skill.skippedevent citing the reason. No silent failures. -
Subpath traversal refused at parse time + at fetch time:
..segments and absolute paths are rejected with a canonical-path check inside the clone root. Mirrorssafety::resolve_within_workspacefromsrc/mcp/extended_tools.rs. -
No new dependencies. Shells out to
git; missing git refuses with a clear error pointing at[provisioner] git = "latest". Cached--depth 1clone refreshes viagit fetch + git checkout <ref>. -
Why skills only: SKILL.md carries its own frontmatter, so Jarvy has everything needed to build a manifest entry from one file. AI hooks (script bodies) and MCP servers (command/args/env tables) don't — those still ship via
manifest.json. A publisher who wants both in one Git repo putsmanifest.jsonat the root and uses the existing URL form. -
New modules:
src/library_registry/url_parser.rs(scheme +@<ref>+#<subpath>parsing with safety refusals),src/library_registry/git_fetch.rs(clone + frontmatter walker -
manifest synthesizer).
sync()inmod.rsdispatches by scheme.read_file_url()extends the installer's fetch path to handlefile://URLs that point into the git cache. -
Trust gate inherits unchanged from PRD-054 — remote-fetched configs CANNOT declare
library_sourcesof any scheme.
Added — library registry (PRD-054)¶
-
src/library_registry/shared module: manifest schema (tagged bykind:ai_hook/mcp_server/skill), HTTPS-bounded fetch (MAX_MANIFEST_BYTES = 16 MiB,MAX_ITEM_BYTES = 1 MiB), on-disk cache at~/.jarvy/library.d/<sha256-of-url>/manifest.json, in-process resolver across all cached libraries. Atomic write pattern (.new→ rename) for cache durability. -
One manifest, three consumers: a single
manifest.jsonURL can publish AI hooks, MCP servers, and skills simultaneously — each consumer filters bykind. Publishers write one manifest; teams point[ai_hooks] library_sources,[mcp_register] library_sources, and[skills] library_sourcesat the same URL.
[[ai_hooks.library_sources]]
url = "https://cdn.myorg.com/jarvy/manifest.json"
[[ai_hooks.hook]]
use = "no-prod-deploys"
-
Trust model uniform across consumers: remote-fetched configs (
jarvy setup --from <url>) CANNOT declarelibrary_sources— refused withlibrary.remote_refusedevent. Mirrors[packages] allow_remotesemantics. There is no override flag; adding one would defeat the purpose. Teams that want to shiplibrary_sourcesto every developer copy them into each user's local~/.jarvy/config.toml. -
Built-in items win over library items:
crate::ai_hooks::LIBRARY(the canonical Jarvy-shipped hooks) is checked BEFORE library fallbacks, so name collisions favor the audited built-in. -
sha256 verification for skill
SKILL.mdbodies (mandatory) and scaffolded for ai_hookbash_url(v1 only honors inlinebash:for hooks). A publisher mutating a versioned artifact in place surfaces a clearlibrary.sha_mismatchevent and refuses to install. -
Offline tolerance: on network failure, the cached on-disk manifest is served with a
library.fetch.cached_hit reason="fetch_failed"event so log scrapers can see staleness. -
Cosign signature verification scaffolded but not enforced in v1.
require_signature = true(default) is honored once cosign wiring lands;falsetoday emits alibrary.signature_disabledwarning. Phase 5 of PRD-054.
Added — AI agent skills installation (PRD-049 v1)¶
[skills]config block withlibrary_sources,installmap, per-skillagents = [...]narrowing.jarvy skillssubcommand:install(all or--name <skill>),list(per-agent status),status(drift summary),agents(detect installed AI agents).- Setup integration:
jarvy setupauto-installs every configured skill when[skills] auto_install = true(default). - Per-agent path layout:
~/.{agent}/skills/<skill-name>/SKILL.mdacross claude-code, cursor, codex, windsurf, cline, continue. Two narrowing layers (consumeragents = [...]+ publishersupported_agents = [...]) both apply. .jarvy-skill.jsonsidecar records version + sha256 + install time per skill per agent.jarvy skills statususes it for drift detection without needing to re-fetch.- v1 explicitly skips skills.sh API integration (search / info /
popular), companion file fetching,
jarvy skills update/remove, version-range pinning, project-scope skills. Tracked under PRD-049 phase 2.
Added — library_sources consumers for AI hooks + MCP register¶
-
[ai_hooks].library_sources: fetch + register library hook items.use = "hook-name"resolves built-inLIBRARYfirst, then cached library items. Hook bodies are taken inline from manifestbash:/powershell:fields. Per-source-failure-is-advisory:applycontinues with cached + built-in hooks if a library URL is unreachable. -
[[mcp_register.server]] use = "library-name": pullscommand/args/envdefaults from a previously synced library item. Locally-declared fields on the spec override the library defaults (e.g. specenv = { ... }wins over library env). Subject to the existingallow_custom_serversgate plus the newlibrary_sourcesremote-refusal gate.
Tracking¶
- Drafts + closes PRD-054 (Library Registry — v1 shipped, sig verify
jarvy libraryCLI tracked as follow-up)- Drafts + closes PRD-055 (Git skill sources — full v1 shipped;
git+ssh://and sparse-checkout tracked as follow-up) - Closes PRD-049 phase 1 (Skills Registry Integration — library-based install ships; skills.sh API + remove/update commands tracked as PRD-049 phase 2)
- Continues PRD-048 (Pre-Commit Hook Installation) + PRD-052 (Progress Indicators) from the prior commit
- Continues PRD-011 / 013 / 014 / 037 / 038 / 039 closures from the first commit
[Unreleased — earlier: pre-commit hooks + progress spinners]¶
(Originally a separate [Unreleased] entry; merged into the section
above so the awk extractor sees a single curated block.)
A documentation + maintainability + ecosystem-breadth pass that closes
eight long-open PRDs across three commits: gem/go package handlers +
main.rs extraction + documentation gaps in the first, pre-commit hook
framework + spinner output in the second. No user-visible behavior
changes for existing configs — all new surface is additive ([gem] /
[go] / [git_hooks] sections, jarvy hooks subcommand,
opt-out-friendly progress spinners).
Added — pre-commit hook framework integration (PRD-048)¶
[git_hooks]section auto-installs and manages git pre-commit hooks fromjarvy.toml. Today thepre-commitframework (https://pre-commit.com) is fully supported;huskyandlefthookare recognized by auto-detection but their handlers are stubbed with a clear "framework configured but not yet supported" error so configs can declare intent without silent no-ops.
[git_hooks]
# block presence is the opt-in; auto-detects from .pre-commit-config.yaml
[git_hooks.pre_commit]
version = "3.6.0" # pin the framework version
install_hooks = true # warm hook envs eagerly
-
jarvy hookssubcommand:install,update,status,list,run(with--all-files/--hook <id>),uninstall. Status output parses.pre-commit-config.yamldirectly, so hook counts work even when thepre-commitCLI itself isn't installed yet. -
Setup integration:
jarvy setupauto-runsjarvy hooks installbetween the git-config and ai-hooks phases. Gated on[git_hooks] auto_install = true(default). New phase emitsgit_hooks.phase_started/_completed/_skipped/_install_failedtelemetry events. -
Trust boundary: deliberately a new top-level block, NOT
[hooks].git_hooks—[hooks]is already taken by setup-lifecycle shell scripts (PRD-003). Remote-config trust gate via[git_hooks] allow_remote = true(mirrors[packages] allow_remote): a friendly-looking remote config cannot silently land arbitrary git hooks on the consuming machine without explicit opt-in in the SOURCE config. Refusals loggit_hooks.remote_refusedfor audit. -
New module:
src/git_hooks/{mod.rs, config.rs, detection.rs, precommit.rs}. Husky / lefthook handler stubs returnHookError::UnsupportedFrameworkwith the framework name, so the surface is stable for follow-up work.
Added — progress spinners (PRD-052)¶
-
indicatifdependency plussrc/progress.rshelper providingProgress::start()→Spinnerwithfinish_ok/finish_skipped/finish_failed. All long-running commands route through this helper rather than constructingProgressBardirectly, keeping the muting decision in one place. -
Auto-disable when any of: stdout is not a TTY,
JARVY_QUIET=1or--quiet/-qon argv,--format json/--log-format json, sandbox / CI detected bysandbox::is_seamless_auto(), orJARVY_NO_PROGRESS=1(explicit kill switch). In sandbox / CI mode, spinners fall through to plainprintln!lines so log scrapers still see start / finish events. -
Wired into
jarvy update check(network spinner) andjarvy hooks install/update. Deeper integration insetup_cmd's parallel-install loop is deferred — needs design to avoid clashing with subprocess streaming stdout. -
Uses stdlib
std::io::IsTerminalrather than dragging in a directlibcdep.
Changed¶
src/main.rs+src/lib.rs: registerprogressandgit_hooksmodules.CLAUDE.mdmodule map updated.src/config.rs: newgit_hooks: Option<GitHooksConfig>field;TOP_LEVEL_SECTIONSextended;top_level_sections_matches_config_fieldsdestructure test updated.src/commands/dispatch.rs: routeCommands::Hooks { action, file }tocommands::hooks_cmd::run_hooks.
Docs¶
docs/git-hooks.mdcovers configuration, commands, status output, trust boundary, CI considerations, troubleshooting.mkdocs.ymladds "Git hooks (pre-commit)" under Guides.tasks/prd-048-pre-commit-hook-installation.jsonandtasks/prd-052-progress-indicators.jsoncreated with completion notes and explicit follow-up scope.
Tracking¶
- Closes PRD-048 (Pre-Commit Hook Installation — pre-commit framework only; husky / lefthook tracked as follow-up)
- Closes PRD-052 (Progress Indicators — helper module +
update check hooks install/update; deeper setup_cmd integration tracked as follow-up)
Earlier in the day — gem/go handlers, main.rs extraction, documentation gaps¶
(Originally committed separately; merged into this unreleased block so
the awk extractor sees a single curated [Unreleased] section.)
Added — language package ecosystems (PRD-039)¶
[gem]section installs Ruby gems viagem install --no-document <name> [-v <version>]against the active ruby.--no-documentis unconditional — provisioning runs don't need RDoc/RI, and skipping the build cuts install time from ~30s to ~3s on chatty gems likerubocop.
[go]section installs Go binaries viago install <module>@<version>to the user'sGOBIN. Module paths are full import paths (require quoting in TOML); version is mandatory outside ago.modtree, use"latest"for floating installs.
[go]
"github.com/golangci/golangci-lint/cmd/golangci-lint" = "latest"
"github.com/cosmtrek/air" = "v1.49.0"
-
Both handlers wired into
PackagesConfigRef,install_packagesdispatcher,Configstruct,TOP_LEVEL_SECTIONS,validate_package_section, andrun_packages_phasetelemetry.GEM_KNOBS/GO_KNOBSslices pinned by destructure tests so adding a future config knob without updating the slice fails compilation instead of silently making the validator reject the new knob as a hostile package name. -
packages.phase_started/packages.phase_completedevents now carrygemandgobooleans alongside the existingnpm/pip/cargo/nugetflags.packages.phase_previewedcarries matchinggem_count/go_countfor dry-run preview observability. -
Per-package name + version validation (control bytes, leading-
-, URL schemes) inherits unchanged frompackages/common.rs. The trust-gate refusal of remote-config installs without[packages] allow_remote = truenow also covers[gem]/[go].
Changed — src/main.rs extraction (PRD-037)¶
-
src/main.rsreduced 734 → 271 LOC (-63%). The original 1500-linemain()match block is fully eliminated. -
All CLI dispatch + 14
handle_*glue helpers moved to a newsrc/commands/dispatch.rs(486 LOC).mainnow retains only process init that genuinely belongs at the entry point: telemetry config merge precedence (env > project > global), sandbox banner muting, panic hook, OTLP flush at exit, and theextract_config_pathhelper for early telemetry config loading. -
Per-command modules already lived at
src/commands/*_cmd.rsfrom earlier PRD-037 phases; this round finishes the extraction by taking the routing table out ofmaintoo. -
Zero behavior change: same exit codes, same output, same flag forwarding, same OTLP flush sequence.
cargo fmt,clippy --all-features -- -D warnings, 814 lib tests, and the full integration test matrix are all green on the refactored layout.
Added — documentation (PRD-011)¶
Closes the six remaining docs/ gaps from the PRD-011 audit. All new
pages match the existing flat layout (no new subdirectories) and the
Material for MkDocs style (tabbed code blocks, admonitions, fenced
code with title=):
docs/installation.md— full install guide for macOS, Linux, Windows, and from-source. Covers winget / scoop / choco / brew / cargo, verify steps, update channels, and clean-uninstall.docs/services.md— operational guide for[services]: Docker Compose, Tilt, inline service blocks, auto-start duringjarvy setup, CI auto-disable, and--wait-healthypatterns.docs/environment.md—[env]guide: plain variables, tool-scoped overrides, secret resolvers (prompt /from_env/ 1Password / Vault / AWS Secrets Manager),.envvs shell rc, trust boundaries.docs/tools-by-category.md— 235+ tools grouped by purpose so users browsing for "what's available" can scan instead ofjarvy search-ing blindly.docs/contributing-testing.md— contributor testing guide: when to reach for unit / integration / E2E layers,assert_cmdpatterns,instasnapshots,JARVY_TEST_MODE/JARVY_FAST_TEST/JARVY_E2Eflags, common pitfalls.-
docs/decisions.md— architecture decisions index. Pointers to the canonical sources (prd/*.md+CLAUDE.md) with one-line summaries for the highest-leverage trust, architecture, and convention decisions. -
docs/packages.mdupdated with[nuget],[gem],[go]sections matching the existing[npm]/[pip]/[cargo]format; updated module-source line; expanded order-of-operations to list all six ecosystems. -
mkdocs.ymlnav extended: Installation under Get Started; Environment variables + Services under Guides; Tools by category + Architecture decisions under Reference; Contributor testing guide under Community.
Changed — PRD task tracker hygiene¶
-
Updated
tasks/prd-*.jsonfor nine PRDs whose JSON status had drifted from on-disk reality: 002 (tool post-install hooks), 011 (documentation), 013 (235 tool dirs vs the 150 target), 014 (real-world testing — examples + smoke tests + e2e workflow ship), 021 (MCP server —src/mcp/ships), 027 (observability —src/observability/ships), 037 (main.rs refactor), 038 (E2E harness — Phase 1 GitHub-hosted ships; Phase 2 AWS EC2 deferred), 039 (language packages — gem/go added in this release). -
Each updated JSON carries a
completionNotefield with verification evidence: the on-disk files, the LOC delta, or the directory count that demonstrates the work is actually shipped (not just intended).
Tracking¶
- Closes PRD-011 (Comprehensive Documentation System)
- Closes PRD-013 (Expand Tool Coverage)
- Closes PRD-014 (Real-World Testing and Example Configurations)
- Closes PRD-037 (Main.rs Code Maintainability Refactor)
- Closes PRD-038 (Hybrid Cross-Platform E2E Testing Harness — Phase 1)
- Closes PRD-039 (Language Package Dependencies)
- Stale-status sync for PRD-002, PRD-021, PRD-027 (code shipped earlier)
v0.3.0 — Repo relocation to Cliftonz + MCP auto-register default-on (2026-06-26)¶
First release under the new canonical home, github.com/Cliftonz/Jarvy.
The repository was transferred from the bearbinary org; the old URL
continues to auto-redirect for git and HTTP traffic, but signing and
package metadata now point at the new owner. Existing users on v0.2.x
do not need to re-clone — git pull will follow the redirect — but the
cosign cert-identity baked into v0.3.0 is anchored to Cliftonz/jarvy
and will reject artifacts signed under the old subject. There is no
backwards-compatible overlap window; this is a clean cut.
Bundled with the move: [mcp_register] now opts in by default when
jarvy setup runs against a project with no explicit block, the
scripts/bootstrap.sh one-command onboarding is now the canonical
entry point for contributors, and two CI bugs that surfaced during the
v0.2.2 publish (missing Linux tarballs for AUR/chocolatey, parent-vs-
templates version gating) are fixed.
Changed — repository home¶
- Repository relocated to
github.com/Cliftonz/Jarvy. All in-tree references — Cargo.tomlrepository, install scripts, package manifests (Homebrew, AUR, RPM, Debian, winget, chocolatey, Helm), documentation URLs, CODEOWNERS, FUNDING — rewritten in a single sweep. Cosign cert-identity regex anchored to the new owner; releases signed underbearbinary/jarvywill no longer verify. GitHub Pages (jarvy.dev), crates.io ownership (jarvy,jarvy-templates), and Actions secrets carried over via the transfer. CODEOWNERS team syntax (@bearbinary/maintainers) collapsed to@Cliftonzsince the new home is a user account, not an org.
Added¶
[mcp_register]default-on auto-register.jarvy setupnow synthesizes a default[mcp_register]block when the project has no explicit block and at least one supported AI agent (Claude Code, Cursor, Codex, Windsurf, Cline, Continue) is detected on disk. The built-injarvyMCP server is registered against each detected agent with project scope (this repo only), not user scope. Fires themcp_register.auto_detectedtelemetry event withcount,agents,platform. Suppressed in dry-run, test mode, seamless / CI sandboxes, and whenJARVY_MCP_REGISTER=0. Explicit blocks always win.
Fixed¶
- AUR and chocolatey downstream publish unblocked. The v0.2.2
release workflow built macOS+Windows artifacts but skipped the Linux
tarballs that AUR
PKGBUILD-binand chocolatey's MSI bundler expect (CPMR0041). The release matrix now producesx86_64-unknown-linux-gnuandaarch64-unknown-linux-gnutarballs alongside the existing platforms, so the downstream publish step finds its inputs. jarvy-templatespublish gate now reads the templates crate's own version. Before this fix, the publish workflow keyed off the parentjarvycrate's version, which meant a parent-only release would attempt to re-publish ajarvy-templatesversion that already existed on crates.io (and fail). The workflow now readscrates/jarvy-templates/Cargo.toml::versionand only publishes when that specific version is new.
Docs¶
scripts/bootstrap.shis now the canonical one-command onboarding path. End-user repos integrating Jarvy should copy it into their ownscripts/so contributors run./scripts/bootstrap.shto install Jarvy (viadist/scripts/install.sh) and executejarvy setupagainst the repo-rootjarvy.toml. Idempotent. Flags:--no-setup,--channel <stable|beta|nightly>, passthrough args tojarvy setup. Quickstart and contributor docs updated to surface this over hand-rolled curl-pipe +cargo installsnippets.
v0.2.2 — Opt-out telemetry default + P0 seamless-gate fix (2026-06-25)¶
Patch release on the v0.2.x line, but a behavior-significant one: the telemetry default flipped from opt-in to opt-out, and a P0 security regression in the CI / sandbox auto-disable was caught and fixed before any stable shipped with the flip. The two changes are bundled because they were authored back-to-back in the same evening — the opt-out flip introduced the regression, and the follow-up commit closed it along with 15 review findings from a five-persona parallel code review (security / Rust perf / QA / observability / maintainability).
Users on a pre-[telemetry]-block legacy config also now see the
disclosure on first post-upgrade run, closing the silent-enrollment
loop the security reviewer found. Privacy-disclosure surfaces (PRIVACY.md,
UPGRADING.md, data/faq.json) were swept to match the new posture.
Changed — privacy posture¶
- Telemetry default flipped from opt-in to opt-out. New installs and
existing installs whose
~/.jarvy/config.tomlhas no explicit[telemetry] enabled = …line now ship anonymized usage data tohttps://telemetry.jarvy.devby default. The first-run boxed notice declares telemetry enabled and surfaces the disable path; the end-of-setupnudge fires when the user is still on the default and points atjarvy telemetry disable. Trust boundary unchanged: a remotejarvy.tomlcan still only narrow telemetry, never broaden it.
Disable persistently with jarvy telemetry disable, per-invocation
with JARVY_TELEMETRY=0 jarvy <cmd>, or via [telemetry]
enabled = false. CI / unattended AI sandboxes still auto-disable —
that guardrail was hardened (see Fixed below).
Public docs and disclosure surfaces updated: CLAUDE.md, PRIVACY.md,
UPGRADING.md, docs/telemetry.md,
docs/operations/telemetry-forwarder.md, docs/ai-hooks.md,
docs/ai-sandboxes.md, docs/index.md, docs/release-testing.md,
docs/for-ai-agents.md, data/faq.json.
Added¶
telemetry.disclosure_shownevent. Fires after the first-run boxed banner (or the legacy-upgrade banner for users whose config pre-dates the[telemetry]block) renders. Carriestrigger(first_run/legacy_upgrade) andplatform. Gives on-call an audit trail when users file privacy complaints.telemetry.undecided_nudge_shownevent. Fires when the end-of-setup"Note: opt-out and currently on" line emits. Carriesplatform. Lets operators graph what fraction of the fleet is still in the undecided state and decide when to retire the nudge.- Legacy-upgrade disclosure. Users with a
~/.jarvy/config.tomlthat pre-dates the[telemetry]block now see the boxed disclosure on the next post-upgrade run, after which the block is persisted withenabled = trueso the disclosure doesn't repeat. Closes a silent-enrollment loop that would otherwise leave the long tail of pre-d039d9bconfigs without ever seeing the banner.
Fixed¶
jarvy setupno longer re-prompts "Do you want to install Oh My Zsh?" when~/.oh-my-zshalready exists. The macOS hard-dep check asked first and then detected the existing install. Detection now runs before the prompt; thetool.already_installedtelemetry event still fires (prompted_user = false). The decision logic moved into a puredecide_omz_actionfunction with a table-driven regression test — including anever_promptclosure that panics if invoked, pinning the "AlreadyInstalled short-circuits before any prompt" invariant.- CI / sandbox telemetry auto-disable now actually fires under the
opt-out default.
from_env's seamless-detection branch correctly computedenabled = falsewhenJARVY_TELEMETRYwas unset, butmain.rsonly propagatedenv_config.enabledwhen the env var was set — discarding the disable in exactly the case it was supposed to fire. Under the prior opt-in default this was masked because the disk value was already false. The seamless gate now applies unconditionally after the config merge whenJARVY_TELEMETRYis unset. Forced sandbox (JARVY_SANDBOX=1without real detection) is deliberately NOT in this gate — a hostile dotfile must not silence telemetry on the victim's machine. tool.already_installedinstall_path is now home-prefix redacted. Pre-flip the event only fired after a user prompt; post-flip it can fire automatically on everyjarvy setup(the OMZ short-circuit). The raw/Users/<name>/.oh-my-zshpath is now routed throughredact_pathto~/.oh-my-zshbefore emit. The forwarder's server-side scrub remains the defense-in-depth backstop, not the contract.search.executedno longer emits the raw query string. The user's free-text input previously shipped verbatim — invisible under opt-in, but a leak surface once telemetry was on by default. Replaced withhad_results(bool) andquery_len_bucket(0/1-4/5-15/16-40/40+) so hit-rate dashboards still work without storing the query text.emit_telemetry_hint_if_undecideduses a section-aware TOML parse, not a line-by-line string match. The prior predicate treated[mcp_register]\nenabled = true(or any sibling section'senabledkey) as a telemetry decision, suppressing the nudge for users who never made one. Extracted astelemetry::user_decided(content)with five table-driven test cases pinning the section-aware behavior.
v0.2.1 — Registry pull QA suite + sync.rs supply-chain fixes (2026-06-25)¶
Patch release on the v0.2.x line. Dominantly defensive: a comprehensive
end-to-end QA suite for the jarvy registry sync feature shipped in
v0.2.0, plus the two real bugs that suite caught in the supply-chain
verification path. Also closes Windows test-isolation tech debt that had
been silently red on every tag-push CI run going back to v0.2.0-rc.1.
Soaked as v0.2.1-rc.1 → -rc.8 over 2026-06-24 → 2026-06-25; soak
record in #39.
The two registry-sync bug fixes are the user-impacting items. Operators
running jarvy registry sync against a cosign-signed manifest in v0.2.0
were getting fail-CLOSED behavior that looked correct on the surface
(verification rejected) but happened for the wrong reason (the sig/pem
staging paths never matched what cosign verify-blob looked for), so
the actual signature was never checked. The second fix closes a window
where a malformed manifest body could be promoted to the canonical
manifest.json path before validation rejected it — a subsequent
jarvy registry status would then dump the invalid bytes verbatim.
Both shipped silently in v0.2.0 because the original PR only had
in-process tests of run_sync_with_config; the new e2e suite drives
the real binary against a programmable mock registry + cosign shim and
is what surfaced them.
Known limitation — bootstrap-mode gates carry forward¶
Same status as v0.2.0: #30 is still open, so the Path ⅔/4 (upgrade / skip-version / rollback) CI gate still runs in bootstrap mode. No regression vs v0.2.0; the gap closes when tarballs ship.
Added¶
- Comprehensive registry-pull QA suite (~1900 LOC across 4 new test
files). End-to-end lifecycle (configure → sync → status → clear),
cosign signature path with a FakeCosign shim, resilience (oversized
manifest, truncated body, HTTP 500, parallel-fetch stress, recovery
after prior failed sync, duplicate names, invalid UTF-8, unparseable
TOML), and tracing-event regression guards that pin
registry.sync.{started,completed,sha_mismatch,signature_disabled,failed}by name + level + field shape against the documented OTEL taxonomy. Replaces the prior in-process-only coverage that missed the staging-path bug.
Fixed¶
- Registry
cosign verify-blobactually verifies now. Prior to this release,verify_sigstore_signature_with_identitylooked formanifest.json.unverified.{sig,pem}as siblings of the staged manifest, but the orchestrator wrote them atmanifest.json.{sig,pem}.unverified. Cosign returnedSignatureFilesMissingon every invocation, whichsignature_outcome_is_acceptablecorrectly rejected — so the failure mode was fail-CLOSED ("sync refused") rather than silent-bypass, but no signature was ever actually checked. Staging now uses the path shape cosign's extension derivation expects. - Malformed manifest bodies no longer poison the cache. Previously,
sync.rswrotemanifest.json.unverifiedto disk and then promoted to the canonicalmanifest.jsonBEFORE parsing the bytes. A non-UTF-8 or syntactically invalid manifest would error out of sync but leave the canonical file populated with the bad bytes, whichjarvy registry statusthen printed verbatim. Manifest is now parsed in-memory before any disk write; promotion happens only after a successful parse. - Windows test-isolation tech debt cleared across the suite. Eight
previously-silent Windows-only test failures (
paths::tests,network::propagate::tests,update::installer::tests, plus 12ai_hooks_integration+ 2mcp_register_integrationtests) had been red on every tag-push CI run since v0.2.0-rc.1 because (a) test helpers hard-coded/tmppaths that aren't absolute on Windows, (b)Path::starts_withis component-aware but string-prefix checks withformat!("{prefix}/")weren't, ©dirs::home_dir()on Windows is Win32-API-backed and ignores HOME/USERPROFILE env vars (so test sandboxes had no effect), and (d)cosigndiscovery only knew about.exe, not.cmd/.bat. All fixed; the Test workflow now runs Windows-green on every tag push. v0.2.0 stable shipped with these failures as inherited sev-2. - Test-mode bypass for
jarvy audit.audit::run_one_scannernow honorsJARVY_FAST_TEST=1(the documented test-mode contract for "skip external command execution") and returns synthetic "not available" results. The test for this code path went from 683s to 1.7s locally.
Changed¶
- Registry CLI + cache events now route through
telemetry_gate::emit. Closes the opt-in contract for theregistry.*event family — v0.2.0 leakedregistry.cli.sync_failed,registry.cache.swap_failed, and theregistry.cache.index_*events to OTLP even when the user had settelemetry.enabled = false. Matches the contract already documented for thepackage.*event family. - CI Test workflow on
cargo-nextest. Switched fromcargo testtocargo nextest run --all-features --no-fail-fast. Process-level parallelism per test; the Windows lane went from ~14 min to ~3-4 min warm-cache. Also dropped--show-output(Windows terminal I/O sink) and--verbosefromcargo check. - CI actions on Node 24. Bumped
actions/checkoutv4→v7,actions/upload-artifactv4→v7.0.1,actions/deploy-pagesv4→v5,softprops/action-gh-releasev2.2.1→v3.0.0,KSXGitHub/github-actions-deploy-aurv2.7.2→v4.1.3. Clears the Node 20 deprecation warnings the runner had been forcing through.
Tooling¶
- Cursor + JetBrains Toolbox Linux install support (#35). Both were macOS+Windows only in v0.2.0; Linux now lands via tarball fallback paths.
- 9 networking tools (#36):
cloudflared,headscale,nebula,netbird,openvpn,tailscale,twingate,wireguard-tools,zerotier. Covers VPN + overlay-mesh stacks for both home-lab and corp deployments.
v0.2.0 — Tooling breadth, MCP surface, AI hooks, release-soak hardening (2026-06-22)¶
First minor release in the v0.x line. Bigger than its predecessor — 32
commits adding two new tool ecosystems (NATS messaging, .NET / NuGet), a
significant MCP tool surface, AI-hooks distribution to six coding agents,
auto-registration of the Jarvy MCP server, and the release-soak CI gates
that catch regressions before promotion. Soaked as v0.2.0-rc.1 →
-rc.2 over 2026-06-16 → 2026-06-22; soak record in
#25.
Known limitation — binary self-update gate ships in bootstrap mode¶
The Path ⅔/4 (upgrade / skip-version / rollback) CI gate is live but
#30 is open: release.yml
does not yet emit .tar.gz / .zip binary tarballs as release assets, so
the BinaryInstaller self-update path has nothing to consume. Users on a
package-manager path (Homebrew, cargo, apt, dnf, pacman, winget,
Chocolatey, scoop, AUR) update normally. Users on the binary fallback see
"No binary for this platform" — same documented gap as v0.1.x. Tracked for
v0.3.0.
Added¶
- NATS messaging toolchain (4 tools).
nats-server,natsCLI,nsc(account credentials), plus anats-servicesbuilt-in template that wires a working three-service mesh into a freshjarvy.toml. - .NET / NuGet ecosystem. New
[nuget]package section +NugetHandlerwith end-to-end dry-run + install support. 5 .NET dev tools (full set validated against upstream channel docs), 5 .NET-flavored templates, 5 example configs, andgrpcurlfor grpc service introspection. - 12 queuing / messaging tools across two batches. First batch: 6
workflow + broker tools. Second batch:
pulsar,kaf,kafkactl,emqx,argo(Workflows CLI),kn(Knative CLI). Tools without first-party Windows manifests omit thewingetblock entirely rather than ship placeholder IDs that could be hijacked under supply-chain attack (see Security). - Extended MCP tool surface. AI hooks, MCP register, drift, roles,
services, templates, validation — all exposed over MCP. Mutating tools
(
services_start,templates_use) gated bygate_mutation+MutationCtx: rate limit → stderr TTY confirm → audit log. Workspace containment enforced bysafety::resolve_within_workspace(canonical- root check; refuses.., absolute escapes, endpoint symlinks). ai_hooksdistribution to six AI coding agents. Curated guardrail hooks (the "don'trm -rfyour homedir", "respect .gitignore", "stop-on-tests" class of safeguard) provisioned uniformly to Claude Code, Cursor, Codex, Windsurf, Cline, and Continue. Bash → PowerShell translation on Windows handled in-process so the same hook YAML works cross-platform.mcp_registerauto-registration to the same six agents. One-shot setup that places the Jarvy MCP server entry in each agent's config with the correct stdio invocation, so users don't have to copy-paste per-agent boilerplate. Trust-gated: only the built-injarvyserver registers from a remote config unlessallow_custom_servers = true.- Telemetry category plumbing.
categoryfield travels through everytool.requested/tool.installed/tool.failedevent, plustemplate.materialized. Operators can graph "what fraction of NATS rollouts succeeded?" without pivoting on tool name. tool.already_installedevent. Surfaces the skip path withinstall_path,detection_method,prompted_userfields — previously invisible in telemetry, now visible.- Telemetry
error_kinddiscrimination.tool.failedcarries anerror_kindenum (tap_fetch,command_failed,permission_denied, …) so an operator can split "the brew tap was unreachable" from "the binary install actually broke". - Drift report category grouping. Tools group by category in human
output (
messaging,workflow,runtime, …) instead of one flat list, making diff review tractable at scale. - CI: Path 8 asset download sweep workflow.
.github/workflows/verify-release.ymlfetches every release asset, verifies HTTP 200, sha256 againstSHA256SUMS.txt, cosign signature, SBOM well-formedness, and asserts the.deb-extracted binary's--versionmatches the tag's core version. Auto-fires onrelease: publishedand weekly to catch asset rot. - CI: Path ⅔/4 release-paths validation workflow.
.github/workflows/release-paths.ymlexercises upgrade-from-N-1, skip-version-from-N-2, and rollback flows on macOS arm64 / Ubuntu 22.04 / Windows. Runs in bootstrap mode until #30 ships tarballs; auto-tightens to hard-fail after. - CI: one-shot winget submission helper. For first-time Jarvy.Jarvy publisher onboarding.
Changed¶
- Dash ↔ underscore tool aliasing is now uniform.
nats-serverandnats_serverresolve to the same tool in three places that previously diverged:registry::get_tool(),commands::validate::validate_tools, andtools::spec::get_tool_spec(). The third site was the sev-2 found during rc.1 soak and fixed in rc.2 —validateacceptednats-serverbutsetup --dry-runreportedtool.unsupportedfor the same name. - Brew tap auto-tap. When
macos.brew(orlinux.brewfallback) isorg/tap/formulaform (exactly two slashes), install path runsbrew tap org/tapfirst so a fresh box doesn't surface an "untrusted tap" error. Soft-fail; already-tapped is not a blocker. jarvy validateandjarvy setup --dry-runnow surface[nuget]. Previously the new section silently dropped from the validate report — users would think their NuGet packages were configured when they weren't.publish-packages.ymldecouples downstream channels from crates.io. Previously a transient crates.io publish failure left winget / chocolatey / homebrew unsynced. Each channel now has independent secret gates and failure modes.- Release binary
--versioncomparison uses core version, not full tag. rc tags likev0.2.0-rc.2build binaries that reportjarvy 0.2.0(no prerelease suffix); the verify-release step now matches on core only.
Fixed¶
- Drop placeholder Windows package IDs from tool definitions.
Six tools previously listed placeholder
wingetIDs likePivotal.RabbitMQfor upstream namespaces that the publisher had not actually claimed. Any party who registered that publisher could ship a malicious installer pinned bywinget install -e --id. Replaced with explicit// No first-party winget manifest as of YYYY-MMnotes;tool.unsupportedtelemetry fires in place at runtime. - Telemetry gate respects
[telemetry] enabled = false. Everypackage.*/packages.*/package_command.failedevent readsobservability::telemetry_gate::is_enabled()before emitting. Prior implementation leaked package events to OTLP when telemetry was disabled but an endpoint was set for unrelated reasons. Broke the documented opt-in contract. - MCP safety boundary applies to extended mutating tools. The new
drift/roles/templates/services tools all run through
resolve_within_workspace— a path containing..or an absolute escape that lands outside the workspace root canonicalizes to a refusal, not a silent file write. - De-flaked
telemetry_smokeintegration test. Ephemeral port +#[serial]annotation + 30s timeout, replacing the prior flaky hardcoded port that intermittently lost to other tests' bound sockets. - Mass conversion of ~200
_registered_returns_sometautology tests to_registration_shapetests. The old tests verifiedSome(_).is_some()after registration — a tautology that always passed even when the underlyingToolSpecwas structurally broken. Replaced with shape-asserting tests that fail when a tool's platform matrix degrades.
Security¶
- Supply-chain: no more placeholder winget IDs. See Fixed above.
- Package-name validation.
validate_package_name/validate_package_versionrefuse leading--, URL schemes, shell-meta, and control bytes (ESC/BEL/DEL/NUL — closes ANSI injection in dry-run preview).jarvy validateruns them on every[npm]/[pip]/[cargo]/[nuget]entry. - Remote-config trust narrowing only.
ConfigOrigin::Remotetags remote-fetched configs;allow_custom_commands,allow_custom_servers,allow_remote(packages), and telemetry endpoint override are all refused for remote configs. Library hooks and the built-injarvyMCP server remain trustable; user-authored extensions do not.
Impact on v0.1.x users¶
- Cargo (
cargo install jarvy) — resolves to v0.2.0; no breaking API surface in command flags. Existingjarvy.tomlparses unchanged. .deb/.rpm/.dmg/.msi/.AppImage— install normally from the GitHub release.- Homebrew, install.sh, install.ps1 — still broken pending #30, same as v0.1.x. No regression; no improvement.
jarvy update— package-manager paths upgrade fine. Binary fallback returns the documented "No binary for this platform" — same state as v0.1.x, tracked in #30.
Patch release closing the crates.io gap that v0.1.0 left open. No runtime code changes — release-pipeline metadata only.
Fixed¶
jarvy-templatesis now publishable. The crate was markedpublish = falseand lacked therepository/homepagemetadata crates.io requires. Bothjarvyandcargo-jarvydepend on it via{ version = "X", path = "..." }; crates.io stripspathon publish and resolves from the registry, so the dep must already be available there. Withpublish = false+ no version spec on the parents, the v0.1.0cargo publishfailed aterror: failed to verify manifest ... 'jarvy-templates' does not specify a versionbefore either crate could upload.- Both
jarvy-templatespath dependency declarations now carry aversion = "0.1.1"requirement. Required bycargo publish— without it the parent crate cannot verify against the published registry form of the dep. publish-packages.yml::publish-crates-iostep is now ordered. Previously onecargo publishcall attempted to publishjarvyas the workspace root;jarvy-templateswas never published, so the parent's resolve always 404'd. The job now publishesjarvy-templatesfirst, polls the crates.io index for up to 150s until the dep surfaces, then publishesjarvywith--no-verify(the workspace verify already ran at tag-build time; the post-publish re-verify would race the index refresh).
Impact on v0.1.0 users¶
- The GitHub Release for v0.1.0 (all 49 binary assets + Sigstore
signatures) is unaffected.
.deb/.rpm/.dmg/.msi/.AppImageinstall paths work exactly as documented. cargo install jarvyresolves to v0.1.1 (the first crates.io release in the v0.1.x line). Users who triedcargo install jarvyduring the v0.1.0 → v0.1.1 window sawerror: could not find jarvy 0.1.0 in registry crates-io.- Other channels (Homebrew tap, AUR, winget, Chocolatey) were not affected by this gap.
[helm-v0.6.1] — Defense-in-depth: anonymize record-level attrs (2026-05-25)¶
Fixed — jarvy-telemetry-forwarder Helm chart¶
transform/anonymizenow runs at the record level in addition to resource context. The 0.6.0 chart's hash statements were scoped tocontext: resource, so any client SDK that emitted a PII-shaped attribute as a per-event field (e.g.tracing::info!(hostname = %h)) bypassed the SHA256 hash and reached the backend in plaintext. The companion SDK fix moveshost.nameto the resource where it belongs; this chart change makes the privacy contract hold even when a future SDK regression mis-slots an attribute.- Adds a second OTTL statement context to each pipeline
(
log/datapoint/span). Samepii.hashedAttributeslist is reused — single source of truth. keep_keysis intentionally NOT applied at record level: event- specific attributes (event,tools,duration_ms) are not PII and must pass through. Resource-contextkeep_keysremains the allowlist enforcement point for per-process identity attrs.
Migration¶
No action needed. Patch bump; no values surface change. Consumers can no-op-upgrade.
[helm-v0.6.0] — Grafana Cloud OTLP region default fix (2026-05-25)¶
Fixed — jarvy-telemetry-forwarder Helm chart¶
exporter.endpointdefault hardcodedprod-us-east-0, but Grafana Cloud API keys are region-bound — keys issued for any other region 401 at the gateway and silently drop every export. The home stack lives onprod-us-east-3; consumers relying on the chart default had 100% export failure for traces, metrics, and logs.
Changed — jarvy-telemetry-forwarder Helm chart¶
- Region hoisted to a new top-level value
grafanaCloud.region(defaultprod-us-east-3). Whenexporter.endpointis empty the chart now composeshttps://otlp-gateway-<region>.grafana.net/otlpvia a singleexporterEndpointhelper. Explicitexporter.endpointstill wins unchanged — operators pointing at Honeycomb / Datadog / in-cluster Tempo keep their override path. - Both the Deployment's
BACKEND_OTLP_ENDPOINTenv and theCiliumNetworkPolicy's FQDN derivation flow through the same helper, so a region bump can't desync the egress allow-list from the actual gateway.
Migration — BREAKING¶
exporter.endpoint default is now empty (was a hardcoded URL).
Consumers that depended on the chart-default us-east-0 URL must
either:
- Set
exporter.endpointexplicitly to keep their previous URL, or - Align
grafanaCloud.regionwith their stack's region (defaultprod-us-east-3works for home-cluster installs).
[helm-v0.5.3] — helm test smoke pod actually works now (2026-05-20)¶
The 0.5.2 ship landed the helm test smoke pod + supporting infra
but the pod itself never ran green in CI on the first push (or on
local kind clusters). Three fixes were needed; this release rolls
them into a clean cut.
Fixed — jarvy-telemetry-forwarder Helm chart¶
- NetworkPolicy: explicit egress allow for in-namespace
helm testpods (paired with the 0.5.2 ingress rule). Production CNIs (Cilium, Calico) are conntrack-aware and don't need this — it's defense-in-depth for CNIs that evaluate egress per-packet (kindnet). - Test pod hook-delete-policy drops
hook-succeededso the pod sticks around after a green run. Without this,helm test --logsfailed withpods ... not foundbecause the pod was deleted before the log fetch ran. - Test pod template is now nil-safe (nested
if .Values.testsbefore.enabled). Fixes a render failure when the template file from a newer chart is checked out alongside an oldervalues.yamlthat doesn't carry thetests:block (CI upgrade-leg pattern).
Fixed — helm-chart-ci workflow¶
- Live install + upgrade step deletes the NetworkPolicy before
running
helm test. kindnet's netpol enforcement isn't conntrack-aware, so the collector'swide-except-rfc1918egress filter drops reply SYN-ACKs to in-cluster test pods. The netpol structure itself is fully covered by the render + kubeconform matrix; this step covers the receiver only. - Common-annotations fanout test now sees the test pod carrying the chart's common annotations.
- Diagnostics-on-failure step dumps pods, services, endpoints, netpol, collector logs, test-pod logs, and runs a netpol-free repro curl. Costs nothing on green runs.
- Three other pre-existing matrix failures fixed in the same
iteration (kept here for the changelog reader's context):
helm/kind-action SHA pin corrected, promtool input shape
(extract
.specfor RuleGroups), extraEnv reject assertion accepts both helm 3.18 and helm 4.x schema messages.
Migration¶
No action needed. The chart now passes helm test cleanly on
production CNIs. On stock kindnet (only relevant for in-cluster
test runs, not production), drop the NetworkPolicy before
running helm test — see the workflow comment for the rationale.
[helm-v0.5.2] — helm test smoke pod + live HTTPS smoke script (2026-05-20)¶
Added — jarvy-telemetry-forwarder Helm chart¶
templates/tests/otlp-smoke.yaml—helm testhook pod that POSTs minimal OTLP/HTTP payloads at/v1/{logs,metrics,traces}on the Collector Service and asserts 2xx. Validates the receiver pipeline end-to-end afterhelm installwithout depending on the public ingress. Imagecurlimages/curl:8.10.1pinned, restricted-PSS compliant.tests.*values + schema validation (enabled,image,resources,securityContext). Disable with--set tests.enabled=false.- NetworkPolicy now whitelists pods carrying BOTH the chart-test
component label AND the release instance label — required so the
helm testpod can reach the Collector through the otherwise locked-down ingress. scripts/smoke-live.sh— bash script that smokes the public HTTPS endpoint with the same three OTLP payloads. A diff between this and the in-clusterhelm testisolates ingress (TLS, gateway, middlewares) as the suspect.- Makefile targets:
helm-smoke-live(live HTTPS) andhelm-test-kind(in-cluster). helm-chart-cikind job now runshelm testafter the fresh install — receiver-pipeline regressions fail CI alongside the rendering/lint suite.
Migration¶
No action needed; new behavior is purely additive. helm test
becomes opt-in once you upgrade — run it whenever you want
in-cluster validation of the receiver path.
[helm-v0.5.1] — HTTPRoute filters: null lint fix (2026-05-17)¶
Fixed — jarvy-telemetry-forwarder Helm chart¶
- HTTPRoute template no longer emits an empty
filters:key (which YAML-parses asnull) when traefik middlewares are disabled and noextraFiltersare supplied. Surfaced by thehelm-chart-cimatrix'sgatewayclass-envoy-acceptedscenario, which has been failing kubeconform-strict since the field was added — the Gateway API HTTPRoute schema typesfiltersasarray, notarray | null. The fix wraps the key in anorguard so it is omitted entirely when no filters apply, which is the spec-compliant equivalent and produces no Argo CD drift.
Migration¶
No action needed. Behavior at runtime is unchanged — a missing
filters key and an empty filters list both mean "no filters
applied". The diff visible on helm diff upgrade is purely the
removal of an null-valued field from the rendered HTTPRoute when
running without traefik middlewares.
[helm-v0.5.0] — ExternalSecret Argo CD drift fix (2026-05-17)¶
Rendered ExternalSecrets now emit the two server-side defaults the ESO
admission webhook fills in (target.deletionPolicy: Retain,
data[].remoteRef.conversionStrategy: Default). Without these in the
chart's desired manifest, Argo CD's compare saw the webhook-injected
values as drift on every reconcile, leaving every install of this
chart perpetually sync=OutOfSync, health=Healthy. Discovered while
diagnosing the jarvy-telemetry Argo app on the home cluster on
2026-05-17.
Added — jarvy-telemetry-forwarder Helm chart¶
secrets.externalSecrets.deletionPolicy(defaultRetain) andsecrets.externalSecrets.conversionStrategy(defaultDefault) values. Both default to the ESO server-side default so existing installs see no semantic change — only that Argo CD diffs now show zero drift after the nexthelm upgrade. Override either if your use case needsDelete/Merge(deletionPolicy) orUnicode(conversionStrategy).values.schema.jsonconstraints for both new fields with enum validation.
Fixed — jarvy-telemetry-forwarder Helm chart¶
- ExternalSecret resources no longer drift in Argo CD when the ESO
admission webhook fills server-side defaults. Bump and
helm upgradeto clear the perpetual OutOfSync state.
Migration¶
No action needed beyond helm upgrade. Defaults match ESO
server-side, so rendered output is functionally identical — the diff
visible on helm diff upgrade is purely the two new explicit
field assignments.
helm-v0.4.0 — Chart enhancement plan v3 (2026-05-14)¶
Multi-perspective parallel review (perf, security, QA, observability,
maintainability) produced a 27-item enhancement plan; all 27 items
ship together. Probe semantics, graceful shutdown, queue-saturation
alert, dashboard, recording rules, image-digest default, FQDN egress
mode, DoS-protection gate, split Service, container security context
schema constraints, runbook anchors in the ops doc, and 5 new CI
guards (kind install/upgrade, helm 3.14/3.16/3.18 matrix, promtool,
README↔schema drift, runbook-anchor check). 13 render scenarios pass,
8 template-time guards fire, helm lint --strict clean. Backward
compatible: defaults harden but no required-field renames; legacy
networkPolicy.cilium.enabled=true still works (now a synonym for
egressMode: fqdn).
Added — jarvy-telemetry-forwarder Helm chart¶
A multi-perspective review (perf, security, QA, observability, maintainability) produced a 27-item enhancement plan; all 27 items shipped together. Chart version bump pending.
- Probe split + pipeline-aware health. Liveness no longer flips
on
memory_limiterbackpressure (which would cascade-restart all replicas during burst — defeating the design). Readiness still flips so the LB sheds load.health_check_v2'scheck_collector_pipelineexposes pipeline status on/; liveness gets a longer failureThreshold (6), readiness shorter periodSeconds (5). NewstartupProbecovers cold-pull on fresh nodes. - Graceful shutdown.
terminationGracePeriodSeconds: 60+preStop: sleep 15so the LB drains and the batch/exporter flushes in-flight records before SIGKILL. - Exporter queue saturation alert — leading indicator that fires
before
JarvyForwarderExporterFailingstarts dropping records. Backed by a recording rule (jarvy_forwarder:exporter_queue_utilization:ratio). - Pod restart alert — closes the loop when pipeline alerts can't fire (pod never gets healthy enough to emit metrics).
- Grafana dashboard ConfigMap shipped via
grafana_dashboard=1sidecar label. 10 panels: receiver rate, queue utilization, exporter rate, memory/CPU, tail-sampling decisions, allowlist drops, batch throughput, pod restarts, cert expiry. - Receiver auth (
collector.receiverAuth.enabled, opt-in) fronts the OTLP receiver withbearertokenauth/receiver. Multi- tenant deployments should enable. - Recording rules. Repeated
rate(...)over 5-10m windows hoisted into named recording rules; alerts + dashboard share one computation instead of recomputing per evaluation. networkPolicy.egressMode. Three modes:wide(legacyto: []on 443),wide-except-rfc1918(new default — excludes private IP ranges),fqdn(requires Cilium — restricts to the parsed exporter hostname).- DoS-protection gate: non-Traefik GatewayClasses must supply
httpRoute.extraFiltersOR setdosProtection.acceptUnprotected: true— fails install otherwise. Closes the "I installed on Envoy and forgot the rate limit" exposure. - Split Service: public OTLP Service (port 4318) + in-cluster metrics Service (port 8888). In-cluster scrapers cannot accidentally reach the OTLP receiver and self-metrics no longer mix with public ingress traffic.
- Production-overlay digest pinning: chart ships with
collector.image.digestset to a realsha256:digest by default; CI scenarioproduction-overlayasserts the rendered image string carries the digest. - Grafana dashboard's
runbook_urlanchors all exist indocs/operations/telemetry-forwarder.md(11 new{#alert-*}-anchored subsections with diagnosis steps). - CI: kind install + upgrade smoke test (k8s 1.31); helm 3.14/3.16/3.18 render matrix; promtool PromRule validation; README ↔ schema drift check; runbook-anchor grep.
Changed — jarvy-telemetry-forwarder Helm chart¶
- CPU limit removed from
collector.resources.limits. CFS-quota throttling on an I/O-bound forwarder adds 10-100ms p99 latency on burst with no upside. Floor preserved viarequests.cpu: 100m. - HPA
scaleDownpolicy is now explicit (drop 1 pod / 60s) instead of the K8s default (halve replicas per 15s) which causes replica thrash nearmemory_limiterpressure. - PDB uses
maxUnavailable: 1(notminAvailable: 1) so node drains proceed one pod at a time without stalling forever waiting for real-Ready. Mutually exclusive withminAvailable— template-timefail()catches both-set misconfiguration. pdb.minAvailable+pdb.maxUnavailablemutually exclusive (templatefail).tls.certManager.enabled=true+tls.existingSecretNamemutually exclusive (templatefail)._helpers.tpllabels order: chart-managed labels are emitted LAST socommonLabelscannot overwriteapp.kubernetes.io/nameand steer NetworkPolicy / ServiceMonitor away from real pods.automountServiceAccountToken: falsestays hardcoded in both ServiceAccount and Pod spec (no values knob); render-time CI asserts catch regressions.enableServiceLinks: falseon the pod — saves env-var bloat on busy namespaces; speeds cold start.- ServiceMonitor:
honorLabelsis now actually rendered (was a ghost setting).path: /metrics,scheme: http, andscrapeTimeoutexplicit so a future port change doesn't break scrape silently. ServiceMonitor selector now matches the new metrics-only Service (app.kubernetes.io/component: metrics). - ServiceMonitor
metricRelabelings: tightened keep-list. Drops high-cardinalityotelcol_processor_transform_*_modifiedseries (none of which exist — see Fixed below) and keeps the operational subset. saltStalealert rebuilt: now readsexternal_secrets_sync_calls_total(the only series that exists for "salt content was refreshed"). The old query referenced a non-existentkube_secret_createdmetric and would have stayed silent forever.allowlistDroppingKeysalert rebuilt: comparesotelcol_processor_incoming_itemsvsoutgoing_itemson thetransform/keep_allowlist_attrsprocessor. The old query referenced non-existent*_modifiedcounters.bearertokenauthextension for the backend exporter, plus optionalbearertokenauth/receiverfor inbound auth.- Container
securityContextexplicitly setsrunAsNonRoot: trueandseccompProfile: RuntimeDefault(belt-and-suspenders over the pod-level setting). Schema rejects flippingprivileged,allowPrivilegeEscalation,readOnlyRootFilesystem, or droppingcapabilities.drop: ALL. exporterFailingalert threshold units documented as records/sec; docs/values comments aligned (was previously conflicting on per-second vs per-minute).- Gateway listener TLS
options:rendered through as-is so operators can pass GatewayClass-specific knobs (e.g.gateway.envoyproxy.io/min-tls-version). - README updated: salt-rotation wording, accurate schema invariants list, new ConfigMap/dashboard/PrometheusRule entries in "What gets installed", egressMode and DoS-protection notes.
Removed — jarvy-telemetry-forwarder Helm chart¶
- The
cilium.enabledvalues knob is still accepted but is now a synonym foregressMode: fqdn; future versions may remove.
The entries below belong to the Jarvy CLI's pending [Unreleased]
section; they ship with the next CLI tag, NOT with helm-v0.4.0.
Listed here so the helm-v0.4.0 release notes do not absorb them.
Sandbox auto-detection (PRD-053)¶
- Sandbox auto-detection (PRD-053). New
src/sandbox/module detects AI agent sandboxes (Claude Code, Cursor, e2b, Modal, Daytona, Replit), long-running container envs (GitHub Codespaces, Gitpod, devcontainers), and a generic/.dockerenv+ non-TTY fallback.crate::sandbox::is_seamless()is the canonical "unattended" predicate; CI detection is now a strict subset.JARVY_SANDBOX=0disables detection,JARVY_SANDBOX=1forces generic-container (or whatever named provider also matches). - Seamless mode wires through telemetry auto-disable, update-
check suppression, first-run welcome suppression, brew auto-install
block, and secrets non-interactive default — five subsystems that
previously each carried their own
env::var("CI")heuristic now share one predicate. - Verify-only fallback in
jarvy setup. When the sandbox cannot install tools (read-only rootfs, no user-scope package manager, no passwordless sudo), setup runs the doctor pipeline inline and exitsPREREQ_MISSING (3)on gaps; clean runs return0with a verify-only success message. The probe records why via aVerifyOnlyReasonenum (NoJarvyHome/ReadOnlyRoot/NoInstallPath/Forced) so support tickets explain which gate tripped. - Auto-baseline. On the first seamless-mode run with zero gaps,
Jarvy snapshots the current state as
.jarvy/state.jsonso subsequent runs can do meaningful drift checks. Gated on a full doctor match — partial matches never auto-baseline (PRD-053 risk row 2). Works on both the install-capable and verify-only paths so pre-loaded sandbox images still get a baseline. - Seamless banner on stderr, one line per process, summarizing
which provider was detected and the
JARVY_SANDBOX=0escape hatch. Muted by--quiet,-q,--json,--format=json,--log-format=json, orJARVY_QUIET=1. The correspondingtracing::info!(event = "sandbox.detected")fires regardless sojarvy.logrecords the decision even for JSON consumers. is_seamless_auto()— same asis_seamless()minus forced sandbox detection. Telemetry + update auto-disable now route through this variant so a hostile dotfile or compromised devcontainer base image that setsJARVY_SANDBOX=1cannot silence security-patch updates or anomaly telemetry on a victim's machine (PRD-053 security review F1).
Changed¶
JARVY_HOMEvalidation. Paths must be absolute and contain no..traversal components; on Unix, existing paths must be owned by the current uid. Defends againstsudo -E jarvy ...patterns where a less-privileged actor's env points a privileged jarvy run at/etcor/root/.ssh(PRD-053 security review F2).- Install-capability probe writes to a per-PID
.probe-<pid>filename viaOpenOptions::create_new(true)(O_CREAT|O_EXCL) instead offs::writeto.probe. A pre-staged symlink at the probe path now errors out instead of being silently followed and clobbered (PRD-053 security review F3). - Banner emission moved after panic-hook install in
main.rsso any future stderr-write failure during banner emission produces a structured panic message instead of a default backtrace dump. detect()andci::detect()are now cached viaOnceLock— env vars and/.dockerenvdo not change mid-run, and the previous implementation re-walked ~25getenvcalls peris_seamless()invocation × 4 callers perjarvy setup. Telemetryci_detectedevent likewise fires at most once per process instead of once per call.InstallCapability::VerifyOnlycarries aVerifyOnlyReasonso log lines and tickets explain which probe tripped.
Removed¶
update::config::is_ci_environmentand the parallel shim inonboarding::detection. Both were thin re-exports ofsandbox::is_seamless(); in-tree callers now use the canonical predicate directly. Jarvy is abincrate, no external library consumers to break.- Hand-rolled
which()helper insrc/sandbox/mod.rsreplaced by thewhichcrate (already a project dep). Local impl ignored the Unix exec bit and only handled three Windows extensions.
Security¶
- Test images pinned by sha256 digest.
debian:bookworm-slimandbuildpack-deps:bookworm-scmintests/sandbox_integration.rsresolve to specific bytes regardless of registry tag drift or tag- replay MITM. - Read-only binary bind-mount. The host's jarvy binary is mounted
into integration-test containers via
Mount::bind_mount(...).with_access_mode(AccessMode::ReadOnly)so a malicious container cannot truncate or replace the host binary mid-test (PRD-053 security review F8).
Tests¶
- 10 new sandbox unit tests: forced-with/without named signal,
JARVY_SANDBOX=0 && CI=trueprecedence,is_seamless_automatrix, generic-container truth table,VerifyOnlyReasonDisplay, force- verify-only probe short-circuit, banner idempotence. - 4 new docker-backed integration tests: partial-match negative gate
(must not auto-baseline on gaps), banner suppression with
--format=json, banner suppression withJARVY_QUIET=1, verify- only must not overwrite an existingstate.json. - Cross-module env-isolation via
#[serial_test::serial(ci_sandbox_env)]on everyci::testsandsandbox::testsfunction so the two suites cannot race on shared env vars (CI,GITHUB_ACTIONS,CODESPACES).
v0.1.0 — First feature-complete milestone (2026-05-27)¶
First feature-complete stable. Closes the round-2 hardening review
(45 items across two passes), ships clean-laptop onboarding, and
publishes 14 ready-to-copy jarvy.toml project templates.
Telemetry-enabled deployments now actually export records — four
compounding OTLP bugs that left env-only opt-in silently emitting
zero records are fixed (see ### Fixed below). The public surface
from v0.0.5 is preserved; everything below is either additive,
fail-closed by default, or a tightening of internal invariants.
Upgrading from v0.0.5¶
jarvy update --channel beta (and jarvy update in general) is broken in
v0.0.5 — it exits 0 without actually upgrading. Two pre-existing bugs in
v0.0.5: a hardcoded version = "0.2" clap string that makes v0.0.5 think
it is newer than v0.1.0, plus an update path that never triggers an
artifact download. Both are fixed in v0.1.0 but cannot be patched
retroactively. v0.0.5 users must upgrade by reinstalling via their
package manager, not via jarvy update:
- macOS (Homebrew tap restored):
brew upgrade jarvy - Debian/Ubuntu:
sudo apt install ./jarvy_0.1.0_amd64.deb - Fedora/RHEL:
sudo dnf install ./jarvy-0.1.0-1.x86_64.rpm - Arch (AUR):
yay -Syu jarvy-bin - Windows (winget):
winget upgrade Jarvy.Jarvy - Cargo:
cargo install jarvy --force
From v0.1.0 onward, jarvy update --channel beta and jarvy update
work as documented.
Added¶
- Project templates.
examples/<stack>/jarvy.tomlships 14 validated drop-in configs (node-npm/pnpm/bun, deno, python-api/uv, go-api, rust-cli/workspace, ruby-rails, java-spring, react-app, fullstack, k8s-platform). Companion docs atdocs/templates-index.mdgive an AI-agent decision table mapping detect-by signals (lockfiles, manifests) to template URLs. - Clean-laptop onboarding. New
Makefile+ idempotentscripts/bootstrap.shgive contributors a two-command setup (curl install.sh | bashthenmake setup). Bootstrap script honorsJARVY_CHANNELfor stable/beta/nightly, falls back towgetifcurlis missing, and forwards extra args tojarvy setup. shellcheck-clean. jarvy validaterecognizes the full top-level surface.[npm],[pip],[cargo],[commands],[drift],[git],[network],[logging]no longer trigger "unknown configuration section" warnings. Toolchain channel aliases (stable,beta,nightly,lts,current) are accepted as valid version strings —rust = "stable"validates cleanly.SecretError::PathEscapesProject+JARVY_ALLOW_EXTERNAL_SECRETSoverride.[env.secrets] from_filepaths that resolve outside the project root and$HOMEafter symlink-resolving canonicalization are refused by default. Common legitimate paths (~/.aws/credentials,<project>/.env.secret) keep working. Override withJARVY_ALLOW_EXTERNAL_SECRETS=1.tools::pinned_installer::PinnedInstallerhelper for the curl-bash class of installers. arctl, kmcp, and ollama (Linux fallback only) now fetch their installer scripts at a pinned commit, sha256-verify the body, and refuse to exec on mismatch — same pattern Homebrew already used. Refreshing a pinned installer requires updating the commit + sha256 constants together.- POSIX env-var grammar validation before writing
[env.vars]to shell rc files. Keys not matching^[A-Za-z_][A-Za-z0-9_]*$are skipped with a structuredevent="env.refused_invalid_key"warning instead of corrupting~/.bashrc/~/.zshrc. tools::install_methodcanonical classifier (Brew/Cargo/Nvm/Pyenv/Rustup/Snap/System/NotFound/Unknown).commands::diagnose,commands::drift, andobservability::bundleall delegate here instead of hand-rolling three near-identical detectors.- Unsupported-tool feedback loop with telemetry-first delivery.
When a user (or AI agent) hits a tool Jarvy doesn't support, the
run now surfaces a structured request payload — fuzzy Levenshtein
suggestions with prefix-match boost, a
define_tool!scaffold snippet, exit codeTOOL_UNSUPPORTED(8), and a delivery channel. Telemetry is canonical: no GitHub account needed and zero triage work for the maintainer. The pre-filledtool_request.ymlissue URL is surfaced only when telemetry is off, withjarvy telemetry enableoffered as a one-time alternative. Newjarvy tools --request <name> [--open]flag with pretty / JSON / YAML / TOML output. Setup-path returns exit 8 only when every configured tool was unknown — mixed runs still return 0 so partial setups succeed. Canonicaltool.unsupportedevent with uniform field shape across both call sites; OTEL counterjarvy.tool.unsupportedrenamed from…not_supportedto match. crates/jarvy-templatesworkspace member — dep-free crate shippingvalidate_tool_name,render_tool_template,MAX_TOOL_NAME_LEN, and the embeddeddefine_tool!template.cargo-jarvydepends only on this crate now; clean-build time drops from minutes (full jarvy lib) to ~7s.
Changed¶
- Logging pipeline rewired to
tracing_appender::rollingfor daily rotation +tracing_appender::non_blockingfor buffered writes.analytics::shutdown_logging()flushes both theSdkLoggerProviderand the fileWorkerGuardbeforeprocess::exit, so buffered records aren't lost on early termination.EnvFilternow has a default-on floor ofwarn,jarvy=infoifRUST_LOGis unset. Hook::run_with_policycollapsed from a 3-stateHookOutcomeenum toResult<(), HookError>. Production callers only ever checkedFailvs not-Fail; the warning-on-continue_on_errorside effect already conveyed the difference. The newErrcase returns the underlyingHookErrorsoerror_codes::HOOK_FAILEDcallers keep working.Sanitizer::sanitize_borrowedreturnsCow<'_, str>so the no-match path skips allocation entirely.Sanitizer::sanitizepreserves the same fast path internally.tracing::warn!→tracing::error!ontool.failed,hook.failed,hook.timeout,config.parse_error, andtelemetry.endpoint.refused. These are operator-actionable conditions, not advisory.- Subprocess spans.
services::run_commandandtools::common::run_captureare now wrapped intracing::info_span!("subprocess.exec", cmd, args_count, ...)with start/duration/exit_code events. paths.rscleanup.cache_dirinlined intoremote_config_cache_dir(only caller);#![allow(dead_code)]removed since every public function has external callers now.
Security¶
- CA-bundle trust check tightened.
network::propagateno longer accepts paths under the broad~/.jarvy/cache prefix — only~/.jarvy/ca/is trusted, with a trailing-slash anchor so~/.jarvy/ca-attacker/...can't slip through. - Cross-origin redirects refused on
remote::validated_get/fetch_remote_config.ureqagent now uses.max_redirects(0); redirects must be revalidated through the policy gate. - Sigstore companion verification.
update::releasereturnsNonefor cosign companion files when the.sig/.pemaren't exact-match siblings — a substring-match bug that would have let a malicious tarball claim sibling signatures was closed. exec.rsdeleted (zero-caller speculative seam).team::inheritance::transform_github_urlduplicate deleted; callers route through the canonicalremote::transform_github_urlso URL hardening lives in one place.
Fixed¶
validate_getrejected URLs with empty hosts underfile://scheme but didn't match the documented "scheme not allowed" error string. Test relaxed to accept any error variant; behavior unchanged.paths::remote_config_cache_dirnow readsJARVY_HOMEconsistently with the rest ofpaths.rs(was hand-rolling the override before).update_rc_contentargument order documented; previously the test suite caller had(content, &vars, &ctx, ShellType)instead of the actual(content, ShellType, &vars, &ctx).- OTLP env-only opt-in now actually exports. Four compounding
bugs caused
JARVY_TELEMETRY=1+JARVY_OTLP_ENDPOINT=…to silently produce zero records, and even file-flag opt-in lost every metric point on short-lived commands: (1)init_logginggated on the file flag, missing the env override — the OTEL log layer was excluded from the subscriber whenever telemetry was opt-in via env only; (2)opentelemetry-otlp0.31'swith_endpoint()is the FULL URL not a base — a barehttp://localhost:4318producedPOST /and the collector 404'd every batch. Newanalytics::resolve_otlp_endpoint(base, signal)appends/v1/{logs|metrics|traces}idempotently; (3)otlp_logs_endpoint()ignored the file config's[telemetry] endpoint— setting it viajarvy telemetry set-endpointsilently failed to reroute logs. The logger builder now reads the mergedTelemetryConfig; (4)telemetry::shutdown()was defined but never called frommain, so theSdkMeterProvider's 60sPeriodicReaderhad no chance to flush onjarvy setup-length runs. Now called alongsideanalytics::shutdown_logging()in the exit path. host.nameemitted as resource attribute, not per-event field. Grafana Cloud was receiving plaintexthostname=<machine>.localfrom thesetup.inventoryevent, defeating the chart-side anonymize pipeline (which only operated on resource-context attrs). Build a sharedopentelemetry_sdk::Resourceonce at telemetry init withservice.name,service.version,host.name,os.type,os.description; attach to bothSdkLoggerProviderandSdkMeterProvider. Previouslyservice.namedefaulted tounknown_service, which broke stack-level filtering and made "where did this record come from" guesswork. Local file logger and stderr layers still print plaintext (those are operator- owned sinks, not the egress channel).emit_telemetry_hint_if_undecidednow consultstelemetry::is_enabled()first so a user running withJARVY_TELEMETRY=1doesn't see "telemetry is opt-in and currently off" right after a run that just emitted records.- Drift hash respects
--file.jarvy drifthashed<project_dir>/jarvy.tomlregardless of the--fileflag, so drift detection silently used the wrong file when a non-default config path was supplied. set_up_osmatchesenv::consts::OScasing. A capitalization mismatch in the platform-dispatch table caused setup to fall through to the unknown-OS path on some platforms.
Tests¶
- 1,633+ tests passing across lib + binary + integration suites (was ~1,580). Highlights of the new coverage:
validated_getrejection tests for HTTP-to-remote, disallowed host,file://scheme, missing scheme.Hook::run_with_policyoutcome matrix (dry-run / success / failure × continue_on_error true|false).verify_no_tar_escapecontainment tests + symlink-escape refusal.- Cosign companion exact-match (no substring) regression.
- Path-containment refusal +
JARVY_ALLOW_EXTERNAL_SECRETS=1override path for[env.secrets] from_file. - Shell-interpreted-key table-driven test
(
every_shell_interpreted_key_refuses_bang_prefix) so adding a new shell-interpreted git config key lights up the test suite immediately. #[serial_test::serial]annotations added forJARVY_ALLOW_*env mutations to keep parallel runs isolated.
Docs¶
CLAUDE.mdLogging section rewritten to match the actualsrc/logging/(thin re-export layer) andsrc/observability/(where rotation + sanitizer + analytics live) split.examples/README.md+docs/templates-index.mdpublished as the human/AI-facing template indexes.llms-full.txt"Project Templates" section added (withdocs/llms.txt+docs/llms-full.txtsymlinks for the published docs site).
v0.0.5 — Chocolatey install script + bundled v0.0.4 fixes (2026-05-05)¶
Folds in everything queued for v0.0.4 (which was tagged but never publicly published) plus a Chocolatey install-script fix.
Fixed¶
- Chocolatey package v0.0.3 failed moderation with
404 Not Foundfor the install URL. Two bugs indist/windows/chocolatey/tools/chocolateyinstall.ps1: - URL pattern referenced
jarvy-vVERSION_PLACEHOLDER-x86_64-pc-windows-msvc.zip— but cargo-packager produces.msiand.exe, no.zipfor Windows. - VERSION_PLACEHOLDER and SHA256_PLACEHOLDER were never substituted
because the publish workflow only ran sed against
jarvy.nuspec, not the install script.
Rewrote the install script to use Install-ChocolateyPackage with
-FileType msi and silent install args, pointing at the actual
jarvy_<v>_x64_en-US.msi asset. Updated
publish-packages.yml::update-chocolatey to substitute both files
AND pull the real msi SHA256 from SHA256SUMS.txt so the integrity
check passes.
- cargo fmt --check drift in src/team/inheritance.rs:760-768
(single-quoted TOML literals from v0.0.3 needed compaction).
- OpenSSF Scorecard failed on v0.0.3 tag with Only the default
branch main is supported. ossf/scorecard-action explicitly refuses
tag-push triggers. Restored push: branches: [main] for scorecard
only — every other validating workflow stays tag-triggered.
- Homebrew tap publish now gracefully skips when
HOMEBREW_TAP_DEPLOY_KEY is not configured. Previously the missing
secret failed the whole publish-packages.yml workflow, masking
the success of crates.io, AUR, winget, and Chocolatey jobs.
Validated downstream (v0.0.3)¶
After the v0.0.3 fixes, the following propagation channels worked:
- ✅ crates.io: [email protected] + [email protected] published
- ✅ AUR (jarvy-bin)
- ✅ Submit to winget (publish-packages.yml job; separate winget.yml still needs manual first submission)
- ✅ GitHub Pages docs site (after maintainer enabled Pages)
- ❌ Chocolatey: failed moderation due to broken install script (v0.0.5 fixes)
- ⚠️ Homebrew tap: pending secret config (now non-blocking)
Note¶
v0.0.4 was tagged but the draft was never publicly published — v0.0.4's fixes ship together with the Chocolatey fix as v0.0.5 to reduce propagation churn (one round of crates.io / AUR / etc. updates instead of two back-to-back).
v0.0.4 — Lint formatting + scorecard + homebrew-tap guard (2026-05-05)¶
Fixed¶
cargo fmt --checkfailed in the Lint job onsrc/team/inheritance.rs:760-768because the v0.0.3 single-quoted TOML literal edits left format strings on multiple lines that rustfmt wanted compacted. Re-rancargo fmtto normalize.- OpenSSF Scorecard failed on the v0.0.3 tag with
Only the default branch main is supported. ossf/scorecard-action explicitly refuses tag-push triggers; v0.0.3's trigger trim moved scorecard off main-push, which broke it. Restoredpush: branches: [main]for scorecard only — every other validating workflow stays tag-triggered. Release-tag scorecard runs produce no useful signal anyway since the action only inspects the default branch. - Homebrew tap publish now gracefully skips when
HOMEBREW_TAP_DEPLOY_KEYis not configured. Previously the wholepublish-packages.ymlworkflow exited 1 with "API_TOKEN_GITHUB and SSH_DEPLOY_KEY are empty", masking the success of crates.io, AUR, winget, and Chocolatey jobs. New behavior: missing secret emits a warning ("set per docs/MAINTAINER_RELEASE_GUIDE.md") and the push step is skipped viaif:guard.
Validated downstream (v0.0.3)¶
After the v0.0.3 fixes, the following propagation channels worked:
- ✅ crates.io: [email protected] + [email protected] published
- ✅ Submit to winget (job inside publish-packages.yml; the separate winget.yml workflow still requires manual first submission per v0.0.3 release notes)
- ✅ Chocolatey
- ✅ AUR (jarvy-bin)
- ✅ GitHub Pages docs site (after maintainer enabled Pages in repo Settings)
- ⚠️ Homebrew tap: blocked on
HOMEBREW_TAP_DEPLOY_KEYsecret; v0.0.4 makes this a non-blocker so missing-secret no longer fails the whole workflow.
v0.0.3 — Unblock crates.io and Homebrew downstream publish (2026-05-05)¶
Patch release. v0.0.2 went live on the GitHub release page but the
crates.io and Homebrew workflows that fire on release: published
both failed, leaving cargo install jarvy and
brew install Cliftonz/tap/jarvy unavailable.
Fixed¶
- Cargo.toml declared
readme = "README.md"(uppercase) but the tracked file isReadme.md(mixed case). On macOS the difference is invisible (case-insensitive filesystem); on the Linux CI runner it failedcargo publishwithreadme "README.md" does not appear to exist. BothPublish CrateandPublish to Package Managersworkflows hit the same error. Same fix in theinclude = [...]manifest list. Now matches what's actually in the git tree. .github/workflows/winget.ymlwas scaffolded from a different project's template and never customized —identifier: Benji377.Tookaandfork-user: Benji377referenced a totally unrelated package. Rewrote with placeholder TODO values forJarvy.Jarvy/Cliftonzand changed the trigger fromrelease: publishedtoworkflow_dispatchonly. winget-releaser cannot create a brand-new package registration; the first submission must go throughwingetcreate newand a hand-reviewed PR to microsoft/winget-pkgs. After that's merged the trigger can be flipped back.
Removed¶
- Duplicate
.github/workflows/crates.ymldeleted. Both that andpublish-packages.yml::publish-crates-iowere firing onrelease: publishedand trying tocargo publish. Even if both had the right secret, the second one would race-fail with "crate version already exists". Kept the version insidepublish-packages.ymlbecause it composes with the Homebrew tap update vianeeds:. docs/release-testing.mdanddocs/release-quirks-jarvy.mdreferences tocrates.ymlupdated to point at the surviving workflow path.
Known issues (not fixed in this release)¶
- GitHub Pages is not enabled for
Cliftonz/Jarvyrepo — the Deploy Docs workflow fails withHttpError: Not Found ... Ensure GitHub Pages has been enabled. Fix is in repo Settings → Pages, not in code. Until enabled, the docs site at jarvy.dev (or whichever Pages URL ends up provisioned) won't update on release. - winget first submission still requires manual
wingetcreate newintervention (see Fixed above for the workflow disable).
v0.0.2 — Cosign verify-command case fix (2026-05-05)¶
Patch release fixing the cosign verification snippet baked into release notes, SECURITY.md, and docs/release-quirks-jarvy.md.
Fixed¶
- release notes / SECURITY.md / docs: the
--certificate-identity-regexpvalue usedCliftonz/jarvy(lowercase j). The actual Sigstore cert subject GitHub Actions produces isCliftonz/Jarvy/...(capital J — the repo's canonical case). cosign's regex is case-sensitive, so users copy-pasting the verify command from the v0.0.1 release page saw "none of the expected identities matched" even though the signature was valid. Corrected all three sources toCliftonz/Jarvy/. github.com URLs elsewhere in the repo are unchanged because GitHub URL matching is case-insensitive — only cosign's regex was affected.
v0.0.1 — Initial public release (2026-05-05)¶
First publicly tagged stable release. Validated through the v0.1.0-rc.1 → v0.1.0-rc.9 soak cycle (same tree, version-string only differs); cut as 0.0.1 to keep the first-stable surface narrow and reserve room for 0.1.0 as the first feature-complete milestone.
Features¶
- provisioner: Cross-platform tool provisioner driven by
jarvy.toml(macOS, Linux, Windows) with native package managers - tools: 154+ tool registry covering compilers, runtimes, CLIs, container tools, Kubernetes ecosystem (kubectl, helm, k9s, kagent, kmcp, arctl), cloud CLIs (gcloud, aws, az), security tools, observability (opentelemetry-collector), Dockerfile converter (dfc) (PRD-013)
- tools: Parallel version checking with rayon for ~5x speedup; batch package-manager operations
- tools: Declarative
define_tool!macro for tool definitions (~2000 lines reduced) - tools: Strict (
depends_on) and flexible (depends_on_one_of) tool dependencies with topological install ordering (PRD-034) - hooks: 29+ default post-install hooks for shell completion and configuration; idempotent, advisory, user-overridable
- roles: Role-based configurations with deep inheritance, version overrides,
roles list|show|diffcommands (PRD-033) - packages: Language package deps via
[npm],[pip],[cargo]— package-manager auto-detection, virtualenv support, lockfile install (PRD-039) - git: Git configuration automation — identity, SSH/GPG signing, default branch, aliases, credential helper auto-detect per OS (PRD-041)
- drift: Configuration drift detection with SHA-256 file hashing, version
policies,
jarvy drift check|status|accept|fix(PRD-043) - update: Self-updating with stable/beta/nightly channel selection, throttled checks, rollback, multi-method install detection (Homebrew, Cargo, apt, dnf, winget, Chocolatey, Scoop, binary fallback) (PRD-035)
- telemetry: OTEL-unified logs, metrics, optional traces; OTLP HTTP/gRPC
endpoints; CI auto-disable;
jarvy telemetry status|enable|disable|test|preview(PRD-022, PRD-050) - logging: Persistent file logging with rotation, gzip compression,
sensitive-data redaction;
jarvy logs view|stats|clean|config(PRD-050) - ticket: Debug bundles via
jarvy ticket create|show|list|clean— ZIP with system info, tool versions, sanitized logs (PRD-050) - network: Corporate proxy support — HTTP/HTTPS/SOCKS, NO_PROXY, custom CA bundles, per-tool overrides, secure password sources (PRD-019)
- services: Docker Compose and Tilt backend support
- ci: Auto-detection for 11 CI/CD providers with provider-specific output
- env: Environment variable management with
.envgeneration and shell rc updates - mcp: MCP server exposing tools and resources for AI assistants
- interactive: Menu mode when running
jarvywithout a subcommand - bootstrap:
jarvy bootstrap,jarvy configure,jarvy diagnosefor onboarding (PRD-023)
Distribution¶
- Multi-channel: crates.io, Homebrew tap, AUR (source + binary),
.deb,.rpm, winget, Chocolatey, universal install scripts for macOS/Linux/Windows (PRD-012) - Prebuilt platforms: macOS arm64, Linux x86_64 (musl), Linux aarch64,
Linux armv7, Windows x86_64. macOS Intel (x86_64) not shipped as prebuilt —
Intel users install via
cargo install jarvyor Homebrew (both compile from source). Seedocs/release-testing.mdfor rationale. - Sigstore keyless signing for all release artifacts (PRD-020)
- SBOM generation in SPDX 2.3 and CycloneDX 1.4 formats per release (PRD-020)
- GitHub build provenance attestation per release (PRD-020)
- Opt-in early-release channel:
JARVY_CHANNEL=betaenv var on install scripts;[update] channel = "beta"in~/.jarvy/config.toml;jarvy update --channel beta
Quality & Security¶
- Clippy gate, mutation testing, fuzzing, coverage, benchmarks, OpenSSF Scorecard (PRD-018)
- Hybrid cross-platform E2E testing harness (PRD-038)
- Tag-signing enforcement (SSH or GPG) on release workflow
- Cosign keyless signing via GitHub OIDC for all release artifacts
Infrastructure¶
- Semantic version checking with proper semver operators
- Cross-platform shell detection and hook execution
- Workspace lint configuration; Rust 2024 edition; MSRV 1.85