Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
5976f9f
docs: hoist design-docs policy to root AGENTS.md; rename design docs …
martin-kolinek Jul 21, 2026
b82ffbd
feat(cargo-each): new cargo subcommand to run a command per workspace…
martin-kolinek Jul 22, 2026
106a266
chore: regenerate cargo-anvil and automation READMEs
martin-kolinek Jul 24, 2026
d7ac47a
review: address reviewer feedback on cargo-each
martin-kolinek Jul 27, 2026
db3c7df
review: enforce --exclude, refine fail-fast exit code, add logos
martin-kolinek Jul 27, 2026
217a178
review: fix is_whole_workspace to match resolve() for --workspace + -p
martin-kolinek Jul 27, 2026
8498156
review: validate metadata keys and use a linear glob matcher
martin-kolinek Jul 27, 2026
f9bd551
review: match name@version specs, clarify filter composition, doc fixes
martin-kolinek Jul 28, 2026
f6519cb
review: implement cargo partial-version matching for name@version specs
martin-kolinek Jul 28, 2026
d633ffc
fix(cargo-each): avoid != operator in rustdoc that trips spellcheck
martin-kolinek Jul 28, 2026
eb90504
review: cargo-accurate semver matching and validate templates on empt…
martin-kolinek Jul 28, 2026
f5c4163
test(cargo-each): cover prerelease/build version matching end-to-end
martin-kolinek Jul 28, 2026
686abed
chore(spelling): add 'versioned' to dictionary
martin-kolinek Jul 28, 2026
e95b23e
test(cargo-each): make the --chdir test observe the working directory
martin-kolinek Jul 28, 2026
994c6bf
review: tighten version_matches build/prerelease rules; test filter a…
martin-kolinek Jul 28, 2026
b9c7f8d
test(cargo-each): kill surviving mutants in version_matches guard
martin-kolinek Jul 28, 2026
a9cc752
review: reject malformed version specs and cover metadata projection
martin-kolinek Jul 28, 2026
34b7f4e
review: address round-12 reviewer feedback
martin-kolinek Jul 29, 2026
bfc75e3
review: address round-13 meta-findings
martin-kolinek Jul 29, 2026
f4f64c6
docs: explain how cargo-each differs from cargo-workspaces exec
martin-kolinek Jul 29, 2026
21d200d
docs: compare cargo-each to cargo-hack in the design doc
martin-kolinek Jul 29, 2026
442c361
refactor!: make cargo-each binary-only (no public library API)
martin-kolinek Jul 29, 2026
d4660dd
fix: repair intra-doc links broken by the binary-only restructure
martin-kolinek Jul 29, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .spelling
Original file line number Diff line number Diff line change
Expand Up @@ -448,3 +448,15 @@ READMEs
RUSTSEC
UB
subdirectory
dep
io
deps
fmt
justfile
readme
templating
udeps
unscoped
backtracker
prerelease
versioned
75 changes: 75 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,81 @@ The spell checker dictionary is in the `.spelling` file, one word per line in ar

Do not manually edit `CHANGELOG.md` files. Changelogs are automatically updated on release.

## Design Docs Are Part of the Source

Each crate's design is captured under `crates/<crate>/docs/`, kept in
lockstep with the code (as pioneered by `cargo-anvil` and
`cargo-coverage-gate`). Design is the contract; code is the realization.

