feat: Introduce cargo-ox-check for unified Rust build/CI scaffolding#33
Draft
martin-kolinek wants to merge 119 commits into
Draft
feat: Introduce cargo-ox-check for unified Rust build/CI scaffolding#33martin-kolinek wants to merge 119 commits into
martin-kolinek wants to merge 119 commits into
Conversation
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wires clap (derive) with the single 'update' subcommand and flags --backend (repeatable), --no-backends (mutually exclusive with --backend), and --dry-run. Adds anyhow, thiserror, tracing, tracing-subscriber deps. main.rs strips the 'ox-check' token cargo injects for subcommand binaries. run_update is a no-op that logs its inputs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the backend module with Backend enum (GitHub, Ado), URL parsing (https/scp/ssh forms), git-config invocation for origin, and a resolve() function that implements the CLI resolution order: --no-backends > --backend > autodetect. Unit tests cover URL parsing edge cases, name parsing, and resolution. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the workspace module: find_workspace_root() walks up from a start path to the nearest [workspace] ancestor (falling back to the nearest [package] for single-crate repos), and load_workspace() parses the manifest with toml_edit and resolves members. Supports literal member entries and 'crates/*'-style trailing globs (the only form observed in surveyed repos). Adds toml_edit to workspace deps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the manifest module implementing the sidecar file documented in updates.md. Manifest::load() returns Manifest::default() for missing files. Manifest::save() writes atomically via temp+rename. to_toml() serializes deterministically with sorted entries (BTreeMap-backed), always ending in a trailing newline so diffs are minimal. Rejects schema versions newer than 1; missing/duplicate entries are hard errors. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds checksum and decision modules. checksum_bytes/checksum_str return the canonical 'sha256:<hex>' string used throughout the codebase. decide() implements the table from updates.md section 5 with five outcomes: InSync, Skipped (opted out), Write, Propose, LeaveAlone. Includes should_emit_proposed_for_opt_out() to handle the empty-stub case where we still proactively surface upstream churn. Adds sha2 to workspace deps. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the region module: find_region(), upsert_region(), and render_region() implement the sentinel-delimited managed-region machinery for any host file. Supports two comment syntaxes (# for Justfile/TOML/YAML and // for future hosts). Recognizes indented sentinels (needed for YAML). Detects malformed regions (duplicate opener, missing closer, closer-before-opener) and reports clean errors. Treats whitespace-only bodies as empty (= opt-out signal). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the plan module: PlanItem encapsulates one accumulated decision plus the rendered content (or spliced host body for regions). Plan::apply() writes owned files, splices region updates, writes .ox-check-proposed siblings, and returns an updated manifest. Plan::summary() renders a stable line-oriented digest; Plan::dry_run_exit_code() returns 1 iff any item would write. For region proposals the proposed file lives next to the *host* (host.ox-check-proposed), and per updates.md section 7 it contains the full spliced host file rather than just the region body. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the emit module (mod, owned_file, local). plan_owned_file() drives one file through the decision algorithm: reads the disk content, detects opt-out (empty/whitespace-only), and produces a PlanItem. plan_tools_just() is the first concrete emitter, embedding templates/justfiles/ox-check/tools.just via include_str! and dispatching through the owned-file driver. The template is wrapped in a sentinel-delimited block per the design convention but is itself an owned file. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the full catalog from checks.md as one recipe per check (ox-check-<name>): all pr-fast, pr-test, pr-mutants, nightly-runtime, and nightly-exhaustive members. pr-title is the lone [script('pwsh')] block; everything else is a one-line cargo invocation gated by an ox-check-tools-check or _ox-check-require dependency. plan_checks_just() drives it through the same owned-file path as tools.just.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the two remaining files of the justfiles/ox-check/ tree. groups.just defines the seven check-groups (3 pr + 4 nightly) as just-recipe dependency lists pointing at the per-check recipes from commit 9. tiers.just aggregates them into ox-check-pr, ox-check-nightly, and ox-check-full. plan_local_just_tree() bundles all four file emitters into a single helper for the run driver. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the managed_region driver — the region equivalent of plan_owned_file — and the first managed-region emitter for the user's Justfile. plan_justfile_imports() inserts the four 'import' lines plus the 'alias ox-check := ox-check-pr' line into the ox-check-imports region. Creates the Justfile if absent; appends the region if the file has user content; replaces just the region body otherwise. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the cargo_toml emitter producing managed regions for the lint catalog. For multi-crate workspaces: one ox-check-workspace-lints region in the root Cargo.toml ([workspace.lints] in dotted-key form) plus an ox-check-lints region with 'workspace = true' in each member. For single-crate repos: one ox-check-lints region with the full catalog at [lints] scope. The dotted-key form lets users extend the same scope outside the sentinels (TOML forbids re-declaring [workspace.lints.clippy]). Includes a round-trip test that splices the body into a real Cargo.toml and re-parses with toml_edit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds shared_configs emitter producing one managed region per file. Bodies stay intentionally small so most adopters never need to override: deny.toml carries a permissive SPDX allowlist plus advisory/yanked rules, rustfmt.toml sets edition/width/newline-style only, and .delta.toml configures the root files that invalidate impact-scoping. Each can be opted out by emptying its region — no special flag needed. Round-trip test parses every spliced body through toml_edit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Wires the run module to actually drive the update algorithm: discovers the workspace root, loads the manifest, resolves backends, builds the full plan from all local emitters (just tree + Justfile imports + Cargo.toml lints + shared configs), applies (or dry-runs) it, and rewrites .ox-check.lock. Exposes run_update() taking an explicit start directory so integration tests can drive the algorithm without std::process::exit. Adds e2e tests covering first-run write, idempotent second run, dry-run abstinence, opt-out via empty region, and user-edit-with-template-unchanged leave-alone. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the first layer of the GitHub Actions backend. Two shared composite actions live as static template files: ox-check-setup installs just and the catalog tools; ox-check-impact runs cargo-delta and emits excludes/skip outputs for the impact job. The seven per-group composite actions are rendered programmatically (render_group_action) since they differ only by group name; each takes excludes/skip inputs from the impact job and invokes just ox-check-<group>. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the wiring layer: ox-check-pr-impl.yml and ox-check-nightly-impl.yml. Both are workflow_call entry points. PR runs an impact job (cargo-delta) then fans out into pr-fast, pr-test (matrix across test_os input), and pr-mutants jobs that each consume the excludes/skip outputs. Nightly skips the impact job (slow checks always run on main) and fans out into the four nightly groups, uploading the coverage lcov artifact from the Linux test leg. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the two root workflows (ox-check-pr.yml, ox-check-nightly.yml) that own triggers, permissions, and concurrency, then call the reusable workflows. plan_github_backend() bundles all 13 files (2 shared actions + 7 group actions + 2 reusable workflows + 2 root workflows). The run driver dispatches to it when Backend::GitHub is in the resolved set. After this commit --backend github is fully functional. Integration test creates a workspace and asserts the full .github tree is present after one update; a second test confirms idempotency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds the first layer of the ADO backend. setup.yml installs just + catalog tools; impact.yml runs cargo-delta and publishes excludes/skip variables via ##vso[task.setvariable] with isOutput=true so the stages template can consume them as stage outputs. The seven per-group step templates are rendered programmatically (render_group_step); each takes excludes/skip parameters, includes setup.yml, and invokes the matching just recipe with OX_CHECK_EXCLUDES set. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds pr.yml and nightly.yml stages templates. PR stages: impact stage publishes excludes/skip outputs that downstream stages (pr_fast, pr_test, pr_mutants) consume via stageDependencies. pr_test fans out across linux/windows jobs. Every group stage condition is succeededOrFailed() so a failing pr-fast doesn't gate pr-test. Nightly stages: four parallel stages with empty dependsOn arrays, plus an lcov artifact publish from the linux nightly-test leg. Both templates expose linuxPool/windowsPool object parameters so users can override pools when wrapping in a 1ESPT pipeline. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds ox-check-pr.yml and ox-check-nightly.yml root pipelines. PR pipeline is PR-only (trigger: none, pr triggers on main); nightly carries a daily cron schedule. Both pass default vmImage-based pools to the stages templates; users wrapping in 1ESPT override these by replacing the root pipeline. plan_ado_backend() bundles all 13 files. The run driver dispatches to it when Backend::Ado is in the resolved set. After this commit --backend ado is fully functional. Integration tests cover ado-only and ado+github combined runs for idempotency. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds tests/schemas.rs running external validators against the full emitted tree from a fresh workspace. Validators (taplo for TOML, actionlint for GH workflows, just --list for the Justfile) are invoked via Command; any validator not installed produces a 'skipping' message rather than a test failure, so the suite works on every developer machine while enforcing schema correctness in CI. ADO YAML has no public standalone validator we depend on, so the ado test confirms only structural properties (no tabs, even-space indentation). Also adds 'set unstable' to checks.just so just accepts the [script('pwsh')] attribute on pr-title.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds .github/workflows/regenerate-check.yml: builds cargo-ox-check from the PR branch and runs 'cargo ox-check update --dry-run' against the repo, failing the PR if the in-tree state diverges from what the templates would render. This is the primary dogfooding mechanism from verification.md section 3. Note: the workflow will report a divergence until a maintainer runs the actual bootstrap step (regenerate the workspace lints region in dotted-key form, write the justfiles/ox-check tree, etc.) — that is a separate human-driven activity, intentionally not folded into this implementation PR. The verification.md bootstrap section covers the procedure. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Expands lib.rs doc-comments into a publishable-quality crate description covering the what/install/usage/daily-driver/customization story, all linking back to the docs/design/ files. Adds a placeholder README.md following the cargo_heather pattern (auto-generated from doc-comments by 'just readme' from the repo root). 'cargo publish --dry-run -p cargo-ox-check --allow-dirty' completes successfully: 49 files, 341 KiB packaged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Swaps the error type to match the convention used by the other crates in this repo (cargo_heather, cargo_ensure_no_cyclic_deps). Function signatures now return Result<T, ohno::AppError>; error construction uses ohno::app_err! and ohno::bail!; context attachment uses IntoAppError::into_app_err / into_app_err_with. The allowed_external_types whitelist is updated accordingly. All 155 tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Empty content has a stable checksum that no template ever produces, so emptying a file or region body always lands in the standard D != L branches. The steady-state opt-out case (after at least one prior render) reaches LeaveAlone naturally — silent — and a template change reaches Propose — surfaces upstream churn. Both outcomes preserve the user's empty stub, matching the design contract. The only behavior change is the edge case where the user creates an empty file *before* the first ox-check update (no manifest entry yet): instead of a silent skip, ox-check now writes a one-time .ox-check-proposed sibling showing what the template would render. That's arguably better UX since it documents what the user is opting out of. Removes: - DecisionInputs::emptied field - Decision::Skipped variant - should_emit_proposed_for_opt_out helper - the trim-empty detection in owned_file and managed_region drivers All 155 tests still pass. The behavior the tests covered survives: opt-out tests now assert LeaveAlone (when template unchanged) or Propose (when changed) instead of Skipped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
tools.just, checks.just, groups.just, tiers.just are owned files — the manifest tracks them by full-file checksum. Sentinels are reserved for managed regions inside user-composed hosts (Justfile, Cargo.toml, deny.toml, rustfmt.toml, .delta.toml) where ox-check carves out a section within other content. The 'Owned by cargo-ox-check' advisory comment stays as a one-line notice for human readers. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…files Three coupled cleanups: 1. Add justfiles/ox-check/mod.just as the single entry point. It imports the four sibling .just files and defines 'alias ox-check := ox-check-pr'. The user's Justfile region is now a single 'import justfiles/ox-check/mod.just' line — everything else is owned files the user never edits directly. 2. Move the alias out of the user's Justfile region. The user no longer has any recipe content in their Justfile; mod.just owns it. Easier to evolve (renaming or retargeting the alias is a template update, not a managed-region change). 3. Move every region body out of Rust constants into templates/regions/*.toml and templates/regions/*.just. cargo-lints-body.toml carries the dotted-key catalog with no host-specific header; cargo_toml.rs prepends [workspace.lints] or [lints] based on the manifest shape. Eliminates ~50 lines of inline string and array-of-tuples plumbing. All 152 unit + 4 integration tests still pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The two remaining YAML-in-Rust blobs (render_group_action, render_group_step) used format! to splice the group name into a multi-line string. Replaced with templates/github/group-action.yml and templates/ado/steps/group.yml carrying a __GROUP__ placeholder; Rust now just include_str!s the file and runs a single .replace(). __GROUP__ chosen over a curly-brace placeholder so it can't collide with GitHub Actions' expression syntax inside the same file. All templates now live as files on disk; no YAML or TOML appears inline in .rs sources. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Drops the hand-written placeholder in favor of the standard ox-tools README generated by 'just readme' (uses ../README.j2 with the crate-level doc-comments from src/lib.rs). Adds the standard crates.io/docs.rs/MSRV/CI/Coverage/License badge row and the project footer. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report❌ Patch coverage is ❌ Your project status has failed because the head coverage (96.7%) is below the target coverage (100.0%). You can increase the head coverage or adjust the target coverage. Additional details and impacted files@@ Coverage Diff @@
## unified-builds #33 +/- ##
=================================================
+ Coverage 86.4% 96.7% +10.3%
=================================================
Files 16 30 +14
Lines 665 4706 +4041
=================================================
+ Hits 575 4554 +3979
- Misses 90 152 +62 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
cargo-spellcheck flags 100+ words in our own docstrings (autodetect, subcommand, prepended, splice, sentinels, LeaveAlone, OrphanedKept, rustfmt, clippy, nextest, binstall, etc.) that aren't in the hunspell en_US base dictionary. Add the obvious technical jargon batch — 43 new entries. There will likely be a long tail of remaining flagged words; iterating on CI to find them. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Extracted from CI's caret positions on the previous run's failures: md (13x — markdown file refs in docstrings), Checksum (15x), CRLF/LF (4-5x — line ending names), aggregator, argv, autocrlf, B6, checksums, footgun, FS, globbing, invariants, L, schemas, toml, whitespace, plus a few stems of already-added words. Down from 109 -> 78 errors on the prior pass; this batch should clear the remaining 17 unique offenders. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous batch added lowercase 'toml', 'checksum', etc. that were missing — but PowerShell's Sort-Object -Unique is case-insensitive by default, so each lowercase add was silently dropped because the uppercase variant existed. The dict ended up with only TOML, Checksum, MD, ADO, GitHub, CLI capitalizations — which Hunspell (case-sensitive) doesn't accept as matches for the lowercase tokens in our docstrings. Re-merged with Sort-Object -CaseSensitive -Unique so both casings survive. Verified both 'toml' and 'TOML', 'checksum' and 'Checksum', 'md' and 'MD', etc., now appear in .spelling and will be carried through preprocessing to target/spelling.dic. Expected to clear the remaining 34 spellcheck errors driven by 3 unique words (checksum 11x, toml 5x, L 1x). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-spellcheck Hunspell is flagging 'md' as a bare word in markdown link text like [design.md](../path/design.md). Despite md being in .spelling, the matching isn't kicking in for these 2-letter tokens — likely a Hunspell short-word issue or extra_dictionaries merge behavior we don't fully understand. Pragmatic fix: wrap the link text in backticks so cargo-spellcheck treats it as inline code (which it skips by default). The link itself still resolves; the displayed text just shows as monospace 'design.md' in rendered docs. 16 occurrences across 11 source files in cargo_ox_check. Touched files: backend, cli, decision, manifest, plan, region, workspace, emit/ado, emit/cargo_toml, emit/github, emit/local. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…it's Five remaining occurrences after the .md backtick batch: - plan.rs:129 '(D ≠ L, L = T)' — single-letter Hunspell flags; wrap the whole math expression in backticks: '(\D ≠ L, L = T\)'. - emit/github.rs:44 '§1.' after the markdown link close paren — outside the link's backticks; wrap as '\§1\.'. - checksum.rs:13 'autocrlf=true' has '=' getting flagged; wrap as '\�utocrlf=true\'. - checksum.rs:12 'Git's' apostrophe form not in dict; added to .spelling (case-sensitive). Used in two doc lines kept as prose. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Empty commit to re-trigger CI now that the PR title has been renamed to match the conventional-commits pattern enforced by ox-check-pr-title. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Two real deny.toml issues: 1. The repo had a hand-authored [licenses] block from before ox-check existed. Our managed region also defined [licenses]. TOML forbids re-declaring a table, so 'cargo deny check' errored out with 'redefinition of table 'licenses''. Removed the pre-existing block; our managed region is now the canonical source. (The removed entries — BSL-1.0, confidence-threshold=0.8, unused-allowed-license — were a strict subset; the catalog's defaults are more comprehensive.) 2. unmaintained = 'warn' is stale cargo-deny syntax — newer versions take a scope value: 'all' | 'workspace' | 'transitive' | 'none'. Bumped catalog default to 'all' (surfaces transitive unmaintained crates too); commented the alternative in the template body. Verified locally with 'cargo deny check': advisories ok, bans ok, licenses ok, sources ok. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-udeps flagged 4 genuinely-unused dependency entries (verified no source references exist for any of them): - cargo_ox_check: drop thiserror (dep), assert_cmd + predicates (dev-deps). The crate uses ohno's AppError throughout, not thiserror; tests use insta + walkdir, not assert_cmd/predicates. - cargo_ensure_no_cyclic_deps: drop tempfile (dev-dep). The integration tests don't actually create temp dirs. Cargo.lock updated accordingly. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…kage cargo-semver-checks errored with 'no library targets found in package cargo-ensure-no-cyclic-deps' — the explicit --package args from the impact stage included binary-only crates that have no public API surface to verify. With --workspace these would be silently skipped, but per-package mode requires the caller to filter. Both ox-check-semver-check and ox-check-external-types use rustdoc and hit the same constraint. Added pre-filter: for each --package arg, look up cargo metadata for a 'lib' target and drop those that don't have one. If no library packages remain, skip the recipe entirely (same exit-0 path as the --skip sentinel). The filter only fires when explicit --package args are present (impact-scoped runs); --workspace mode passes through unchanged. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-semver-checks reports as errors several conditions that aren't actual SemVer violations: 1. 'no library targets found in package X' — bin-only crate 2. 'not found in registry' — unpublished or never-released 3. bin->lib transition: published baseline lacks the lib target the current source has All 4 of this repo's crates hit one of these in some combination: - automation: publish=false; not in registry - cargo-ensure-no-cyclic-deps: was bin-only at v0.2.0, now bin+lib - cargo_heather: not in crates.io despite version=0.1.0 - cargo-ox-check: brand new, never published Rewrote the recipe to: 1. With --workspace, defer to cargo-semver-checks (it handles these cases naturally). 2. With explicit --package args, pre-filter to library-bearing crates via cargo metadata, then run cargo-semver-checks per-package and tolerate output matching 'not found in registry|no library targets found' as a warning. Anything else is treated as a real failure. Verified locally with all 4 in-PR packages: each warns and skips cleanly; recipe exits 0. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-check-external-types CLI is per-manifest: cargo check-external-types --manifest-path <path/to/Cargo.toml> It does not accept --package or --workspace. Our previous recipe passed --package args through, which produced 'error: unexpected argument --package found'. Rewrote the recipe to: 1. Build a pkg-name -> manifest-path map from cargo metadata, filtered to library-bearing crates. 2. Map --workspace -> all library crates' manifests. 3. Map --package <name> args -> the matching manifest paths (drop names that have no library target). 4. Iterate the resulting set, run cargo check-external-types --manifest-path <p> for each. Continue on per-package failure; exit 1 only if any failed. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-check-external-types requires a nightly Rust toolchain (uses unstable rustdoc -Z flags). On the stable channel CI runs were hitting 'error: the option Z is only accepted on the nightly compiler'. Switch the invocation to 'cargo +nightly check-external-types'. rustup will auto-install nightly on first use; adopters using msrustup with no nightly channel can override the recipe. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-check-external-types pins a specific rustdoc-types schema
that tracks a narrow window of nightly rustdoc JSON output. Newer
nightlies routinely drift past it — v0.4.0 of the tool requires
JSON format version 56, current nightly produces 57. This is a
tooling-cadence mismatch, not a real API violation.
Swallow the specific 'JSON format version X, but this tool
requires format version Y' error as a warning ('nightly drift';
skip this package), continue with the rest. Other errors still
fail.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-aprz has no 'check' subcommand — its CLI is: cargo aprz <crates|deps|init|validate|help> oxidizer's recipe uses 'cargo aprz deps --error-if-high-risk --console appraisal'. Adopted the same invocation (without the HTML report path / cache dir options; adopters who want those extend the recipe). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo +nightly auto-install via rustup can hang silently on some runners — observed in the previous PR run where all 4 pr-fast legs sat in 'Run ./.github/actions/ox-check-pr-fast' for 70+ minutes with no visible progress, after we switched ox-check-external-types to use cargo +nightly. Pre-install nightly explicitly in the setup composite via 'rustup toolchain install nightly --profile minimal --no-self-update'. +nightly invocations in recipes are then no-ops on the toolchain side and proceed immediately. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-check-external-types requires nightly rustdoc + compiles its own dependencies from source against nightly (rustdoc-types is heavy) + runs rustdoc JSON generation per package. On the dogfooded ox-tools-gh repo this combination consistently pushed pr-fast past 70 minutes — twice in a row, the previous attempts were cancelled. This is fundamentally a heavyweight check that doesn't fit the pr-fast 'all the lightweight static analysis' budget. Move it to nightly-advisories where it sits alongside other heavy checks (deny, audit, clippy on workspace, udeps) that can take longer without blocking PR throughput. Updates groups.just (recipe wiring) and docs/design/checks.md (documents the move + rationale). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These nightly-toolchain / rustdoc-based checks were causing pr-fast to run 100+ minutes by compiling their tools and deps from source. Aligns with external-types (already moved). pr-fast now stays on stable-only static analysis. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
cargo-aprz queries the GitHub API; unauthenticated requests are rate-limited to 60/hr, which makes the recipe hang ~50 minutes waiting on the next bucket. Forward the built-in GITHUB_TOKEN so aprz uses the authenticated 1000/hr quota. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
These checks' outcomes can only change with a code change in this repo (both analyze the workspace's own deps/API surface), so the nightly-only justification doesn't hold. The pr-fast hang they were moved to avoid was actually caused by cargo-aprz GitHub API rate limiting, now fixed via GITHUB_TOKEN. external-types stays in nightly-advisories because it pins a specific nightly rustdoc JSON schema and breaks on toolchain drift independent of repo changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Strategy:
- Refactor 3 copies of the fs::read_to_string + NotFound passthrough
helper into a single crate::io::read_file_if_present, marked
#[mutants::skip] (trivial fs wrapper; mutations on the Ok arms and
NotFound match guard are not behavior-meaningful and are already
exercised through every plan/emit path).
- Mark main() #[mutants::skip] (tracing/clap glue).
- Mark backend::read_origin_url #[mutants::skip] (shells out to git;
integration-tested via backend autodetect).
- Mark run::run #[mutants::skip] (thin process-boundary glue with
std::process::exit; behavior covered by run_update tests).
- Add targeted unit tests for the 3 remaining genuine logic mutants:
* manifest::to_toml always ends with newline
* region::upsert_region adds exactly one blank separator and does
NOT add an extra one when text already ends with double newline
(catches the && -> || mutation)
* run::plan_removals queues orphaned-clean files for Remove, and
keeps customized orphans as OrphanedKept (covers disabling a
backend after first writing it)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
9e452bf to
bcf6d3b
Compare
…tolerance
Adds an owned justfiles/ox-check/versions.just holding the pinned nightly
toolchains used by udeps, miri, careful, and check-external-types:
rust_nightly := "nightly-2026-01-21" general
rust_nightly_external_types := "nightly-2025-10-18" narrow
One source of truth, two consumers:
- Recipes read pins via {{ var }} interpolation
(cargo +{{ rust_nightly }} udeps ...).
- Setup composites (GH setup-action.yml, ADO steps/setup.yml) read pins
via 'just --evaluate <var>' and call 'rustup toolchain install' so the
+nightly invocations later are no-ops. Just is installed before nightly
so the extract works.
Drops the regex-based "JSON format version X but requires Y" tolerance
arm in the check-external-types recipe. With the pin matched to the
tool's required rustdoc schema there is no drift to absorb; if a future
bump breaks the check it means the pin or the tool needs to move,
deliberately.
Why pin instead of float: with floating nightly we needed tolerance
shims to absorb rustdoc / lint / intrinsic drift. Pinning handles all
present and future cases with one mechanism; tolerance shims are
bespoke and silently degrade what the check validates. General nightly
bumps on a regular cadence; external-types pin bumps alongside
cargo-check-external-types upgrades. Both live in versions.just (an
owned file), so adopters can override either.
Other changes:
- mod.just imports versions.just.
- Cache keys (GH, ADO) include versions.just hash so nightly bumps
invalidate cleanly.
- local.md gets new section 3.6 "Nightly pinning" documenting the
policy and bump cadence.
- Regression test: checks.just has no bare +nightly invocations.
- Regression test: versions.just defines both pin variables (setup
composites depend on these names).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Adds spellcheck.toml as the fourth managed-region shared-config file, alongside deny.toml, rustfmt.toml, and .delta.toml. Closes the oversight where ox-check ships the cargo-spellcheck recipe and the .spelling -> target/spelling.dic generation, but no spellcheck.toml config tying them together — adopters had to discover and author the config themselves. Default body mirrors oxidizer-github's spellcheck.toml: [Hunspell] lang = "en_US" search_dirs = ["."] extra_dictionaries = ["target/spelling.dic"] skip_os_lookups = true cross-platform determinism use_builtin = true no system hunspell dep [Hunspell.quirks] allow_concatenation = true CamelCase identifier handling The extra_dictionaries path matches the target/spelling.dic that the ox-check-spellcheck recipe generates from .spelling — recipe and config now agree out of the box. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…nions
Moves ox-check-fmt from stable rustfmt to the pinned nightly defined
in versions.just (rust_nightly) and turns on the unstable options
oxidizer uses in its own unstable-rustfmt.toml:
unstable_features = true
format_code_in_doc_comments = true
imports_granularity = "Module"
group_imports = "StdExternalCrate"
These are the high-value rustfmt opinions the surveyed Microsoft Rust
repos reach for; stable rustfmt's option set doesn't include them.
Pinning nightly is the prerequisite that makes this sustainable:
formatting churn now happens on a deliberate pin bump rather than on
every
ustup update.
Other changes:
- ox-check-fmt now invokes cargo +{{ rust_nightly }} fmt --all --check.
- setup-action.yml and ADO setup.yml install the rust_nightly toolchain
with --component rustfmt so the nightly rustfmt binary is available.
The external-types nightly still installs with minimal profile only
(it doesn't need rustfmt).
- rustfmt_body_sets_edition_and_width test grows to cover the four
new unstable opinions; if any are dropped, the regression fires.
- local.md \xc2\xa73.6 documents fmt as a nightly-pinned check.
- checks.md mt recipe row reflects the +<pinned-nightly> invocation.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Applies cargo +nightly-2026-01-21 fmt --all to align with the new opinion set in the catalog rustfmt.toml (imports_granularity = Module, group_imports = StdExternalCrate, format_code_in_doc_comments). Changes are all import-grouping / sorting; no code-logic edits. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…t removals) Two adjustments to the workspace-lints catalog body, with the dogfood state in ox-tools updated to match: 1. Fold 16 new lints from the oxidizer + oxidizer-github two-repo consensus into the catalog (Bucket A). 14 restriction-group warns: as_pointer_underscore, assertions_on_result_states, deref_by_slicing, empty_drop, empty_enum_variants_with_brackets, fn_to_numeric_cast_any, if_then_some_else_none, multiple_unsafe_ops_per_block, redundant_type_annotations, renamed_function_params, semicolon_outside_block, unnecessary_safety_doc, unneeded_field_pattern, unused_result_ok. Plus 2 suppressions: redundant_pub_crate, should_panic_without_expect. 2. Drop three contested lints from the catalog: * rust.missing_docs noisy on large workspaces (oxidizer omits) * clippy.expect_used over-strict for tools with legitimate panic paths * clippy.panic same rationale; unwrap_used stays Rationale: a workspace-wide lint we ship can only be selectively disabled by an adopter via per-crate overrides or by taking ownership of the entire managed region. TOML's no-duplicate-key rule means adopters can't simply add ust.missing_docs = "allow" outside the region to override it. So the catalog should be conservative: enable something only when the consensus is strong enough to justify that adopter cost. The 16 Bucket A lints meet that bar (two independent repos enable them); the three dropped lints don't (oxidizer omits all three). ox-tools dogfood state aligned: the managed region was previously a user-customized subset (rust lints only) with the rest cohabited outside the sentinels. With the new catalog the region is fully regenerated; the cohabit now keeps only the two genuinely repo-specific entries (rust.unexpected_cfgs check-cfg list, clippy.empty_structs_with_brackets - oxidizer is inconsistent on this so it stayed per-repo). Regression tests added in cargo_toml.rs: - catalog_intentionally_omits_contested_lints: locks in the three removals; accidental re-additions fire here. - catalog_includes_consensus_restriction_lints: locks in the 16 Bucket A entries; accidental removals fire here. Two trivial clippy fixes in src/run.rs that the new clippy.manual_contains lint surfaces on the orphan-removal tests added in an earlier commit. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Not in the ox-check catalog consensus (oxidizer has it inconsistent in its own config). No code in this workspace currently relies on it, so dropping shrinks the cohabit section to its only genuinely repo-specific entry (rust.unexpected_cfgs check-cfg list). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Introduces
cargo-ox-check: a cargo subcommand that ships and maintains an opinionated, unified build/CI scaffolding for Rust workspaces. Onecargo ox-check updateinvocation emits a complete, reviewable tree of GitHub Actions workflows, Azure DevOps pipelines,justfiles/ox-check/recipes, and managed regions inCargo.toml/deny.toml/rustfmt.toml/.delta.toml. Subsequent runs keep the tree in sync as the tool's catalog evolves, while preserving any local customizations the adopter has made.Why this exists
Today every Rust repo in the
oxidizer/oxidizer-github/ox-toolsfamily hand-rolls its own CI on the same opinionated checks (fmt, clippy, deny, audit, miri, careful, mutants, llvm-cov, cargo-hack, semver-check, doc, ...). The cost of keeping those copies in sync — toolchain bumps, lint updates, new advisory feeds, cross-OS matrix changes — is paid per-repo, by hand, every time. cargo-ox-check centralizes the catalog in one place and renders it into the per-repo CI surface.What an adopter gets
After one
cargo ox-check update, the repo contains:.github/workflows/ox-check-pr.yml/ox-check-pr-impl.yml+ composite actions for the PR tier (pr-fast,pr-test,pr-mutants)..github/workflows/ox-check-nightly.yml/ox-check-nightly-impl.yml+ composite actions for the nightly tier (nightly-test,nightly-advisories,nightly-runtime,nightly-exhaustive)..pipelines/ox-check/mirror of the same wiring for Azure DevOps.justfiles/ox-check/recipe tree (checks.just,groups.just,tiers.just,tools.just,mod.just,tool-minimums.txt) — localjust ox-check-prreproduces the PR CI without any ox-check binary present.Cargo.toml(opinionated workspace + per-crate lints),rustfmt.toml,deny.toml,.delta.toml..ox-check.lockmanifest that tracks per-file/per-region checksums for the three-checksum update algorithm.Design highlights
modified,affected,required) backed bycargo-delta. Each check is tagged with the tier it scopes to; the CI wiring emits one include-list env var per tier with a--skipsentinel for empty tiers. The required tier is included for tools whose correctness resolves through the dep graph (cargo doc,cargo hack,cargo udeps); the unscoped bucket is reserved forCargo.lock- and PR-context-only checks (deny,audit,aprz,pr-title).Write,LeaveAlone,Propose) keyed on a three-way comparison of (original-template, current-template, live-file) checksums, all tracked in the lock manifest. A user edit inside a managed region is preserved when the template hasn't moved; a template bump becomes a.ox-check-proposedsidecar with a one-line summary, so customizations are never silently overwritten.origin's URL; explicit--backend github/--backend adooverride.actions/cache, ADOCache@2) keyed on OS + arch + rustc version + lockfile-family hashes +tool-minimums.txthash, so toolchain bumps and catalog updates invalidate cleanly..github/workflows/regenerate-check.yml— the one hand-written workflow that buildscargo-ox-checkfrom the PR's branch, runscargo ox-check update --dry-run, and fails the PR if the in-tree state drifts from what the templates would render.Verification
crates/cargo_ox_check/src/(run/plan/decision/region/emit/workspace/manifest/checksum).tests/schemas.rs(every emitted TOML parseable; manifest schema valid).tests/snapshots.rscovering the three representative backend combinations (local-only, GitHub-backend, ADO-backend) — full byte-exact emitted tree.tests/update.rscovering single-crate (no[workspace]), opt-outs (emptied managed region preserved), customized (user edit inside a managed region preserved), migration (pre-existing hand-writtenJustfile/deny.toml/[profile.release]survive splicing).cargo clippy -p cargo-ox-check --all-targets -- -D warningsclean.cargo-deltaCLI and JSON shape verified by running the actual binary against this workspace; the impact-step shell scripts encode the real two-snapshot--baseline/--currentflow.Total: 168 tests green locally.
Documentation
Lives under
crates/cargo_ox_check/docs/:design/design.md— top-level design and CLI shape.design/local.md— thejustrecipe tree, impact env vars, daily-driver flow.design/github.md— owned reusable workflows, per-group composite actions, impact scoping.design/ado.md— owned stages templates, per-group step templates, 1ESPT composition guidance.design/checks.md— the opinionated catalog and per-group OS scope matrix.design/updates.md— the three-checksum state machine validated by fixture tests.verification.md— continuous-validation strategy (dogfooding + snapshot + fixtures + schema).implementation-plans/0000.md— initial implementation plan (history).implementation-plans/0001.md— design-vs-implementation reconciliation plan executed in this PR.Follow-ups (not in this PR)
cargo ox-check updateagainstox-toolsto land the emitted CI surface (the regenerate-check workflow will start passing the moment the maintainer commits that bootstrap output).pr-titleConventional Commits regex hasn't been exhaustively validated against edge cases (scoped, breaking-change, mixed-case).