Skip to content

feat(scripts): detect modified-but-unreleased dependencies during release#436

Open
sandersaares wants to merge 96 commits into
mainfrom
feat/release-deps-upstream-scan
Open

feat(scripts): detect modified-but-unreleased dependencies during release#436
sandersaares wants to merge 96 commits into
mainfrom
feat/release-deps-upstream-scan

Conversation

@sandersaares
Copy link
Copy Markdown
Member

@sandersaares sandersaares commented May 20, 2026

Problem

When releasing a workspace package, an author may have also modified one of its workspace dependencies but forgotten to release that dependency. Locally everything builds via path-references, but once published the released package resolves against the last released version of each dependency on crates.io — missing the new changes. The dependency-side modification may even have landed in a previous PR that merged to main without a version increment, so a plain "diff vs PR base" check is not enough to catch it.

What changes for the user

Dependency review during release

release-packages.ps1 now scans the dependency graph of every package in the release plan and surfaces every workspace dependency with unreleased modifications — evaluated per package against that dependency's own last version = / publish = commit, not against the PR base. For each finding the user can view the diff, skip the package, or release it alongside the original target.

The same analysis runs on every PR via a new release-deps GitHub Actions job, which posts a sticky PR comment listing any unreleased-but-modified workspace dependencies of the packages being released. Reviewers can sanity-check the author's materiality decisions even when the script was never run interactively.

Multi-package releases are now atomic

Previously the script handled one package per invocation, with cascade decisions surfaced incrementally across re-runs. The new driver takes the entire release plan up-front:

./scripts/release-packages.ps1 -Packages 'bytesbuf@breaking','bytesbuf_io@nonbreaking'

After plan review, all writes — Cargo.toml version increments, CHANGELOG.md entries, README.md regeneration, workspace [workspace.dependencies] updates — happen in one shot, followed by a workspace cargo check. Two guided alternatives are available when you don't have the full list ready up-front: -Changed (walk every package with unreleased modifications) and -All (walk every publishable package, modified or not).

Semantic change type instead of numeric version components

The previous -Bump major|minor|patch CLI conflated numeric version-string components with semantic intent — and they are not interchangeable: a breaking change on a 0.x package moves the minor component, on a >=1.x package the major component. The new change-spec is a semantic label per package: breaking, nonbreaking, or patch. Users who want to pin an exact target can do so explicitly — foo@1.0.0, bar@1.0.0-rc.1 — with full SemVer 2.0 (three numeric components plus optional pre-release / build metadata) accepted.

If the cascade computes a stronger change type than the user requested (e.g. the user said patch but the dependent exposes a breaking-changed dependency in its public API), the change type is auto-upgraded and the upgrade is shown in the plan display. Explicit version pins are not silently overridden — by default the plan is rejected; the new -Force switch overrides this when the pin really is intentional.

Breaking changes for tooling muscle memory

  • scripts/release-crate.ps1 is removed; use scripts/release-packages.ps1.
  • -Bump major|minor|patch is gone; supply -Packages '<name>@<change-spec>', ... instead.
  • A Crate*Package* symbol rename swept the release tooling (release-record property, function names, log strings). Cargo's own terminology (crates/, crates.io, Cargo.toml, cargo metadata) is preserved verbatim.

Documentation and tests

Release vocabulary, workflow, and invariants now live in docs/releasing.md; AGENTS.md shrinks to a list of pointers. A Pester suite under scripts/tests/Pester/ covers pure helpers, BFS / aggregation analyses on synthetic workspaces, and agent-driven end-to-end interactive scenarios. Runs locally via just test-scripts and on Windows + Linux in the script-tests CI job.

sandersaares and others added 2 commits May 20, 2026 14:44
…lease

When releasing a crate, an author may have also modified one of its upstream
workspace dependencies but forgotten to release the dependency too. Locally
everything builds via path-references, but once published the released crate
resolves to the last released version on crates.io, missing the new changes.

This adds two layers of automation:

1. Interactive layer (release-crate.ps1):
   After the existing downstream cascade finishes, scan the release set for
   transitive workspace dependencies that have file changes vs the PR base ref
   but are not themselves being released. Prompt the author (y/N + bump kind)
   for each finding so material changes get an extra release queued, while
   immaterial changes (formatting, doc tweaks) can be declined.

