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..b52b70c 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,16 @@ 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.
+ #
+ # `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
@@ -573,18 +591,35 @@ 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 }})"
+
+ 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}.${COUNT}"
+ echo "Dev-stamp version: ${VERSION} (event=${{ github.event_name }}, ref=${{ github.ref }}, existing releases today=${COUNT})"
+ fi
+ echo "version=${VERSION}" >> $GITHUB_OUTPUT
# Publish .NET Tool to NuGet
publish-nuget:
@@ -741,6 +776,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 +794,7 @@ jobs:
else
binary="copilot_here"
fi
-
+
# Create archive
cd artifacts/copilot_here-$rid
if [[ "$rid" == win-* ]]; then
@@ -770,11 +814,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..7f1d568 100644
--- a/app/Infrastructure/BuildInfo.cs
+++ b/app/Infrastructure/BuildInfo.cs
@@ -1,14 +1,46 @@
+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) ||
+ 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, 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 == '+')
+ {
+ 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."