Skip to content

v0.1.0

Choose a tag to compare

@github-actions github-actions released this 27 May 14:29
v0.1.0
e6ecc16

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.toml ships 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 at
    docs/templates-index.md give an AI-agent decision table mapping
    detect-by signals (lockfiles, manifests) to template URLs.
  • Clean-laptop onboarding. New Makefile + idempotent
    scripts/bootstrap.sh give contributors a two-command setup
    (curl install.sh | bash then make setup). Bootstrap script
    honors JARVY_CHANNEL for stable/beta/nightly, falls back to
    wget if curl is missing, and forwards extra args to
    jarvy setup. shellcheck-clean.
  • jarvy validate recognizes 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_SECRETS
    override. [env.secrets] from_file paths that resolve outside
    the project root and $HOME after symlink-resolving
    canonicalization are refused by default. Common legitimate paths
    (~/.aws/credentials, <project>/.env.secret) keep working.
    Override with JARVY_ALLOW_EXTERNAL_SECRETS=1.
  • tools::pinned_installer::PinnedInstaller helper 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 structured
    event="env.refused_invalid_key" warning instead of corrupting
    ~/.bashrc / ~/.zshrc.
  • tools::install_method canonical classifier
    (Brew/Cargo/Nvm/Pyenv/Rustup/Snap/System/
    NotFound/Unknown). commands::diagnose, commands::drift,
    and observability::bundle all 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 code TOOL_UNSUPPORTED (8), and a delivery channel.
    Telemetry is canonical: no GitHub account needed and zero triage
    work for the maintainer. The pre-filled tool_request.yml issue
    URL is surfaced only when telemetry is off, with
    jarvy telemetry enable offered as a one-time alternative. New
    jarvy 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. Canonical tool.unsupported event with uniform
    field shape across both call sites; OTEL counter
    jarvy.tool.unsupported renamed from …not_supported to match.
  • crates/jarvy-templates workspace member — dep-free crate
    shipping validate_tool_name, render_tool_template,
    MAX_TOOL_NAME_LEN, and the embedded define_tool! template.
    cargo-jarvy depends only on this crate now; clean-build time
    drops from minutes (full jarvy lib) to ~7s.

Changed

  • Logging pipeline rewired to tracing_appender::rolling for
    daily rotation + tracing_appender::non_blocking for buffered
    writes. analytics::shutdown_logging() flushes both the
    SdkLoggerProvider and the file WorkerGuard before
    process::exit, so buffered records aren't lost on early
    termination. EnvFilter now has a default-on floor of
    warn,jarvy=info if RUST_LOG is unset.
  • Hook::run_with_policy collapsed from a 3-state HookOutcome
    enum to Result<(), HookError>. Production callers only ever
    checked Fail vs not-Fail; the warning-on-continue_on_error
    side effect already conveyed the difference. The new Err case
    returns the underlying HookError so error_codes::HOOK_FAILED
    callers keep working.
  • Sanitizer::sanitize_borrowed returns Cow<'_, str> so the
    no-match path skips allocation entirely. Sanitizer::sanitize
    preserves the same fast path internally.
  • tracing::warn!tracing::error! on tool.failed,
    hook.failed, hook.timeout, config.parse_error, and
    telemetry.endpoint.refused. These are operator-actionable
    conditions, not advisory.
  • Subprocess spans. services::run_command and
    tools::common::run_capture are now wrapped in
    tracing::info_span!("subprocess.exec", cmd, args_count, ...)
    with start/duration/exit_code events.
  • paths.rs cleanup. cache_dir inlined into
    remote_config_cache_dir (only caller); #![allow(dead_code)]
    removed since every public function has external callers now.

Security

  • CA-bundle trust check tightened. network::propagate no
    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. ureq agent
    now uses .max_redirects(0); redirects must be revalidated
    through the policy gate.
  • Sigstore companion verification. update::release returns
    None for cosign companion files when the .sig/.pem aren't
    exact-match siblings — a substring-match bug that would have let
    a malicious tarball claim sibling signatures was closed.
  • exec.rs deleted (zero-caller speculative seam).
  • team::inheritance::transform_github_url duplicate deleted;
    callers route through the canonical remote::transform_github_url
    so URL hardening lives in one place.