This is prospective, not a retroactive invariant: new crates land with a
design doc (see [New features and new crates](#new-features-and-new-crates)),
and pre-existing crates without one (e.g. `automation`, `cargo-heather`,
`cargo_ensure_no_cyclic_deps`) are brought into compliance opportunistically
when substantially changed — not in a single sweep.

### Layout

- `docs/design/README.md` — the top-level conceptual design: the problem,
guiding principles, and the user-visible shape (UX, inputs/outputs,
exit-code semantics, configuration, CI integration, cross-cutting
concerns, out-of-scope). Named `README.md` so it renders as the
`docs/design/` landing page on GitHub and ADO. For larger crates, split
detail into companion docs (e.g. `docs/design/<topic>.md`) linked from
it.

Optionally, a crate may keep implementation plans under
`docs/implementation-plans/NNNN.md` (rolling, zero-padded) to sequence
large multi-commit efforts. They are never required — skip them for small
crates or self-contained changes.

### Why they exist

1. **Review aid** — a human or AI reviewer can validate the solution
end-to-end without reading every file.
2. **AI-friendly evolution** — future agentic changes start from a
current, accurate spec instead of reverse-engineering intent, which
lowers the cost of every later review/refactor pass.

### Rules for changes

When you implement a change in a crate:

- **If an externally observable contract changes** (behavior, UX, exit
codes, config shape, output format, public API), update that crate's
design doc in the *same* change.
- **If the change is purely internal** (refactor with no contract delta,
dependency bump, test-only), no doc update is required — but fix any
design drift you notice opportunistically.

### Don't reference design docs from code

Design docs are a review and evolution aid, not a stable API. Do **not**
cite design sections from rustdoc, code comments, or other source
(e.g. "see design §6.5"): it couples the code to the doc's structure and
blocks reorganizing the design without churning source. References flow
one way — design docs may point at code, not the reverse. Link between
design docs freely.

The codebase is mid-migration to this rule: some existing rustdoc (notably
under `crates/cargo-anvil/src/`) still carries section-anchored references
(`§N`). Remove those anchors opportunistically when you touch nearby code —
there is no need for a dedicated sweep.

### New features and new crates

- A new user-facing feature starts with a design-doc update (or a new
companion doc) *before or alongside* the implementation.
- A new crate lands with at least `docs/design/README.md`. Add a
crate-level `AGENTS.md` only if the crate has rules beyond this root
file (e.g. extra CI gates); it need not restate the shared policy.

### Don't delete the design docs

The design docs — and any implementation plans — are intentional,
persistent artifacts. Don't remove `docs/` to "clean up" — keeping them
in sync is far cheaper than regenerating that context on every review or
extension.

## Maintainability

While it is fine to use `.expect()`, the precondition is that it is either a programming error (the caller did something wrong)
Expand Down
14 changes: 14 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

22 changes: 21 additions & 1 deletion crates/automation/README.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,23 @@
# automation ![License: MIT](https://img.shields.io/badge/license-MIT-blue) [![automation on crates.io](https://img.shields.io/crates/v/automation)](https://crates.io/crates/automation) [![automation on docs.rs](https://docs.rs/automation/badge.svg)](https://docs.rs/automation) [![Rust Version: 1.88.0](https://img.shields.io/badge/rustc-1.88.0-orange.svg)](https://github.com/rust-lang/rust/releases/tag/1.88.0)
<div align="center">
<img src="./logo.png" alt="Automation Logo" width="96">
Comment thread
martin-kolinek marked this conversation as resolved.

# Automation

[![crates.io](https://img.shields.io/crates/v/automation.svg)](https://crates.io/crates/automation)
[![docs.rs](https://docs.rs/automation/badge.svg)](https://docs.rs/automation)
[![MSRV](https://img.shields.io/crates/msrv/automation)](https://crates.io/crates/automation)
[![CI](https://github.com/microsoft/ox-tools/actions/workflows/main.yml/badge.svg?event=push)](https://github.com/microsoft/ox-tools/actions/workflows/main.yml)
[![Coverage](https://codecov.io/gh/microsoft/ox-tools/graph/badge.svg?token=FCUG0EL5TI)](https://codecov.io/gh/microsoft/ox-tools)
[![License](https://img.shields.io/badge/license-MIT-blue.svg)](../../LICENSE)
<a href="../.."><img src="../../logo.svg" alt="This crate was developed as part of the Oxidizer project" width="20"></a>

</div>

An unpublished crate for shared code used for writing Rust scripts


<hr/>
<sub>
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/ox-tools/tree/main/crates/automation">source code</a>.
</sub>

3 changes: 3 additions & 0 deletions crates/automation/logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
4 changes: 2 additions & 2 deletions crates/cargo-anvil/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,7 +296,7 @@ See `docs/design/extensibility.md`.

See `docs/design/` for the full architecture:

* `design.md` — overall principles and CLI shape.
* `README.md` — overall principles and CLI shape.
* `checks.md` — the opinionated check catalog.
* `local.md` — the `justfiles/anvil/` tree.
* `updates.md` — the drift-detection algorithm.
Expand All @@ -312,7 +312,7 @@ And `docs/verification.md` for the continuous-validation strategy.
This crate was developed as part of <a href="../..">The Oxidizer Project</a>. Browse this crate's <a href="https://github.com/microsoft/ox-tools/tree/main/crates/cargo-anvil">source code</a>.
</sub>

[__cargo_doc2readme_dependencies_info]: ggGmYW0CYXZlMC43LjJhdIQbFhzZ8rzWNNYbuRaDSGWynFgbH4PMdoT7GNcbVwNPtPjAhvFhYvRhcoQbI_VKBuSOMRUbfx_41ISPNUAbU-pof2TAXRkbObPMDStfcEhhZIGDa2NhcmdvLWFudmlsZTAuMy4wa2NhcmdvX2Fudmls
[__cargo_doc2readme_dependencies_info]: ggGkYW0CYXSEGxYc2fK81jTWG7kWg0hlspxYGx-DzHaE-xjXG1cDT7T4wIbxYXKEG0A0CAgK-7JhG3kcsE0K8nZGG9CCeHb5HZ-zG1w2gtfKsIP6YWSBg2tjYXJnby1hbnZpbGUwLjMuMGtjYXJnb19hbnZpbA
[__link0]: https://crates.io/crates/cargo-delta
[__link1]: https://crates.io/crates/cargo-spellcheck
[__link2]: https://crates.io/crates/cargo-coverage-gate
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-anvil/docs/design/ado.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ need to change:

See also:

- [design.md §6](./design.md#6-repo-layout) for the file-category model.
- [README.md §6](./README.md#6-repo-layout) for the file-category model.
- [checks.md](./checks.md) for what each group runs.
- [local.md](./local.md) for the `just` recipes the templates invoke.
- [github.md](./github.md) for the GitHub Actions counterpart.
Expand Down
6 changes: 3 additions & 3 deletions crates/cargo-anvil/docs/design/checks.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ canonical source for "what does anvil actually run?"

See also:

- [design.md](./design.md) for the overall principles and CLI shape.
- [README.md](./README.md) for the overall principles and CLI shape.
- [local.md](./local.md) for how the catalog is exposed as `just` recipes.
- [github.md](./github.md) / [ado.md](./ado.md) for how groups map to cloud-workflow building blocks.

Expand Down Expand Up @@ -139,7 +139,7 @@ the per-leg runner-label inputs and forking the workflow when the matrix shape i
needs to change, [ado.md §4](./ado.md#4-owned-stages-templates) for
`linuxPool`/`windowsPool`).
Locally there is no OS matrix; `just anvil-pr-slow` (the umbrella recipe) runs the three sub-recipes in sequence against whatever OS the
developer is on. See [design.md §8.3](./design.md#83-cross-os-test-matrices) for the
developer is on. See [README.md §8.3](./README.md#83-cross-os-test-matrices) for the
overall rationale.

The `scheduled-exhaustive` group's checks are independent and could in principle live in
Expand All @@ -166,7 +166,7 @@ that provided the strongest version of the check.
| `doc-build` | `RUSTDOCFLAGS='-D warnings' cargo doc --workspace --all-features --no-deps` | oxidizer-github |
| `readme-check` | `cargo doc2readme --check` for each crate that opts in (presence of a `[package.metadata.doc2readme]` table) | oxidizer-github |
| `spellcheck` | `cargo spellcheck check --code 1` | oxidizer-github |
| `pr-title` | Conventional-Commits regex applied to the title in the `PR_TITLE` env var. Skipped silently only when the env var is **unset** (intermediate local runs, scheduled-tier builds); a non-empty-but-invalid value fails loudly (a misconfigured cloud variable is a real error, not a skip). Written as a `[script("pwsh")]` recipe (the one check that needs scripting; see [design.md §8.3](./design.md#83-cross-os-test-matrices)). cloud-workflow wiring: GitHub's per-group composite action reads `${{ inputs.pr_title }}` populated from `${{ github.event.pull_request.title }}` in the reusable PR workflow; ADO has **no** PR-title predefined variable (`System.PullRequest.Title` does not exist), so `group.yml` resolves the title from the REST API on PR builds and publishes it as `PR_TITLE` (empty on non-PR / fork / API failure, where the recipe no-ops). | oxidizer-github |
| `pr-title` | Conventional-Commits regex applied to the title in the `PR_TITLE` env var. Skipped silently only when the env var is **unset** (intermediate local runs, scheduled-tier builds); a non-empty-but-invalid value fails loudly (a misconfigured cloud variable is a real error, not a skip). Written as a `[script("pwsh")]` recipe (the one check that needs scripting; see [README.md §8.3](./README.md#83-cross-os-test-matrices)). cloud-workflow wiring: GitHub's per-group composite action reads `${{ inputs.pr_title }}` populated from `${{ github.event.pull_request.title }}` in the reusable PR workflow; ADO has **no** PR-title predefined variable (`System.PullRequest.Title` does not exist), so `group.yml` resolves the title from the REST API on PR builds and publishes it as `PR_TITLE` (empty on non-PR / fork / API failure, where the recipe no-ops). | oxidizer-github |
| `deny` | `cargo deny check` | all |
| `audit` | `cargo audit` | oxidizer |
| `udeps` | `cargo +<pinned-nightly> udeps --workspace --all-features` run **twice** — once with default targets (lib + bins) and once with `--all-targets`. cargo-udeps only analyzes the targets it's told to, and each run catches a variant the other masks: the default-targets run surfaces a dep in `[dependencies]` referenced only by tests/benches/examples (it should be a dev-dep; `--all-targets` would see it as "used"), while the `--all-targets` run surfaces unused `[dev-dependencies]` (never compiled by the default-targets run). Together they cover unused deps, unused dev-deps, and deps that should be dev-deps. | oxidizer, oxidizer-github |
Expand Down
6 changes: 3 additions & 3 deletions crates/cargo-anvil/docs/design/extensibility.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
> reusing the same anvil engine (decision table, region splicing, manifest tracking, backend
> resolution, dry-run, summary).

This is a companion to the top-level [design.md](./design.md). It assumes familiarity with the
This is a companion to the top-level [README.md](./README.md). It assumes familiarity with the
core concepts defined there and in [checks.md](./checks.md), [local.md](./local.md), and
[updates.md](./updates.md).

Expand Down Expand Up @@ -330,7 +330,7 @@ OwnedFileSpec {
}
```

Selection is unchanged from [design.md §5.2](./design.md): the engine resolves a backend set from
Selection is unchanged from [README.md §5.2](./README.md): the engine resolves a backend set from
explicit `--backend` flags or autodetection over the *fixed* `{github, ado}`, and only emits an
owned file whose `gate` is `None` or names a selected backend. Each built-in backend file is
exposed as a public artifact (§4.1), and a fork manipulates them — and adds new ones — with the
Expand Down Expand Up @@ -529,7 +529,7 @@ ancestors defined seamlessly, as one tool managing one namespace.
sentinels, `justfiles/anvil/`, or the `anvil-` recipe prefix. Those belong to the engine.
- **Runtime plugins / dynamic loading.** A catalog is Rust code compiled into the downstream
binary, not a config file discovered at runtime. This keeps the "writes files, then exits"
stance ([design.md §3](./design.md)) and avoids a plugin ABI.
stance ([README.md §3](./README.md)) and avoids a plugin ABI.
- **Fork-authored backends.** The backend set is closed (`github`, `ado`); a fork cannot add a
backend (§4.3). It can override, drop, or add individual files gated on an *existing* backend,
but the `Backend` enum and backend selection/autodetection are engine-owned.
Expand Down
2 changes: 1 addition & 1 deletion crates/cargo-anvil/docs/design/github.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ need to change:

See also:

- [design.md §6](./design.md#6-repo-layout) for the file-category model.
- [README.md §6](./README.md#6-repo-layout) for the file-category model.
- [checks.md](./checks.md) for what each group runs.
- [local.md](./local.md) for the `just` recipes the composite actions invoke.
- [ado.md](./ado.md) for the ADO counterpart.
Expand Down
4 changes: 2 additions & 2 deletions crates/cargo-anvil/docs/design/local.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ don't — that's the design).

See also:

- [design.md](./design.md) for the overall principles.
- [README.md](./README.md) for the overall principles.
- [checks.md](./checks.md) for the catalog the recipes implement.
- [updates.md](./updates.md) for how these files are tracked / regenerated.

Expand Down Expand Up @@ -495,7 +495,7 @@ tools.

## 7. Customization at the recipe level

Per the four customization tiers in [design.md §7](./design.md#7-customization):
Per the four customization tiers in [README.md §7](./README.md#7-customization):

- **Add your own recipes** to the top-level `Justfile` outside the managed region. The
Justfile's managed region only contains `import` lines and an alias — your recipes never
Expand Down
4 changes: 2 additions & 2 deletions crates/cargo-anvil/docs/design/updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ its content again on subsequent runs.

See also:

- [design.md](./design.md) for the overall principles and the CLI shape.
- [README.md](./README.md) for the overall principles and the CLI shape.
- [local.md](./local.md) for the just-recipe files this algorithm manages.
- [github.md](./github.md) / [ado.md](./ado.md) for the cloud workflow building-block files.

Expand Down Expand Up @@ -143,7 +143,7 @@ Its purpose is to disambiguate **builds that report the same `tool_version` but
catalogs** — not a normal situation in released usage, but routine during development, where two
locally-built binaries can both say `0.0.0` while their templates differ. Recording the catalog
checksum in the lock makes "which build last wrote this" answerable, and `--version` prints the
current build's value (see [design.md §5.2](./design.md#52-the-single-command)) so it can be
current build's value (see [README.md §5.2](./README.md#52-the-single-command)) so it can be
compared against the lock.

The checksum is **provenance and diagnostics, not a gate**: a differing `catalog_checksum` is
Expand Down
6 changes: 3 additions & 3 deletions crates/cargo-anvil/docs/implementation-plans/0000.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Initial Implementation Plan

This document breaks the [design](./design/design.md) into review-sized commits. Each commit
This document breaks the [design](./design/README.md) into review-sized commits. Each commit
should be independently reviewable, build cleanly, and (where reasonable) ship with unit
tests. Integration tests against full repo fixtures arrive late in the plan; until then we
lean on unit tests over individual modules.
Expand All @@ -9,7 +9,7 @@ The plan is sequenced so that **every commit leaves the crate compiling**, and s
the dogfooding wiring (final commit) can light up incrementally.

Companion docs:
- [design.md](./design/design.md) — overall design.
- [README.md](./design/README.md) — overall design.
- [checks.md](./design/checks.md), [local.md](./design/local.md),
[updates.md](./design/updates.md), [github.md](./design/github.md),
[ado.md](./design/ado.md) — detail.
Expand Down Expand Up @@ -149,7 +149,7 @@ top-level driver feeds through the decision module and the manifest.
- Use `toml_edit` to manipulate the `[workspace.lints]` table (or `[lints]` in
single-crate mode) without disturbing surrounding content.
- Emit dotted-key form (`rust.unsafe_op_in_unsafe_fn = "deny"`, …) per
[design.md §6](./design/design.md#6-repo-layout) so users can add their own lints
[README.md §6](./design/README.md#6-repo-layout) so users can add their own lints
in the same scope outside the sentinels.
- For each member crate, add the `[lints] workspace = true` region.

Expand Down
6 changes: 3 additions & 3 deletions crates/cargo-anvil/docs/implementation-plans/0001.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ Single doc-only commit. No behavior change.
`bench-only`) (audit D2, C5-name).
- **`local.md` §2**: mention `[group("anvil")]` decorators and
`set unstable` in `checks.just` (audit C7).
- **`updates.md` §3, `design.md` §6**: lints baseline severity in the
- **`updates.md` §3, `README.md` §6**: lints baseline severity in the
examples is `warn`, not `deny` (matching `cargo-lints-body.toml`).
Add `clippy.expect_used` and `rustdoc.broken_intra_doc_links` to
the example list (audit B10 — the missing lints land in Phase 4).
Expand All @@ -112,7 +112,7 @@ Single doc-only commit. No behavior change.
produces meaningful include lists, because cargo-delta itself
handles a missing or empty config file gracefully (audit C6,
resolved doc-side).
- **`design.md` §3 (Non-Goals)**: add a note that `cargo-anvil` may
- **`README.md` §3 (Non-Goals)**: add a note that `cargo-anvil` may
in future introduce small runtime subcommands for tasks tightly
coupled to the cloud workflow execution (coverage gating, etc.); the
"tool only writes files" stance remains the default but isn't
Expand Down Expand Up @@ -216,7 +216,7 @@ Single doc-only commit. No behavior change.
workspace's `rust-toolchain.toml`.
- **`tools.just`**: `_anvil-require pwsh` checks `command -v pwsh`
with a per-OS install hint (linux: package manager; macos: brew;
windows: built-in) — matches `design.md` §8.3.
windows: built-in) — matches `README.md` §8.3.
- **`checks.just`**: `anvil-pr-title` (the lone `[script("pwsh")]`
check) gains `(_anvil-require "pwsh")` as a dependency.
- Audit refs: A6.
Expand Down
Loading
Loading