From e56bbce127f8c83d5f1e8fb66393e6fd0f25d4d3 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sat, 30 May 2026 15:02:05 +1200 Subject: [PATCH 1/7] ci: realign triggers to the 3-tier ladder + hygiene (milestone #18) (#330) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements the trigger/hygiene slice of milestone #18 (CI cost & pipeline structure). #325 (publish-lane realignment) already landed in the ladder PR. - #318/#326 Cross-platform gated to release intent. windows/macos no longer run on main/experimental pushes (or any routine push). They run only on PR-to- release/* or support/*, and on v* tag pushes. ("On main we've got our edge": the ubuntu-latest PR gate + alpha/preview pipelines.) workflow_dispatch is not emitted — the generator only writes it with inputs; GitHub's run re-run covers on-demand cross-platform. - #322 concurrency cancel-in-progress on ubuntu/windows/macos (generator) + experimental.yml + preview.yml. NOT on release.yml (never cancel a publish). - #323/#328 Canonical CI-ignore list (docs/**, .assets/**, **/*.md) on every PR/push trigger. (release.yml is tag-triggered, so path-ignore is N/A there.) - #327 Codified "feature branches run zero CI until PR'd" + the trigger model in docs/agents/conventions.md, with what-not-to-do guards. - #329 Dropped dead 'submodules: recursive' from all checkouts + the generator (no .gitmodules; full build passes without it) and the stale vendor comment. Generated workflows regenerated from build/Build.CI.GitHubActions.cs. Deferred (own follow-ups): #324 split Build/Test/Pack stages; #328 caching deep-dive; #327 automated reflective guard-test (docs guard in place now). Also unblocked publishing separately: the github-packages environment deployment policy now allows experimental/main/release/*/support/* + v* tags. Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/experimental.yml | 6 +++- .github/workflows/macos-latest.yml | 14 +++++++-- .github/workflows/preview.yml | 6 +++- .github/workflows/release.yml | 1 - .github/workflows/ubuntu-latest.yml | 5 +++- .github/workflows/windows-latest.yml | 14 +++++++-- build/Build.CI.GitHubActions.cs | 44 ++++++++++++++++++---------- docs/agents/conventions.md | 14 +++++++++ 8 files changed, 79 insertions(+), 25 deletions(-) diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index 76faad6d7..f84d89f5b 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded alpha build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: diff --git a/.github/workflows/macos-latest.yml b/.github/workflows/macos-latest.yml index b17aacacd..426d42ffa 100644 --- a/.github/workflows/macos-latest.yml +++ b/.github/workflows/macos-latest.yml @@ -18,11 +18,20 @@ name: macos-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: macos-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index 5b90bedfa..ec62ed2e3 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -28,6 +28,11 @@ on: - '.assets/**' - '**/*.md' +# Cancel a superseded preview build when a newer commit lands (#322). Never on release.yml. +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + permissions: contents: read @@ -44,7 +49,6 @@ jobs: - uses: actions/checkout@v6 with: fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index d7820252e..9af4be383 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -94,7 +94,6 @@ jobs: with: ref: ${{ inputs.tag || github.ref }} fetch-depth: 0 # Nerdbank.GitVersioning needs full history - submodules: recursive # vendor/vs-solutionpersistence - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 with: diff --git a/.github/workflows/ubuntu-latest.yml b/.github/workflows/ubuntu-latest.yml index e1e942332..45f548d6e 100644 --- a/.github/workflows/ubuntu-latest.yml +++ b/.github/workflows/ubuntu-latest.yml @@ -28,6 +28,10 @@ on: - '.assets/**' - '**/*.md' +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: ubuntu-latest: name: ubuntu-latest @@ -35,7 +39,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 repository: ${{ github.event.pull_request.head.repo.full_name || github.repository }} ref: ${{ github.head_ref }} diff --git a/.github/workflows/windows-latest.yml b/.github/workflows/windows-latest.yml index abfd9026f..bc97f4c73 100644 --- a/.github/workflows/windows-latest.yml +++ b/.github/workflows/windows-latest.yml @@ -18,11 +18,20 @@ name: windows-latest on: push: + tags: + - 'v*' + pull_request: branches: - - experimental - - main - 'release/*' - 'support/*' + paths-ignore: + - 'docs/**' + - '.assets/**' + - '**/*.md' + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: windows-latest: @@ -31,7 +40,6 @@ jobs: steps: - uses: actions/checkout@v6 with: - submodules: recursive fetch-depth: 0 - name: 'Cache: .fallout/temp, ~/.nuget/packages' uses: actions/cache@v4 diff --git a/build/Build.CI.GitHubActions.cs b/build/Build.CI.GitHubActions.cs index 835e50ea9..6a272c419 100644 --- a/build/Build.CI.GitHubActions.cs +++ b/build/Build.CI.GitHubActions.cs @@ -1,37 +1,51 @@ using Fallout.Common.CI.GitHubActions; using Fallout.Components; -// macOS and Windows runs are reserved for post-merge validation on the -// long-lived branches (experimental, main, release/YYYY, support/*). PRs and -// feature-branch pushes get Linux-only for fast, cheap feedback. Cross-platform -// regressions on those branches surface as a red commit — same fail-fast model. +// Cross-platform (macOS/Windows) full Test+Pack is gated to RELEASE INTENT +// (#318/#326): it runs only on a PR into a production branch (release/YYYY, +// support/*) and on a release tag push (v*) — never on routine pushes to +// main/experimental, never on a per-merge basis. On main/experimental "we've +// got our edge": the ubuntu-latest PR gate + the alpha/preview pipelines. +// (workflow_dispatch as a manual cross-platform trigger isn't emitted here — +// the generator only writes workflow_dispatch when it has inputs; GitHub's +// built-in run re-run covers the on-demand case.) +// +// concurrency cancel-in-progress (#322): superseded runs are cancelled rather +// than stacked. Never applied to release.yml (a publish must not be cancelled). [GitHubActions( "macos-latest", GitHubActionsImage.MacOsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] [GitHubActions( "windows-latest", GitHubActionsImage.WindowsLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, - OnPushBranches = new[] { ExperimentalBranch, MainBranch, ReleaseBranchPattern, SupportBranchPattern }, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, + OnPushTags = new[] { "v*" }, + OnPullRequestBranches = new[] { ReleaseBranchPattern, SupportBranchPattern }, + OnPullRequestExcludePaths = new[] { "docs/**", ".assets/**", "**/*.md" }, InvokedTargets = new[] { nameof(ITest.Test), nameof(IPack.Pack) }, PublishArtifacts = false)] -// pull_request only — same-repo branches would otherwise fire both push and -// pull_request events on every push, double-running the validation. -// -// CheckoutRef = github.head_ref pins checkout to the PR source branch instead of the merge SHA, -// keeping HEAD attached so GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads -// .git/HEAD via GitRepository.FromLocalDirectory) resolves a non-null branch. +// The Linux PR gate — the only required status check. pull_request only: +// feature-branch pushes run zero CI until a PR is opened against a long-lived +// branch (#327). CheckoutRef = github.head_ref pins checkout to the PR source +// branch instead of the merge SHA, keeping HEAD attached so +// GitHubTasksTest.GitHubRepositoryFromLocalDirectoryTest (which reads .git/HEAD +// via GitRepository.FromLocalDirectory) resolves a non-null branch. [GitHubActions( "ubuntu-latest", GitHubActionsImage.UbuntuLatest, FetchDepth = 0, - Submodules = GitHubActionsSubmodules.Recursive, + ConcurrencyGroup = "${{ github.workflow }}-${{ github.ref }}", + ConcurrencyCancelInProgress = true, CheckoutRef = "${{ github.head_ref }}", // Trigger for PRs targeting experimental, main, or any release/YYYY / support/* // branch — all are long-lived and protected; all require the ubuntu-latest check. diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index b85d0e49f..6d591bdde 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -34,9 +34,23 @@ public sealed class NewPluginHost - **Channel discipline differs.** On the `experimental` (alpha) / `main` (preview) test lanes, churn is expected and the attribute is a courtesy. On a `release/YYYY` **production line**, any risky-but-shipped public surface **must** wear `[Experimental]` — that contract is what keeps the stable line trustworthy while still carrying new work. - **Don't apply it speculatively.** Because the diagnostic is error-by-default, marking an API that's already used internally breaks the build everywhere it's referenced. Only add `[Experimental]` to a genuinely not-yet-stable API, and suppress every internal usage in the same change so the build stays green. +## CI pipeline & triggers + +Shaped by [milestone #18](https://github.com/ChrisonSimtian/Fallout/milestone/18) and the [ADR-0004](../adr/0004-calendar-versioning-and-dual-pace-channels.md) ladder. Invariants: + +- **Feature branches run zero CI until a PR is opened.** Push triggers list **only** long-lived branches; nothing fires on `feature/*`, `bugfix/*`, etc. until they're PR'd against `experimental`/`main`/`release/*`/`support/*`. Do **not** add a working-branch pattern to any `OnPush*`/`branches:` trigger. +- **The Linux PR gate (`ubuntu-latest`) is the only required check** — runs on PRs to the four long-lived branches. +- **`experimental` (push) → `-alpha`, `main` (push) → `-preview`** to GitHub Packages (`experimental.yml` / `preview.yml`). +- **Cross-platform `windows`/`macos` are gated to release intent** — PR-to-`release/*`/`support/*` or a `v*` tag push only. They do **not** run on `main`/`experimental` pushes. ("On `main` we've got our edge.") +- **`concurrency: cancel-in-progress` on every build workflow except `release.yml`** — never cancel a publish mid-flight. +- **Canonical CI-ignore paths:** `docs/**`, `.assets/**`, `**/*.md` — applied to every PR/push trigger. +- The `ubuntu-latest` / `windows-latest` / `macos-latest` workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `experimental.yml` / `preview.yml` / `release.yml` are hand-written. + ## What not to do - Don't reintroduce `source/` — production code lives under `src/`, tests under `tests/`. Same for `images/` (now `.assets/`). +- Don't add `main`/`experimental` (or any working-branch pattern) to the **push** triggers of the cross-platform workflows — they're release-intent-gated on purpose (milestone #18 / #318 / #326). +- Don't add `submodules: recursive` to checkouts — there are no submodules (no `.gitmodules`); it's a dead init step. - Don't add `secret`/`default` defaults to tool JSON files (see CONTRIBUTING.md). - Don't introduce a new test framework or assertion library — stay on xUnit + FluentAssertions + Verify. - Don't commit `output/` or any `bin/`/`obj/` directory. From 5ee0600a99f4d7f22174e152943f165233873c02 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sat, 30 May 2026 15:20:54 +1200 Subject: [PATCH 2/7] ci: test before publishing on every lane + cache restore-keys (#324, #328) (#331) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit #324 — experimental.yml and preview.yml ran `dotnet fallout Pack` only, publishing alpha/preview packages WITHOUT running tests. Both now run `dotnet fallout Test Pack` (release.yml + the PR gate already did). One invocation = NUKE's discrete internal stages (Restore → Compile → Test → Pack), failing at the breaking stage; a test failure stops the job before the push step, so untested packages never publish. Separate per-step `dotnet fallout` invocations are avoided on purpose — each re-runs the dependency graph (double-compile); the single invocation is the staged build. #328 — added `restore-keys:` prefix fallback to the hand-written workflows' caches for faster partial restores on key miss. Evaluation: current key (global.json + *.csproj + Directory.Packages.props) is the right dependency set; no packages.lock.json exists to add; build-output (bin/obj) caching deliberately not done (stale-artifact risk). Canonical ignore list (docs/.assets/md) already applied in the trigger PR. Codified both in docs/agents/conventions.md. Deferred: #327 automated reflective guard-test (docs guard already in place). Co-authored-by: Claude Opus 4.8 (1M context) --- .github/workflows/experimental.yml | 11 +++++++++-- .github/workflows/preview.yml | 11 +++++++++-- .github/workflows/release.yml | 2 ++ docs/agents/conventions.md | 2 ++ 4 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index f84d89f5b..ad9ca7fee 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -56,14 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-experimental-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-experimental- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack + # build + unit-test before publishing alpha (#324). One invocation: NUKE runs + # this as discrete internal stages (Restore → Compile → Test → Pack) and fails + # at the breaking stage — separate `dotnet fallout` steps would re-run the + # dependency graph (double-compile), so we keep it a single invocation. + # If Test fails, the job stops here and nothing is published. + - name: 'Run: Test + Pack' + run: dotnet fallout Test Pack - name: 'Push: all *.nupkg to GitHub Packages (alpha)' run: | set -euo pipefail diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index ec62ed2e3..f3d34cc1a 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -56,14 +56,21 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-preview-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-preview- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: global-json-file: global.json - name: 'Restore: dotnet tools' run: dotnet tool restore - - name: 'Run: Pack' - run: dotnet fallout Pack + # build + unit-test before publishing preview (#324). One invocation: NUKE runs + # this as discrete internal stages (Restore → Compile → Test → Pack) and fails + # at the breaking stage — separate `dotnet fallout` steps would re-run the + # dependency graph (double-compile), so we keep it a single invocation. + # If Test fails, the job stops here and nothing is published. + - name: 'Run: Test + Pack' + run: dotnet fallout Test Pack - name: 'Push: all *.nupkg to GitHub Packages (preview)' run: | set -euo pipefail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9af4be383..ce115724e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -101,6 +101,8 @@ jobs: .fallout/temp ~/.nuget/packages key: ${{ runner.os }}-release-${{ hashFiles('**/global.json', '**/*.csproj', '**/Directory.Packages.props', 'version.json') }} + restore-keys: | + ${{ runner.os }}-release- - name: 'Setup: .NET SDK' uses: actions/setup-dotnet@v4 with: diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index 6d591bdde..555ce8125 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -45,6 +45,8 @@ Shaped by [milestone #18](https://github.com/ChrisonSimtian/Fallout/milestone/18 - **`concurrency: cancel-in-progress` on every build workflow except `release.yml`** — never cancel a publish mid-flight. - **Canonical CI-ignore paths:** `docs/**`, `.assets/**`, `**/*.md` — applied to every PR/push trigger. - The `ubuntu-latest` / `windows-latest` / `macos-latest` workflows are **generated** from `build/Build.CI.GitHubActions.cs` — edit the attributes + constants there and regenerate (`./build.sh`), never hand-edit the `.yml`. `experimental.yml` / `preview.yml` / `release.yml` are hand-written. +- **Every publishing lane runs `Test` before it publishes** (#324). `experimental.yml`, `preview.yml`, and `release.yml` all run a single `dotnet fallout Test Pack` invocation — NUKE executes it as discrete internal stages (Restore → Compile → Test → Pack) and fails at the breaking stage, so a test failure stops the job before the push step. Don't split a lane into separate `dotnet fallout Compile`/`Test`/`Pack` steps — each invocation re-runs the dependency graph (double-compile); the single invocation *is* the staged build. +- **Caching** (#328): every workflow caches `~/.nuget/packages` + `.fallout/temp`, keyed on `global.json` + `**/*.csproj` + `Directory.Packages.props` (the dependency-affecting set), with a `restore-keys:` prefix fallback for partial restores. There is no `packages.lock.json` to add to the key, and build outputs (`bin`/`obj`) are deliberately **not** cached (stale-artifact correctness risk). ## What not to do From 58f1ca94e17af0265c0db9611feff1f7b3069081 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Sun, 31 May 2026 23:37:16 +1200 Subject: [PATCH 3/7] chore: repoint to Fallout-build org after repo transfer (main) (#345) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Repo moved ChrisonSimtian/Fallout → Fallout-build/Fallout. Owner-scoped refs that don't auto-redirect, fixed on main: - GitHub Packages feed URL nuget.pkg.github.com/ChrisonSimtian → /Fallout-build (release.yml, preview.yml, experimental.yml, + consumer docs: shim READMEs, from-nuke.md, release-and-versioning.md, CHANGELOG mention) - CODEOWNERS @ChrisonSimtian → @Fallout-build/maintainers (team now has Maintain access) Feed stays PAT-gated (GitHub Packages has no anonymous access even when public; #344). Web links / badges / issue refs redirect automatically and are left for a later sweep. Co-authored-by: Claude Opus 4.8 (1M context) --- .github/CODEOWNERS | 4 ++-- .github/workflows/experimental.yml | 2 +- .github/workflows/preview.yml | 2 +- .github/workflows/release.yml | 2 +- CHANGELOG.md | 2 +- docs/agents/release-and-versioning.md | 2 +- docs/migration/from-nuke.md | 2 +- src/Shims/Nuke.Build/README.md | 2 +- src/Shims/Nuke.Common/README.md | 4 ++-- src/Shims/Nuke.Components/README.md | 2 +- 10 files changed, 12 insertions(+), 12 deletions(-) diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 3b08bb53b..713f0c064 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -8,5 +8,5 @@ # changelogs, etc.) have no code owner and are gated only by the required CI # status check. -/src/** @ChrisonSimtian -/tests/** @ChrisonSimtian +/src/** @Fallout-build/maintainers +/tests/** @Fallout-build/maintainers diff --git a/.github/workflows/experimental.yml b/.github/workflows/experimental.yml index ad9ca7fee..f1009949a 100644 --- a/.github/workflows/experimental.yml +++ b/.github/workflows/experimental.yml @@ -83,7 +83,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages (alpha)..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/preview.yml b/.github/workflows/preview.yml index f3d34cc1a..98f84874f 100644 --- a/.github/workflows/preview.yml +++ b/.github/workflows/preview.yml @@ -83,7 +83,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages (preview)..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index ce115724e..10e1e1e4d 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -199,7 +199,7 @@ jobs: for pkg in "${packages[@]}"; do echo "Pushing $pkg to GitHub Packages..." dotnet nuget push "$pkg" \ - --source "https://nuget.pkg.github.com/ChrisonSimtian/index.json" \ + --source "https://nuget.pkg.github.com/Fallout-build/index.json" \ --api-key "${{ secrets.GITHUB_TOKEN }}" \ --skip-duplicate done diff --git a/CHANGELOG.md b/CHANGELOG.md index 8ac51bd0f..49a482d1a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -123,7 +123,7 @@ The NUKE → Fallout hard fork. Originally NUKE by [@matkoch](https://github.com ### Backwards-compat: Nuke.* transition shims - **`Nuke.Common` MVP shim package** (#70): thin wrapper assembly in the `Nuke.*` namespace whose types inherit from the corresponding `Fallout.*` types — lets pre-rename consumers compile against the new packages without source changes. Lives at `src/Shims/Nuke.Common/`. - **`TransitionShimGenerator` source generator** (#69): emits shim type wrappers per-target, covering the Nuke.Common "Easy tier" surface (plain types, interfaces, enums) and static-class method delegation. Hard-tier types (sealed structs, delegates, etc.) raise `SHIM001` warnings with a pointer to `fallout-migrate`. -- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/ChrisonSimtian/index.json`). Production-build feed is nuget.org with `Fallout.*` only. +- **Shim packages publish to GitHub Packages** (#47): `Nuke.*` package IDs belong to matkoch on nuget.org, so the transition shim feed lives at GH Packages (`https://nuget.pkg.github.com/Fallout-build/index.json`). Production-build feed is nuget.org with `Fallout.*` only. ### Vendored fork of `Microsoft.VisualStudio.SolutionPersistence` - **Replaced `matkoch.Microsoft.VisualStudio.SolutionPersistence` with a vendored fork** (#86). The upstream Microsoft package only ships `net472` + `net8.0`; our source generators need `netstandard2.0`. Matt's fork had the netstandard2.0 patches — we forked his repo at [`ChrisonSimtian/vs-solutionpersistence`](https://github.com/ChrisonSimtian/vs-solutionpersistence) (preserves the MIT license + upstream Microsoft history + attribution chain). Sources are a submodule at `vendor/vs-solutionpersistence/`; wrapper csproj at `src/Fallout.VisualStudio.SolutionPersistence/` compiles them with the TFMs we need. Assembly name stays `Microsoft.VisualStudio.SolutionPersistence` so type identity is preserved. (Note: in 10.2 the wrapper packed as `IsPackable=false` and caused the restore bug fixed in 10.3.0 above — known issue, fix-forward.) diff --git a/docs/agents/release-and-versioning.md b/docs/agents/release-and-versioning.md index 64493bf57..b6ab8dec4 100644 --- a/docs/agents/release-and-versioning.md +++ b/docs/agents/release-and-versioning.md @@ -92,7 +92,7 @@ If you only discover the breaking nature mid-review, apply all relevant steps be | Job | Environment | Fires on tag push? | What ships | Gating | |---|---|---|---|---| | `publish-nuget-org` | `nuget-org` | **No — opt-in only** via `workflow_dispatch` flag | `Fallout.*.nupkg` to https://api.nuget.org/v3/index.json | Workflow flag + approval-gated env | -| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/ChrisonSimtian/index.json | None | +| `publish-github-packages` | `github-packages` | Yes | **All** `*.nupkg` (Fallout.* + Nuke.*) to https://nuget.pkg.github.com/Fallout-build/index.json | None | | `publish-github-releases` | `github-releases` | Yes | All `*.nupkg` attached to a GitHub Release on the tag, auto-generated notes | None | ### Test lanes (from `experimental` and `main`) diff --git a/docs/migration/from-nuke.md b/docs/migration/from-nuke.md index 1332f6d86..f1eba5a94 100644 --- a/docs/migration/from-nuke.md +++ b/docs/migration/from-nuke.md @@ -82,7 +82,7 @@ The shim ships on **GitHub Packages** (not nuget.org — that namespace belongs - + diff --git a/src/Shims/Nuke.Build/README.md b/src/Shims/Nuke.Build/README.md index c4d6797d8..69f5a348b 100644 --- a/src/Shims/Nuke.Build/README.md +++ b/src/Shims/Nuke.Build/README.md @@ -11,7 +11,7 @@ Coverage and limitations are the same as the `Nuke.Common` shim — see [`../Nuk Add this fork's GitHub Packages feed to your `nuget.config`: ```xml - + ``` Then bump your `Nuke.Build` package reference to the latest 10.3.x or later. diff --git a/src/Shims/Nuke.Common/README.md b/src/Shims/Nuke.Common/README.md index 3d7a48380..6d515d5c7 100644 --- a/src/Shims/Nuke.Common/README.md +++ b/src/Shims/Nuke.Common/README.md @@ -45,14 +45,14 @@ Until automated dual-publish lands (tracked in [#69](https://github.com/ChrisonS dotnet pack src/Shims/Nuke.Common/Nuke.Common.csproj -c Release dotnet nuget push **/Nuke.Common.*.nupkg ` --api-key $env:GITHUB_TOKEN ` - --source https://nuget.pkg.github.com/ChrisonSimtian/index.json + --source https://nuget.pkg.github.com/Fallout-build/index.json ``` ## Migration path For projects that just want their `class Build : NukeBuild { ... }` to compile against our framework: -1. Add `https://nuget.pkg.github.com/ChrisonSimtian/index.json` as a NuGet source (`nuget.config`). +1. Add `https://nuget.pkg.github.com/Fallout-build/index.json` as a NuGet source (`nuget.config`). 2. Bump the `Nuke.Common` package version to the latest published here. 3. Build. If the shim covers what you use, you're done. Otherwise: 4. Run `fallout-migrate` to rewrite the remaining references. diff --git a/src/Shims/Nuke.Components/README.md b/src/Shims/Nuke.Components/README.md index afe0142e7..65c2f49d2 100644 --- a/src/Shims/Nuke.Components/README.md +++ b/src/Shims/Nuke.Components/README.md @@ -11,7 +11,7 @@ Limitations are the same as the `Nuke.Common` shim — see [`../Nuke.Common/READ Add this fork's GitHub Packages feed to your `nuget.config`: ```xml - + ``` Then bump your `Nuke.Components` package reference to the latest 10.3.x or later. From d84fad5cd7a61c8c3349eeb148545296bc20bc6d Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Tue, 2 Jun 2026 15:19:34 +1200 Subject: [PATCH 4/7] simplify claude.json --- .claude/settings.json | 46 ++++--------------------------------------- 1 file changed, 4 insertions(+), 42 deletions(-) diff --git a/.claude/settings.json b/.claude/settings.json index 8cc878e58..33fe326e2 100644 --- a/.claude/settings.json +++ b/.claude/settings.json @@ -1,52 +1,14 @@ { "$schema": "https://json.schemastore.org/claude-code-settings.json", - "statusLine": { - "type": "command", - "command": "pwsh -NoProfile -Command '$input | & (Get-ChildItem $HOME/.claude/plugins/cache/myfoodbag/mfb-statusline/*/statusline-command.ps1 | Select-Object -Last 1)'" - }, - "enabledPlugins": { - "mfb-statusline@myfoodbag": true - }, "permissions": { "allow": [ - "Bash(dotnet build:*)", - "Bash(dotnet test:*)", - "Bash(dotnet restore:*)", - "Bash(dotnet pack:*)", - "Bash(dotnet --info)", - "Bash(dotnet --version)", - "Bash(dotnet list:*)", + "Bash(dotnet:*)", "Bash(./build.ps1:*)", "Bash(./build.sh:*)", "Bash(./build.cmd:*)", - "Bash(pwsh ./build.ps1:*)", - "Bash(pwsh -NoProfile ./build.ps1:*)", - "Bash(git status)", - "Bash(git status:*)", - "Bash(git diff:*)", - "Bash(git log:*)", - "Bash(git show:*)", - "Bash(git branch:*)", - "Bash(git remote -v)", - "Bash(git rev-parse:*)", - "Bash(git ls-files:*)", - "Bash(git config --get:*)", - "Bash(gh pr list:*)", - "Bash(gh pr view:*)", - "Bash(gh pr diff:*)", - "Bash(gh issue list:*)", - "Bash(gh issue view:*)", - "Bash(gh run list:*)", - "Bash(gh run view:*)", - "Bash(gh workflow list:*)", - "Bash(gh api:*)" - ], - "deny": [ - "Bash(git push --force:*)", - "Bash(git push -f:*)", - "Bash(git reset --hard:*)", - "Bash(git clean -f:*)", - "Bash(rm -rf:*)" + "Bash(pwsh:*)", + "Bash(git:*)", + "Bash(gh:*)" ] } } From 7adf167507e4c629de9af7f55a90c638fbff6eee Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Wed, 3 Jun 2026 15:29:57 +1200 Subject: [PATCH 5/7] Replace star-history with repostars for tracking Updated star tracking links and attribution in README. --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index d8c82e46b..3efa64741 100644 --- a/README.md +++ b/README.md @@ -107,9 +107,9 @@ Generated by [Repobeats](https://repobeats.axiom.co). ### Stars over time -[![Star History Chart](https://api.star-history.com/svg?repos=ChrisonSimtian/Fallout&type=Date)](https://star-history.com/#ChrisonSimtian/Fallout&Date) +[![RepoStars](https://repostars.dev/api/embed?repo=Fallout-build%2FFallout&theme=terminal)](https://repostars.dev/?repos=Fallout-build%2FFallout&theme=terminal) -Generated by [star-history.com](https://star-history.com). Auto-updates as new stargazers arrive. +Generated by [repostars.dev](https://www.repostars.dev/). Auto-updates as new stargazers arrive. ## Sponsorship From b4866d9259cbadda2685e39c889494d290fffdf5 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Wed, 3 Jun 2026 19:24:56 +1200 Subject: [PATCH 6/7] docs(conventions): add writing-style rule for issues, PRs, commits (#361) User feedback: AI-written issues are too complex and hard to read, especially for non-native English speakers. Require short, plain, bulleted output for issues, PR bodies/comments, and commit messages. Co-authored-by: Claude Opus 4.8 (1M context) --- docs/agents/conventions.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/docs/agents/conventions.md b/docs/agents/conventions.md index 555ce8125..646e2f979 100644 --- a/docs/agents/conventions.md +++ b/docs/agents/conventions.md @@ -12,6 +12,18 @@ Three groups: conventions to respect, things never to do, and the tool-wrapper r - **No per-file license headers.** The MIT notice lives in [`LICENSE`](../../LICENSE) at the repo root, and NuGet packages declare MIT via `PackageLicenseExpression`. Per-file headers were stripped in v11 (one source of truth + the header URL would have rotted on the repo-org transfer). Vendored third-party code keeps its own copyright headers — don't touch those (e.g. files under `src/Persistence/Fallout.Persistence.Solution/` retain Microsoft's MIT notice). - **`[Experimental]` for opt-in unstable public APIs.** Not-yet-stable public surface is marked with `[Experimental("FALLOUT0xx")]` rather than held back or shipped silently. See [the `[Experimental]` convention](#experimental-for-opt-in-unstable-apis) below and the [diagnostic-ID registry](../experimental-apis.md). +## Writing style for issues, PRs, and commits + +Applies to AI tools and humans. Many readers are non-native English speakers — keep it readable. + +- Be short and precise. Lead with the point. +- Prefer bullet points over paragraphs. +- Use plain, simple English. Short sentences, common words. +- Cut filler: no preamble, no hedging, no AI-flavored padding. +- Say what changed and why. Drop the rest. + +Covers GitHub issues, PR titles/bodies/comments, and commit messages. + ## `[Experimental]` for opt-in unstable APIs Per [ADR-0004 §5](../adr/0004-calendar-versioning-and-dual-pace-channels.md), public APIs that aren't ready to commit to a stability guarantee are marked with [`System.Diagnostics.CodeAnalysis.ExperimentalAttribute`](https://learn.microsoft.com/dotnet/api/system.diagnostics.codeanalysis.experimentalattribute) instead of being held back or shipped silently. The attribute ships in the .NET 8+ BCL — **no package reference needed** (the repo targets .NET 10). From 1c5fea66899762c47e4d26f8c40af32613b8e009 Mon Sep 17 00:00:00 2001 From: Chrison Simtian Date: Wed, 3 Jun 2026 19:43:32 +1200 Subject: [PATCH 7/7] docs: record keep-rationale for justified hand-rolled code - add docs/dependencies-kept.md listing the deliberate keeps (crypto, path API, CI config writers, parameter schema) - add 'why hand-rolled' XML-doc remarks to each area - cross-link from dependencies.md Closes #358 Co-Authored-By: Claude Opus 4.8 (1M context) --- docs/dependencies-kept.md | 24 +++++++++++++++++++ docs/dependencies.md | 2 ++ src/Fallout.Build/CICD/ConfigurationEntity.cs | 8 +++++++ src/Fallout.Build/CICD/CustomFileWriter.cs | 11 +++++++++ src/Fallout.Build/Utilities/SchemaUtility.cs | 3 +++ src/Fallout.Utilities/IO/AbsolutePath.cs | 7 ++++++ src/Fallout.Utilities/IO/PathConstruction.cs | 4 ++++ .../Security/EncryptionUtility.cs | 5 ++++ 8 files changed, 64 insertions(+) create mode 100644 docs/dependencies-kept.md diff --git a/docs/dependencies-kept.md b/docs/dependencies-kept.md new file mode 100644 index 000000000..490dba265 --- /dev/null +++ b/docs/dependencies-kept.md @@ -0,0 +1,24 @@ +# Deliberately hand-rolled code (do not "use a library") + +Companion to [dependencies.md](dependencies.md). That page lists the libraries we *do* pull in; +this page records the areas we keep hand-rolled **on purpose**, with the reason, so they aren't +re-litigated in future "replace the reinvented wheel" passes. + +These keeps come out of the dependency-consolidation audit (2026-06-02, [#358](https://github.com/ChrisonSimtian/Fallout/issues/358)). +Each area also carries the same rationale as an XML-doc remark on the type itself. + +**Before proposing a library swap for anything below, read the reason and the linked discussion first.** + +| Area | Files | Why it stays hand-rolled | +|---|---|---| +| Secret encryption | `src/Fallout.Utilities/Security/EncryptionUtility.cs` | Built directly on BCL crypto (`AesGcm` + `Rfc2898DeriveBytes`): AES-GCM, random salt/nonce, PBKDF2-SHA256 at 600,000 iterations. OWASP-aligned and security-audited under [#212](https://github.com/ChrisonSimtian/Fallout/issues/212). This is correct BCL crypto — a third-party crypto library would add risk, not remove it. | +| Path API | `src/Fallout.Utilities/IO/AbsolutePath.cs`, `src/Fallout.Utilities/IO/PathConstruction.cs` (~500 LOC) | Type-safe, OS-independent path model — a core selling point of the framework. The BCL `System.IO.Path` API is string-typed and OS-dependent; our type captures the rooted/relative distinction, the `/` and `+` append operators, and automatic normalization. No general-purpose NuGet path library matches this surface. | +| CI/CD config writers | `src/Fallout.Build/CICD/CustomFileWriter.cs`, `src/Fallout.Build/CICD/ConfigurationEntity.cs`, `src/Fallout.Common/CI/**/Configuration/*Configuration.cs` (~1,800 LOC) | TeamCity and SpaceAutomation emit a **Kotlin DSL** — no YAML/JSON serializer can produce that. The YAML targets (GitHub Actions, Azure Pipelines, AppVeyor) need exact control over comments, quoting, and indentation; a serializer like YamlDotNet would silently rewrite those and break round-trips users depend on. The hand-rolled `CustomFileWriter` gives us that control. | +| Parameter schema | `src/Fallout.Build/Utilities/SchemaUtility.cs` (~393 LOC) | Emits the draft-04 JSON Schema envelope (definitions block + `allOf[user, base]`) that the NUKE ecosystem has emitted since day one. The exact output shape is a contract consumed by `Fallout.Cli` for editor autocomplete/validation of `parameters.json`. A schema library (NJsonSchema, Newtonsoft) would change the shape. Already hand-rolled on `System.Text.Json` nodes — no extra dependency. | + +## When does a keep get revisited? + +A keep is not permanent. Re-open the question only when the reason no longer holds — e.g. a CI target +drops its Kotlin DSL, the schema contract is versioned so the shape can change, or a vetted crypto/path +library covers the exact surface with no behavioral drift. In that case, raise it on the issue tracker +referencing this page rather than silently swapping in a dependency. diff --git a/docs/dependencies.md b/docs/dependencies.md index d579f84c7..61e8cb534 100644 --- a/docs/dependencies.md +++ b/docs/dependencies.md @@ -6,6 +6,8 @@ A flat overview of the non-trivial libraries Fallout pulls in, what each is for, Central package versions are pinned in `Directory.Packages.props`; this page links each entry back to it. +For the flip side — areas we keep **hand-rolled on purpose** instead of pulling in a library (crypto, paths, CI config writers, schema) — see [dependencies-kept.md](dependencies-kept.md). + --- ## Microsoft / .NET BCL diff --git a/src/Fallout.Build/CICD/ConfigurationEntity.cs b/src/Fallout.Build/CICD/ConfigurationEntity.cs index f04a12d98..319372897 100644 --- a/src/Fallout.Build/CICD/ConfigurationEntity.cs +++ b/src/Fallout.Build/CICD/ConfigurationEntity.cs @@ -4,6 +4,14 @@ namespace Fallout.Common.CI; +/// +/// Base for a CI/CD config node that writes itself through a . +/// +/// +/// The CI config writers are hand-rolled on purpose (Kotlin DSL targets + exact comment/quote/indent +/// control on YAML targets); see CustomFileWriter and +/// docs/dependencies-kept.md. +/// public abstract class ConfigurationEntity { public abstract void Write(CustomFileWriter writer); diff --git a/src/Fallout.Build/CICD/CustomFileWriter.cs b/src/Fallout.Build/CICD/CustomFileWriter.cs index ba334ab2e..f988e23c0 100644 --- a/src/Fallout.Build/CICD/CustomFileWriter.cs +++ b/src/Fallout.Build/CICD/CustomFileWriter.cs @@ -4,6 +4,17 @@ namespace Fallout.Common.Utilities; +/// +/// Low-level, indentation-aware line writer backing the CI/CD configuration generators +/// (ConfigurationEntity.Write in Fallout.Common.CI.*). +/// +/// +/// Deliberately hand-rolled, not replaced by a serializer (e.g. YamlDotNet). TeamCity and +/// SpaceAutomation targets emit a Kotlin DSL, which no YAML/JSON serializer can produce; the +/// YAML targets (GitHub Actions, Azure Pipelines, AppVeyor) need exact control over comments, quoting, +/// and indentation that a serializer would silently rewrite. See +/// docs/dependencies-kept.md. +/// public class CustomFileWriter { private readonly StreamWriter _streamWriter; diff --git a/src/Fallout.Build/Utilities/SchemaUtility.cs b/src/Fallout.Build/Utilities/SchemaUtility.cs index 7c6e5c171..4a5d2ac62 100644 --- a/src/Fallout.Build/Utilities/SchemaUtility.cs +++ b/src/Fallout.Build/Utilities/SchemaUtility.cs @@ -20,6 +20,9 @@ namespace Fallout.Common.Execution; /// can autocomplete and validate the consumer's parameters.json. Hand-rolled on top of /// — no NJsonSchema, no Newtonsoft. The output shape is the draft-04 envelope /// the NUKE ecosystem has emitted since day one (definitions block + allOf[user, base]). +/// The exact output shape is a contract consumed by Fallout.Cli, so it stays hand-rolled +/// rather than swapped for a schema library. See +/// docs/dependencies-kept.md. /// public static class SchemaUtility { diff --git a/src/Fallout.Utilities/IO/AbsolutePath.cs b/src/Fallout.Utilities/IO/AbsolutePath.cs index c95fd5136..31231c4eb 100644 --- a/src/Fallout.Utilities/IO/AbsolutePath.cs +++ b/src/Fallout.Utilities/IO/AbsolutePath.cs @@ -13,6 +13,13 @@ namespace Fallout.Common.IO; /// /// Represents an absolute path without distinction between files and directories. /// +/// +/// Deliberately hand-rolled, not delegated to a NuGet path library. The BCL +/// API is string-typed and OS-dependent; this type plus give a +/// type-safe, OS-independent path model (rooted/relative distinction, / and + operators, +/// automatic normalization) that is a core selling point of the framework. See +/// docs/dependencies-kept.md. +/// [Serializable] [TypeConverter(typeof(TypeConverter))] [DebuggerDisplay("{" + nameof(_path) + "}")] diff --git a/src/Fallout.Utilities/IO/PathConstruction.cs b/src/Fallout.Utilities/IO/PathConstruction.cs index 6761ff189..e1e9bc727 100644 --- a/src/Fallout.Utilities/IO/PathConstruction.cs +++ b/src/Fallout.Utilities/IO/PathConstruction.cs @@ -17,6 +17,10 @@ namespace Fallout.Common.IO; /// used intentionally. Casting with (AbsolutePath) ensures that the path is rooted as Windows/Unix/UNC-path. The operators /// / and + allow to append sub-directories.

///

Resulting paths are automatically normalized if possible. So C:\foo\..\bar\. will become C:\bar.

+///

Deliberately hand-rolled rather than delegated to a NuGet path library: the BCL +/// API is string-typed and OS-dependent, whereas this type-safe, +/// OS-independent path model is a core selling point of the framework. See +/// docs/dependencies-kept.md.

/// /// /// diff --git a/src/Fallout.Utilities/Security/EncryptionUtility.cs b/src/Fallout.Utilities/Security/EncryptionUtility.cs index f5978a29f..ce87fd969 100644 --- a/src/Fallout.Utilities/Security/EncryptionUtility.cs +++ b/src/Fallout.Utilities/Security/EncryptionUtility.cs @@ -28,6 +28,11 @@ namespace Fallout.Common.Utilities; /// See #212 for the security /// audit that motivated the v2 format. /// +/// +/// Deliberately built on the BCL crypto primitives ( + ), +/// not a third-party crypto library. The construction is OWASP-aligned and security-audited (#212); no +/// swap is warranted. See docs/dependencies-kept.md. +/// internal static class EncryptionUtility { private const string V1Prefix = "v1:";