From 248ae338bfea5060d0a9a30fddd6f667b2781339 Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Thu, 21 May 2026 21:03:17 +1000 Subject: [PATCH 1/2] chore(versioning): collapse 7 stamping sites down to one CI step The repo's documented schema -- `YYYY.MM.DD.N` where `N` is the Nth release of the day -- never matched reality. Every PR carried a chore tax: bump the date in `VERSION`, stamp it into the two shell scripts (4 lines), the BuildInfo const, and the example versions in copilot-instructions.md. Eight files, every PR, just to make the version match today. This rip-and-replace: - Deletes `VERSION` and `scripts/bump-version.sh`. Neither is needed. - Source files (copilot_here.sh, .ps1, BuildInfo.cs) now carry the literal `0.0.0-dev` sentinel forever. `BuildInfo.BuildDate` derives its value from the assembly's InformationalVersion at runtime and strips the git-sha suffix, so the const is no longer a stamp target. - `Directory.Build.props` falls back to `today.0` instead of reading from `VERSION`. Local `dotnet run -- --version` still prints a YYYY.MM.DD.N string the format tests accept. - The `compute-version` CI job replaces `github.run_number` (currently 591, meaningless) with `gh release list` count of `cli-v$DATE.* + 1` on main pushes, matching the documented schema. PR/schedule/branch dispatch builds stamp `today.0`. - `scripts/stamp-version.sh` slimmed to just the two shell scripts, switched to `~` as the sed delimiter so the alternation regex doesn't get eaten by BSD sed on macOS. - `release-cli` stamps before copying the scripts into `release/` (the old order stamped source after the cp, so the released scripts were unstamped). - `test-shell-functions` now stamps the scripts before sourcing, so the auto-update check inside copilot_here.sh doesn't see a remote-newer release and prompt during the --version test. - `docs/versioning.md` and the `.github/copilot-instructions.md` "Script Versioning" block rewritten around "merge a PR, CI handles it". After this lands, the next merged PR becomes `cli-v2026.05.21.1`, not `.591`, and no version literal in source ever changes again. Co-authored-by: Claude Co-authored-by: GitButler --- .github/copilot-instructions.md | 75 ++++----------------------------- .github/workflows/publish.yml | 60 ++++++++++++++++++-------- Directory.Build.props | 9 +++- VERSION | 1 - app/Infrastructure/BuildInfo.cs | 47 +++++++++++++++++---- copilot_here.ps1 | 4 +- copilot_here.sh | 4 +- docs/versioning.md | 58 +++++++++---------------- scripts/bump-version.sh | 17 -------- scripts/stamp-version.sh | 39 +++++++++-------- 10 files changed, 140 insertions(+), 174 deletions(-) delete mode 100644 VERSION delete mode 100755 scripts/bump-version.sh diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 3a84e80..4895dcf 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -14,69 +14,14 @@ You can also use the command `session-info` for more info on mounts for this pro ## Script Versioning -**CRITICAL RULE**: ALL VERSION NUMBERS MUST BE IDENTICAL ACROSS ALL FILES. No exceptions. +Don't edit version strings by hand. CI sets them. -### Version Format +- Format: `YYYY.MM.DD.N`. `N` is the Nth release of that day. +- `copilot_here.sh`, `copilot_here.ps1`, and `app/Infrastructure/BuildInfo.cs` all carry the literal placeholder `0.0.0-dev` in source. They stay that way. +- The `compute-version` job in `.github/workflows/publish.yml` picks today's UTC date and the daily iteration. `scripts/stamp-version.sh` then writes the real string into the shell scripts before tests and packaging. The .NET binary's version comes from `Directory.Build.props` (local default: `today.0`) or from `-p:CopilotHereVersion=...` in CI. `BuildInfo.BuildDate` reads back from the assembly's `AssemblyInformationalVersion` at runtime. +- There is no `VERSION` file and no `bump-version.sh`. If you're reaching for one of those, stop. Open a PR with the code change and let CI pick the next number on merge. -- **Primary version**: Use current date in format `YYYY.MM.DD` (e.g., `2025.12.02`) -- **Same-day updates**: If the version date already matches today's date, append `.1`, `.2`, `.3`, etc. - - Example: `2025.12.02` → `2025.12.02.1` → `2025.12.02.2` -- **CRITICAL**: Always increment the version when making changes - this triggers re-download for users - -### Where to Update Versions (ALL MUST MATCH) - -**EVERY TIME** you modify shell functions, CLI binary code, or any functionality, update ALL FOUR version locations to the SAME version: - -1. **Bash/Zsh script**: `copilot_here.sh` - - - Line 2: `# Version: YYYY.MM.DD` - - Line 8: `COPILOT_HERE_VERSION="YYYY.MM.DD"` - -2. **PowerShell script**: `copilot_here.ps1` - - - Line 2: `# Version: YYYY.MM.DD` - - Line 8: `$script:CopilotHereVersion = "YYYY.MM.DD"` - -3. **Build properties**: `Directory.Build.props` - - - Reads `CopilotHereVersion` from the `VERSION` file automatically (no manual edit needed) - -4. **Build info**: `app/Infrastructure/BuildInfo.cs` - - - Line 13: `public const string BuildDate = "YYYY.MM.DD";` - -### Verification Checklist - -Before committing, verify all 5 locations have the EXACT SAME version: - -```bash -# Quick check - all should show the same version -cat VERSION -grep "Version: " copilot_here.sh -grep "Version: " copilot_here.ps1 -grep "COPILOT_HERE_VERSION=" copilot_here.sh -grep "CopilotHereVersion =" copilot_here.ps1 -grep "BuildDate = " app/Infrastructure/BuildInfo.cs -``` - -Use `scripts/bump-version.sh YYYY.MM.DD` to update all locations at once. - -### When to Update Version - -- Any modification to shell function code -- Adding new features or options -- Bug fixes in the scripts or CLI binary -- Changes to the CLI binary code -- **Any commit that affects functionality should increment the version** -- **When in doubt, increment the version** - -### Script File Synchronization - -**CRITICAL**: The standalone script files (`copilot_here.sh` and `copilot_here.ps1`) are the source of truth. - -- The README.md uses `curl` commands to download these files directly from the repository. -- Ensure both scripts are kept in sync regarding functionality and version numbers. -- Both scripts MUST have identical version numbers at all times. +See `docs/versioning.md` for the full flow. ## Technology Stack @@ -707,7 +652,6 @@ git commit --no-gpg-sign -m "feat: Add multi-image build pipeline" \ 2. Review documentation in `/docs` for requirements 3. Ensure changes align with project goals 4. Consider impact on both native binary and shell wrappers -5. Check if version numbers need updating ### Making Changes @@ -715,7 +659,6 @@ git commit --no-gpg-sign -m "feat: Add multi-image build pipeline" \ 2. Follow existing code patterns and conventions 3. Update relevant documentation if making structural changes 4. Test changes locally before committing -5. Update version numbers if changing functionality (see Script Versioning section) ### After Making Changes @@ -807,7 +750,6 @@ docker run --rm -it copilot_here:test copilot --version - Test affected functionality - Review file changes with `git diff` - Ensure documentation is updated -- Check version numbers are updated if functionality changed ### Edge Cases to Consider @@ -869,9 +811,8 @@ Each image variant gets multiple tags: 9. **Minor tasks update existing files** - Don't create duplicate task files 10. **Document major changes** - Create task files for significant work 11. **Test before committing** - Build binary and verify functionality -12. **Update versions** - Increment version numbers when changing functionality -13. **Stop containers before updating** - dev-build.sh will prompt to stop running containers -14. **Debug logging** - Use `COPILOT_HERE_DEBUG=1` to enable detailed logging +12. **Stop containers before updating** - dev-build.sh will prompt to stop running containers +13. **Debug logging** - Use `COPILOT_HERE_DEBUG=1` to enable detailed logging ### When to Update These Instructions diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 849e7fd..af52356 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -83,7 +83,7 @@ jobs: # Test shell function scripts can be sourced and call the native binary test-shell-functions: name: Shell Functions (${{ matrix.os }}) - needs: [test-cli] + needs: [test-cli, compute-version] runs-on: ${{ matrix.os }} strategy: fail-fast: false @@ -98,8 +98,17 @@ jobs: with: global-json-file: global.json + # Stamp the in-memory `0.0.0-dev` sentinel to the computed version so the + # update-check inside copilot_here.sh / .ps1 doesn't compare against a + # remote-newer release and prompt during the --version test. + - name: Stamp version into shell scripts + shell: bash + run: | + chmod +x scripts/stamp-version.sh + ./scripts/stamp-version.sh "${{ needs.compute-version.outputs.version }}" + - name: Build CLI binary - run: dotnet publish app/CopilotHere.csproj -c Release -o publish/shell-test --nologo + run: dotnet publish app/CopilotHere.csproj -c Release -p:CopilotHereVersion=${{ needs.compute-version.outputs.version }} -o publish/shell-test --nologo - name: Test Bash script syntax if: runner.os != 'Windows' @@ -565,7 +574,11 @@ jobs: working-directory: tests/CopilotHere.IntegrationTests run: dotnet run --configuration Release - # Compute version from VERSION file + run number as revision + # Compute version: today's UTC date + iteration-of-the-day. + # On main pushes, iteration = (count of cli-v$DATE.* releases) + 1, matching + # the schema documented in docs/versioning.md. PR / scheduled / branch + # workflow_dispatch builds stamp `$DATE.0` so the version-format tests pass + # without consuming a release slot. compute-version: name: Compute Version runs-on: ubuntu-24.04 @@ -573,18 +586,27 @@ jobs: version: ${{ steps.compute.outputs.version }} short_sha: ${{ steps.compute.outputs.short_sha }} steps: - - name: Checkout repository - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - - - name: Compute version with revision + - name: Compute version id: compute + env: + GH_TOKEN: ${{ github.token }} + GH_REPO: ${{ github.repository }} run: | - BASE_VERSION=$(cat VERSION | tr -d '[:space:]') - VERSION="${BASE_VERSION}.${{ github.run_number }}" - echo "version=$VERSION" >> $GITHUB_OUTPUT + DATE=$(date -u +%Y.%m.%d) SHORT_SHA="${GITHUB_SHA:0:7}" echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT - echo "Computed version: $VERSION (run_number=${{ github.run_number }})" + + if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then + COUNT=$(gh release list --limit 200 --json tagName \ + --jq "[.[] | select(.tagName | startswith(\"cli-v${DATE}.\"))] | length") + N=$((COUNT + 1)) + VERSION="${DATE}.${N}" + echo "Release version: ${VERSION} (release #${N} for ${DATE})" + else + VERSION="${DATE}.0" + echo "Dev-stamp version: ${VERSION} (event=${{ github.event_name }}, ref=${{ github.ref }})" + fi + echo "version=${VERSION}" >> $GITHUB_OUTPUT # Publish .NET Tool to NuGet publish-nuget: @@ -741,6 +763,15 @@ jobs: with: path: artifacts + # Stamp the source-of-truth scripts before copying them into release/. + # Earlier this ran *after* the copy and silently left the released + # copilot_here.sh / .ps1 at whatever the sentinel value was, breaking + # users' auto-update check. + - name: Stamp version into shell scripts + run: | + chmod +x scripts/stamp-version.sh + ./scripts/stamp-version.sh "${{ needs.compute-version.outputs.version }}" + - name: Prepare release assets run: | mkdir -p release @@ -750,7 +781,7 @@ jobs: else binary="copilot_here" fi - + # Create archive cd artifacts/copilot_here-$rid if [[ "$rid" == win-* ]]; then @@ -770,11 +801,6 @@ jobs: ls -la release/ - - name: Stamp version into release shell scripts - run: | - chmod +x scripts/stamp-version.sh - ./scripts/stamp-version.sh "${{ needs.compute-version.outputs.version }}" - - name: Create versioned release uses: softprops/action-gh-release@a06a81a03ee405af7f2048a818ed3f03bbf83c7b # v2 with: diff --git a/Directory.Build.props b/Directory.Build.props index 3e32ac2..16bb6e7 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,11 @@ - - $([System.IO.File]::ReadAllText('$(MSBuildThisFileDirectory)VERSION').Trim()) + + $([System.DateTime]::UtcNow.ToString('yyyy.MM.dd')).0 diff --git a/VERSION b/VERSION deleted file mode 100644 index b4cbaab..0000000 --- a/VERSION +++ /dev/null @@ -1 +0,0 @@ -2026.05.13 diff --git a/app/Infrastructure/BuildInfo.cs b/app/Infrastructure/BuildInfo.cs index 3e842c3..eab9729 100644 --- a/app/Infrastructure/BuildInfo.cs +++ b/app/Infrastructure/BuildInfo.cs @@ -1,14 +1,43 @@ +using System.Reflection; + namespace CopilotHere.Infrastructure; -/// -/// Provides build-time information for the application. -/// The BuildDate is stamped during CI/CD from the VERSION file via scripts/stamp-version.sh. -/// public static class BuildInfo { - /// - /// The build date in yyyy.MM.dd or yyyy.MM.dd.N format. - /// This is replaced during build via MSBuild property. - /// - public const string BuildDate = "2026.05.13"; + public static string BuildDate { get; } = ComputeBuildDate(); + + private static string ComputeBuildDate() + { + var informational = typeof(BuildInfo).Assembly + .GetCustomAttribute() + ?.InformationalVersion; + + if (string.IsNullOrEmpty(informational)) + { + return "0.0.0-dev"; + } + + // Strip the git-sha suffix appended by CopilotHere.csproj + // (`$(Version).$(GitSha)`) + // so consumers see just the YYYY.MM.DD.N portion. Stop at the 5th segment, + // a '+' (SemVer build metadata), or a '-' (pre-release tag like `0.0.0-dev`). + var dots = 0; + for (var i = 0; i < informational.Length; i++) + { + var c = informational[i]; + if (c == '+' || c == '-') + { + return informational[..i]; + } + if (c == '.') + { + dots++; + if (dots == 4) + { + return informational[..i]; + } + } + } + return informational; + } } diff --git a/copilot_here.ps1 b/copilot_here.ps1 index 9bf3215..c129a64 100644 --- a/copilot_here.ps1 +++ b/copilot_here.ps1 @@ -1,5 +1,5 @@ # copilot_here PowerShell functions -# Version: 2026.05.13 +# Version: 0.0.0-dev # Repository: https://github.com/GordonBeeming/copilot_here # Set console output encoding to UTF-8 for Unicode character support @@ -23,7 +23,7 @@ $script:DefaultCopilotHereBin = Join-Path $script:DefaultCopilotHereBinDir $scri $script:CopilotHereBin = if ($env:COPILOT_HERE_BIN) { $env:COPILOT_HERE_BIN } else { $script:DefaultCopilotHereBin } $script:CopilotHereReleaseUrl = "https://github.com/GordonBeeming/copilot_here/releases/download/cli-latest" -$script:CopilotHereVersion = "2026.05.13" +$script:CopilotHereVersion = "0.0.0-dev" # Debug logging function function Write-CopilotDebug { diff --git a/copilot_here.sh b/copilot_here.sh index 17a47c7..dfb08cb 100755 --- a/copilot_here.sh +++ b/copilot_here.sh @@ -1,11 +1,11 @@ # copilot_here shell functions -# Version: 2026.05.13 +# Version: 0.0.0-dev # Repository: https://github.com/GordonBeeming/copilot_here # Configuration COPILOT_HERE_BIN="${COPILOT_HERE_BIN:-$HOME/.local/bin/copilot_here}" COPILOT_HERE_RELEASE_URL="https://github.com/GordonBeeming/copilot_here/releases/download/cli-latest" -COPILOT_HERE_VERSION="2026.05.13" +COPILOT_HERE_VERSION="0.0.0-dev" # Ensure user bin directory is on PATH (required for the native binary + shell integration checks) if [ -d "$HOME/.local/bin" ]; then diff --git a/docs/versioning.md b/docs/versioning.md index 611262d..3299187 100644 --- a/docs/versioning.md +++ b/docs/versioning.md @@ -1,52 +1,36 @@ # Versioning -## Single Source of Truth - -The `VERSION` file in the repository root contains the base version. All other version references are derived from it. - ## Format -`YYYY.MM.DD.N` where `.N` is the GitHub Actions run number (e.g., `2026.03.08.42`). - -- The **date portion** is manually maintained by developers. -- The **revision** is the GitHub Actions `run_number`, which auto-increments on every workflow run, ensuring every build has a unique version. - -## How It Works +`YYYY.MM.DD.N`. Date plus iteration. `N` is the Nth release of that day, so the first release on a given day is `.1`, the second is `.2`, and so on. -### VERSION file -Contains a single line with the base version (e.g., `2026.03.08`). +## Releasing a new version -### scripts/stamp-version.sh -Takes a version argument and stamps it into all locations that contain version strings: -- `copilot_here.sh` (comment + variable) -- `copilot_here.ps1` (comment + variable) -- `app/Infrastructure/BuildInfo.cs` (BuildDate constant) +Open a PR. Merge it to `main`. That's the whole process. -Files keep real versions in source (not placeholders) so local dev works without stamping. +There is no version to bump and no file to edit. When the merge lands, the `compute-version` job in `.github/workflows/publish.yml` reads today's UTC date, counts the existing `cli-v$DATE.*` releases, picks the next number, and runs `scripts/stamp-version.sh` to write it into the shell scripts before they get packaged. The .NET binary picks up the same version through `-p:CopilotHereVersion=...` on the `dotnet` command line. -WinGet manifests are not stamped — `wingetcreate update` reads them from `microsoft/winget-pkgs` and writes new ones there using the `--version` flag. Local copies under `packaging/winget/` are gitignored and used only for the first manual submission. +## Where the version lives -### scripts/bump-version.sh -Convenience wrapper: updates the VERSION file and runs stamp-version.sh. +| Path | Source value | Stamped to | +| --- | --- | --- | +| `copilot_here.sh` (2 lines) | `0.0.0-dev` | real version, in CI only | +| `copilot_here.ps1` (2 lines) | `0.0.0-dev` | real version, in CI only | +| `app/Infrastructure/BuildInfo.cs` | derives at runtime from the assembly | n/a | +| `Directory.Build.props` | falls back to `today.0` if `-p:CopilotHereVersion` isn't passed | overridden in CI | -```bash -./scripts/bump-version.sh 2026.03.08 -``` +The `0.0.0-dev` placeholder stays in source forever. Stamping happens on a fresh checkout in CI and is never committed back. -### Directory.Build.props -Reads the version from the VERSION file at build time. CI can override with `-p:CopilotHereVersion=X.Y.Z.N`. +## CI behaviour -### CI (publish.yml) -The `compute-version` job runs on every workflow trigger and: -1. Reads the base version from `VERSION` -2. Appends `github.run_number` as the revision -3. Outputs the full version: `YYYY.MM.DD.N` (e.g., `2026.03.08.42`) +- Push to `main`: `N` = `gh release list` count of `cli-v$DATE.*` + 1. Real release. +- Pull request, schedule, manual dispatch from a branch: `N` = `0`. The dev stamp keeps the version-format tests passing without consuming a release slot. -The `build-cli` job runs `stamp-version.sh` before building, so all artifacts contain the computed version. +## Local development -## Releasing a New Version +`dotnet run --project app -- --version` prints today's date with `.0` because `Directory.Build.props` falls back to that when no version override is passed. To preview what a real stamp would look like, pass it explicitly: -1. Update the date in `VERSION` -2. Run `./scripts/bump-version.sh YYYY.MM.DD` -3. Commit and push to `main` -4. CI auto-computes the revision and publishes +```bash +dotnet publish app/CopilotHere.csproj -c Release -p:CopilotHereVersion=2099.01.02.7 -o /tmp/preview +./scripts/stamp-version.sh 2099.01.02.7 # stamps the shell scripts; revert with `git restore` +``` diff --git a/scripts/bump-version.sh b/scripts/bump-version.sh deleted file mode 100755 index 2900ab1..0000000 --- a/scripts/bump-version.sh +++ /dev/null @@ -1,17 +0,0 @@ -#!/usr/bin/env bash -# bump-version.sh — Update VERSION file and stamp all files. -# Usage: ./scripts/bump-version.sh -# Example: ./scripts/bump-version.sh 2026.03.08 - -set -euo pipefail - -if [ $# -ne 1 ]; then - echo "Usage: $0 " >&2 - echo "Example: $0 2026.03.08" >&2 - exit 1 -fi - -SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" - -echo "Bumping version to: $1" -"$SCRIPT_DIR/stamp-version.sh" "$1" diff --git a/scripts/stamp-version.sh b/scripts/stamp-version.sh index 9b1b262..ecbfce9 100755 --- a/scripts/stamp-version.sh +++ b/scripts/stamp-version.sh @@ -1,26 +1,35 @@ #!/usr/bin/env bash -# stamp-version.sh — Stamps a version string into all files that contain it. +# stamp-version.sh — Stamps a version string into the shell scripts so users +# who download the released `copilot_here.sh` / `copilot_here.ps1` get a real +# version their auto-update check can compare against. +# +# Source files carry `0.0.0-dev` placeholders; CI calls this script with the +# version produced by the `compute-version` job before testing and packaging. +# The .NET binary's version is set via -p:CopilotHereVersion=... on the dotnet +# invocation — it doesn't need stamping into source. +# # Usage: ./scripts/stamp-version.sh -# Example: ./scripts/stamp-version.sh 2026.03.08 +# Example: ./scripts/stamp-version.sh 2026.05.21.1 set -euo pipefail if [ $# -ne 1 ]; then echo "Usage: $0 " >&2 - echo "Example: $0 2026.03.08" >&2 + echo "Example: $0 2026.05.21.1" >&2 exit 1 fi NEW_VERSION="$1" -# Validate version format: YYYY.MM.DD or YYYY.MM.DD.N if ! echo "$NEW_VERSION" | grep -qE '^[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?$'; then echo "Error: Version must be in YYYY.MM.DD or YYYY.MM.DD.N format" >&2 exit 1 fi REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" -VERSION_REGEX='[0-9]{4}\.[0-9]{2}\.[0-9]{2}(\.[0-9]+)?' +# Matches either the dev sentinel or any previously-stamped real version. +# Flat alternation (no nested groups) keeps BSD sed -E happy on macOS. +VERSION_REGEX='(0\.0\.0-dev|[0-9]{4}\.[0-9]{2}\.[0-9]{2}\.[0-9]+|[0-9]{4}\.[0-9]{2}\.[0-9]{2})' stamp_file() { local file="$1" @@ -32,12 +41,13 @@ stamp_file() { return fi + # Use `~` as the sed delimiter: `|` is the ERE alternation operator and + # `#` appears in the script comments we're matching, so both would terminate + # the s/// expression mid-pattern. `~` shows up nowhere in our regexes. if sed --version >/dev/null 2>&1; then - # GNU sed - sed -i -E "s|${pattern}|${replacement}|g" "$file" + sed -i -E "s~${pattern}~${replacement}~g" "$file" else - # BSD sed (macOS) - sed -i '' -E "s|${pattern}|${replacement}|g" "$file" + sed -i '' -E "s~${pattern}~${replacement}~g" "$file" fi echo " OK $file" } @@ -45,11 +55,6 @@ stamp_file() { echo "Stamping version: $NEW_VERSION" echo "" -# VERSION file -echo "$NEW_VERSION" > "$REPO_ROOT/VERSION" -echo " OK VERSION" - -# copilot_here.sh — line 2 (comment) and line 8 (variable) stamp_file "$REPO_ROOT/copilot_here.sh" \ "^(# Version: )${VERSION_REGEX}" \ "\1${NEW_VERSION}" @@ -57,7 +62,6 @@ stamp_file "$REPO_ROOT/copilot_here.sh" \ "^(COPILOT_HERE_VERSION=\")${VERSION_REGEX}(\")" \ "\1${NEW_VERSION}\3" -# copilot_here.ps1 — line 2 (comment) and line 26 (variable) stamp_file "$REPO_ROOT/copilot_here.ps1" \ "^(# Version: )${VERSION_REGEX}" \ "\1${NEW_VERSION}" @@ -65,10 +69,5 @@ stamp_file "$REPO_ROOT/copilot_here.ps1" \ "^(\\\$script:CopilotHereVersion = \")${VERSION_REGEX}(\")" \ "\1${NEW_VERSION}\3" -# app/Infrastructure/BuildInfo.cs -stamp_file "$REPO_ROOT/app/Infrastructure/BuildInfo.cs" \ - "(BuildDate = \")${VERSION_REGEX}(\")" \ - "\1${NEW_VERSION}\3" - echo "" echo "Done." From de07ee8b7cc09cc3dd01e99954122d503f9d6f67 Mon Sep 17 00:00:00 2001 From: Gordon Beeming Date: Thu, 21 May 2026 21:18:59 +1000 Subject: [PATCH 2/2] fix(versioning): preserve -dev sentinel, treat dispatch-on-main as release, anchor dev stamps to latest released N MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Addresses 4 review threads on PR #115: - BuildInfo.cs (gemini + copilot): the strip loop was cutting at '-' so an InformationalVersion of `0.0.0-dev` collapsed to `0.0.0`, losing the sentinel that signals an unstamped build. Added an explicit StartsWith check for the sentinel and removed `-` from the strip set — pre-release SemVer tags now survive the strip pass intact. Production InformationalVersion uses `.sha` (no pre-release marker), so the 5-segment cutoff and `+` metadata strip still handle the real release case. - publish.yml compute-version gate (codex P1): a `workflow_dispatch` run triggered on `main` would publish (the gate downstream is just `event_name != schedule && ref == main`) but would stamp `${DATE}.0`, silently shipping a release version older than any `.N` already cut that day. Widened the `IS_RELEASE` gate to accept both `push` and `workflow_dispatch` when the ref is `main`. Manual re-runs of a release now correctly take the next iteration number. - publish.yml dev-stamp value (copilot): the previous `${DATE}.0` for non-release builds meant that on a day where `cli-v${DATE}.1` was already cut, PR builds would source a script stamped older than `cli-latest`, and the shell wrappers' startup update-check would print the "Update available" prompt mid-test. Dev-stamps now use `${DATE}.${COUNT}`, matching the latest already-released number for the day (or `.0` if no release has happened yet). PR scripts can never be "older than" the day's latest release. Co-authored-by: Claude Co-authored-by: GitButler --- .github/workflows/publish.yml | 31 ++++++++++++++++++++++--------- app/Infrastructure/BuildInfo.cs | 11 +++++++---- 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index af52356..b52b70c 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -575,10 +575,15 @@ jobs: run: dotnet run --configuration Release # Compute version: today's UTC date + iteration-of-the-day. - # On main pushes, iteration = (count of cli-v$DATE.* releases) + 1, matching - # the schema documented in docs/versioning.md. PR / scheduled / branch - # workflow_dispatch builds stamp `$DATE.0` so the version-format tests pass - # without consuming a release slot. + # + # `Release` mode (main + push OR workflow_dispatch) → iteration = + # (count of cli-v$DATE.* releases) + 1, matching the schema in + # docs/versioning.md. + # `Dev-stamp` mode (everything else) → iteration = COUNT (the latest + # already-released number for today). PR / scheduled / branch builds + # need to match-or-beat the cli-latest version so the shell wrappers' + # startup update-check doesn't fire during test-shell-functions; if no + # release exists yet today, COUNT is 0 and we stamp `$DATE.0`. compute-version: name: Compute Version runs-on: ubuntu-24.04 @@ -596,15 +601,23 @@ jobs: SHORT_SHA="${GITHUB_SHA:0:7}" echo "short_sha=$SHORT_SHA" >> $GITHUB_OUTPUT - if [[ "${{ github.event_name }}" == "push" && "${{ github.ref }}" == "refs/heads/main" ]]; then - COUNT=$(gh release list --limit 200 --json tagName \ - --jq "[.[] | select(.tagName | startswith(\"cli-v${DATE}.\"))] | length") + COUNT=$(gh release list --limit 200 --json tagName \ + --jq "[.[] | select(.tagName | startswith(\"cli-v${DATE}.\"))] | length") + + IS_RELEASE=false + if [[ "${{ github.ref }}" == "refs/heads/main" ]] && \ + { [[ "${{ github.event_name }}" == "push" ]] || \ + [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; }; then + IS_RELEASE=true + fi + + if [[ "$IS_RELEASE" == "true" ]]; then N=$((COUNT + 1)) VERSION="${DATE}.${N}" echo "Release version: ${VERSION} (release #${N} for ${DATE})" else - VERSION="${DATE}.0" - echo "Dev-stamp version: ${VERSION} (event=${{ github.event_name }}, ref=${{ github.ref }})" + VERSION="${DATE}.${COUNT}" + echo "Dev-stamp version: ${VERSION} (event=${{ github.event_name }}, ref=${{ github.ref }}, existing releases today=${COUNT})" fi echo "version=${VERSION}" >> $GITHUB_OUTPUT diff --git a/app/Infrastructure/BuildInfo.cs b/app/Infrastructure/BuildInfo.cs index eab9729..7f1d568 100644 --- a/app/Infrastructure/BuildInfo.cs +++ b/app/Infrastructure/BuildInfo.cs @@ -12,20 +12,23 @@ private static string ComputeBuildDate() .GetCustomAttribute() ?.InformationalVersion; - if (string.IsNullOrEmpty(informational)) + if (string.IsNullOrEmpty(informational) || + informational.StartsWith("0.0.0-dev", StringComparison.Ordinal)) { return "0.0.0-dev"; } // Strip the git-sha suffix appended by CopilotHere.csproj // (`$(Version).$(GitSha)`) - // so consumers see just the YYYY.MM.DD.N portion. Stop at the 5th segment, - // a '+' (SemVer build metadata), or a '-' (pre-release tag like `0.0.0-dev`). + // so consumers see just the YYYY.MM.DD.N portion. Stop at the 5th + // segment, or a '+' (SemVer build metadata). Pre-release tags ('-dev' + // etc.) are handled by the sentinel check above so we don't accidentally + // truncate them mid-string. var dots = 0; for (var i = 0; i < informational.Length; i++) { var c = informational[i]; - if (c == '+' || c == '-') + if (c == '+') { return informational[..i]; }