2. CI layer (scripts/check-unreleased-dependencies.ps1 +
   .github/workflows/main.yml release-deps job):
   Runs the same analysis non-interactively and posts a sticky PR comment
   listing any findings so reviewers can sanity-check materiality decisions.

Shared logic lives in the new scripts/lib/releasing.ps1 library, dot-sourced
by both entry-point scripts. Workspace dependency types kind=normal and
kind=build are tracked; kind=dev is excluded (cannot affect downstream
consumers via crates.io). Cascade re-bump is idempotent: re-cascading into a
crate already at a sufficient version appends a maintenance bullet to its
existing changelog section instead of double-bumping.

The .delta.toml Cargo.toml trip-wire is documented as a dependency of the
release-deps CI gate so any version bump touches Cargo.toml -> skip=false.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Migrate the three remaining Invoke-GitCommand callsites in release-crate.ps1
(tag --list, git log, remote get-url) to the array-argument Invoke-Git wrapper
in releasing.ps1, then delete the legacy wrapper. Removes the last use of
Invoke-Expression-based git invocation in the release tooling.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented May 20, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 100.0%. Comparing base (877def3) to head (d9e55af).

Additional details and impacted files
@@           Coverage Diff           @@
##             main     #436   +/-   ##
=======================================
  Coverage   100.0%   100.0%           
=======================================
  Files         335      335           
  Lines       25586    25586           
=======================================
  Hits        25586    25586           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@ralfbiedert
Copy link
Copy Markdown
Collaborator

Whops, I missed the draft on this