Fixed

  • validate_get rejected URLs with empty hosts under file://
    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_dir now reads JARVY_HOME
    consistently with the rest of paths.rs (was hand-rolling the
    override before).
  • update_rc_content argument 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_logging gated 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-otlp 0.31's with_endpoint() is the FULL URL
    not a base — a bare http://localhost:4318 produced POST /
    and the collector 404'd every batch. New
    analytics::resolve_otlp_endpoint(base, signal) appends
    /v1/{logs|metrics|traces} idempotently;
    (3) otlp_logs_endpoint() ignored the file config's
    [telemetry] endpoint — setting it via
    jarvy telemetry set-endpoint silently failed to reroute
    logs. The logger builder now reads the merged
    TelemetryConfig;
    (4) telemetry::shutdown() was defined but never called from
    main, so the SdkMeterProvider's 60s PeriodicReader had
    no chance to flush on jarvy setup-length runs.
    Now called alongside analytics::shutdown_logging() in
    the exit path.
  • host.name emitted as resource attribute, not per-event
    field.
    Grafana Cloud was receiving plaintext
    hostname=<machine>.local from the setup.inventory event,
    defeating the chart-side anonymize pipeline (which only operated
    on resource-context attrs). Build a shared
    opentelemetry_sdk::Resource once at telemetry init with
    service.name, service.version, host.name, os.type,
    os.description; attach to both SdkLoggerProvider and
    SdkMeterProvider. Previously service.name defaulted to
    unknown_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_undecided now consults
    telemetry::is_enabled() first so a user running with
    JARVY_TELEMETRY=1 doesn't see "telemetry is opt-in and
    currently off" right after a run that just emitted records.
  • Drift hash respects --file. jarvy drift hashed
    <project_dir>/jarvy.toml regardless of the --file flag, so
    drift detection silently used the wrong file when a non-default
    config path was supplied.
  • set_up_os matches env::consts::OS casing. 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_get rejection tests for HTTP-to-remote, disallowed
      host, file:// scheme, missing scheme.
    • Hook::run_with_policy outcome matrix (dry-run / success /
      failure × continue_on_error true|false).
    • verify_no_tar_escape containment tests + symlink-escape
      refusal.
    • Cosign companion exact-match (no substring) regression.
    • Path-containment refusal + JARVY_ALLOW_EXTERNAL_SECRETS=1
      override 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 for
    JARVY_ALLOW_* env mutations to keep parallel runs isolated.

Docs

  • CLAUDE.md Logging section rewritten to match the actual
    src/logging/ (thin re-export layer) and src/observability/
    (where rotation + sanitizer + analytics live) split.
  • examples/README.md + docs/templates-index.md published as
    the human/AI-facing template indexes.
  • llms-full.txt "Project Templates" section added (with
    docs/llms.txt + docs/llms-full.txt symlinks for the published
    docs site).

Full Changelog: v0.1.0-rc.15...v0.1.0

Installation

Quick Install (Unix)

curl -fsSL https://raw.githubusercontent.com/bearbinary/jarvy/main/dist/scripts/install.sh | bash

Quick Install (Windows PowerShell)

irm https://raw.githubusercontent.com/bearbinary/jarvy/main/dist/scripts/install.ps1 | iex

Homebrew

brew install bearbinary/tap/jarvy

Cargo

cargo install jarvy

Early-Release Channel

Pre-release tags (-rc.N, -beta.N) are routed through the beta channel.
Opt in: JARVY_CHANNEL=beta on the install script, or jarvy update --channel beta.
See docs/release-testing.md.

See installation docs for more options.

Security

Verify Signatures

All release artifacts are signed with Sigstore keyless OIDC. Verify any artifact:

ARTIFACT=jarvy-linux-x86_64.tar.gz
BASE=https://github.com/bearbinary/jarvy/releases/download/v0.1.0
curl -LO $BASE/$ARTIFACT
curl -LO $BASE/$ARTIFACT.sig
curl -LO $BASE/$ARTIFACT.pem
cosign verify-blob \
  --signature $ARTIFACT.sig \
  --certificate $ARTIFACT.pem \
  --certificate-identity-regexp '^https://github\.com/bearbinary/Jarvy/\.github/workflows/release\.yml@refs/tags/v[0-9.]+(-[A-Za-z0-9.]+)?$' \
  --certificate-oidc-issuer 'https://token.actions.githubusercontent.com' \
  $ARTIFACT

Tag Signature

This release was cut from a signed git tag. Verify with:

git -c gpg.ssh.allowedSignersFile=.github/allowed_signers tag --verify v0.1.0

SBOM

Software Bill of Materials in SPDX 2.3 (sbom.spdx.json) and CycloneDX 1.4 (sbom.cdx.json).

Checksums

SHA256 checksums for all artifacts are in SHA256SUMS.txt.