Skip to content

feat(cli): support yaml-only workspace roots in list/run/install/query/why#486

Merged
jdx merged 9 commits intomainfrom
worktree-agent-a4274114162f5d88b
May 3, 2026
Merged

feat(cli): support yaml-only workspace roots in list/run/install/query/why#486
jdx merged 9 commits intomainfrom
worktree-agent-a4274114162f5d88b

Conversation

@jdx
Copy link
Copy Markdown
Contributor

@jdx jdx commented May 2, 2026

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 (monorepo/index.ts:56) → recorded as "landed" alongside this PR.

Test plan

  • cargo build --workspace
  • cargo clippy --all-targets -- -D warnings
  • cargo fmt --check
  • cargo test --workspace
  • 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)
  • mise run test:bats test/install.bats test/list.bats test/run.bats — no regressions

This PR was generated by Claude.


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.

Reviewed by Cursor Bugbot for commit 190f201. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented May 2, 2026

Greptile Summary

This PR adds yaml-only workspace root support to aube install, list, run -r, query, and why by introducing two new resolution helpers (project_or_workspace_root and workspace_or_project_root) and a load_manifest_or_default fallback that synthesizes an empty PackageJson when no root package.json exists. Single-project commands (add, remove, root-only run <script>) are deliberately left on the hard-erroring project_root() path.

The root-resolution priorities are consistent across commands: install, patch, and main.rs settings resolution all use workspace-first (workspace_or_project_root), while the read-only commands use project-first (project_or_workspace_root). The ensure_installed freshness check already used the same workspace-first ordering, so install and its auto-install guard remain in sync.

Confidence Score: 5/5

Safe to merge — logic is correct, previous review concerns are addressed, and the only finding is a stale comment.

All three concerns raised in prior review threads (silent fallback, misleading error message, install priority reversal) are correctly addressed in this revision. The workspace_or_project_root helper maintains workspace-first precedence consistent with the old guarded logic and with ensure_installed. The load_manifest_or_default fallback is clean and narrow in scope. The only remaining issue is a stale inline comment in run_script_filtered (P2).

No files require special attention.

Important Files Changed

Filename Overview
crates/aube/src/dirs.rs Adds project_or_workspace_root (project-first, workspace fallback) and workspace_or_project_root (workspace-first, project fallback), plus a shared no_root_error helper with an accurate error message covering both yaml markers.
crates/aube/src/commands/mod.rs Adds load_manifest_or_default which synthesizes an empty PackageJson when package.json is absent; used by all five workspace-scoped commands.
crates/aube/src/commands/install/mod.rs Switches root resolution to workspace_or_project_root (workspace-first, same precedence as before but now accepting yaml-only roots) and reads the manifest via load_manifest_or_default to synthesize an empty manifest for yaml-only coordinator roots.
crates/aube/src/commands/run.rs Adds a None if !filter.is_empty() branch to accept yaml-only workspace roots for filtered/recursive runs, with an explicit error when neither a project root nor a workspace root is found. One stale comment in run_script_filtered remains.
crates/aube/src/commands/list.rs Switches to project_or_workspace_root and load_manifest_or_default; read_from logic for lockfile location is unchanged and correctly handles yaml-only roots.
crates/aube/src/commands/query.rs Switches to project_or_workspace_root and load_manifest_or_default; no logic changes beyond yaml-only root support.
crates/aube/src/commands/why.rs Switches both the unfiltered and filtered paths to load_manifest_or_default; no logic regressions.
crates/aube/src/commands/patch.rs Switches to workspace_or_project_root so aube patch from a workspace member finds the shared .aube/ store; no manifest read at this level so yaml-only roots don't cause issues here.
crates/aube/src/main.rs Aligns run_install_command's settings resolution root to workspace_or_project_root, keeping it consistent with install::run's resolution.
test/yaml_only_root.bats New bats suite with 8 tests covering all five workspace-scoped commands on a yaml-only root plus three hard-error guards for single-project commands.
test/project_root_walk_up.bats Updated error-message assertion to match the new no_root_error text that now mentions both package.json and workspace yaml markers.
test/PNPM_TEST_IMPORT.md Updated triage doc to mark the yaml-only-root fault line as "landed" and note the remaining wording divergence for empty workspace output.

Fix All in Claude Code

Reviews (7): Last reviewed commit: "docs(cli): clarify patch.rs handles yaml..." | Re-trigger Greptile