sandersaares and others added 2 commits May 21, 2026 13:53
The release-deps job previously depended on the delta job and used
`delta.outputs.skip != 'true'` as a compute optimization to skip the
analysis when no crate was affected. The optimization was structurally
fragile - it relied on the implicit invariant that Cargo.toml and
scripts/* remain in .delta.toml's trip_wire_patterns - and only saved a
few seconds when there are no findings.

Drop both the needs: [delta] dependency and the delta.skip gate. The
check-unreleased-dependencies.ps1 script already exits fast (no
findings -> no markdown, no comment) when there is nothing to report,
so always running it is safe and simple.

Also remove the corresponding NOTE in .delta.toml since that
dependency no longer exists.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sandersaares sandersaares marked this pull request as ready for review May 21, 2026 11:06
Copilot AI review requested due to automatic review settings May 21, 2026 11:06
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR strengthens the workspace crate release tooling by detecting “modified-but-unreleased” upstream workspace dependencies that could be missed when publishing to crates.io, and surfaces the same signal in CI via a new informational job.

Changes:

  • Extracts shared release/dependency-graph and git helper logic into a dot-sourced PowerShell library (scripts/lib/releasing.ps1).
  • Extends scripts/release-crate.ps1 to (optionally) scan for modified-but-unreleased upstream workspace deps after the existing downstream cascade, with interactive prompting to release them.
  • Adds a CI-only analyzer script and a new workflow job that posts/removes a sticky PR comment when such unreleased upstream dependency changes are detected.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
scripts/release-crate.ps1 Adds base-ref/non-interactive options, refactors cascade flow, and performs a post-release upstream dependency scan.
scripts/lib/releasing.ps1 New shared library providing safe git invocation, SemVer helpers, workspace metadata, and unreleased-dependency analysis.
scripts/check-unreleased-dependencies.ps1 New CI companion script that emits a markdown report + step output for unreleased upstream dependency changes.
.github/workflows/main.yml Adds release-deps job to run the CI analyzer and post/remove a sticky PR comment.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread scripts/release-crate.ps1 Outdated
Comment thread scripts/check-unreleased-dependencies.ps1 Outdated
- release-crate.ps1: drop -AllowFailure on the fetch in the base-ref resolver
  so the surrounding try/catch can actually trigger and emit a warning on fetch
  failure (previously the catch was unreachable because -AllowFailure returns
  $null instead of throwing).

- check-unreleased-dependencies.ps1: route Get-RepoRoot through Invoke-Git
  instead of shelling out directly, matching the design described in the PR.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 12:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.

Comment thread scripts/release-crate.ps1 Outdated
sandersaares and others added 2 commits May 21, 2026 15:33
…rades them

When the post-release dep scan runs a nested `Invoke-ReleaseFlow` for an
upstream crate the user opts to release, the resulting cascade may upgrade a
crate that was already in the release set (e.g., the initial release patch-bumped
`foo` and the nested major release of an upstream dep now requires a major on
`foo`). Previously the merge skipped duplicates outright, so `Show-ReleaseSummary`
and the final `feat(crate): release v<version>` message reported a stale
version that did not match what was actually written to `Cargo.toml`.

Replace the skip-on-duplicate logic with an in-place update: keep the original
`OldVersion` (the pre-PR baseline) and adopt the latest `NewVersion` from the
nested cascade. New crates are still appended as before.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The previous logic compared the working tree against the PR base ref to
decide which workspace crates had unreleased modifications. That missed a
real scenario: an earlier PR merges a source change to `bytesbuf` without
bumping its version, and a later PR bumps `bytesbuf_io` (which depends on
`bytesbuf`). On crates.io the published `bytesbuf_io` resolves to the last
released `bytesbuf`, which does *not* include the unreleased modification.
Because the modification predates the PR's base ref, the old `BaseRef`-
relative scan saw nothing to flag.

Switch each crate's "modification baseline" to its own most-recent commit
that touched `version =` or `publish =` in its `Cargo.toml`, derived via
`git log -1 -G '^(version|publish)\s*='`. Any change under `crates/<folder>/`
newer than that commit (committed, working-tree, or untracked) is treated
as unreleased.

`Get-CratesWithVersionBumps` (release-set detection) intentionally still
diffs against the PR base ref — that's the correct anchor for "what is this
PR releasing".

Replaces `Get-GitFileChangeSet` / `Get-CratesWithFileChanges` with the new
`Get-CrateLastReleaseBaseline` + `Get-CratesWithUnreleasedChanges` helpers
and rewrites `Get-UnreleasedModifiedDependencies` to consume the per-crate
modification map.

Also persists the manual test plan as `scripts/RELEASE-DEPS-TEST-CASES.md`
so future agents can re-run T1-T16 (original PR-vs-base coverage) and
N1-N10 (multi-PR baseline coverage) when the logic changes. All N1-N9
scenarios were verified in a scratch worktree before this commit; N10
(brand-new crate) is structurally covered by the new-crate branch in
`Get-CratesWithVersionBumps`.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 21, 2026 14:54
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 2 comments.

Comment thread scripts/check-unreleased-dependencies.ps1 Outdated
Comment thread scripts/release-crate.ps1 Outdated
sandersaares and others added 2 commits May 21, 2026 18:32
Two real defects flagged on the latest review pass:

1. `check-unreleased-dependencies.ps1` was supposed to alphabetically sort
   the release-set listing in the sticky PR comment, but the chain
   `@(Get-CratesWithVersionBumps ...) | Sort-Object` silently broke. The
   helper returns its HashSet via `Write-Output -NoEnumerate` so callers
   can use `.Contains()`. That wrapping makes `Sort-Object` receive a
   single object (the HashSet itself), so the sort is a no-op and the
   foreach below iterates the HashSet in insertion order. Fixed by
   unwrapping with `... | ForEach-Object { $_ }` before sorting.

2. `Add-CascadeBulletToVersionSection`'s `if ($subStart -ge 0)` branch
   built the new file content with
   `@($lines[0..($insertAt - 1)]) + @($bullet) + @($lines[$insertAt..($lines.Count - 1)])`.
   When `$insertAt` equals `$lines.Count` (target sub-header is the last
   content in the file, no bullets yet, no trailing blank lines), the
   right-hand slice becomes `$lines[N..N-1]` which is a reverse-range that
   silently aliases to the last element — so the last line was duplicated.
   Reproduced on a synthesised changelog before fixing. Mirrored the EOF
   guard that the `else` branch already has.

Verified by direct PowerShell repros for both: sort now yields
alphabetical order, EOF insertion no longer duplicates the sub-header
line, and the non-EOF + idempotency paths are unchanged.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Anticipates a future home for other script-related test material. The doc
is unchanged; only its location moves. References inside the doc point at
other scripts (`scripts/lib/releasing.ps1`, `scripts/release-crate.ps1`,
`scripts/check-unreleased-dependencies.ps1`) from the repo root and remain
valid after the move.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 22, 2026 04:56
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 5 out of 5 changed files in this pull request and generated 4 comments.

Comment thread scripts/check-unreleased-dependencies.ps1 Outdated
Comment thread .github/workflows/main.yml Outdated
Comment thread scripts/release-crate.ps1 Outdated
Comment thread scripts/tests/RELEASE-DEPS-TEST-CASES.md Outdated
sandersaares and others added 2 commits May 22, 2026 08:13
…e set

Documentation drift after the per-crate baseline refactor (cedd750). Four
Copilot review comments on PR #436 flagged that synopses, parameter docs,
and the user-facing PR-comment / interactive-warning text still said
"modified vs the PR base ref" — but only the release-set anchor uses
BaseRef; modifications are evaluated per crate against each crate's own
last `version =` / `publish =` commit.

Updated:
- `scripts/check-unreleased-dependencies.ps1` — synopsis, description,
  BaseRef parameter docs, and the markdown body line ("unreleased
  modifications — changes newer than their last `version =` or `publish =`
  bump").
- `scripts/release-crate.ps1` — BaseRef parameter comment, the
  `Invoke-PostReleaseDepScan` function docstring, and the interactive
  warning text. (Switched the warning from a `"..."` to a `'...'` literal
  so the inline `` `version =` `` / `` `publish =` `` backticks aren't
  interpreted as PowerShell escape sequences — `` `v `` is vertical tab.)
- `.github/workflows/main.yml` — `release-deps` job comment.

Self-reviewed the rest of the PR diff for similar drift; the remaining
"vs base" mentions (release-set BFS docstring, "Fetch base ref" workflow
step name, "What this means" sticky-comment block) are legitimate and
unchanged. Re-ran N1-N9 against the updated scripts in a scratch worktree:
9/9 pass.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Capture the non-obvious lessons learned while building the N-series
and T-series test harnesses, so future maintainers/agents who need to
rebuild a harness from scratch don't have to relearn them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 54 out of 54 changed files in this pull request and generated 15 comments.

Comment thread scripts/lib/release-flow.ps1
Comment thread scripts/lib/release-flow.ps1 Outdated
Comment thread scripts/tests/Pester/scenarios/Scenarios.Tests.ps1 Outdated
Comment thread scripts/tests/Pester/unit/releasing/GitFs.Tests.ps1
Comment thread scripts/tests/Pester/unit/releasing/GitFs.Tests.ps1
Comment thread scripts/lib/release-flow.ps1
Comment thread scripts/lib/release-flow.ps1 Outdated
Comment thread scripts/tests/Pester/scenarios/Scenarios.Tests.ps1 Outdated
Comment thread scripts/tests/Pester/unit/releasing/GitFs.Tests.ps1
Comment thread scripts/tests/Pester/unit/releasing/GitFs.Tests.ps1
sandersaares and others added 5 commits June 1, 2026 21:29
- release-flow.ps1: Set-Content on package + root Cargo.toml now uses
  -LiteralPath and -Encoding utf8, matching the surrounding changelog
  writes and guaranteeing deterministic UTF-8 output.
- Scenarios.Tests.ps1: hoisted the scenario discovery to top-level so
  the cached list is the single source of truth used by both the
  BeforeAll wiring and the It -ForEach generator (Pester 5 evaluates
  -ForEach at Discovery, BeforeAll at Run, so the top-level body is
  the only place both phases can share state).
- GitFs.Tests.ps1: fixed 8 mis-indented \ assignments inside
  per-Describe BeforeAll blocks.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ith -Changed/-All modes

release-packages.ps1 becomes a parameter-set CLI shell exposing three mutually-exclusive target-selection modes:

  - -Packages '<name>@<change-spec>', ... (existing targeted mode; the only non-interactive mode).
  - -Changed (interactive guided walk through every workspace package with on-disk modifications; merged in from release-changed-packages.ps1).
  - -All (interactive guided walk through every publishable workspace package, even ones with no detected changes; new mode for coordinated multi-package release planning).

release-changed-packages.ps1 is deleted (no compatibility shim — the unified entry point IS the canonical interface).

Invoke-ReleaseChangedPackagesMain is folded into Invoke-ReleasePackagesMain with a -Mode parameter ('targeted'|'changed'|'all'). -All mode synthesises a private modified-snapshot — adding ChangedFileCount=0 stubs for every published package not already present — so Get-UnreleasedModifiedDependencies's surfacing predicate accepts every published package without threading a new mode flag through the planner. The synthetic snapshot is scoped to this entry point so its zero-count entries never escape into PR-comment / dep-scan reporting contexts.

Format-PackageMenu adapts to ChangedFileCount: when zero (or the property is missing) the header switches to 'Reviewing package (no detected changes): <folder>' and slot 1 is relabelled 'View diff (no changes in this package)'. The View-diff option stays in slot 1 in both flavours so muscle memory works.

Scenarios S19/S20/S21 are renamed (dropping the all- prefix) and their Mode field migrated to 'changed'. New scenarios S22 (-All ignore-everything walk) and S23 (-All force-release with cascade-breaking propagation) lock down the new mode. Four new PromptFlow unit tests cover the header/label switch on missing-or-zero ChangedFileCount.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…/-Changed/-All modes

The top-of-file script list now describes the single release-packages.ps1 with all three target-selection modes.

How to release one or more packages now shows three command examples (one per mode) and an updated step list that reflects the unified pipeline. The previous 'Guided changed-packages workflow' section is rewritten as 'Guided modes (-Changed, -All)' covering both interactive modes, the View-diff label rewrite in -All, the elevation-suppression behaviour, the BFS-root model, and the interactive-only restriction.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…chain printout)

The per-package review menu rendered every full in-workspace dependency chain ending at the package under review, one per line. In a workspace with hundreds of packages this multi-line printout was visually overwhelming and rarely told the reviewer anything actionable — what they actually need at decision time is the set of direct dependents (those are what cascade-toward-dependents pivots on).

Replace the multi-line chain block with a single comma-separated line:

    Direct dependents in this workspace: bytesbuf, http_extensions

The set is derived from WorkspaceDependencyChains (chain[-2] of every chain, deduplicated and sorted ordinal) so it stays release-set-independent. The empty-set branch ('no in-workspace dependents') is preserved unchanged.

Updated PromptFlow unit tests to assert the new format (header line count, dedup across chains, transitive root is filtered out, scoped no-'->' check on the dependents line only since sibling version-transition hints legitimately contain '->').

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sandersaares sandersaares changed the title feat(scripts): detect modified-but-unreleased upstream deps during release feat(scripts): detect modified-but-unreleased dependencies during release Jun 4, 2026
sandersaares and others added 8 commits June 4, 2026 16:11
… SemVer 2.0 + MINIMUMS footer

Address reviewer feedback for round 5:

- Remove all 'non-interactive' framing from release-packages.ps1 help text and docs/releasing.md. Every mode (Packages/Changed/All) is interactive; the script is intended for interactive use by maintainers. Test-InteractiveSession is now an unconditional gate.

- Accept SemVer 2.0 pre-release and build-metadata suffixes in -Packages explicit version pins (e.g. foo@1.2.3-pre01, bar@1.0.0+meta). Strictly reject 1- or 2-component versions and leading-zero numeric identifiers. New Split-SemanticVersion helper drives all version math (Get-NextVersion strips pre-release on increment; Compare-SemanticVersions validates then casts to [semver] for SemVer-2.0 ordering).

- Add MINIMUMS-semantics footer to Show-ReleasePlan: clarifies that user-requested change types are minimums subject to cascade auto-upgrade, except explicit version pins which are not auto-upgraded (the planner errors out if a cascade requires more).

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…e edits as baseline-resetting

Get-PackageLastReleaseBaseline now matches both literal `version =` / `publish =` keys AND the TOML inheritance form `version.workspace =` / `publish.workspace =` (which inherit from the workspace root). Previously, packages that switched their publish or version key from a literal to the dotted-key inheritance form (or vice versa) would not have their baseline reset, leaving stale change-detection results.

The regex is a POSIX ERE because git's -G flag does not accept PCRE extensions like (?:...) — capturing groups are used instead.

Synthetic-workspace fixture extended with a PublishSyntax option (omitted / literal-true / literal-false / dotted-workspace / inline-workspace) and an optional [workspace.package] block, so tests can exercise every TOML shape.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…pin-vs-cascade rejection

By default, Resolve-ReleaseSet rejects the release plan when an explicit version pin (e.g. `bar@1.1.0`) numerically undershoots what cascade analysis requires (e.g. cascade requires v2.0.0). The rejection forces the user to either revise the pin or pick a change-type keyword, which guarantees that an explicit pin is honored verbatim or the script refuses to proceed.

-Force relaxes this specific rejection: the explicit pin is honored verbatim, the package's EffectiveChangeType tag is upgraded so any further cascade decisions for the package are correct, and a warning is printed flagging that downstream consumers may break. The resolved entry carries a new `PinHonoredAgainstCascade` flag so Show-ReleasePlan can warn the user clearly (per-entry tag plus footer line in yellow).

-Force does NOT relax the always-fatal `pin not strictly greater than current on-disk version` check, and has no effect on change-type tokens (which the planner always auto-upgrades silently). The default-mode rejection error message now mentions -Force so the user knows about the override.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…-All)

-Changed and -All only accept change-type answers (breaking / non-breaking / patch) from the per-package prompt — they never accept explicit version pins. The pin-vs-cascade rejection that -Force overrides therefore cannot fire in those modes, so the switch is meaningless there.

Bind -Force to the 'ByPackages' parameter set so PowerShell rejects `-Changed -Force` / `-All -Force` at parameter resolution time with a clear `Parameter set cannot be resolved` error. Also add a defensive guard in Invoke-ReleasePackagesMain for callers that invoke the library function directly, with a clearer human-readable error.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ding with 'Skip package'

The previous wording implied a single motivation (immateriality of the changes) for not releasing a surfaced package, but there are many other reasons a reviewer may pick this option in any given iteration (e.g. wanting to release the package separately, deferring the decision, etc.). 'Skip' is also a better fit semantically — it conveys that the decision is for the current iteration, not a permanent verdict; cascade may still pull the package into the plan on a later iteration.

Updates the per-package post-skip console line for the same reason: drops the 'reviewer should confirm the change is immaterial' framing in favor of 'cascade may still pull it into the release plan on a later iteration', which describes the actual mechanics.

Internal action vocabulary ('ignore') is unchanged — that is implementation detail and renaming it would churn many tests/scenarios without user-visible benefit.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… consistency

Other lines in the per-package menu start with a capital letter (e.g. 'Direct dependents in this workspace: ...', 'View diff', 'Skip package', 'Release as ...'). Capitalise this one too.

Also touch up the docs/releasing.md reference and replace a leftover 'ignore every prompt' phrasing with 'skip every prompt' to match the new menu wording.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
… of run

Show-ReleaseSummary previously rendered the entries in the topological order Invoke-ResolvedRelease processed them in (dependencies first, dependents last), which is meaningful while releases are being executed but is hard to scan as a final summary. Sort alphabetically by package name so users can quickly locate any specific package.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 55 out of 55 changed files in this pull request and generated 4 comments.

Comment thread scripts/lib/check-unreleased-deps.ps1
Comment thread scripts/lib/check-unreleased-deps.ps1 Outdated
Comment thread .github/workflows/main.yml
Comment thread scripts/mutants.rs Outdated
Two threads of polish for PR #436.

## Round-5 reviewer comments (Copilot bot)

* `scripts/lib/check-unreleased-deps.ps1`:
  - `Set-StepOutput`: switch `Add-Content` to `-LiteralPath` so wildcard
    metacharacters in `$env:GITHUB_OUTPUT` cannot trigger glob expansion.
    Pin `-Encoding utf8` explicitly so the file format is deterministic
    across PowerShell versions (Windows PowerShell 5.1 defaults to
    UTF-8 with BOM otherwise).
  - `Invoke-CheckUnreleasedDependencies`: same `-LiteralPath` switch for
    the `Set-Content` that writes the comment body.

* `.github/workflows/main.yml`:
  - Merge `origin/main` so the workflow gets the dependabot bump
    `actions/checkout` v6.0.2 -> v6.0.3 (#470). Bump the two
    newly-introduced jobs (`release-deps`, `script-tests`) to v6.0.3
    too so the file is consistent.
  - Replace the `release-deps` job comment's "workspace crates" /
    "the crate's own last `version =`" language with "workspace
    packages" / "the package's own last `version =`" for terminology
    consistency with the rest of the release tooling.

* `scripts/mutants.rs`:
  - Replace the `{s}` suffix-trick that produced "as individual group"
    in the singular case with a proper if-branched phrase:
    "Added 1 ungrouped package as an individual group" vs
    "Added N ungrouped packages as individual groups".

## Pluralisation pass

PR-wide audit for "coward pluralisation" (printing plural-only forms
even when the count is 1). Every user-facing string now picks the
correct singular or plural form based on the count it is reporting.

* `Format-PackageMenu` (per-package review menu):
  - "Direct dependents in this workspace: foo" -> "Direct dependent ..."
    when exactly one direct dependent is listed.

* `Show-ReleasePlan` (cascade-upgrade footer):
  - "Items above tagged 'auto-upgraded by cascade' were upgraded ..."
    -> "Item above tagged ... was upgraded ..." when exactly one entry
    is auto-upgraded.
  - "Items above tagged '-Force: pin honored over cascade' kept their
    explicit version pin ..." -> "Item above tagged ... kept its
    explicit version pin ..." when exactly one entry is forced.

* `Format-UnreleasedDependenciesReport` (PR comment markdown):
  - "This PR releases the following workspace packages:" ->
    "... workspace package:" when exactly one is being released.
  - "The following workspace packages have **unreleased modifications**
    (changes newer than their last ...) and are *not* part of this
    release:" -> "... package has ... its last ... and is *not* part
    ..." for one finding.
  - "The following workspace packages **are** part of this release,
    but their change type is ... while they also contain ..." ->
    "... package **is** part ... but its change type ... while it also
    contains ..." for one finding.
  - "Locally, the released packages build ... they will resolve ..." ->
    "Locally, the released package builds ... it will resolve ..." for
    a single-package release set.

* `Invoke-CheckUnreleasedDependencies`:
  - "Wrote N finding(s) to ..." -> proper "Wrote N finding" vs
    "Wrote N findings" branch (refactor of the existing inline
    if-suffix trick to a noun variable).

## Tests

* `PromptFlow.Tests.ps1`:
  - Update the existing "deduplicates direct dependents" test (which
    leaves exactly one distinct dependent) to assert the singular
    "Direct dependent" label.
  - Update the two existing tests with one-chain `WorkspaceDependencyChains`
    fixtures (formerly asserting plural "Direct dependents: a" /
    "Direct dependents: b") to assert singular.
  - Add three new tests covering the explicit singular and plural
    cases of the menu label (`,@(...)` trick to keep PowerShell from
    flattening a single-chain input).
  - Add four new tests in the cascade-upgrade footer Describe block
    for the singular vs plural wording of the two footer lines.

* `CheckUnreleasedDeps.Tests.ps1`:
  - Add a "uses singular release-set header and footer" test and a
    "uses plural release-set header and footer" test for
    `Format-UnreleasedDependenciesReport`.
  - Add singular/plural intro tests for the NotReleasedFindings and
    ElevationCandidates tables.

Total Pester count: 388 passed / 1 skipped.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 55 out of 55 changed files in this pull request and generated no new comments.

The change detection that powers -Changed (and the snapshot used for
elevation review under -Packages) is scoped to files under
`crates/<package>/`. Modifications elsewhere in the repository — the
workspace-level Cargo.toml, .cargo/, deny.toml, shared CI workflows,
top-level scripts — are invisible to the scan even when they affect
how a package builds or behaves.

Document this in three places so the limitation cannot be missed:

* `scripts/release-packages.ps1` — Description block for -Changed mode
  and the `.PARAMETER Changed` help entry.
* `docs/releasing.md` — both the top-of-document `-Changed` bullet and
  the dedicated "Guided modes" section.

Each note cross-references `-All` (which surfaces every publishable
package regardless of detected changes) and `-Packages` (which lets the
user list affected packages explicitly) as the two correct mitigations
for cross-cutting changes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@sandersaares sandersaares marked this pull request as ready for review June 5, 2026 06:42
Copilot AI review requested due to automatic review settings June 5, 2026 06:42
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 55 out of 55 changed files in this pull request and generated 1 comment.

Comment on lines +23 to +28
# Appends "$Name=$Value" to $env:GITHUB_OUTPUT, when defined. A no-op outside
# GitHub Actions so local invocations don't blow up. -LiteralPath bypasses
# wildcard interpretation in case $env:GITHUB_OUTPUT contains characters PowerShell
# would otherwise treat as glob metacharacters; -Encoding utf8 is pinned explicitly so
# the file format is deterministic across PowerShell versions (Windows PowerShell 5.1
# defaults to UTF-8 with BOM, which GitHub Actions accepts but is needlessly fragile).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants