shelley: add version/tag to build via goreleaser ldflags

Philip Zeyliger and Claude created

Include Version and Tag in `shelley version` output, injected at build
time by GoReleaser. Local builds show "dev" for version.

Also fix false positive "modified=true" in UI build by filtering out
dist/ directory from git status check.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Change summary

.goreleaser.yml     |  2 ++
ui/scripts/build.js |  9 +++++++--
version/version.go  | 13 ++++++++++++-
3 files changed, 21 insertions(+), 3 deletions(-)

Detailed changes

.goreleaser.yml 🔗

@@ -18,6 +18,8 @@ builds:
       - arm64
     ldflags:
       - -s -w
+      - -X shelley.exe.dev/version.Version={{ .Version }}
+      - -X shelley.exe.dev/version.Tag={{ .Tag }}
 
 archives:
   - id: shelley

ui/scripts/build.js 🔗

@@ -81,8 +81,13 @@ async function build() {
     try {
       commit = execSync('git rev-parse HEAD', { encoding: 'utf8' }).trim();
       commitTime = execSync('git log -1 --format=%cI', { encoding: 'utf8' }).trim();
-      const status = execSync('git status --porcelain', { encoding: 'utf8' });
-      modified = status.length > 0;
+      // Check for modifications, excluding the dist/ directory (which we're currently building)
+      const status = execSync('git status --porcelain --ignore-submodules', { encoding: 'utf8' });
+      // Filter out dist/ changes since those are expected during build
+      const significantChanges = status.split('\n').filter(line =>
+        line.trim() && !line.includes('dist/')
+      );
+      modified = significantChanges.length > 0;
     } catch (e) {
       // Git not available or not a git repo
     }

version/version.go 🔗

@@ -8,8 +8,16 @@ import (
 	"shelley.exe.dev/ui"
 )
 
+// Version and Tag are set at build time via ldflags
+var (
+	Version = "dev"
+	Tag     = ""
+)
+
 // Info holds build information from runtime/debug.ReadBuildInfo
 type Info struct {
+	Version    string `json:"version,omitempty"`
+	Tag        string `json:"tag,omitempty"`
 	Commit     string `json:"commit,omitempty"`
 	CommitTime string `json:"commit_time,omitempty"`
 	Modified   bool   `json:"modified,omitempty"`
@@ -18,7 +26,10 @@ type Info struct {
 // GetInfo returns build information using runtime/debug.ReadBuildInfo,
 // falling back to the embedded build-info.json from the UI build.
 func GetInfo() Info {
-	var info Info
+	info := Info{
+		Version: Version,
+		Tag:     Tag,
+	}
 
 	buildInfo, ok := debug.ReadBuildInfo()
 	if ok {