Comment thread crates/aube/src/commands/run.rs
Comment thread crates/aube/src/dirs.rs Outdated
Comment thread crates/aube/src/commands/run.rs
Comment thread crates/aube/src/commands/list.rs Outdated
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 2, 2026

Benchmark changes

Versions:

  • pnpm: 11.0.3 -> 11.0.4

Public ratios: warm installs vs Bun 7x -> 10x; warm installs vs pnpm 11x -> 12x.

Benchmark aube bun pnpm
Fresh install (warm cache) 332ms -> 211ms (-36%) 2242ms -> 2189ms (-2%) 3500ms -> 2614ms (-25%)
CI install (warm cache, GVS disabled) 930ms -> 349ms (-62%) 1447ms -> 2235ms (+54%) 2475ms -> 2480ms (+0%)
CI install (cold cache, GVS disabled) 4364ms -> 3853ms (-12%) 4083ms -> 4278ms (+5%) 5380ms -> 5017ms (-7%)

190f201 vs 5f05906 | aube/bun/pnpm | 3 scenarios | 3 runs | 500mbit/50ms | generated by Codex.

@jdx jdx force-pushed the worktree-agent-a4274114162f5d88b branch from 70edb61 to d2f5b26 Compare May 2, 2026 22:08
jdx added a commit that referenced this pull request May 2, 2026
Two PR #486 review fixes:

- The previous commit flipped install root resolution to prefer the
  workspace root over the project root, which broke the legacy `cd
  packages/app && aube install` shape (the patch.bats workspace test
  hit "is-odd is not installed" because install ran at the workspace
  root rather than the member). Restore the legacy precedence: project
  first, fall back to the workspace root for yaml-only coordinator
  roots that have no sibling `package.json`. Members install into
  their own dir as before; only the bare yaml-only root takes the
  workspace path.
- `project_or_workspace_root` now mentions both `package.json` and
  the workspace yaml in its not-found error so users on a
  pure-coordinator monorepo see the actionable signal. Mirrored in
  install::run's local error path.

Addresses Greptile P2 + Cursor low-severity on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Addressed CI bats failure + Greptile P2 + Cursor low-severity:

CI patch.bats failure: the previous commit flipped install root resolution to prefer the workspace root, which broke cd packages/app && aube install (the test hit "is-odd is not installed" because install ran at the workspace root, not the member). Restored legacy precedence — project root first, fall back to workspace root only when no sibling package.json exists. The yaml-only-root case still works because the bare workspace root has no project root to take precedence.

Greptile P2 (misleading error in project_or_workspace_root): mentioned both package.json and the workspace yaml in the not-found error so users on pure-coordinator monorepos see the actionable signal. Mirrored in install::run's local error path.

Cursor low-severity (silent fallback in run.rs): reviewed; the existing code at run.rs:230-244 already returns a clear error via ok_or_else when both find_project_root and find_workspace_root are None. No change needed — the bot's read of "silently uses initial_cwd" appears to be from a different branch state.

Cursor low-severity (manifest-or-default duplication across 5 sites): noted but left in-place this round; refactoring 5 call sites into a load_manifest_or_default helper would expand scope beyond the bug-fix pass. Tracked as follow-up.

Validation: cargo build/clippy/fmt clean, mise run test:bats test/patch.bats test/yaml_only_root.bats 12/12.

Written with Claude.

@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Addressed Greptile P1 / Cursor low: `aube run -r` was silently falling back to `initial_cwd.clone()` when both `find_project_root` and `find_workspace_root` returned `None`. Replaced with a hard error matching `project_or_workspace_root`'s wording.

Verified locally: `cargo build` / `cargo clippy --all-targets -- -D warnings` / `cargo fmt --check` clean, full `patch.bats` passes (the test that was failing in CI before jdx's earlier fix).

Cursor's "duplicated manifest-or-default fallback pattern" (low severity) left as-is for now — five call sites is borderline for extraction and the inline form is easy to follow.

Written with Claude.

jdx added a commit that referenced this pull request May 2, 2026
The "check if package.json exists, load it or fall back to default"
pattern was copy-pasted across five call sites (install/mod.rs,
list.rs, query.rs, why.rs main + run_filtered). Extract a single
`load_manifest_or_default(root)` helper next to the existing
`load_manifest` so future changes to the fallback land in one place.

No behavior change.

Addresses Cursor low-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Addressed Cursor's last open low-severity finding: extracted load_manifest_or_default(root) next to the existing load_manifest in commands/mod.rs and replaced all five duplicated call sites (install/mod.rs, list.rs, query.rs, why.rs main + run_filtered). 36 lines deleted, 19 added; no behavior change.

cargo build/clippy/fmt clean, mise run test:bats test/yaml_only_root.bats test/list.bats 26/26.

Written with Claude.

Comment thread crates/aube/src/commands/install/mod.rs Outdated
Comment thread crates/aube/src/commands/install/mod.rs
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Three CI bats failures resolved (all from my last commit's install-precedence flip):

  • aube install from a workspace member resolves up to the workspace root and pnpm_monorepo_index.bats:512 (topological filter) — restored workspace-first install precedence so cd packages/app && aube install runs at the workspace root, matching pnpm parity. The agent's original design.
  • aube patch-commit works from workspace package with --workspace-root — the prior workspace-first install put the .aube/ store at the workspace root, but aube patch looked for it at the member level via project_root(). Mirror install's resolution (workspace-first, project fallback) so patch finds the shared store.
  • aube install errors clearly when no package.json exists in any ancestor — updated assertion to the new error wording from PR docs(test): triage every pnpm-test-import gap with explicit support decisions #471 ("no package.json or workspace yaml…").

Verified locally: mise run test:bats test/project_root_walk_up.bats test/workspace_member_install_walks_up.bats test/yaml_only_root.bats test/patch.bats test/pnpm_monorepo_index.bats all pass. cargo clippy/fmt clean.

Also rebased PR #483 against current main (it had merge conflicts on PNPM_TEST_IMPORT.md from #471's triage doc landing).

Written with Claude.

jdx added a commit that referenced this pull request May 2, 2026
Install and patch both want workspace-first precedence (so members
share the workspace's lockfile + `.aube/` store), which is the
opposite of `project_or_workspace_root`. Both call sites had the
inline match block duplicated. Extract `workspace_or_project_root`
next to the existing helper, share the no-root error builder, and
collapse both call sites.

No behavior change.

Addresses Cursor low-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Extracted workspace_or_project_root helper next to project_or_workspace_root and collapsed the duplicated inline match blocks in install::run and patch::run. Shared error builder. -41/+38 LOC, no behavior change. Written with Claude.

Comment thread crates/aube/src/main.rs Outdated
jdx added a commit that referenced this pull request May 2, 2026
`run_install_command` was using `project_or_workspace_root` (project-
first) to load `.npmrc` and workspace yaml, while `install::run`
itself uses `workspace_or_project_root` (workspace-first). When both
roots exist, the two diverged: settings loaded from the member's
config, install acted against the workspace root. Use the same
helper at both sites.

Addresses Cursor medium-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 2, 2026

Aligned run_install_command's settings-resolution to use workspace_or_project_root (matching install::run). When both project and workspace roots existed, the previous project_or_workspace_root would have loaded .npmrc from the member while install ran at the workspace — now both sites resolve to the same dir. Written with Claude.

Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit a88fba0. Configure here.

Comment thread crates/aube/src/commands/patch.rs
jdx and others added 9 commits May 2, 2026 19:24
…y/why

Adds `project_or_workspace_root()` to `crates/aube/src/dirs.rs` and
routes the five workspace-scoped commands through it so a pure-
coordinator monorepo (Turborepo defaults: `pnpm-workspace.yaml` at the
root, sub-packages under `packages/*`, no root `package.json`) works
end-to-end. `install` synthesizes an empty `PackageJson` for the root
when no manifest is on disk; the other four commands skip the root
manifest read in the same way.

Single-project commands (`add`, `remove`, root-only `run <script>`,
`ci`, `version`, …) keep using `project_root()` and still hard-error
on yaml-only roots because they need a manifest to act on.

Triage decision in PR #471 (test/PNPM_TEST_IMPORT.md, monorepo/index.ts:56).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Eight bats cases in `test/yaml_only_root.bats` exercise install/list
-r/run -r/query/why against a `pnpm-workspace.yaml` root with no
sibling `package.json`, and pin three single-project commands
(`add`/`remove`/root-only `run`) to their existing hard-error path.

Promotes the monorepo/index.ts:56 entry in `test/PNPM_TEST_IMPORT.md`
to "landed" and trims the related divergence note.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Two PR #486 review fixes:

- The previous commit flipped install root resolution to prefer the
  workspace root over the project root, which broke the legacy `cd
  packages/app && aube install` shape (the patch.bats workspace test
  hit "is-odd is not installed" because install ran at the workspace
  root rather than the member). Restore the legacy precedence: project
  first, fall back to the workspace root for yaml-only coordinator
  roots that have no sibling `package.json`. Members install into
  their own dir as before; only the bare yaml-only root takes the
  workspace path.
- `project_or_workspace_root` now mentions both `package.json` and
  the workspace yaml in its not-found error so users on a
  pure-coordinator monorepo see the actionable signal. Mirrored in
  install::run's local error path.

Addresses Greptile P2 + Cursor low-severity on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Greptile P1 / Cursor low: when `aube run -r` is invoked from a
directory with no `package.json` ancestor and no workspace root, the
filter branch was silently falling back to `initial_cwd.clone()`,
silently proceeding against an arbitrary directory. Replace the
`unwrap_or_else` with a hard error matching `project_or_workspace_root`'s
wording.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The "check if package.json exists, load it or fall back to default"
pattern was copy-pasted across five call sites (install/mod.rs,
list.rs, query.rs, why.rs main + run_filtered). Extract a single
`load_manifest_or_default(root)` helper next to the existing
`load_manifest` so future changes to the fallback land in one place.

No behavior change.

Addresses Cursor low-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Revert the install root-precedence flip from the previous commit —
the explicit `workspace_member_install_walks_up.bats` test enforces
pnpm-parity (install at the workspace root, not at the member). My
"project-first" flip broke that test plus the
`pnpm_monorepo_index.bats:512` topological-order port (running
`aube --filter=...<pkg>` from a member ran auto-install at the
member, which then tried to fetch workspace-internal package names
from the registry).

`aube patch` continued to use `project_root()` and looked for the
`.aube/` store at the member level — empty in a workspace install,
so `aube patch is-odd@3.0.1` from `packages/app/` reported "not
installed". Mirror the install resolution (workspace-first, project
fallback) so patch finds the shared store.

Update `project_root_walk_up.bats` for the new error wording from
the previous commit ("no package.json or workspace yaml found").

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Install and patch both want workspace-first precedence (so members
share the workspace's lockfile + `.aube/` store), which is the
opposite of `project_or_workspace_root`. Both call sites had the
inline match block duplicated. Extract `workspace_or_project_root`
next to the existing helper, share the no-root error builder, and
collapse both call sites.

No behavior change.

Addresses Cursor low-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
`run_install_command` was using `project_or_workspace_root` (project-
first) to load `.npmrc` and workspace yaml, while `install::run`
itself uses `workspace_or_project_root` (workspace-first). When both
roots exist, the two diverged: settings loaded from the member's
config, install acted against the workspace root. Use the same
helper at both sites.

Addresses Cursor medium-severity review on PR #486.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Note that `upsert_patched_dependency` already routes through
`config_write_target`, which lands the entry in the workspace yaml
when the resolved root has no `package.json`. Addresses Cursor
medium-severity feedback on PR #486 — flagged a hypothetical
silent-loss bug that doesn't actually occur.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@jdx jdx force-pushed the worktree-agent-a4274114162f5d88b branch from a88fba0 to 190f201 Compare May 3, 2026 00:28
@jdx
Copy link
Copy Markdown
Contributor Author

jdx commented May 3, 2026

Rebased onto current main (post-#475/#481/#483/#485). Conflict was on test/PNPM_TEST_IMPORT.md's triage table — resolved by taking main's post-481 retriages and updating the monorepo:56 row to "support — landed."

Three of the four review items were stale (already addressed by recent commits on this branch):

  • Greptile P1 (run.rs:241 silent fallback) — code now errors out via find_workspace_root's ok_or_else
  • Greptile P2 (dirs.rs misleading error) — error message now reads "no package.json or workspace yaml (...) found in ..."
  • Cursor low (run.rs:241 silent fallback) — same as Greptile P1, fixed
  • Cursor low (list.rs duplicated manifest-or-default pattern) — kept as-is per CLAUDE.md "smallest possible change, no refactoring unless asked"

The Cursor medium ("Patch from yaml-only workspace root breaks patch-commit") turned out to be a false alarm: upsert_patched_dependency already routes through config_write_target, which lands the entry in the workspace yaml when there's no package.json. Added a one-paragraph clarifying comment at patch.rs:42 so future readers don't trip on the same intuition.

Validation: cargo build/clippy/fmt clean, mise run test:bats test/patch.bats 4/4, mise run test:bats test/yaml_only_root.bats 8/8.

Written with Claude.

@jdx jdx merged commit 070252e into main May 3, 2026
18 checks passed
@jdx jdx deleted the worktree-agent-a4274114162f5d88b branch May 3, 2026 00:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant