diff --git a/.cursor/rules/learnings-index.mdc b/.cursor/rules/learnings-index.mdc index 6ff67a0..e5bce88 100644 --- a/.cursor/rules/learnings-index.mdc +++ b/.cursor/rules/learnings-index.mdc @@ -23,7 +23,7 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. bare `Then`/`Expect` statements are assertions; outside a transformed class they silently assert nothing. → .agents/skills/gem-rspock--rspock/ (ships in the rspock gem; linked by - `dev up` / `dev install-deps`) + `dev up` / `dev deps install`) ## process @@ -40,7 +40,7 @@ propose a retirement, a consolidation, or a glob-scoped sub-index split. - [architecture/module-map] What owns what: src/dev is the typed CLI core; each `lib/dev/` is one feature whose Accessor is its only CLI surface. → .cursor/skills/architecture/module-map/ -- [architecture/hook-points] `dev up` / `install-deps` / `dev plan` double +- [architecture/hook-points] `dev up` / `dev deps install` / `dev plan` double as the idempotent hygiene hooks (skill links, learnings sync): never raise, network only as a bounded pull before distribution. → .cursor/skills/architecture/hook-points/ diff --git a/.cursor/skills/architecture/hook-points/SKILL.md b/.cursor/skills/architecture/hook-points/SKILL.md index eb88ebe..4e6e8f2 100644 --- a/.cursor/skills/architecture/hook-points/SKILL.md +++ b/.cursor/skills/architecture/hook-points/SKILL.md @@ -2,12 +2,12 @@ name: hook-points description: >- MUST be used when adding side-work (links, syncs, renders) to dev up, - dev install-deps, or dev plan — the rules hygiene rides by. + dev deps install, or dev plan — the rules hygiene rides by. --- # dev hook points: hygiene rides, never blocks -`dev up` / `dev install-deps` (`Runner#install_locked_deps`) and every +`dev up` / `dev deps install` (`Runner#install_locked_deps`) and every `dev plan` invocation (`Plan::Accessor#run`) double as the refresh points for agent-facing hygiene: shipped-skill links, gem-skill links, the knowledge repo cache pull, and the org-invariants render + project link. diff --git a/.cursor/skills/learnings/command-runner-exec/SKILL.md b/.cursor/skills/learnings/command-runner-exec/SKILL.md index f61d0fc..eed7f8b 100644 --- a/.cursor/skills/learnings/command-runner-exec/SKILL.md +++ b/.cursor/skills/learnings/command-runner-exec/SKILL.md @@ -26,7 +26,7 @@ failed `dev up` as installed). Only outcome-independent work may move before the exec point. Right (dev#85): Runner sets `wait: true` on ExecutionContext for -`STAMPING_COMMANDS` (`up`, `install-deps`); CommandRunner then runs the +`STAMPING_COMMANDS` (`dev up`, `dev deps install`); CommandRunner then runs the child spawn-and-wait (`Kernel.system`) instead of exec-replace, raising `CommandFailedError` with the child's exit status on failure, which Runner turns into `Kernel.exit` — stamp only on success, exit code @@ -35,6 +35,6 @@ passthrough, no double process tree). Diagnostic signature of a missing wait: an exec-style provisioning command "succeeds" but the staleness gate keeps reporting "never installed" — fatal in a CI=true shell. -learned-from: dev#73 build pass (dev up never stamped; install-deps +learned-from: dev#73 build pass (dev up never stamped; dev deps install did); fixed by CommandRunner wait mode in dev#85 date: 2026-08-03 diff --git a/.gitignore b/.gitignore index c9c9252..3596f2e 100644 --- a/.gitignore +++ b/.gitignore @@ -33,5 +33,5 @@ tmp/ # knowledge repo's invariants) .cursor/rules/org-invariants.mdc -# Generated by dev install-deps/up: skill links from lockfile-matched gems +# Generated by dev deps install/up: skill links from lockfile-matched gems .agents/skills/ diff --git a/README.md b/README.md index bfb23c9..b80bff5 100644 --- a/README.md +++ b/README.md @@ -26,7 +26,7 @@ dev [args...] # extra args are forwarded to the command dev walks up from your current directory to the git repo root and reads the `dev.yml` there. Every command is one of two kinds: a **project command** declared in that `dev.yml` (`dev test`, `dev build`, …), or a **builtin** that ships with dev. A project may declare a command on a builtin's name (typically `up`); the builtin body runs first, then the project's `run:` — a hardcoded `super()`. -Commands form a **tree**, and `dev` itself is its root: any command may have subcommands (`dev deps path`, `dev runner status`, a project's `dev test unit`). Resolution follows argv one token at a time from the root — a token naming a child descends; the first token that doesn't is where the args begin. A command with nothing of its own to run prints its usage when invoked bare (so bare `dev` lists everything, `dev deps` lists `deps path`, `dev plan` lists the plan verbs); `dev help ` prints the same view for any node. Commands with subcommands show a trailing `…` in the listing, and Tab completion follows the same tree (`dev plan ` offers `new link pull push status init`) once the [shell hook](#shell-hook-install) is installed. +Commands form a **tree**, and `dev` itself is its root: any command may have subcommands (`dev deps path`, `dev runner status`, a project's `dev test unit`). Resolution follows argv one token at a time from the root — a token naming a child descends; the first token that doesn't is where the args begin. A command with nothing of its own to run prints its usage when invoked bare (so bare `dev` lists everything, `dev deps` lists its verbs (`update`, `install`, `check`, `path`), `dev plan` lists the plan verbs); `dev help ` prints the same view for any node. Commands with subcommands show a trailing `…` in the listing, and Tab completion follows the same tree (`dev plan ` offers `new link pull push status init`) once the [shell hook](#shell-hook-install) is installed. ### Built-in commands @@ -37,9 +37,9 @@ Grouped as `dev help` lists them. **Scope** says where the command works: *anywh | Command | Scope | Purpose | Details | |---|---|---|---| | `dev up` | project, or anywhere (host layer only) | Converge host tooling, install locked deps, run the project's `up:` | [Dependency commands](#dependency-commands) | -| `dev update-deps` | project | Resolve `dependencies.rb` and write the lockfiles | [Dependency commands](#dependency-commands) | -| `dev install-deps` | project | Install host-handled locked deps (gh releases, steam apps, …) | [Dependency commands](#dependency-commands) | -| `dev check` | project | Report dependency staleness (manifest vs lockfiles vs installed) | [Dependency commands](#dependency-commands) | +| `dev deps update` | project | Resolve `dependencies.rb` and write the lockfiles (≈ `bundle update`) | [Dependency commands](#dependency-commands) | +| `dev deps install` | project | Install host-handled locked deps (gh releases, steam apps, …) (≈ `bundle install`) | [Dependency commands](#dependency-commands) | +| `dev deps check` | project | Report dependency staleness (manifest vs lockfiles vs installed); exits non-zero when stale (≈ `bundle check`) | [Dependency commands](#dependency-commands) | | `dev deps path []` | project | Print a locked artifact's absolute path | [Dependency commands](#dependency-commands) | | `dev runner register\|status` | anywhere | Enroll or inspect this host as a self-hosted runner (`runner-setup` is an alias for `register`) | [dev runner](#dev-runner--enroll-a-host-as-a-self-hosted-runner) | | `dev provide-image` | gated: `build.container` (hidden) | Resolve the build image (local → pull → build) and print its tag | [Container commands](#container-commands) | @@ -109,7 +109,7 @@ dev's feature set is three independent opt-ins; a repo takes whichever rungs it 1. **Command running** — add a `dev.yml` with a `commands:` map. That alone gets you `dev up` / `dev test` / etc. with the standard UI, from anywhere in the repo. dev does not touch your toolchain or dependencies; your scripts keep doing whatever they did before. 2. **Toolchain provisioning** — add a `dependencies.rb` with just a `ruby` directive (see [Ruby version resolution](#ruby-version-resolution)). dev provisions that exact Ruby (rbenv + shadowenv) and every `dev ` runs under it. This does *not* hand your Gemfile to dev — a hand-written Gemfile stays yours, managed by plain bundler. -3. **Dependency management** — declare gems, brew formulae, engine artifacts, etc. in `dependencies.rb`. `dev update-deps` locks them and `dev up` installs them; for `gem()` declarations dev generates and owns the `Gemfile`. +3. **Dependency management** — declare gems, brew formulae, engine artifacts, etc. in `dependencies.rb`. `dev deps update` locks them and `dev up` installs them; for `gem()` declarations dev generates and owns the `Gemfile`. A gem repo typically stops at rungs 1–2 (commands + a pinned Ruby, hand-written gemspec/Gemfile); an app repo usually takes all three. @@ -136,7 +136,7 @@ On `dev up`, dev provisions the declared version through rbenv (installing it if **Commit `.ruby-version` when the project declares its Ruby.** It is deterministic generated output — same idea as a lockfile — and it is exactly what contributors without dev consume. Do not commit it for fallback-Ruby repos: there it reflects whatever Ruby the machine happens to have. -Keep the file a bare version string. rbenv only reads the first word, but other consumers (setup-ruby, Bundler, editors) parse the file strictly, so comments would break them. There is no drift risk in the other direction either: `dev up` rewrites the file from the declared version every run, so a hand edit never survives — to change the Ruby, edit `dependencies.rb`, run `dev update-deps`, then `dev up`. +Keep the file a bare version string. rbenv only reads the first word, but other consumers (setup-ruby, Bundler, editors) parse the file strictly, so comments would break them. There is no drift risk in the other direction either: `dev up` rewrites the file from the declared version every run, so a hand edit never survives — to change the Ruby, edit `dependencies.rb`, run `dev deps update`, then `dev up`. ### Supported shells @@ -300,11 +300,11 @@ Dev includes a built-in dependency management system for reproducible builds acr Dependencies flow through four stages: 1. **Declare** — list what you need in `dependencies.rb` using the Ruby DSL -2. **Resolve & lock** — `dev update-deps` resolves constraints to exact versions and writes lockfiles +2. **Resolve & lock** — `dev deps update` resolves constraints to exact versions and writes lockfiles 3. **Install** — `dev up` installs pinned dependencies from lockfiles (build group first) 4. **Use** — `dev ` provisions the project's toolchain environment and runs your command -Lockfiles are the source of truth for stages 3 and 4. After changing `dependencies.rb`, run `dev update-deps` to re-resolve before building. +Lockfiles are the source of truth for stages 3 and 4. After changing `dependencies.rb`, run `dev deps update` to re-resolve before building. ### Lockfiles @@ -313,7 +313,7 @@ Two YAML lockfiles, same format, two purposes: - **`deps.lock`** — pins every runtime dependency (app + test groups) to exact version + SHA256 integrity hash. - **`build-deps.lock`** — pins every build dependency (build group). Separate file for CI cache convenience — `hashFiles('build-deps.lock')` as Docker image cache key means runtime dep changes don't invalidate build tooling. -Both files are generated by `dev update-deps` and committed to git. Never edit them by hand. +Both files are generated by `dev deps update` and committed to git. Never edit them by hand. ### Host tooling: the Brewfile contract @@ -381,7 +381,7 @@ Four orthogonal axes scope a declaration; each answers a different question: ### Built-in integrations -All built-in integrations are declared in one place — `lib/dev/deps/registry.rb` — and `dev install-deps` installs every host-scoped one. `registry_consistency_test.rb` fails the build if a repository/integration class or a declaration DSL verb is added without a registry entry. +All built-in integrations are declared in one place — `lib/dev/deps/registry.rb` — and `dev deps install` installs every host-scoped one. `registry_consistency_test.rb` fails the build if a repository/integration class or a declaration DSL verb is added without a registry entry. | DSL method | Integration | Repository | Lockfile | |---|---|---|---| @@ -397,7 +397,7 @@ All built-in integrations are declared in one place — `lib/dev/deps/registry.r `xcode "26.1.1"` pins the Xcode toolchain (macOS only; a no-op on other hosts). dev installs the pin to `/Applications/Xcode-.app` via the [xcodes](https://github.com/XcodesOrg/xcodes) CLI — declare `brew "xcodes", host: :darwin` in `:build` so it exists first — and publishes `DEVELOPER_DIR` into the project shadowenv. Interactive runs pass any Apple ID/2FA/sudo prompt through to you; headless runs fail fast with remediation instead of hanging (normal practice: pre-install the pin interactively once during machine bring-up, e.g. a CI runner's). -`gem()` declares Ruby gems: dev generates a `Gemfile`/`Gemfile.lock` from your declarations (a top-level `gem` lands in the default group; `group(:test) { gem ... }` scopes it to a bundler group), and `dev install-deps` runs `bundle install`. `brew()` dual-writes — the container build path keeps reading the group structure while `dev install-deps` also installs the formulae on the host (idempotently). +`gem()` declares Ruby gems: dev generates a `Gemfile`/`Gemfile.lock` from your declarations (a top-level `gem` lands in the default group; `group(:test) { gem ... }` scopes it to a bundler group), and `dev deps install` runs `bundle install`. `brew()` dual-writes — the container build path keeps reading the group structure while `dev deps install` also installs the formulae on the host (idempotently). `python "3.12"` pins the Python toolchain: dev provisions the interpreter (Homebrew `python@3.12`) and a project-local `.venv`, and publishes it into the project shadowenv (`VIRTUAL_ENV` + `.venv/bin` on `PATH`). `pip()` declares packages installed into that venv — like `luarocks()`, you declare only the top-level packages and pip resolves the transitive tree at install time. Gate heavy, platform-specific stacks (e.g. a PyTorch-backed ML tool) with `host:` so only the machines that use them pay the download. @@ -425,12 +425,12 @@ Custom integrations implement `Dev::Deps::Integration` (with `install_all(pins, ### Dependency commands -The Lifecycle builtins (see [Built-in commands](#built-in-commands)) that drive the four stages above: +The Lifecycle builtins (see [Built-in commands](#built-in-commands)) that drive the four stages above. The dependency verbs live under one noun, `dev deps` (bare `dev deps` lists them), and are Bundler's, because that is the model every Rubyist already carries: -- **`dev update-deps`** — resolve constraints from `dependencies.rb`, write lockfiles (recording the manifest digest for the staleness check). Always available (no need to define in `dev.yml`). -- **`dev install-deps`** — install locked deps handled on the host (gh releases, steam apps) into their version-keyed install dirs, filtered to the detected env and host OS. Finishes by refreshing agent skill links (see [Agent skills & org learnings](#agent-skills--org-learnings)). -- **`dev up`** — first converges the host layer (self-update + org Brewfile, see [Host tooling: the Brewfile contract](#host-tooling-the-brewfile-contract)), then auto-installs all deps from lockfiles (build group first), then runs the project's `up:` command from `dev.yml` if defined. On success, stamps the installed lockfile digest (see `dev check`). Finishes by refreshing agent skill links, like `install-deps`. Also valid outside any project: converges the host layer only — the fresh-box bootstrap (`brew install //dev` → `dev up` → ready). -- **`dev check`** — report dependency-state staleness explicitly: `dependencies.rb` vs lockfiles (digest recorded by `update-deps`), and lockfiles vs the per-machine installed stamp (`~/.dev/state//installed-digest`, written after a fully-successful `up`/`install-deps`). The same two O(1) checks run at every command start — warning on workstations, erroring in CI. +- **`dev deps update`** — resolve constraints from `dependencies.rb`, write lockfiles (recording the manifest digest for the staleness check). Always available (no need to define in `dev.yml`). +- **`dev deps install`** — install locked deps handled on the host (gh releases, steam apps) into their version-keyed install dirs, filtered to the detected env and host OS. Finishes by refreshing agent skill links (see [Agent skills & org learnings](#agent-skills--org-learnings)). +- **`dev up`** — first converges the host layer (self-update + org Brewfile, see [Host tooling: the Brewfile contract](#host-tooling-the-brewfile-contract)), then auto-installs all deps from lockfiles (build group first), then runs the project's `up:` command from `dev.yml` if defined. On success, stamps the installed lockfile digest (see `dev deps check`). Finishes by refreshing agent skill links, like `dev deps install`. Also valid outside any project: converges the host layer only — the fresh-box bootstrap (`brew install //dev` → `dev up` → ready). +- **`dev deps check`** — report dependency-state staleness explicitly and exit non-zero when anything drifted: `dependencies.rb` vs lockfiles (digest recorded by `dev deps update`), and lockfiles vs the per-machine installed stamp (`~/.dev/state//installed-digest`, written after a fully-successful `dev up`/`dev deps install`). The same two O(1) checks run at every command start — warning on workstations, erroring in CI. - **`dev deps path `** — print the absolute path of a locked artifact (e.g. `dev deps path ficsit SML LinuxServer`, `dev deps path xcode` for the pinned DEVELOPER_DIR, or `dev deps path gh UnrealEngineMac` for a gh release's version-keyed install dir under the data root) so scripts don't reconstruct cache keys or layout conventions. ## dev plan — sync plans with GitHub issues @@ -448,11 +448,11 @@ Global (works without a `dev.yml`; the workspace is the nearest dev.yml or git r ## Agent skills & org learnings -dev distributes agent-facing skills (Cursor-style `SKILL.md` directories) over three channels, all refreshed at the same cheap, idempotent hook points — `dev up`, `dev install-deps`, and `dev plan` — so there is no separate setup step: +dev distributes agent-facing skills (Cursor-style `SKILL.md` directories) over three channels, all refreshed at the same cheap, idempotent hook points — `dev up`, `dev deps install`, and `dev plan` — so there is no separate setup step: - **dev's own skills** (`share/cursor-skills/*`) link user-globally into `~/.cursor/skills/`; `brew upgrade` refreshes them automatically because the symlinks resolve through the installed tree. -- **Gem-shipped skills.** A gem's skill is part of what installing that dependency means, so `dev up` / `dev install-deps` finish by scanning the resolved (lockfile-matched) gem set for `skills/*/SKILL.md` and linking each project-scoped as `.agents/skills/gem---` (gitignored; an agent-neutral dir, so the mechanism isn't Cursor-locked). Links for gems that leave the lock are pruned on the next install — a skill-set change rides the same staleness story as any dependency change. -- **Org learnings** (opt-in). With `knowledge_repo: /` in `~/.config/dev/config.yml` (or `DEV_KNOWLEDGE_REPO`), dev keeps a machine-local cache of the org knowledge repo under `~/.local/share/dev/knowledge`. Hooks refresh it inline with a short timeout (~2s, with a hardcoded ~30s courtesy floor between pulls — the repo is tiny, so there is no TTL knob): the pull happens *before* distribution, so a hook never renders content it just found stale, and on timeout or offline the current cache is served (the pull finishes detached). The fetch rides the user's `gh` auth. From the cache, dev links the repo's `skills/*` user-globally into `~/.cursor/skills/` and renders the index's `## Invariants (always-on)` section **once, cache-side**, then links each project's `.cursor/rules/org-invariants.mdc` at that render as a symlink — one refresh updates every project on the machine simultaneously, nothing is committed (a participating repo's only footprint is one `.gitignore` line), and drift from the canonical repo is structurally impossible. Machines without the setting simply have no org sync: dev is public and ships only the mechanism, never the content. `dev learnings sync` forces a blocking refresh of the whole read path; `dev learnings status` reports what's cached, rendered, and linked; `dev learnings invariants` prints the Tier-0 prompt block. **Runner bootstrap contract:** an agent-runner workflow (e.g. ai-flow's) runs an explicit blocking `dev learnings sync` step before starting agent sessions, so they never start on stale invariants — the dependency is stated in the workflow instead of hiding as a side effect of `install-deps`. +- **Gem-shipped skills.** A gem's skill is part of what installing that dependency means, so `dev up` / `dev deps install` finish by scanning the resolved (lockfile-matched) gem set for `skills/*/SKILL.md` and linking each project-scoped as `.agents/skills/gem---` (gitignored; an agent-neutral dir, so the mechanism isn't Cursor-locked). Links for gems that leave the lock are pruned on the next install — a skill-set change rides the same staleness story as any dependency change. +- **Org learnings** (opt-in). With `knowledge_repo: /` in `~/.config/dev/config.yml` (or `DEV_KNOWLEDGE_REPO`), dev keeps a machine-local cache of the org knowledge repo under `~/.local/share/dev/knowledge`. Hooks refresh it inline with a short timeout (~2s, with a hardcoded ~30s courtesy floor between pulls — the repo is tiny, so there is no TTL knob): the pull happens *before* distribution, so a hook never renders content it just found stale, and on timeout or offline the current cache is served (the pull finishes detached). The fetch rides the user's `gh` auth. From the cache, dev links the repo's `skills/*` user-globally into `~/.cursor/skills/` and renders the index's `## Invariants (always-on)` section **once, cache-side**, then links each project's `.cursor/rules/org-invariants.mdc` at that render as a symlink — one refresh updates every project on the machine simultaneously, nothing is committed (a participating repo's only footprint is one `.gitignore` line), and drift from the canonical repo is structurally impossible. Machines without the setting simply have no org sync: dev is public and ships only the mechanism, never the content. `dev learnings sync` forces a blocking refresh of the whole read path; `dev learnings status` reports what's cached, rendered, and linked; `dev learnings invariants` prints the Tier-0 prompt block. **Runner bootstrap contract:** an agent-runner workflow (e.g. ai-flow's) runs an explicit blocking `dev learnings sync` step before starting agent sessions, so they never start on stale invariants — the dependency is stated in the workflow instead of hiding as a side effect of `dev deps install`. ### Repo learnings diff --git a/data/Deps.cmake b/data/Deps.cmake index 944ba2e..8fd2f30 100644 --- a/data/Deps.cmake +++ b/data/Deps.cmake @@ -9,7 +9,7 @@ # (e.g. "Boost::"), targets are prefixed automatically. This replaces hardcoded dep checks. if(NOT EXISTS "${CMAKE_SOURCE_DIR}/deps.lock.cmake") - message(FATAL_ERROR "deps.lock.cmake not found. Run: dev update-deps, commit the lockfile, then run dev up or build.") + message(FATAL_ERROR "deps.lock.cmake not found. Run: dev deps update, commit the lockfile, then run dev up or build.") endif() include("${CMAKE_SOURCE_DIR}/deps.lock.cmake") diff --git a/docs/deps-architecture.md b/docs/deps-architecture.md index 0290e0d..49d8c20 100644 --- a/docs/deps-architecture.md +++ b/docs/deps-architecture.md @@ -100,7 +100,7 @@ ecosystem's canonical form and validated at the DSL boundary (a cmake ## The resolve pipeline -`dev update-deps` runs: +`dev deps update` runs: 1. **Lock** — for each integration with a registered `Locker`, run it over that integration's declarations. Today that is bundler only: @@ -146,12 +146,12 @@ ecosystem's canonical form and validated at the DSL boundary (a cmake (`brew:` → `zlib:` → attrs) so the on-disk key carries the same (integration, name) identity the resolver keys on. The reader also accepts the pre-nesting flat format; that shim is deleted once every - consumer repo's lockfiles have been rewritten by `update-deps`. + consumer repo's lockfiles have been rewritten by `dev deps update`. -`dev install-deps` reads the lockfile and hands each integration its pins; +`dev deps install` reads the lockfile and hands each integration its pins; no resolution happens at install time. -### Resolution flow (`dev update-deps`) +### Resolution flow (`dev deps update`) ```mermaid sequenceDiagram @@ -196,7 +196,7 @@ sequenceDiagram Note over lock: writes deps.lock and build-deps.lock, nested by integration ``` -### Install flow (`dev install-deps`) +### Install flow (`dev deps install`) ```mermaid sequenceDiagram diff --git a/lib/dev/agent_bootstrap.rb b/lib/dev/agent_bootstrap.rb index b28c3da..1a5390f 100644 --- a/lib/dev/agent_bootstrap.rb +++ b/lib/dev/agent_bootstrap.rb @@ -272,7 +272,7 @@ def sudoers_content # The brew escalation edge (the Homebrew single-user gap on cooperative # machines): the agent may run exactly brew as the prefix owner, - # NOPASSWD, so `dev install-deps` converges formulae without a human + # NOPASSWD, so `dev deps install` converges formulae without a human # even though the prefix belongs to the enrolling user. The owner is # stat'd here at bootstrap time — never hardcoded — and re-running # register re-converges the edge. Omitted when the host has no brew or diff --git a/lib/dev/deps/accessor.rb b/lib/dev/deps/accessor.rb index ffaa050..5f0b0d0 100644 --- a/lib/dev/deps/accessor.rb +++ b/lib/dev/deps/accessor.rb @@ -162,7 +162,7 @@ def xcode_developer_dir sig { params(integration: Symbol, name: String).returns(Dependency) } def find_dep(integration, name) dep = @lockfile.read.find { |d| d.integration == integration && d.name == name } - raise NotLockedError, "#{name} (#{integration}) is not in the lockfile — run dev update-deps" unless dep + raise NotLockedError, "#{name} (#{integration}) is not in the lockfile — run dev deps update" unless dep dep end diff --git a/lib/dev/deps/bundler_integration.rb b/lib/dev/deps/bundler_integration.rb index d2e0397..8f23bff 100644 --- a/lib/dev/deps/bundler_integration.rb +++ b/lib/dev/deps/bundler_integration.rb @@ -13,7 +13,7 @@ module Deps # Installs the locked gems with `bundle install` against the Gemfile/ # Gemfile.lock that BundlerRepository generated and committed. The install is # frozen: it must match the committed lockfile exactly, so install never - # silently re-resolves (re-resolution is `dev update-deps`'s job). + # silently re-resolves (re-resolution is `dev deps update`'s job). # # The individual locked deps are informational here — bundler installs the # full graph from the Gemfile.lock — so install_all only needs to know there diff --git a/lib/dev/deps/bundler_locker.rb b/lib/dev/deps/bundler_locker.rb index a6516a7..182cea8 100644 --- a/lib/dev/deps/bundler_locker.rb +++ b/lib/dev/deps/bundler_locker.rb @@ -24,7 +24,7 @@ class LockError < StandardError; end GENERATED_HEADER = <<~HEADER # Generated by dev from dependencies.rb. Do not edit. - # Add or change gems in dependencies.rb and run `dev update-deps`. + # Add or change gems in dependencies.rb and run `dev deps update`. HEADER # @param project_root [Pathname, String] root the Gemfile/Gemfile.lock live in diff --git a/lib/dev/deps/bundler_repository.rb b/lib/dev/deps/bundler_repository.rb index 3cfa20b..64d3764 100644 --- a/lib/dev/deps/bundler_repository.rb +++ b/lib/dev/deps/bundler_repository.rb @@ -43,7 +43,7 @@ def initialize(project_root:) def find(id) pin = pins.fetch(id.name) do raise MissingGemError, - "gem #{id.name.inspect} is not in #{LOCKFILE} — run `dev update-deps`" + "gem #{id.name.inspect} is not in #{LOCKFILE} — run `dev deps update`" end Package.new( diff --git a/lib/dev/deps/cmake_integration.rb b/lib/dev/deps/cmake_integration.rb index 39819b6..78b5fad 100644 --- a/lib/dev/deps/cmake_integration.rb +++ b/lib/dev/deps/cmake_integration.rb @@ -29,14 +29,14 @@ class ExtractError < StandardError; end DEPS_CMAKE_HEADER = <<~TEXT # CMake dep variables: repo+sha or url+hash per runtime dep. Generated by dev - do not edit. # Lists below are per build target (app vs test), not per dev environment (ci/dev). - # Edit dependencies.rb and run dev update-deps to change. + # Edit dependencies.rb and run dev deps update to change. TEXT TARGETS_CMAKE_HEADER = <<~TEXT # Runtime targets/includes per dep. Generated by dev - do not edit. # Used by cmake/Deps.cmake when resolving each build target (app / test). - # Edit dependencies.rb and run dev update-deps to change. + # Edit dependencies.rb and run dev deps update to change. TEXT diff --git a/lib/dev/deps/dsl.rb b/lib/dev/deps/dsl.rb index 27a6b4f..8a6c6d0 100644 --- a/lib/dev/deps/dsl.rb +++ b/lib/dev/deps/dsl.rb @@ -540,7 +540,7 @@ def xcode(version, **spec) # # Dual-writes: the existing @brew/groups entry feeds the container build # path (bin/install-build-deps.rb), while the additional declaration - # rides the resolver -> lockfile -> install pipeline so `dev install-deps` + # rides the resolver -> lockfile -> install pipeline so `dev deps install` # installs it on the host too. BrewIntegration skips already-installed # formulae, so the host install is idempotent. # @@ -571,7 +571,7 @@ def brew(name, **opts) # Scope member declarations to an environment ("ci" / "dev"). The env # name is a first-class declaration field (like host), landing in the - # lockfile's env section so install-deps filters it to the matching + # lockfile's env section so `dev deps install` filters it to the matching # environment — never smuggled through the constraint hash. # # @param name [String, Symbol] environment name diff --git a/lib/dev/deps/ficsit_integration.rb b/lib/dev/deps/ficsit_integration.rb index 61b5474..c74e2c1 100644 --- a/lib/dev/deps/ficsit_integration.rb +++ b/lib/dev/deps/ficsit_integration.rb @@ -78,7 +78,7 @@ def install(dep) if platforms.nil? || platforms.empty? raise MissingPlatformsError, "#{dep.name}@#{dep.version} has no resolved platforms — declare it in a " \ - "group with a platform and run dev update-deps" + "group with a platform and run dev deps update" end platforms.each { |platform, target| install_platform(dep, platform, target) } diff --git a/lib/dev/deps/gem_skill_linker.rb b/lib/dev/deps/gem_skill_linker.rb index efabf12..6436fb7 100644 --- a/lib/dev/deps/gem_skill_linker.rb +++ b/lib/dev/deps/gem_skill_linker.rb @@ -13,7 +13,7 @@ module Deps # A gem's skill is part of what installing that dependency means — # installing rspock without its skill would be an incomplete install, # exactly like installing it without its executables. So `dev up` / - # `dev install-deps` finish by scanning the resolved (lockfile-matched) + # `dev deps install` finish by scanning the resolved (lockfile-matched) # gem set for skills/*/SKILL.md and linking each one project-scoped as # .agents/skills/gem--- (gitignored; an agent-neutral dir so # the mechanism isn't Cursor-locked). A skill-set change rides the same diff --git a/lib/dev/deps/gh_integration.rb b/lib/dev/deps/gh_integration.rb index 943070d..b26e169 100644 --- a/lib/dev/deps/gh_integration.rb +++ b/lib/dev/deps/gh_integration.rb @@ -327,7 +327,7 @@ def matching_assets(dep) raise NoMatchingAssetsError, "no locked assets matching #{pattern.inspect} for #{dep.metadata["repo"]}@#{dep.version} " \ - "— check the assets: glob, or run dev update-deps" + "— check the assets: glob, or run dev deps update" end # Extract all downloaded archives into extracted_dir. Split archives diff --git a/lib/dev/deps/lockfile.rb b/lib/dev/deps/lockfile.rb index 49b865b..cdb02d0 100644 --- a/lib/dev/deps/lockfile.rb +++ b/lib/dev/deps/lockfile.rb @@ -27,19 +27,19 @@ module Deps # The reader also accepts the legacy flat format (name-keyed, integration # recorded in the value hash) so repos keep installing from lockfiles # written before the nesting; that shim is deleted once every consumer - # repo's lockfiles have been rewritten by update-deps (issue #146). + # repo's lockfiles have been rewritten by dev deps update (issue #146). class Lockfile extend T::Sig HEADER = <<~COMMENT # Generated by dev. Do not edit. - # Edit dependencies.rb and run dev update-deps to change. + # Edit dependencies.rb and run dev deps update to change. COMMENT # Header line recording the SHA-256 of the dependencies.rb the lockfiles # were generated from. The staleness check (Dev::Deps::Staleness) compares # it against the current manifest to detect "declarations changed but - # update-deps wasn't run". A comment so the YAML payload stays pure deps. + # dev deps update wasn't run". A comment so the YAML payload stays pure deps. MANIFEST_DIGEST_PREFIX = "# dependencies-digest: " DEPS_LOCK_FILE = "deps.lock" @@ -69,7 +69,7 @@ def lock(deps, manifest_digest: nil) # The manifest digest recorded when the lockfiles were generated, or nil # for lockfiles predating the staleness check (treated as unknown, not - # stale — legacy locks shouldn't nag until their next update-deps). + # stale — legacy locks shouldn't nag until their next dev deps update). # # @return [String, nil] SHA-256 hex sig { returns(T.nilable(String)) } diff --git a/lib/dev/deps/pip_integration.rb b/lib/dev/deps/pip_integration.rb index cc1b693..c7a0edc 100644 --- a/lib/dev/deps/pip_integration.rb +++ b/lib/dev/deps/pip_integration.rb @@ -14,7 +14,7 @@ module Deps # Installs each locked package into the project-local venv (.venv) that # ShadowenvPython provisions — the Python analogue of LuaRocks installing # into lua_modules/. The venv is ensured here (created if absent) so - # `dev install-deps` works on a fresh clone, before any command has run + # `dev deps install` works on a fresh clone, before any command has run # ShadowenvPython.setup!. pip resolves the transitive tree at install. class PipIntegration < Integration extend T::Sig diff --git a/lib/dev/deps/registry.rb b/lib/dev/deps/registry.rb index a1a5308..174af47 100644 --- a/lib/dev/deps/registry.rb +++ b/lib/dev/deps/registry.rb @@ -52,8 +52,8 @@ module Deps # arguments each side needs (drawn from a context the runner assembles). module Registry # Install location for a type: - # :host installed on the host by `dev install-deps` - # :container installed inside the build container (not by install-deps) + # :host installed on the host by `dev deps install` + # :container installed inside the build container (not by dev deps install) # :both installed on the host and, separately, in the container HOST = :host CONTAINER = :container @@ -296,7 +296,7 @@ def schemes end # Build the integration-type -> Locker hash for types whose ecosystem - # tool owns the whole-set solve. update-deps runs these before the + # tool owns the whole-set solve. dev deps update runs these before the # Resolver so each tool lockfile is materialized when find reads it. # # @param project_root [Pathname] project root (threaded to lockers that need it) diff --git a/lib/dev/deps/staleness.rb b/lib/dev/deps/staleness.rb index ccec5af..e85fa87 100644 --- a/lib/dev/deps/staleness.rb +++ b/lib/dev/deps/staleness.rb @@ -13,8 +13,8 @@ module Deps # with two O(1) digest comparisons at every command start: # # 1. manifest vs lockfile: dependencies.rb digest against the digest - # recorded in the lockfile header by `dev update-deps` → - # "declarations changed — run dev update-deps". + # recorded in the lockfile header by `dev deps update` → + # "declarations changed — run dev deps update". # 2. lockfile vs installed stamp: a digest of the lockfile contents # against the stamp written after the last fully-successful install → # "lock changed since last install — run dev up". @@ -27,7 +27,7 @@ module Deps # semantics — CI environments install fresh, so a mismatch there is a # pipeline bug, not a reminder). # - # Stamps catch sequence drift (edit without update-deps, lock bump without + # Stamps catch sequence drift (edit without dev deps update, lock bump without # dev up), not out-of-band mutation of installed artifacts — that's a # deferred doctor-style per-integration sweep. class Staleness @@ -80,14 +80,14 @@ def manifest_message recorded = Lockfile.new(dir: @project_root).manifest_digest # No digest recorded: a legacy lockfile (predates the check) — stay - # quiet until its next update-deps stamps one. No lockfile at all is + # quiet until its next dev deps update stamps one. No lockfile at all is # layer-2's problem (nothing was ever installed either). return nil unless recorded current = Digest::SHA256.file(manifest.to_s).hexdigest return nil if current == recorded - "dependencies.rb changed since the lockfiles were generated — run dev update-deps" + "dependencies.rb changed since the lockfiles were generated — run dev deps update" end # Layer 2: have the lockfiles changed since the last successful install diff --git a/lib/dev/deps/steam_integration.rb b/lib/dev/deps/steam_integration.rb index d1f4095..c96046a 100644 --- a/lib/dev/deps/steam_integration.rb +++ b/lib/dev/deps/steam_integration.rb @@ -112,7 +112,7 @@ def steam_platform_for(platform) # Confirm the installed depot matches the locked buildid. A mismatch means # the lock is stale (the public branch moved) — surface it so the user - # re-runs dev update-deps rather than silently testing a different build. + # re-runs dev deps update rather than silently testing a different build. # # @param dep [Dependency] # @param server_dir [Pathname] @@ -128,7 +128,7 @@ def verify_build_id(dep, server_dir) raise BuildMismatchError, "#{dep.name}: expected buildid #{dep.version}, installed #{installed_build.inspect} " \ - "— run dev update-deps to re-pin" + "— run dev deps update to re-pin" end end end diff --git a/lib/dev/host_service.rb b/lib/dev/host_service.rb index 73bccb9..3304973 100644 --- a/lib/dev/host_service.rb +++ b/lib/dev/host_service.rb @@ -15,7 +15,7 @@ module Dev # operation shares the same contract: no user arguments, idempotent, and # warn-only (host hygiene rides other commands and must never block # them). Commands compose these verbs — `dev up`'s host half is - # converge_tooling + install_rc_hook; `dev plan` and `install-deps` + # converge_tooling + install_rc_hook; `dev plan` and `dev deps install` # refresh the cheap artifact pair on every invocation. # # Anything host-scoped but not convergence-shaped (a user-facing verb diff --git a/lib/dev/learnings/accessor.rb b/lib/dev/learnings/accessor.rb index 687c982..a40b870 100644 --- a/lib/dev/learnings/accessor.rb +++ b/lib/dev/learnings/accessor.rb @@ -16,7 +16,7 @@ module Dev module Learnings # Dispatch for `dev learnings …` — the explicit surface over the learnings # read path. Passive distribution rides dev's hook points (`dev up` / - # `install-deps` / `dev plan`); these verbs are the manual override and + # `dev deps install` / `dev plan`); these verbs are the manual override and # the inspection: # # - `sync` — refresh the whole read path now (blocking): pull the diff --git a/lib/dev/learnings/synchronizer.rb b/lib/dev/learnings/synchronizer.rb index 6813483..1371cb5 100644 --- a/lib/dev/learnings/synchronizer.rb +++ b/lib/dev/learnings/synchronizer.rb @@ -97,7 +97,7 @@ def project_rules_file(project_root) Pathname(project_root).join(*ORG_INVARIANTS_RULE_SUBDIRS) end - # The passive hook entry (`dev up` / `install-deps` / `dev plan`): pull + # The passive hook entry (`dev up` / `dev deps install` / `dev plan`): pull # inline within the cache's short timeout (falling back to the current # cache when the network is slower, or offline), then distribute. # Never raises: learnings sync is hygiene riding another command, and diff --git a/lib/dev/shadowenv_python.rb b/lib/dev/shadowenv_python.rb index 0d245d2..27a263f 100644 --- a/lib/dev/shadowenv_python.rb +++ b/lib/dev/shadowenv_python.rb @@ -13,7 +13,7 @@ module Dev # Mirrors ShadowenvRuby/ShadowenvLua. Triggered when dependencies.rb declares # `python "3.12"`. This module owns only the interpreter + the (empty) venv; # the package set is installed into that venv by Dev::Deps::PipIntegration on - # `dev install-deps`, exactly as LuaRocks fills lua_modules/. + # `dev deps install`, exactly as LuaRocks fills lua_modules/. module ShadowenvPython extend T::Sig include Kernel @@ -63,7 +63,7 @@ def setup!(python_version:, project_root:) # Ensure Homebrew python@ is installed and a project-local .venv # exists, built with that exact interpreter. Idempotent and safe to call from - # both setup! (per command) and PipIntegration (install-deps), so the venv is + # both setup! (per command) and PipIntegration (dev deps install), so the venv is # guaranteed present before packages install into it. # # @param python_version [String] e.g. "3.12" diff --git a/lib/dev/shadowenv_ruby.rb b/lib/dev/shadowenv_ruby.rb index 3f096c1..c22a5a4 100644 --- a/lib/dev/shadowenv_ruby.rb +++ b/lib/dev/shadowenv_ruby.rb @@ -82,7 +82,7 @@ def detect_homebrew_ruby_version end # Guarded provisioning: the O(1) provisioned? check first, so callers on - # every-command paths (CommandRunner, the up/install-deps builtins) pay + # every-command paths (CommandRunner, the `dev up`/`dev deps install` builtins) pay # nothing after the first run. sig { params(ruby_version: String, project_root: T.any(String, Pathname)).void } def ensure!(ruby_version:, project_root:) diff --git a/lib/dev/skill_installer.rb b/lib/dev/skill_installer.rb index aee1f9c..3f91c98 100644 --- a/lib/dev/skill_installer.rb +++ b/lib/dev/skill_installer.rb @@ -13,7 +13,7 @@ module Dev # - org knowledge skills (machine knowledge cache) → ~/.cursor/skills # - gem-shipped skills (lockfile-matched gems) → /.agents/skills # - # Called from cheap, idempotent hook points (`dev up` / `install-deps` / + # Called from cheap, idempotent hook points (`dev up` / `dev deps install` / # `dev plan`), so there is no separate setup step and `brew upgrade` # refreshes shipped skills automatically (symlinks resolve through the # installed tree, wherever brew put it). diff --git a/src/dev/builtins/check_command.rb b/src/dev/builtins/check_command.rb index 44204c6..c22001d 100644 --- a/src/dev/builtins/check_command.rb +++ b/src/dev/builtins/check_command.rb @@ -6,7 +6,7 @@ module Dev module Builtins - # `dev check`: report the dependency-state freshness the staleness guard + # `dev deps check`: report the dependency-state freshness the staleness guard # would act on, and exit non-zero when anything drifted. class CheckCommand < BuiltinCommand extend T::Sig diff --git a/src/dev/builtins/install_deps_command.rb b/src/dev/builtins/install_deps_command.rb index 83b0a76..8de41ef 100644 --- a/src/dev/builtins/install_deps_command.rb +++ b/src/dev/builtins/install_deps_command.rb @@ -14,7 +14,7 @@ module Dev module Builtins - # `dev install-deps`: install everything the lockfiles pin for this + # `dev deps install`: install everything the lockfiles pin for this # machine — shared with the `up` builtin, which composes this command. # Host integrations install on the host (not the build container) so # their artifacts can be volume-mounted in. @@ -62,7 +62,7 @@ def desc = "Install locked dependencies handled on the host (e.g. gh releases)" sig { override.returns(Command::Category) } def category = Command::Category::Lifecycle - # install-deps IS the remediation for a stale install — never nag + # dev deps install IS the remediation for a stale install — never nag # before it. sig { override.returns(T::Boolean) } def staleness_exempt? = true @@ -79,7 +79,7 @@ def stamps? = true sig { override.params(args: T::Array[String], context: ExecutionContext).void } def call(args:, context:) project = context.project! - # Headless boxes (CI, runner services) reach install-deps before any + # Headless boxes (CI, runner services) reach dev deps install before any # dev.yml command has run CommandRunner's provisioning, so the builtin # must provision the pinned Ruby itself — bundler installs against it. ShadowenvRuby.ensure!(ruby_version: project.ruby_version, project_root: project.root) diff --git a/src/dev/builtins/up_command.rb b/src/dev/builtins/up_command.rb index 603b0e3..426c22e 100644 --- a/src/dev/builtins/up_command.rb +++ b/src/dev/builtins/up_command.rb @@ -60,7 +60,7 @@ def call(args:, context:) @host_service.install_skills project = context.project if project.nil? - # In-project runs sync learnings via the composed install-deps + # In-project runs sync learnings via the composed dev deps install # (project-linked); the projectless bootstrap syncs the machine # artifacts here or a fresh box would have none. @host_service.sync_learnings(project_root: nil) diff --git a/src/dev/builtins/update_deps_command.rb b/src/dev/builtins/update_deps_command.rb index 29b0325..223172b 100644 --- a/src/dev/builtins/update_deps_command.rb +++ b/src/dev/builtins/update_deps_command.rb @@ -11,7 +11,7 @@ module Dev module Builtins - # `dev update-deps`: resolve the dependencies.rb declarations and write + # `dev deps update`: resolve the dependencies.rb declarations and write # the lockfiles. Everything here is derived from the per-call project # root, so no collaborators need injecting. class UpdateDepsCommand < BuiltinCommand @@ -23,7 +23,7 @@ def desc = "Resolve dependency constraints and write lockfiles" sig { override.returns(Command::Category) } def category = Command::Category::Lifecycle - # update-deps IS the remediation for a stale manifest — nagging before + # dev deps update IS the remediation for a stale manifest — nagging before # it would block the very fix being run. sig { override.returns(T::Boolean) } def staleness_exempt? = true diff --git a/src/dev/runner.rb b/src/dev/runner.rb index 6db34f3..617645c 100644 --- a/src/dev/runner.rb +++ b/src/dev/runner.rb @@ -38,7 +38,9 @@ class Runner extend T::Sig # The root usage's closing line inside a project. - PROJECT_EPILOGUE = "Examples: dev up dev up -v dev update-deps dev test" + # The root listing already shows every command with its description, so + # the closing line points into the tree rather than repeating rows. + PROJECT_EPILOGUE = "Run 'dev help ' for a command's usage." # …and outside one: the real gap is the missing dev.yml. PROJECTLESS_EPILOGUE = "Run dev inside a project that defines a dev.yml to see its commands." @@ -294,16 +296,20 @@ def build_builtins(manifest, dependency_service, help:, complete:) builtins = T.let({ "help" => help, "complete" => complete, - "update-deps" => Builtins::UpdateDepsCommand.new, - "install-deps" => install_deps, - # `up` composes the same install the install-deps builtin runs. + # `up` composes the same install `dev deps install` runs. "up" => Builtins::UpCommand.new(install_deps_command: install_deps), - "check" => Builtins::CheckCommand.new(dependency_service:), + # Bundler's verbs: update ≈ bundle update, install ≈ bundle install, + # check ≈ bundle check (inspect and exit non-zero when unsatisfied). "deps" => CommandGroup.new( path: ["deps"], - desc: "Inspect locked dependencies", + desc: "Manage dependencies (update | install | check | path)", category: Command::Category::Lifecycle, - children: { "path" => Builtins::DepsPathCommand.new }, + children: { + "update" => Builtins::UpdateDepsCommand.new, + "install" => install_deps, + "check" => Builtins::CheckCommand.new(dependency_service:), + "path" => Builtins::DepsPathCommand.new, + }, ), "cache" => CommandGroup.new( path: ["cache"], diff --git a/test/dev/builtins/install_deps_command_test.rb b/test/dev/builtins/install_deps_command_test.rb index 557ffdd..e300e77 100644 --- a/test/dev/builtins/install_deps_command_test.rb +++ b/test/dev/builtins/install_deps_command_test.rb @@ -40,11 +40,11 @@ class Dev::Builtins::InstallDepsCommandTest < Minitest::Test }, host_service: host_service, ) - # Headless boxes reach install-deps before any CommandRunner provisioning, + # Headless boxes reach dev deps install before any CommandRunner provisioning, # so the builtin provisions the toolchain itself — the true boundary. Dev::ShadowenvRuby.expects(:ensure!).with(ruby_version: "4.0.1", project_root: root).once - When "running install-deps" + When "running dev deps install" command.call(args: [], context: build_context(root)) Then "the linker was scoped to the project in hand" @@ -74,7 +74,7 @@ class Dev::Builtins::InstallDepsCommandTest < Minitest::Test ) Dev::ShadowenvRuby.stubs(:ensure!) - When "running install-deps" + When "running dev deps install" command.call(args: [], context: build_context(root)) Then "the installer got the project-rooted lockfile and the host integration set" @@ -97,7 +97,7 @@ class Dev::Builtins::InstallDepsCommandTest < Minitest::Test command = Dev::Builtins::InstallDepsCommand.new(host_service: quiet_host_service) Dev::ShadowenvRuby.stubs(:ensure!) - When "running install-deps" + When "running dev deps install" command.call(args: [], context: build_context(root)) Then "the real install pass leaves the empty project untouched" diff --git a/test/dev/builtins/up_command_test.rb b/test/dev/builtins/up_command_test.rb index 2c17ca0..e8d530e 100644 --- a/test/dev/builtins/up_command_test.rb +++ b/test/dev/builtins/up_command_test.rb @@ -23,7 +23,7 @@ class Dev::Builtins::UpCommandTest < Minitest::Test !command.hidden? end - test "call ensures the dev cd shell hook and composes the install-deps body" do + test "call ensures the dev cd shell hook and composes the dev deps install body" do Given "an up command with expectations on both collaborators" install_deps = typed_mock(Dev::Builtins::InstallDepsCommand) host_service = quiet_host_service @@ -68,7 +68,7 @@ class Dev::Builtins::UpCommandTest < Minitest::Test When "running up outside any project" stdout = capture_stdout { command.call(args: [], context: context) } - Then "install-deps and credentials never run, and the bootstrap message points at projects" + Then "deps install and credentials never run, and the bootstrap message points at projects" 0 * install_deps.call(args: anything, context: anything) 0 * Dev::Credentials.resolve_build_args(anything) stdout.include?("dev: host layer converged.") diff --git a/test/dev/builtins/update_deps_command_test.rb b/test/dev/builtins/update_deps_command_test.rb index 9fcc4a7..fa723aa 100644 --- a/test/dev/builtins/update_deps_command_test.rb +++ b/test/dev/builtins/update_deps_command_test.rb @@ -30,7 +30,7 @@ class Dev::Builtins::UpdateDepsCommandTest < Minitest::Test old_stdout = $stdout $stdout = StringIO.new - When "running update-deps" + When "running dev deps update" command.call(args: [], context: build_context(root)) Then "the run completes and points at dev up" @@ -56,7 +56,7 @@ class Dev::Builtins::UpdateDepsCommandTest < Minitest::Test old_stdout = $stdout $stdout = StringIO.new - When "running update-deps" + When "running dev deps update" command.call(args: [], context: build_context(root)) Then "the locker received the bundler declarations (asserted on the mock)" @@ -76,7 +76,7 @@ class Dev::Builtins::UpdateDepsCommandTest < Minitest::Test old_stdout = $stdout $stdout = StringIO.new - When "running update-deps" + When "running dev deps update" command.call(args: [], context: build_context(root)) Then "the run resolved an empty config (a leaked ruby pin would try to resolve it)" diff --git a/test/dev/deps/accessor_test.rb b/test/dev/deps/accessor_test.rb index 7e8fd77..70b6916 100644 --- a/test/dev/deps/accessor_test.rb +++ b/test/dev/deps/accessor_test.rb @@ -149,8 +149,8 @@ def setup_locked_gh(dir, installed: true, marker_version: GH_ENGINE_TAG) accessor.path("gh", "UnrealEngineMac") end - Then "the fix is update-deps" - error.message.include?("run dev update-deps") + Then "the fix is dev deps update" + error.message.include?("run dev deps update") Cleanup FileUtils.rm_rf(dir) @@ -229,8 +229,8 @@ def setup_locked_gh(dir, installed: true, marker_version: GH_ENGINE_TAG) accessor.path("xcode") end - Then "the fix is update-deps" - error.message.include?("run dev update-deps") + Then "the fix is dev deps update" + error.message.include?("run dev deps update") Cleanup FileUtils.rm_rf(dir) diff --git a/test/dev/deps/bundler_locker_test.rb b/test/dev/deps/bundler_locker_test.rb index c35ff73..aef39db 100644 --- a/test/dev/deps/bundler_locker_test.rb +++ b/test/dev/deps/bundler_locker_test.rb @@ -29,7 +29,8 @@ def bundler_declarations(&block) locker.lock(decls) gemfile = (Pathname(dir) / "Gemfile").read - Then "the Gemfile pins the source, ruby, default gem, and grouped gem with options" + Then "the Gemfile names the verb that regenerates it, and pins the source, ruby, default gem, and grouped gem with options" + gemfile.include?("run `dev deps update`") gemfile.include?(%(source "https://rubygems.org")) gemfile.include?(%(ruby "~> 4.0")) gemfile.include?(%(gem "ffi", "~> 1.17")) diff --git a/test/dev/deps/cmake_integration_test.rb b/test/dev/deps/cmake_integration_test.rb index 31e9368..617a7f5 100644 --- a/test/dev/deps/cmake_integration_test.rb +++ b/test/dev/deps/cmake_integration_test.rb @@ -55,7 +55,8 @@ def prepopulate_dep(root, name) integration.install_all(deps) cmake_content = File.read(File.join(dir, "deps.cmake")) - Then + Then "the entries land under a header naming the verb that regenerates the file" + cmake_content.include?("run dev deps update to change") cmake_content.include?('set(dep_cereal_repo "https://github.com/USCiLab/cereal")') cmake_content.include?('set(dep_cereal_sha "abc123def456")') diff --git a/test/dev/deps/gh_integration_test.rb b/test/dev/deps/gh_integration_test.rb index 4c28742..54301ca 100644 --- a/test/dev/deps/gh_integration_test.rb +++ b/test/dev/deps/gh_integration_test.rb @@ -187,7 +187,7 @@ def build_integration(fixture_files, cache_dir) integration.install_all([dep]) Then "the marker was recognized and no write was attempted (the retired current-pointer " \ - "rewrite crashed dev install-deps here with EACCES at the plans#36 ceremony)" + "rewrite crashed dev deps install here with EACCES at the plans#36 ceremony)" !File.exist?(File.join(install_dir, "current")) integration.download_count == 0 diff --git a/test/dev/deps/lockfile_test.rb b/test/dev/deps/lockfile_test.rb index d7d86d2..2ecab0a 100644 --- a/test/dev/deps/lockfile_test.rb +++ b/test/dev/deps/lockfile_test.rb @@ -65,9 +65,10 @@ class Dev::Deps::LockfileTest < Minitest::Test When "locking with the digest" lockfile.lock(deps, manifest_digest: digest) - Then "both lockfiles carry it as a header comment and it reads back" + Then "both lockfiles carry it as a header comment, name the verb that rewrites them, and it reads back" File.read(File.join(dir, "deps.lock")).include?("# dependencies-digest: #{digest}") File.read(File.join(dir, "build-deps.lock")).include?("# dependencies-digest: #{digest}") + File.read(File.join(dir, "deps.lock")).include?("run dev deps update to change") lockfile.manifest_digest == digest # The YAML payload stays pure deps — the digest rides a comment. YAML.safe_load(File.read(File.join(dir, "deps.lock")))["cmake"].key?("boost") @@ -284,7 +285,7 @@ class Dev::Deps::LockfileTest < Minitest::Test test "reads legacy flat-format lockfiles" do # Migration shim: delete this test with the legacy read path once every - # consumer repo's lockfiles have been rewritten by update-deps (issue #146). + # consumer repo's lockfiles have been rewritten by dev deps update (issue #146). Given "lockfiles in the pre-nested format, name-keyed with integration in the value" dir = Dir.mktmpdir("dev-lockfile-test-") File.write(File.join(dir, "deps.lock"), <<~YAML) diff --git a/test/dev/deps/staleness_test.rb b/test/dev/deps/staleness_test.rb index a4882bb..4a01bc5 100644 --- a/test/dev/deps/staleness_test.rb +++ b/test/dev/deps/staleness_test.rb @@ -12,7 +12,7 @@ transform!(RSpock::AST::Transformation) class Dev::Deps::StalenessTest < Minitest::Test # A project dir with dependencies.rb plus lockfiles generated from it (the - # manifest digest recorded in the header, like dev update-deps does). + # manifest digest recorded in the header, like dev deps update does). def build_synced_project(dir) project = File.join(dir, "project") FileUtils.mkdir_p(project) @@ -45,7 +45,7 @@ def build_staleness(dir, project) FileUtils.rm_rf(dir) end - test "editing dependencies.rb after update-deps reports the manifest message" do + test "editing dependencies.rb after dev deps update reports the manifest message" do Given "a synced project whose manifest then changes" dir = Dir.mktmpdir("dev-staleness-test-") project = build_synced_project(dir) @@ -53,8 +53,8 @@ def build_staleness(dir, project) staleness.stamp_installed! File.write(File.join(project, "dependencies.rb"), "group :app do\nend\n") - Expect "the fix points at update-deps" - staleness.messages == ["dependencies.rb changed since the lockfiles were generated — run dev update-deps"] + Expect "the fix points at dev deps update" + staleness.messages == ["dependencies.rb changed since the lockfiles were generated — run dev deps update"] Cleanup FileUtils.rm_rf(dir) @@ -106,7 +106,7 @@ def build_staleness(dir, project) staleness = build_staleness(dir, project) staleness.stamp_installed! - Expect "no nag until the next update-deps records a digest" + Expect "no nag until the next dev deps update records a digest" staleness.messages == [] Cleanup diff --git a/test/dev/runner_test.rb b/test/dev/runner_test.rb index c8d2f02..6565bbf 100644 --- a/test/dev/runner_test.rb +++ b/test/dev/runner_test.rb @@ -23,12 +23,12 @@ class RunnerTest < Minitest::Test When "we run with empty argv" runner.run([]) - Then "the root's usage is printed: the tool's invocation, the project's name, its commands, the examples" + Then "the root's usage is printed: the tool's invocation, the project's name, its commands, the help hint" out.string.include?("Usage: dev [args...]") out.string.include?("Development commands for testproject") out.string.include?("up") out.string.include?("Setup") - out.string.lines.last == "#{Dev::Runner::PROJECT_EPILOGUE}\n" + out.string.lines.last == "Run 'dev help ' for a command's usage.\n" end test "run with --help prints usage" do @@ -122,7 +122,7 @@ class RunnerTest < Minitest::Test $stderr = old_stderr end - test "usage includes built-in update-deps command" do + test "usage lists the deps group in Lifecycle, not the retired flat dependency verbs" do Given "a Runner with no project commands" out = StringIO.new runner = build_runner(commands: {}, out: out) @@ -130,9 +130,32 @@ class RunnerTest < Minitest::Test When "we print usage" runner.run([]) - Then "update-deps is listed" - out.string.include?("update-deps") - out.string.include?("Resolve dependency constraints") + Then "deps is one row; update-deps / install-deps / check are gone" + out.string.include?(" deps … Manage dependencies (update | install | check | path)") + !out.string.include?("update-deps") + !out.string.include?("install-deps") + !out.string.match?(/^ check /) + end + + test "the retired flat dependency verbs are not found" do + Given "a Runner" + runner = build_runner(commands: {}) + old_stderr = $stderr + $stderr = StringIO.new + Kernel.expects(:exit).with(1).times(3) + + When "running each flat name" + runner.run(["update-deps"]) + runner.run(["install-deps"]) + runner.run(["check"]) + + Then "each falls through to the not-found error" + $stderr.string.include?("Command 'update-deps' not found") + $stderr.string.include?("Command 'install-deps' not found") + $stderr.string.include?("Command 'check' not found") + + Cleanup + $stderr = old_stderr end test "usage includes both built-in and project commands" do @@ -147,7 +170,7 @@ class RunnerTest < Minitest::Test runner.run([]) Then "all commands appear" - out.string.include?("update-deps") + out.string.include?("deps …") out.string.include?("test") out.string.include?("up") end @@ -339,7 +362,7 @@ class RunnerTest < Minitest::Test out.string.include?(" test … Test suites") end - test "the deps builtin is a group: bare it prints its usage, listing the path leaf" do + test "the deps builtin is a group: bare it prints its usage, listing update, install, check and path" do Given "a Runner" out = StringIO.new runner = build_runner(commands: {}, out: out) @@ -347,8 +370,13 @@ class RunnerTest < Minitest::Test When "running deps bare" runner.run(["deps"]) - Then "the group usage renders" + Then "the group usage renders the four leaves and nothing else" out.string.include?("Usage: dev deps [args...]") + rows = out.string.lines.map(&:chomp).select { |l| l.start_with?(" ") } + rows.map { |l| l.split.first } == %w[check install path update] + out.string.include?(" update Resolve dependency constraints and write lockfiles") + out.string.include?(" install Install locked dependencies handled on the host") + out.string.include?(" check Check dependency state freshness") out.string.include?(" path Print a locked artifact's path") end @@ -390,7 +418,7 @@ class RunnerTest < Minitest::Test Then "help, up, runner and the global nouns are offered; project-only builtins are not" names = out.string.lines.map(&:chomp) (%w[help up runner cd clone config cred learnings plan] - names).empty? - !names.include?("install-deps") + !names.include?("deps") end test "bare dev outside a project prints the projectless root: the global commands and the dev.yml hint" do @@ -525,7 +553,7 @@ class RunnerTest < Minitest::Test Kernel.expects(:exit).with(1).once When "running a project-scoped builtin" - runner.run(["install-deps"]) + runner.run(%w[deps install]) Then "the lookup fails like any other command outside a project" $stderr.string.include?("no dev.yml found in this directory or any parent")