Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: endevco/aube
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v1.6.2
Choose a base ref
...
head repository: endevco/aube
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v1.7.0
Choose a head ref
  • 18 commits
  • 106 files changed
  • 6 contributors

Commits on May 2, 2026

  1. chore: refresh benchmarks for v1.6.2 (#467)

    ## 🤖 Refreshed benchmarks
    
    `benchmarks/results.json` was pinned to aube `1.5.2`; the workspace is
    now `1.6.2`. Re-ran `mise run bench:bump` on the hermetic Verdaccio
    registry (500mbit / 50ms per the mise task) and regenerated
    `benchmarks/results.json` plus the README `BENCH_RATIOS` block. The
    benchmark matrix pins aube's GVS mode via
    `npm_config_enable_global_virtual_store=true|false` (the
    auto-synthesized env alias for the `enableGlobalVirtualStore` setting),
    so GitHub Actions' inherited `CI=true` environment does not change
    whether aube runs with GVS enabled or disabled.
    
    ## Benchmark changes
    
    Versions:
    - aube: 1.5.2 -> 1.6.2
    
    Public ratios: warm installs vs Bun 6x -> 4x; warm installs vs pnpm 10x
    -> 9x; repeat test vs Bun 4x -> 5x; repeat test vs pnpm 35x -> 33x.
    
    | Benchmark | aube | bun | pnpm | npm | yarn |
    | --- | ---: | ---: | ---: | ---: | ---: |
    | Fresh install (warm cache) | 230ms -> 246ms (+7%) | 1488ms -> 1011ms
    (-32%) | 2367ms -> 2227ms (-6%) | 6416ms -> 6091ms (-5%) | 13963ms ->
    11193ms (-20%) |
    | Fresh install (cold cache) | 6047ms -> 4595ms (-24%) | 4333ms ->
    4517ms (+4%) | 4723ms -> 4982ms (+5%) | 8154ms -> 7588ms (-7%) | 26771ms
    -> 24904ms (-7%) |
    | CI install (warm cache, GVS disabled) | 564ms -> 1174ms (+108%) |
    1295ms -> 1530ms (+18%) | 2361ms -> 2049ms (-13%) | 6616ms -> 6287ms
    (-5%) | 13860ms -> 11538ms (-17%) |
    | CI install (cold cache, GVS disabled) | 5800ms -> 4273ms (-26%) |
    4278ms -> 4034ms (-6%) | 4823ms -> 4940ms (+2%) | 7794ms -> 7701ms (-1%)
    | 26621ms -> 25067ms (-6%) |
    | npm install && npm run test | 9ms -> 9ms (0%) | 40ms -> 41ms (+3%) |
    318ms -> 300ms (-6%) | 686ms -> 678ms (-1%) | 375ms -> 364ms (-3%) |
    | Add dependency | 476ms -> 696ms (+46%) | 935ms -> 1052ms (+13%) |
    2500ms -> 2343ms (-6%) | 6214ms -> 6725ms (+8%) | 12967ms -> 12943ms
    (0%) |
    
    **Review the numbers** before merging — if anything looks wildly off vs.
    the previous release, investigate before landing. Hermetic proxy jitter
    or an npmjs uplink hiccup can occasionally skew results.
    
    Once merged to main, the updated bench results flow into the next
    `release-plz-pr` run automatically.
    
    ---
    Generated by the `bench-refresh` workflow.
    
    Co-authored-by: release-plz[bot] <release-plz+bot@users.noreply.github.com>
    mise-en-dev and release-plz[bot] authored May 2, 2026
    Configuration menu
    Copy the full SHA
    3b58f07 View commit details
    Browse the repository at this point in the history
  2. chore(docs): remove shrill.en.dev analytics script (#468)

    Removes the proxied shrill.en.dev analytics snippet from the VitePress
    docs config — the endpoint is going away.
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Low Risk**
    > Low risk: removes a client-side analytics `<script>` from the
    VitePress docs head; no app/runtime logic or data flows are altered
    beyond stopping event beacons.
    > 
    > **Overview**
    > **Removes the proxied `shrill.en.dev` analytics snippet** from the
    VitePress docs configuration by deleting the injected `<script>` entry
    in `docs/.vitepress/config.mts`.
    > 
    > Docs pages will no longer load or send analytics events to
    `https://shrill.en.dev/...`, aligning with the endpoint being retired.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    cdd84cb. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    jdx authored May 2, 2026
    Configuration menu
    Copy the full SHA
    6e64b72 View commit details
    Browse the repository at this point in the history
  3. docs(test): triage every pnpm-test-import gap with explicit support d…

    …ecisions (#471)
    
    ## Summary
    
    - Reframes [test/PNPM_TEST_IMPORT.md](test/PNPM_TEST_IMPORT.md) from
    "what's portable today" to "every documented divergence / skipped /
    blocked entry is a feature gap that needs an owner decision."
    - Adds a triage block at the top with three outcome categories
    (`support` / `won't-support` / `test-only`) and records decisions for
    all 16 untriaged gaps surfaced in the file.
    - Promotes three intentional divergences to the Tier 3 skip section with
    rationale: pnpmfile `updateConfig` hook (misc.ts:479),
    `readPackage`-on-importers (hooks.ts:551 + 661), and `aube update
    --depth N` (update.ts:599).
    - Each remaining `support` entry includes a concrete aube-side fix
    sketch with file:line pointers, so the next porter can act without
    re-reading the cluster.
    - One outcome flagged high-priority: bare `user/repo` GitHub-shorthand
    resolution in `parse_git_spec` (~20-30 LOC) unlocks four dropped
    `kevva/is-negative` assertions across update.ts:14/143/170/197.
    
    ## Notes for the reviewer
    
    - The diff is doc-only (test/PNPM_TEST_IMPORT.md, +58/-8). No code
    changed.
    - One previously-recorded "blocked on git-resolver feature" claim turned
    out to be outdated — aube has full git resolution at
    `crates/aube-resolver/src/local_source.rs` and the fragment parser at
    `crates/aube-lockfile/src/lib.rs` already handles slash-bearing
    committishes. misc.ts:567 is now flagged `test-only`.
    - A new convention note allows network-dependent ports gated behind a
    `slow=1` tag or a separate `test/*_slow.bats` file (relevant to
    lifecycleScripts.ts:128 node-gyp bootstrap and lifecycleScripts.ts:336
    git-dep prepare).
    
    ## Test plan
    
    - [ ] No tests required (doc-only change). Confirm renders cleanly on
    GitHub.
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Low Risk**
    > Low risk: documentation-only updates that do not affect runtime
    behavior or test execution directly.
    > 
    > **Overview**
    > Adds a **triage section** to `test/PNPM_TEST_IMPORT.md` that reframes
    every skipped/divergent pnpm test as an explicit product decision
    (`support` / `won't-support` / `test-only`), including guidance for
    gating network-dependent ports behind `slow=1`.
    > 
    > Records decisions for all previously untriaged gaps, with concrete fix
    sketches for `support` items (notably a high-priority `parse_git_spec`
    change to accept GitHub `user/repo` shorthand) and promotes three
    intentional divergences (`updateConfig`, `readPackage` on importer
    manifests, and `aube update --depth`) into the **Explicitly skipped
    (Tier 3)** list with rationale.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    fc37303. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    7f66aee View commit details
    Browse the repository at this point in the history
  4. perf: streaming sha512, parallel cas, tls prewarm, fetch reorder (#469)

    Second pass over install hot path. Bounded changes, no on-disk output
    drift, no lockfile-byte drift, no fetched-byte drift.
    
    ## Added
    
    ### Shared utils (`aube-util`)
    
    - `cache::{ProcessCache, DiskCache, FreshnessSnapshot}` for in-memory +
    disk + mtime/size/blake3 freshness primitives
    - `buf::{with_scratch_string, with_scratch_bytes}` thread-local scratch
    buffers
    - `hash::{Blake3Builder, TeeReader, ByteHasher, blake3_hash_file}`
    length-prefixed hasher, streaming tee, mmap-rayon BLAKE3 over 4 MiB
    - `fs_atomic::write_excl` direct `O_CREAT|O_EXCL` write returning
    `WriteOutcome`
    - `concurrency::parse_concurrency_env` clamped `AUBE_CONCURRENCY=N`
    override
    - `snapshot::clone_tree` reflink-aware tree copy (CoW on APFS / btrfs /
    ReFS, fallback `fs::copy`)
    
    ### Install pipeline
    
    - `RegistryClient::prewarm_connection` fire-and-forget HEAD warms TLS +
    TCP + HTTP/2 behind manifest parse
    - `RegistryClient::fetch_tarball_bytes_streaming_sha512` streams SHA-512
    over wire chunks, skips post-buffer hash pass
    - `aube_store::verify_precomputed_sha512` returns `Result<bool, Error>`
    (`true` = matched, `false` = non-SHA-512 fallback)
    - `import_verified_tarball_streamed` consumes precomputed digest, falls
    through to buffered path on legacy SRI
    - `is_likely_native_build` allowlist for critical-path fetch reorder
    
    ## Changed
    
    ### Install
    - streaming SHA-512 wired in BOTH lockfile + catch-up fetch paths
    - critical-path fetch sort: native-build packages float to front via
    stable `sort_by_key`
    - shared `Arc<GraphHashes>` between prewarm and link, no double-compute
    - lockfile-only branch reuses resolver's `Arc<RegistryClient>` instead
    of rebuilding
    - network concurrency default raised 64 → 128
    
    ### Store
    - two-phase `import_tarball`: serial tar walk stages entries, rayon
    batch writes when entries ≥ 256
    - `O_CREAT|O_EXCL` direct write replaces tempfile + persist-noclobber
    for content path
    - new internal `CasWriteOutcome` skips redundant len check on `Created`
    outcome
    - post-write `cas_file_matches_len` only on `AlreadyExisted` arm
    
    ### Registry
    - `Accept-Encoding: gzip, br, zstd` on packument fetches (tarballs stay
    `identity`)
    - real `aube/<workspace-version> (<os> <arch>)` UA replaces hardcoded
    `aube/0.1.0`
    - `hickory-dns` enabled for in-process async DNS cache
    - buffered `read_body_capped` + `retry_bytes_body_read` delegate through
    one streaming chunk loop
    
    ### Linker
    - canonicalize result memoized via `OnceLock<RwLock<HashMap>>` on
    Windows
    - reflink-probe result cached process-wide keyed on `(src, dst)`
    - skip `make_executable` chmod on hardlink + reflink paths (CAS source
    already `0o755`)
    
    ### Resolver
    - peer-context fixed-point loop carries forward graph hash, one walk per
    iter instead of two
    
    ### Manifest
    - `PackageJson::from_path_cached` returns `Arc<PackageJson>` from
    process-wide cache
    - `WorkspaceConfig::load` typed cache parallel to existing raw cache
    
    ### Settings
    - `meta::find` switches from linear scan to `binary_search_by` over the
    codegen-sorted table
    
    ### State
    - `package_json_meta` mtime + size fast-path skips BLAKE3 when both
    unchanged
    - additive serde field, forward-compatible with older state files
    
    ### CLI
    - `find_project_root` + `find_workspace_root` memoize via `ProcessCache`
    - 7 sites borrow `process_env()` slice instead of cloning a fresh
    `Vec<(String, String)>`
    
    ### Lockfile
    - `dep_path_filename::short_hash` SHA-256 → BLAKE3 (internal-only, not
    interop)
    - `parse_json` clone-then-mutate contract documented
    
    ## Killswitches
    
    | env | falls back to |
    |---|---|
    | `AUBE_DISABLE_STREAMING_SHA512` | buffered SHA-512 verify pass |
    | `AUBE_DISABLE_SPECULATIVE_TLS` | no prewarm |
    | `AUBE_DISABLE_CRITICAL_PATH` | resolver-discovery fetch order |
    | `AUBE_DISABLE_PARALLEL_IMPORT` | serial tar entry writes |
    | `AUBE_DISABLE_MMAP_BLAKE3` | streaming BLAKE3 |
    | `AUBE_DISABLE_SNAPSHOTS` | per-file `fs::copy` |
    | `AUBE_CONCURRENCY=N` | clamped fixed permit count |
    
    ## Round 2 (post-Greptile)
    
    ### Fixed
    - Linker probe cache `entry().or_insert(strategy)` first-write-wins, was
    `.insert()` last-write-wins. Two concurrent linker probes (prewarm +
    final) racing on shared test files could clobber a correct Reflink
    result with a Copy fallback for the rest of the process
    - `DiskCache::write_bytes` does `create_dir_all(parent)` before
    `atomic_write`. Cold first write would fail with `NotFound` under the
    old code
    - `verify_precomputed_sha512` distinguishes `malformed base64` and
    `decoded N bytes, expected 64` from `integrity mismatch`. Three clear
    messages instead of one misleading bucket
    
    ### Simplified
    - Deleted dead AIMD scaffolding from `aube_util::concurrency`.
    `ConcurrencyProbe` + `bump`/`back_off`/`AdjustReason` had zero non-test
    callers. Replaced with `parse_concurrency_env() -> Option<u32>` (~120
    LOC removed)
    - `Error::IntegrityAlgoUnsupported` variant deleted.
    `verify_precomputed_sha512` returns `Result<bool, Error>` so callers
    detect non-SHA-512 fallback without matching on a sentinel error
    - `import_verified_tarball_streamed` accepts `Option<&[u8; 64]>`, both
    call sites collapse from 22-line match arms to single calls
    - `is_likely_native_build` allowlist trimmed: `ws` and `sass` are
    pure-JS, only `node-sass` is the actual native build
    
    ### Tests
    - 4× `verify_precomputed_sha512` unit tests (happy / mismatch /
    non-SHA-512 fallback / malformed)
    - 4× `is_likely_native_build` + sort stability tests
    - 7× `parse_concurrency_env` env-bound tests
    
    ## Round 3 (cold-path audit, 6-agent sweep)
    
    Targeted at items the agents flagged as cold-relevant only. All preserve
    symlink topology, lockfile bytes, CAS bytes.
    
    ### Registry — biggest single cold win
    - `parse_full_response`: `bytes.to_vec()` was cloning a 5-50 MB
    packument body on every fetch. Replaced with `Bytes::try_into_mut`
    zero-copy → `BytesMut`, dropped the dead `serde_json::from_slice`
    fallback (simd-json is a strict superset of RFC 8259 for valid JSON).
    Estimated ~1-3 s saved on a 200-packument cold install
    - `same_host()` URL re-parsing on every authed request: cached
    `self.config.registry` parse via `OnceLock<reqwest::Url>` on
    `RegistryClient`. Comparison shape (scheme + host + port) preserved
    byte-for-byte to keep the global-auth-token leak guard semantics
    identical
    
    ### Linker
    - Pre-create `aube_dir` + scope dirs once before the GVS step1 par_iter
    (both branches), drop the per-package `mkdirp(parent)` that paid 1.4k
    stat syscalls on a typical install
    - `materialize_into` parents `BTreeSet<PathBuf>` → `Vec` +
    `sort_unstable` + `dedup`. On heavy packages (typescript ~5k entries,
    next ~3k) the BTreeSet's per-insert log-N PathBuf comparison on 50-byte
    paths was a real cost
    - `claimed: HashSet<String>` → `FxHashSet<&str>` in both hoist passes.
    Drops `pkg.name.clone()` × hundreds + SipHash overhead
    
    ### Store
    - `normalize_tar_entry_path` pre-sizes output via
    `String::with_capacity(raw.as_os_str().len())`. Normalized form never
    grows beyond input, so the prealloc eliminates the `String` Vec growth
    on every tar entry
    
    ## Test plan
    
    - [x] `cargo fmt --check` clean
    - [x] `cargo clippy --workspace --all-targets -- -D warnings` clean
    - [x] `cargo test --workspace` 1289+ assertions, 0 failures
    - [ ] hermetic bench (`mise run bench:bump`) shows no cold-install
    regression vs main
    - [ ] verdaccio access-log diff: same set of unique URLs requested
    - [ ] store byte-identity snapshot unchanged
    - [ ] lockfile byte-identity unchanged on `fixtures/medium`
    
    ## Real-world cold install bench (Windows, real npm registry)
    
    3 runs each project, fresh aube store + cache + node_modules between
    runs. Min wall (cleanest signal):
    
    | project | upstream main 1.6.2 | this PR | × faster |
    |---|---:|---:|---:|
    | svelte (56 pkg) | 1393 ms | 1386 ms | 1.01× |
    | vue (117 pkg) | 1590 ms | 1360 ms | 1.17× |
    | nextjs (336 pkg) | 14071 ms | 9160 ms | 1.54× |
    | babylon (21 pkg) | ~6000 ms | 3186 ms | ~1.9× |
    
    vs bun 1.3.13 cold (real npm registry, fresh cache):
    
    | project | bun | this PR | × faster |
    |---|---:|---:|---:|
    | svelte | 7598 ms | 1438 ms | 5.28× |
    | vue | 13792 ms | 1501 ms | 9.19× |
    | nextjs | 46971 ms | 8177 ms | 5.74× |
    | babylon | 4217 ms | 3366 ms | 1.25× |
    imjustprism authored May 2, 2026
    Configuration menu
    Copy the full SHA
    7a231d6 View commit details
    Browse the repository at this point in the history
  5. fix(lockfile): parse bare user/repo as github shorthand (#472)

    ## Summary
    
    - Adds bare `user/repo` GitHub-shorthand recognition to
    [`parse_git_spec`](https://github.com/endevco/aube/blob/feat/lockfile-bare-github-shorthand/crates/aube-lockfile/src/lib.rs)
    — npm/pnpm parity. Placed last after explicit URL/scheme branches so
    existing forms (`github:user/repo`, `git+https://`, scp, full URLs)
    shadow it.
    - Adds a guard in
    [`update.rs`](https://github.com/endevco/aube/blob/feat/lockfile-bare-github-shorthand/crates/aube/src/commands/update.rs)
    that skips git-spec deps in the `update --latest` manifest rewrite.
    Without this guard the parser change creates a footgun: `"is-negative":
    "kevva/is-negative"` would be silently rewritten to `"^<some-version>"`
    (a registry pin) and break install.
    - Adds a network-test convention: `_require_network` skips unless
    `AUBE_NETWORK_TESTS=1` is set, paired with a separate `test/*_slow.bats`
    file. First use:
    [test/pnpm_update_slow.bats](https://github.com/endevco/aube/blob/feat/lockfile-bare-github-shorthand/test/pnpm_update_slow.bats)
    — one regression-guard port covering install + update --latest
    end-to-end with `kevva/is-negative` alongside the registry deps from
    update.ts:143.
    - Restores most of the dropped test assertions referenced in the triage
    doc (PR #471). The remaining assertions in update.ts:14/170/197 need
    CLI-side `aube add <bare-shorthand>` support, which is its own feature
    and is tracked as a follow-up in `test/PNPM_TEST_IMPORT.md`.
    
    ## Notes for the reviewer
    
    - Two commits: parser branch (with the leading-dot bug fix from review
    feedback already squashed in) + the update.rs guard / slow bats / doc
    updates.
    - Addresses Greptile/Cursor P1: `is_bare_github_shorthand` now rejects
    owner starting with `.` so single-component relative paths (`./repo`,
    `../repo`) are correctly rejected. Test extended to cover both single-
    and multi-component forms.
    - The slow bats file uses the same `git checkout` teardown pattern as
    `pnpm_update.bats` to restore mutated `dist-tags` storage.
    - Rebased onto current main (#471 merged after this PR opened).
    
    ## Test plan
    
    - [x] `cargo build --workspace` — green
    - [x] `cargo test -p aube-lockfile` — 239 lib tests pass (+8 new for
    parser branch)
    - [x] `cargo clippy --all-targets -- -D warnings` — no warnings
    - [x] `cargo fmt --check` — clean
    - [x] `mise run test:bats test/pnpm_update.bats` — 22/22 pass (offline,
    unchanged)
    - [ ] `AUBE_NETWORK_TESTS=1 mise run test:bats
    test/pnpm_update_slow.bats` — not run locally (needs Verdaccio + github)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Expands git-spec parsing and changes `update --latest`
    manifest-rewrite behavior; mistakes could misclassify paths/URLs as git
    or skip/incorrectly rewrite dependency specifiers, affecting installs.
    > 
    > **Overview**
    > **Git spec parsing now recognizes bare `user/repo` as GitHub
    shorthand** by expanding it to `https://github.com/<user>/<repo>.git`,
    with new validation to avoid colliding with relative paths, scoped
    package names, and other non-git forms.
    > 
    > **`aube update --latest` now skips git-based dependencies when
    rewriting `package.json`**, preventing git specs (including the new bare
    shorthand) from being converted into registry `latest`/semver pins.
    > 
    > Adds a **network-gated bats regression test**
    (`test/pnpm_update_slow.bats`) and updates the pnpm test-porting doc to
    formalize `_require_network`/`AUBE_NETWORK_TESTS=1` for
    upstream-dependent tests.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    1f7bbf3. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    fcfdb0d View commit details
    Browse the repository at this point in the history
  6. fix(resolver): resolve nested link:/file: deps from local parents and…

    … overrides (#470)
    
    ## Summary
    
    Fixes the install-time error:
    
    ```
    × failed to resolve dependencies
      ╰─▶ registry error for @scope/foo: transitive local specifier
          link:./libs/foo cannot be resolved without the parent
          package source root
    ```
    
    Two cases that previously bailed:
    
    - **`file:`/`link:` parent declares a transitive `link:`** in its own
    `package.json`. The resolver now anchors the relative path on the
    parent's source root (already on the parent's
    `LockedPackage.local_source`) instead of the importer dir.
    - **Root `pnpm.overrides` rewriting a registry dep to
    `link:./libs/foo`.** Override paths are anchored at the project root
    regardless of which workspace package consumes them — mirrors pnpm. The
    resolver tags override-substituted local specs and routes them through
    `project_root` rather than the consumer's importer dir.
    
    Linker side, transitive `link:` deps from a `file:`/`link:` parent get
    no `.aube/<name>@link+...` entry of their own; the parent's sibling
    symlink points straight at the on-disk target, matching how root-level
    `link:` deps already work.
    
    ## Test plan
    
    - [x] Two new bats cases in [test/local_deps.bats](test/local_deps.bats)
    — one for the parent-anchored path, one for the override-anchored path.
    - [x] Existing `local_deps.bats` keeps passing (file:/link:/tarball +
    workspace importer + roundtrip + excludeLinksFromLockfile).
    - [x] `cargo test`, `cargo clippy --all-targets -- -D warnings`, `cargo
    fmt --check` clean.
    - [x] No new failures elsewhere in the bats suite (regressions checked
    against `main`).
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Touches dependency resolution and virtual-store materialization for
    `link:`/`file:` specs, which can affect install correctness and on-disk
    symlink layout across workspaces and the global virtual store. Changes
    are localized and covered by new bats tests, but regressions could break
    installs for edge-case graphs.
    > 
    > **Overview**
    > Fixes resolution and linking of *nested* `link:`/`file:` dependencies.
    > 
    > The resolver now (1) anchors override-rewritten local specs to the
    project root via a new `range_from_override` flag, and (2) allows
    transitive local specs to resolve relative to a local (`file:`/`link:`)
    parent’s source root while keeping registry-parent exotic subdeps
    blocked.
    > 
    > The linker threads a precomputed `dep_path -> absolute target` map for
    `link:` packages through `materialize_into`/`ensure_in_virtual_store` so
    transitive `link:` deps create sibling symlinks directly to the on-disk
    target (avoiding dangling `.aube/<name>@link+...` paths), including
    during GVS prewarming. New `local_deps.bats` cases cover override
    anchoring, GVS behavior, and parent-root anchoring.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    0d293c9. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    7d7ded0 View commit details
    Browse the repository at this point in the history
  7. chore: refresh benchmarks for v1.6.2 (#474)

    ## 🤖 Refreshed benchmarks
    
    `benchmarks/results.json` was pinned to aube `1.6.2`; the workspace is
    now `1.6.2`. Re-ran `mise run bench:bump` on the hermetic Verdaccio
    registry (500mbit / 50ms per the mise task) and regenerated
    `benchmarks/results.json` plus the README `BENCH_RATIOS` block. The
    benchmark matrix pins aube's GVS mode via
    `npm_config_enable_global_virtual_store=true|false` (the
    auto-synthesized env alias for the `enableGlobalVirtualStore` setting),
    so GitHub Actions' inherited `CI=true` environment does not change
    whether aube runs with GVS enabled or disabled.
    
    ## Benchmark changes
    
    Public ratios: warm installs vs Bun 4x -> 7x; warm installs vs pnpm 9x
    -> 11x; repeat test vs Bun 5x -> 4x; repeat test vs pnpm 33x -> 28x.
    
    | Benchmark | aube | bun | pnpm | npm | yarn |
    | --- | ---: | ---: | ---: | ---: | ---: |
    | Fresh install (warm cache) | 246ms -> 332ms (+35%) | 1011ms -> 2242ms
    (+122%) | 2227ms -> 3500ms (+57%) | 6091ms -> 10658ms (+75%) | 11193ms
    -> 19532ms (+75%) |
    | Fresh install (cold cache) | 4595ms -> 6437ms (+40%) | 4517ms ->
    5105ms (+13%) | 4982ms -> 5287ms (+6%) | 7588ms -> 8583ms (+13%) |
    24904ms -> 27515ms (+10%) |
    | CI install (warm cache, GVS disabled) | 1174ms -> 930ms (-21%) |
    1530ms -> 1447ms (-5%) | 2049ms -> 2475ms (+21%) | 6287ms -> 6909ms
    (+10%) | 11538ms -> 13763ms (+19%) |
    | CI install (cold cache, GVS disabled) | 4273ms -> 4364ms (+2%) |
    4034ms -> 4083ms (+1%) | 4940ms -> 5380ms (+9%) | 7701ms -> 8269ms (+7%)
    | 25067ms -> 28609ms (+14%) |
    | npm install && npm run test | 9ms -> 12ms (+33%) | 41ms -> 50ms (+22%)
    | 300ms -> 340ms (+13%) | 678ms -> 798ms (+18%) | 364ms -> 422ms (+16%)
    |
    | Add dependency | 696ms -> 957ms (+38%) | 1052ms -> 1478ms (+40%) |
    2343ms -> 3163ms (+35%) | 6725ms -> 8214ms (+22%) | 12943ms -> 14888ms
    (+15%) |
    
    **Review the numbers** before merging — if anything looks wildly off vs.
    the previous release, investigate before landing. Hermetic proxy jitter
    or an npmjs uplink hiccup can occasionally skew results.
    
    Once merged to main, the updated bench results flow into the next
    `release-plz-pr` run automatically.
    
    ---
    Generated by the `bench-refresh` workflow.
    
    Co-authored-by: release-plz[bot] <release-plz+bot@users.noreply.github.com>
    mise-en-dev and release-plz[bot] authored May 2, 2026
    Configuration menu
    Copy the full SHA
    b2a7c3f View commit details
    Browse the repository at this point in the history
  8. fix(install): allow workspace members without version field (#480)

    ## Summary
    Real-world failure:
    [tuist/tuist#10584](tuist/tuist#10584) tried to
    swap pnpm for aube and CI hit `workspace package noora at noora has no
    \`version\` field` from
    [crates/aube/src/commands/install/mod.rs:1869](crates/aube/src/commands/install/mod.rs:1869).
    pnpm accepts the same repo without complaint.
    
    The unconditional check predates the resolver's current `workspace:*` /
    `workspace:^` / `workspace:~` / bare-`*` fallback. The comment's
    collision rationale ("two unversioned members under one dep_path")
    doesn't hold either: `dep_path` keys on `(name, version)` and two
    members have distinct names by construction.
    
    Fix: fall back to `"0.0.0"` when `version` is absent. `workspace:*` /
    `workspace:^` / `workspace:~` / bare-`*` siblings still link locally
    (those branches in `resolve_workspace` accept any ws version
    regardless). A specific range like `workspace:^2.0.0` against an
    unversioned member correctly fails to satisfy, matching pnpm.
    
    ## Test plan
    - [x] `cargo test` (full suite green)
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] `mise run test:bats test/workspace.bats` — 19 pass, including the
    new regression #4
    - [ ] CI green on push
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    489b4ad View commit details
    Browse the repository at this point in the history
  9. feat(cli): warn when aube update --depth is set (#473)

    ## Summary
    
    - `aube update --depth N` was parsed for pnpm parity and silently
    ignored. Now it emits a one-line warning pointing at `rm aube-lock.yaml
    && aube install` for the only useful semantic case (`--depth Infinity` /
    refresh transitives).
    - Regression-guarded by a new bats case asserting both the warning's
    shape and that bare `aube update` stays silent.
    - Updates [test/PNPM_TEST_IMPORT.md](test/PNPM_TEST_IMPORT.md) to mark
    the update.ts:599 row "won't-support — landed."
    
    ## Why now
    
    Triaged won't-support in #471: intermediate values (`--depth 1`,
    `--depth 2`) have semantics that even pnpm users get tripped up on. aube
    only ever refreshes direct deps (pnpm's `--depth 0` default), so any
    other value is a no-op the user has no way to discover. The warning
    closes that loop.
    
    ## Test plan
    
    - [x] `cargo build --workspace` — green
    - [x] `cargo clippy --all-targets -- -D warnings` — clean
    - [x] `cargo fmt --check` — clean
    - [x] `mise run test:bats test/pnpm_update.bats` — 23/23 pass (+1 new
    `aube update --depth: parsed-but-warn`)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Low Risk**
    > Low risk: behavior is unchanged except when `--depth` is provided,
    where an extra stderr warning could affect scripts that assert on exact
    output.
    > 
    > **Overview**
    > `aube update` now prints a one-line warning when `--depth` is
    provided, clarifying that the flag is ignored and pointing users to `rm
    aube-lock.yaml && aube install` for a full transitive refresh.
    > 
    > The warning is emitted only once even under recursive workspace
    fanout, and CLI/help text + generated docs are updated accordingly; new
    bats tests assert the warning behavior and that plain `aube update`
    stays silent.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    90bbef6. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
    3 people authored May 2, 2026
    Configuration menu
    Copy the full SHA
    6e00055 View commit details
    Browse the repository at this point in the history
  10. feat(install): persist unreviewed-builds warning across repeat instal…

    …ls (#476)
    
    ## Summary
    
    - Repeat `aube install` runs short-circuit through the warm path, which
    returns before the unreviewed-builds warning fires. Users were silently
    losing the nudge to run `aube approve-builds` between installs — exactly
    the failure mode `strictDepBuilds` exists to backstop.
    - Persist the unreviewed-build spec keys in `.aube-state` (new
    `unreviewed_builds: Vec<String>` on both `InstallState` and
    `FreshnessState`) and re-emit the same `tracing::warn!` line from both
    warm-path branches (`missing_lockfile_restore_eligible` and
    `warm_path_eligible`) when the set is non-empty. The allowBuilds
    review-placeholder write stays on the full pipeline, so warm installs
    nudge without re-seeding.
    - Ports `pnpm/test/install/lifecycleScripts.ts:245` ('warning is shown
    when an install with --no-frozen-lockfile reuses an existing
    node_modules with ignored build scripts'). Wording substituted to aube's
    canonical `ignored build scripts for N package(s)` per the existing
    divergence note.
    
    Background: triaged as `support` in #471 (`test/PNPM_TEST_IMPORT.md`).
    Companion to #472, which landed the bare-shorthand parser fix.
    
    ## Test plan
    
    - [x] `cargo build`
    - [x] `cargo test --workspace` — 362+239+169+... all green; the two new
    state round-trip tests
    (`unreviewed_builds_roundtrip_persists_into_fresh_state`,
    `unreviewed_builds_default_when_field_missing_in_state`) confirm legacy
    state files load via the serde default and the new field round-trips
    through the FreshnessState sidecar.
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] `./test/bats/bin/bats test/lifecycle_scripts.bats` — 34/34 ok
    including the new `aube install re-emits ignored-build-scripts warning
    on repeat install`
    - [x] `./test/bats/bin/bats test/approve_builds.bats` — 10/10 ok
    (regression check on the existing warning emission path)
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Touches install warm-path short-circuit logic and the persisted
    `.aube-state` schema; regressions could cause incorrect warning behavior
    or state incompatibilities on upgrade, though the change is additive and
    guarded with serde defaults.
    > 
    > **Overview**
    > Repeat `aube install` runs that short-circuit via the warm path (and
    the missing-lockfile-restore fast path) now **re-emit the ignored build
    scripts warning** by reading a persisted `unreviewed_builds` list from
    `.aube-state`.
    > 
    > The install pipeline now computes the unreviewed-build set once,
    stores it in `InstallState`/`FreshnessState`, and centralizes warning
    formatting in `emit_unreviewed_builds_warning`; new unit tests cover
    round-tripping/defaulting of the new state field, and a new Bats test
    asserts the warning appears on a repeat no-op install.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    daf7bf0. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    3876bd0 View commit details
    Browse the repository at this point in the history
  11. fix(cli): wrap doc comments so -h help stays one line per flag (#478)

    ## Summary
    
    clap merges multi-line `///` doc comments into one paragraph for short
    help (`-h`) and only splits on the first blank `///` line to produce a
    separate first-line summary. Several flags across the CLI were written
    as one continuous paragraph, so `aube <cmd> -h` rendered them as 120+
    char lines that wrapped awkwardly on standard terminals.
    
    This PR splits each offender's doc comment into a short summary line +
    blank `///` separator + the longer prose — same shape `update --latest`
    already uses (precedent: #473).
    
    ## Flags fixed
    
    - `add -g, --global`, `--no-save`, `--save-catalog`,
    `--save-catalog-name`, `-w, --workspace`
    - `approve-builds [PKG]...` (positional doc), plus the subcommand
    summary on `aube --help`
    - `audit --ignore-unfixable`
    - `check` subcommand summary on `aube --help`
    - `clean -l, --lockfile` (also reflects to `purge`)
    - `config list --all`, `--json`
    - `dlx -c, --shell-mode`
    - `find-hash --json`
    - `init --init-package-manager`
    - `install --fix-lockfile`, `--force`, `--lockfile-dir`,
    `--merge-git-branch-lockfiles`, `--resolution-mode`,
    `--verify-store-integrity`
    - `link -g, --global`
    - `list --long`
    - `patch --ignore-existing`
    - `patch-remove [PACKAGES]...`
    - `publish --force`, `--ignore-scripts`, `--json`
    - `query <SELECTOR>`
    - `remove -w, --workspace`
    - `run --parallel`
    - `store status` subcommand summary
    - `update -L, --latest` (same fix as #473, included here since that PR
    is still open)
    - `version [NEW_VERSION]`
    - `view <PACKAGE>`, `[FIELD]`
    
    ## Validation
    
    - `cargo build --workspace` — clean
    - `cargo clippy --all-targets -- -D warnings` — clean
    - `cargo fmt --check` — clean
    - Audit before/after: lines >120 chars went from 6 to 0; lines >115 from
    1 to 0
    - Sample audit script: `for cmd in $(aube --help | grep -E '^ [a-z]' |
    awk '{print $1}'); do aube "$cmd" -h 2>&1 | awk 'length > 120'; done |
    grep -vE '\[possible values:|\[aliases:'`
    
    CLI docs regenerated via `mise run render` (`aube.usage.kdl`,
    `docs/cli/commands.json`, `docs/cli/*.md`).
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] Re-ran `aube <cmd> -h` for every fixed subcommand to confirm short
    help is now one clean summary line and `aube <cmd> --help` still shows
    the full prose
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Low Risk**
    > Pure CLI help-text/doc updates plus regenerated docs/command metadata;
    no behavior or parsing logic changes, so risk is low aside from
    potential wording/regeneration drift.
    > 
    > **Overview**
    > Tightens CLI `-h` output by splitting many commands/flags’
    descriptions into a short first-line summary plus separate long help,
    avoiding overly long wrapped lines in terminal help.
    > 
    > Updates the usage spec (`aube.usage.kdl`), clap doc comments across
    multiple command arg structs, and regenerates the derived CLI docs
    (`docs/cli/*.md`) and `docs/cli/commands.json` to reflect the new
    short/long help text (including `approve-builds`, `check`, `publish`,
    `install`, `run`, etc.).
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    3e243a3. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 2, 2026
    Configuration menu
    Copy the full SHA
    71ec8ef View commit details
    Browse the repository at this point in the history
  12. feat(cli): aube rebuild <pkg> targets a specific package (#477)

    ## Summary
    - `aube rebuild` now takes zero or more positional package names. With
    no args, the existing whole-policy rebuild runs unchanged (back-compat).
    With names, only the named deps' lifecycle scripts run, the build policy
    is bypassed for those deps, and the root preinstall / install /
    postinstall / prepare hooks are skipped.
    - Match is by graph `name` (the in-tree alias when one is configured),
    so `aube rebuild my-alias` works for a manifest entry like `"my-alias":
    "npm:real-pkg@1.0"`, matching pnpm. Unknown names hard-error and list
    every unmatched entry.
    - Workspace `aube --filter <something> rebuild <pkg>` composes: each
    filtered importer runs only the named deps' scripts.
    
    Triage decision in PR #471 (lifecycleScripts.ts:282).
    
    ## Test plan
    - [x] `cargo build`
    - [x] `cargo test`
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] `mise run test:bats test/rebuild.bats` — 15/15 pass, including 4
    new selective-mode tests (single dep, two deps, policy-bypass,
    unmatched-name error)
    - [x] `mise run test:bats test/lifecycle_scripts.bats` — 33/33 pass
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Extends lifecycle-script execution paths and adds an explicit
    policy-bypass mode, which could change when/which dependency scripts run
    if used in automation. Scope is contained to `rebuild` and guarded by
    explicit package arguments plus new error checks for missing
    lockfile/unmatched names.
    > 
    > **Overview**
    > `aube rebuild` now accepts optional positional package names; when
    provided, it **runs lifecycle scripts only for the named installed
    dependencies**, **skips all root hooks**, and **bypasses the
    `allowBuilds`/`onlyBuiltDependencies` policy** as an explicit opt-in.
    > 
    > The dependency lifecycle runner (`run_dep_lifecycle_scripts`) was
    extended with a selective mode filter (matching by lockfile graph
    `name`/alias), and `rebuild` now hard-errors on missing lockfile or
    unknown package names. CLI usage/docs were updated and new Bats tests
    cover selective rebuild behavior, policy bypass, and error cases.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    7a72e31. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
    3 people authored May 2, 2026
    Configuration menu
    Copy the full SHA
    56a5651 View commit details
    Browse the repository at this point in the history
  13. feat(cli): rewrite manifest specifier on update without --latest (#479)

    ## Summary
    
    - Add `updateRewritesSpecifier` setting (default `true`) — restores pnpm
    parity for the most common `aube update <pkg>` use case.
    - With the setting on, `aube update <pkg>` (no `--latest`) rewrites
    caret/tilde manifest ranges (`^X.Y.Z` / `~X.Y.Z`) to track the new
    in-range max. Other shapes (`>=`, `1.x`, exact pins, dist-tags, git,
    `workspace:`) stay frozen.
    - Set `update-rewrites-specifier=false` in `.npmrc` (or env equivalent)
    to keep aube's prior frozen-manifest behavior.
    - Ports pnpm/test/update.ts:51 (single-project) and
    pnpm/test/update.ts:95 (recursive) plus an opt-out test.
    - Triage decision recorded in #471 — drop-in pnpm parity wins over the
    cosmetic-coin-flip rationale.
    
    The semantic divergence this closes:
    
    - Existing aube: `^1.2.0` in manifest + `1.2.0` lockfile + `1.2.5`
    in-range max → `aube update foo` bumps lockfile to `1.2.5`, leaves
    manifest at `^1.2.0`.
    - New aube (default): same flow rewrites manifest to `^1.2.5` to track
    the resolved version. Matches pnpm.
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo test --workspace` (all crates green)
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] `mise run test:bats test/pnpm_update.bats` (25/25 passing,
    including 3 new tests)
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Changes `aube update` to potentially rewrite `package.json` ranges (in
    addition to the lockfile) based on a new default-on setting, which can
    affect repo diffs and upgrade semantics for users relying on frozen
    manifests.
    > 
    > **Overview**
    > `aube update <pkg>` (without `--latest`) can now *optionally* rewrite
    `package.json` caret/tilde ranges to the newly resolved in-range
    version, aligning behavior with pnpm; non-caret/tilde specs (dist-tags,
    exact pins, raw ranges, git, `workspace:`) remain unchanged.
    > 
    > Adds a new `updateRewritesSpecifier` setting (default `true`,
    configurable via env/.npmrc) to control this cosmetic manifest rewrite,
    updates settings docs accordingly, and adds Bats coverage for the
    rewrite path, opt-out behavior, and dist-tag preservation (including
    recursive `update -r`).
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    8c235ba. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com>
    3 people authored May 2, 2026
    Configuration menu
    Copy the full SHA
    c910bd8 View commit details
    Browse the repository at this point in the history

Commits on May 3, 2026

  1. test: port three test-only pnpm tests + retriage two (#481)

    ## Summary
    
    Ports three of the five test-only pnpm tests called out in the triage
    brief, retriages the other two with empirical evidence:
    
    | pnpm test | item | landed | file |
    |---|---|---|---|
    | `misc.ts:567` (git URL with hash containing slash) | 1 | yes |
    `test/pnpm_install_misc_slow.bats` (network-gated, new file) |
    | `hooks.ts:68` (readPackage returning undefined fails) | 2 | no —
    divergence | already-skipped at `pnpm_install_hooks.bats:310`; triage
    retriaged to won't-support |
    | `hooks.ts:580` (readPackage during remove in workspace) | 3 | yes |
    `test/pnpm_install_hooks.bats` |
    | `lifecycleScripts.ts:108` (rollback-on-build-failure) | 4 | yes |
    `test/lifecycle_scripts.bats` + new
    `@pnpm.e2e/aube-test-failing-install` fixture |
    | `lifecycleScripts.ts:179, 200` (verify-deps + preinstall sub-aube) | 5
    | no — real bug | retriaged to support; needs aube fix before port |
    
    ### Items not ported (retriaged with evidence)
    
    **Item 2 (`hooks.ts:68`)**: triage said aube errors with `readPackage
    response missing pkg`. Empirically, the IPC shim at
    `crates/aube/src/pnpmfile.rs:629` falls back to the original pkg via `if
    (out && typeof out === 'object') result = out;` and writes back `{ id,
    pkg: <original> }`. The Rust side at `pnpmfile.rs:809` sees `pkg`
    present and continues with the original manifest — install succeeds
    rather than fails. The existing skipped test at
    `pnpm_install_hooks.bats:310` (added in a previous PR) already documents
    this divergence correctly. Triage doc updated to move this to Tier 3
    (won't-support / divergence).
    
    **Item 5 (`lifecycleScripts.ts:179, 200`)**: triage said aube has the
    parent-set recursion guard, but the only "parent-set" mechanism at
    `run.rs:536` preserves `INIT_CWD`, not lifecycle context. Empirically:
    - `verifyDepsBeforeRun=error` + `preinstall: aube run sayHello` → inner
    `aube run` exits with `dependencies need install before run: install
    state not found` (state isn't written until linking finishes).
    - `verifyDepsBeforeRun=install` → inner `aube run` triggers
    `ensure_installed` → `install::run`, which deadlocks on the project lock
    the outer install holds (`Waiting for another aube process to finish in
    this project...`, observed via 60s timeout).
    
    Per the brief: "If porting surfaces a real recursion bug, STOP — don't
    fight the bug." Aube fix needed: skip `ensure_installed` when
    `npm_lifecycle_event` is set in the env (matches npm/pnpm's "no
    verify-deps inside lifecycle scripts" contract). Tracked in the
    retriaged entry in the triage doc.
    
    ### New offline fixture
    
    `test/registry/storage/@pnpm.e2e/aube-test-failing-install/` (1.0.0,
    `install: exit 1`) — minimal hand-built tarball + packument JSON,
    mirrors the structure of the existing `@pnpm.e2e/install-script-example`
    fixture. Used by the rollback test only.
    
    ### References
    
    - Triage doc: `test/PNPM_TEST_IMPORT.md` (see PR #471 for triage
    context).
    - Network-test convention: PR #472 / `test/pnpm_update_slow.bats`.
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo clippy --all-targets -- -D warnings` (zero warnings)
    - [x] `cargo fmt --check` (clean — no Rust source changes)
    - [x] `mise run test:bats test/pnpm_install_hooks.bats` — 21 passing (3
    documented skips), including the new readPackage-during-remove test
    - [x] `mise run test:bats test/lifecycle_scripts.bats` — 34 passing,
    including the new rollback test
    - [x] `AUBE_NETWORK_TESTS=1 ./test/bats/bin/bats
    test/pnpm_install_misc_slow.bats` — passes (network required)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Low Risk**
    > Test-only changes add new bats coverage (including a network-gated
    case) plus an offline fixture; no production Rust code paths are
    modified, so risk is limited to CI/test stability and reliance on
    external GitHub availability for the slow test.
    > 
    > **Overview**
    > Adds three new pnpm parity ports to the bats suite: a network-gated
    `misc.ts:567` install-from-git regression test (new
    `pnpm_install_misc_slow.bats`), a workspace `aube remove` path hook
    regression test in `pnpm_install_hooks.bats`, and a lifecycle
    rollback/retry contract test in `lifecycle_scripts.bats` backed by a new
    always-failing offline fixture `@pnpm.e2e/aube-test-failing-install`.
    > 
    > Updates `PNPM_TEST_IMPORT.md` to mark the newly-ported items as done
    and to retriage two previously “test-only” gaps: `readPackage` returning
    `undefined` is now documented as an intentional aube divergence, and the
    `verify-deps-before-run` + lifecycle-subcommand case is reclassified as
    a real aube bug requiring a fix before porting.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    6072789. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 3, 2026
    Configuration menu
    Copy the full SHA
    d960e6d View commit details
    Browse the repository at this point in the history
  2. feat(cli): support git specs in aube add (#483)

    ## Summary
    
    - Extends `parse_pkg_spec` in
    [crates/aube/src/commands/add.rs](crates/aube/src/commands/add.rs) to
    detect git specs via `aube_lockfile::parse_git_spec` and route them
    through a new non-packument branch — verbatim spec written to
    `package.json`, resolver dispatches the git path on next install.
    Recognized forms: bare GitHub shorthand (`kevva/is-negative`),
    `github:`/`gitlab:`/`bitbucket:` prefixes, full `git+ssh` / `git+https`
    URLs, and the alias form (`my-alias@kevva/is-negative`).
    - Manifest-key derivation when no alias given: repo segment of the clone
    URL (e.g. `kevva/is-negative` → `is-negative`). 90% solution; pass an
    alias when the upstream `package.json` `name` differs from the repo
    segment.
    - Restores the dropped `kevva/is-negative` assertions in the
    network-gated ports of `update.ts:14, 143, 170, 197` in
    [test/pnpm_update_slow.bats](test/pnpm_update_slow.bats). PR #472 landed
    the parser branch and the `update --latest` skip-non-registry guard;
    this is the CLI-side counterpart so the four assertions can be exercised
    end-to-end.
    - Updates [test/PNPM_TEST_IMPORT.md](test/PNPM_TEST_IMPORT.md) to
    promote the update.ts:14/143/170/197 entry from "partially landed" to
    "landed".
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo test --workspace` — 365+ workspace tests pass; 5 new unit
    tests cover bare GitHub shorthand, `github:` protocol, git URL with
    slash-bearing fragment, alias form, and scoped-name non-git negative
    case
    - [x] `cargo clippy --all-targets -- -D warnings` — no warnings
    - [x] `cargo fmt --check`
    - [x] `mise run test:bats test/pnpm_update.bats` — pre-existing 17/22
    results unchanged (3 unrelated `<pkg>@latest` failures match `main`
    baseline)
    - [x] `mise run test:bats test/add.bats` — all 25 tests pass
    - [x] `mise run test:bats test/update.bats`, `test/catalogs.bats` — all
    pass
    - [x] `mise run test:bats test/pnpm_update_slow.bats` — 4 tests properly
    gated, skip without `AUBE_NETWORK_TESTS=1`
    - [ ] CI exercises the slow file with `AUBE_NETWORK_TESTS=1`
    
    🤖 Generated with [Claude Code](https://claude.com/claude-code)
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Changes `aube add`’s dependency parsing and manifest-writing flow to
    bypass registry resolution for git specs; mistakes here could
    misclassify specs or skip packument fetching, affecting added
    dependencies.
    > 
    > **Overview**
    > `aube add` now detects git dependency specifiers (bare `user/repo`,
    `github:`/`gitlab:`/`bitbucket:` prefixes, and full `git+...` URLs,
    including `alias@<git-spec>`), and routes them through a non-registry
    path.
    > 
    > For git specs it **skips packument fetch + catalog logic**, derives a
    default manifest key from the repo URL when no alias is provided, and
    writes the user’s verbatim specifier into `package.json` so the resolver
    handles git resolution on install.
    > 
    > Adds unit tests for git spec parsing/edge cases and expands the
    network-gated `pnpm_update_slow.bats` ports to cover `aube add
    kevva/is-negative` end-to-end; updates the pnpm test import tracking doc
    accordingly.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    a56d92b. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 3, 2026
    Configuration menu
    Copy the full SHA
    5f05906 View commit details
    Browse the repository at this point in the history
  3. feat(cli): support yaml-only workspace roots in list/run/install/quer…

    …y/why (#486)
    
    ## Summary
    
    - Adds `project_or_workspace_root()` (`crates/aube/src/dirs.rs`) — falls
    back to the workspace root when no ancestor has `package.json` but a
    `pnpm-workspace.yaml` / `aube-workspace.yaml` is present.
    - Routes `aube install`, `aube list`, `aube run -r`, `aube query`, `aube
    why` through it. `install` synthesizes an empty `PackageJson` when the
    workspace root has no manifest; the other four commands read the
    lockfile directly and skip the manifest read on yaml-only roots.
    - Single-project commands (`add`, `remove`, root-only `run <script>`,
    `ci`, `version`, …) still call `project_root()` and continue to
    hard-error without a manifest.
    
    Use case: pure-coordinator monorepos (Turborepo defaults and several
    large OSS repos) with `pnpm-workspace.yaml` at the root and no root
    `package.json`. Today the five commands above hard-error at
    `project_root()`. With this change, they operate against the workspace
    as a whole.
    
    Triage decision in
    [test/PNPM_TEST_IMPORT.md](https://github.com/endevco/aube/blob/main/test/PNPM_TEST_IMPORT.md)
    (monorepo/index.ts:56) → recorded as "landed" alongside this PR.
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo clippy --all-targets -- -D warnings`
    - [x] `cargo fmt --check`
    - [x] `cargo test --workspace`
    - [x] `mise run test:bats test/yaml_only_root.bats` — 8/8 pass
    (install/list/run -r/query/why against yaml-only root + 3 single-project
    hard-error guards)
    - [x] `mise run test:bats test/install.bats test/list.bats
    test/run.bats` — no regressions
    
    *This PR was generated by Claude.*
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Changes root-resolution and manifest-loading behavior for `install`
    and multiple workspace commands, which can affect where lockfiles/stores
    are read/written and how monorepos are interpreted. Risk is mitigated by
    being scoped to workspace root detection and adding targeted bats
    coverage.
    > 
    > **Overview**
    > Adds support for *yaml-only “coordinator” workspaces* (e.g.,
    `pnpm-workspace.yaml` at repo root with no root `package.json`) across
    workspace-scoped commands.
    > 
    > Root resolution is updated via new `dirs::{project_or_workspace_root,
    workspace_or_project_root}` so `install`/`patch` prefer the workspace
    root (shared `aube-lock.yaml`/`.aube/`), while read-style commands
    (`list`, `query`, `why`) can fall back to a workspace root when no
    project manifest is present. Commands that previously unconditionally
    read `<root>/package.json` now use `load_manifest_or_default` to
    synthesize an empty manifest when absent.
    > 
    > Updates CLI error messaging for “no root found” cases and adds
    `test/yaml_only_root.bats` to cover install/list/run -r/query/why
    behavior on yaml-only roots while asserting single-project commands
    still fail without a manifest.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    190f201. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    ---------
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 3, 2026
    Configuration menu
    Copy the full SHA
    070252e View commit details
    Browse the repository at this point in the history
  4. feat(cli): support link: and file: specs in aube add (#487)

    ## Summary
    
    - Adds a non-packument branch in
    [parse_pkg_spec](crates/aube/src/commands/add.rs:175) for `file:` /
    `link:` local-path specs (`file:./pkg`, `link:../sibling`,
    `file:./bundle.tgz`). Sits alongside the workspace-spec fork; mirrors
    the structural shape of #483 (git specs).
    - Manifest-key derivation: alias when given (`my-alias@file:./pkg`),
    otherwise the basename of the path (with `.tgz` / `.tar.gz` stripped).
    Verbatim spec is written into `package.json` and the resolver's existing
    local branch
    ([is_non_registry_specifier](crates/aube-resolver/src/local_source.rs:185))
    dispatches the install on next pass.
    - Skips packument fetch and catalog logic for local specs (catalogs are
    for registry deps). `aube add file:./pkg` no longer 404s on the
    registry.
    
    Sibling to PR #483 (git specs). Closes the local-path-spec followup that
    #483's review surfaced. The new branch is parallel to the git-spec
    branch in `parse_pkg_spec` — when #483 lands the two branches sit
    side-by-side without nesting.
    
    ## Test plan
    
    - [x] `cargo build --workspace`
    - [x] `cargo test --workspace` — 9 new unit tests in
    `commands::add::tests` cover `file:` / `link:` relative + absolute,
    tarball-basename derivation, alias forms, scoped/git non-collisions, and
    the bare `file:` + alias edge case
    - [x] `cargo clippy --all-targets -- -D warnings` — clean
    - [x] `cargo fmt --check`
    - [x] `mise run test:bats test/add.bats` — 28 tests pass (3 new offline
    tests for `file:` / `link:` / aliased `file:`)
    - [x] `mise run test:bats test/local_deps.bats` — regression check, 10
    tests pass
    - [x] `mise run test:bats test/pnpm_install_misc.bats` — regression
    check, 30 tests pass
    
    <!-- CURSOR_SUMMARY -->
    ---
    
    > [!NOTE]
    > **Medium Risk**
    > Extends `aube add` parsing/routing to treat `file:`/`link:` (and
    scoped/aliased variants) as non-registry deps, which could impact how
    ambiguous specs are classified and when registry packument fetches are
    skipped.
    > 
    > **Overview**
    > `aube add` now supports `file:` and `link:` local-path dependencies by
    routing them through the same *non-registry* flow as git specs: skip
    packument fetch/catalog logic and write the verbatim spec into
    `package.json` for the resolver to handle.
    > 
    > This introduces local-spec parsing with basename-based manifest key
    derivation (including stripping `.tgz`/`.tar.gz`/`.tar`), supports alias
    and `@scope/alias@<spec>` forms for both git and local specs, updates
    the packument-fetch skip guards accordingly, and adds unit + bats
    coverage for the new behaviors.
    > 
    > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit
    aba32ba. Bugbot is set up for automated
    code reviews on this repo. Configure
    [here](https://www.cursor.com/dashboard/bugbot).</sup>
    <!-- /CURSOR_SUMMARY -->
    
    Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
    jdx and claude authored May 3, 2026
    Configuration menu
    Copy the full SHA
    579eb0e View commit details
    Browse the repository at this point in the history
  5. chore: release v1.7.0 (#466)

    ## 🤖 New release: `v1.7.0`
    
    This PR bumps the workspace to `v1.7.0` and regenerates CHANGELOG
    entries, `aube.usage.kdl`, the CLI docs, and the benchmark results.
    
    See the diff for the full set of changes, or the per-crate
    `CHANGELOG.md` files for the release notes that will ship.
    
    ---
    Generated by the `release-plz-pr` workflow.
    
    Co-authored-by: release-plz[bot] <release-plz+bot@users.noreply.github.com>
    mise-en-dev and release-plz[bot] authored May 3, 2026
    Configuration menu
    Copy the full SHA
    b3ec965 View commit details
    Browse the repository at this point in the history
Loading