From 314c4c65f6d6e5426ce1ed7c68e4f2a5d0cb81ec Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 27 May 2026 12:36:31 +0100 Subject: [PATCH 1/2] Enrich checkpoint cli_version with tag and dirty state Pivot away from a separate Entire-Version commit trailer: the build identity already lives in metadata.json's cli_version on entire/checkpoints/v1, so enrich that field instead of duplicating it in the commit message. versioninfo.CheckpointVersion() now mirrors Go's pseudo-version scheme via runtime/debug.ReadBuildInfo(): release/nightly/mise builds keep their ldflags Version and gain a "+dirty" marker when vcs.modified was set at build time; plain `go build` binaries fall back to the embedded module pseudo-version (e.g. "v0.6.3-...-15d80761c74b+dirty"), which already carries the last known tag, the originating commit, and the dirty marker. Assisted-by: Claude Opus 4.7 Signed-off-by: Paulo Gomes Entire-Checkpoint: 9f2af0703975 --- cmd/entire/cli/checkpoint/checkpoint_test.go | 17 ++-- cmd/entire/cli/checkpoint/committed.go | 4 +- cmd/entire/cli/versioninfo/versioninfo.go | 68 +++++++++++++++- .../cli/versioninfo/versioninfo_test.go | 78 +++++++++++++++++++ 4 files changed, 158 insertions(+), 9 deletions(-) create mode 100644 cmd/entire/cli/versioninfo/versioninfo_test.go diff --git a/cmd/entire/cli/checkpoint/checkpoint_test.go b/cmd/entire/cli/checkpoint/checkpoint_test.go index 3cfaa455a..4d9986d3d 100644 --- a/cmd/entire/cli/checkpoint/checkpoint_test.go +++ b/cmd/entire/cli/checkpoint/checkpoint_test.go @@ -221,6 +221,11 @@ func TestWriteCommitted_AgentField(t *testing.T) { t.Errorf("commit message should contain %s trailer with value %q, got:\n%s", trailers.AgentTrailerKey, agentType, commit.Message) } + + // metadata.json records the enriched build identity in cli_version. + if want := versioninfo.CheckpointVersion(); summary.CLIVersion != want { + t.Errorf("summary.CLIVersion = %q, want %q", summary.CLIVersion, want) + } } // readLatestSessionMetadata reads the session-specific metadata from the latest session subdirectory. @@ -3447,8 +3452,8 @@ func TestCopyMetadataDir_RedactsSecrets(t *testing.T) { } } -// TestWriteCommitted_CLIVersionField verifies that versioninfo.Version is written -// to both the root CheckpointSummary and session-level CommittedMetadata. +// TestWriteCommitted_CLIVersionField verifies that versioninfo.CheckpointVersion is +// written to both the root CheckpointSummary and session-level CommittedMetadata. func TestWriteCommitted_CLIVersionField(t *testing.T) { t.Parallel() @@ -3532,8 +3537,8 @@ func TestWriteCommitted_CLIVersionField(t *testing.T) { t.Fatalf("failed to parse root metadata.json: %v", err) } - if summary.CLIVersion != versioninfo.Version { - t.Errorf("CheckpointSummary.CLIVersion = %q, want %q", summary.CLIVersion, versioninfo.Version) + if want := versioninfo.CheckpointVersion(); summary.CLIVersion != want { + t.Errorf("CheckpointSummary.CLIVersion = %q, want %q", summary.CLIVersion, want) } // Verify session-level metadata.json (CommittedMetadata) has CLIVersion @@ -3557,8 +3562,8 @@ func TestWriteCommitted_CLIVersionField(t *testing.T) { t.Fatalf("failed to parse session metadata.json: %v", err) } - if sessionMetadata.CLIVersion != versioninfo.Version { - t.Errorf("CommittedMetadata.CLIVersion = %q, want %q", sessionMetadata.CLIVersion, versioninfo.Version) + if want := versioninfo.CheckpointVersion(); sessionMetadata.CLIVersion != want { + t.Errorf("CommittedMetadata.CLIVersion = %q, want %q", sessionMetadata.CLIVersion, want) } } diff --git a/cmd/entire/cli/checkpoint/committed.go b/cmd/entire/cli/checkpoint/committed.go index 8467b2fdb..57f37f83d 100644 --- a/cmd/entire/cli/checkpoint/committed.go +++ b/cmd/entire/cli/checkpoint/committed.go @@ -454,7 +454,7 @@ func (s *GitStore) writeSessionToSubdirectory(ctx context.Context, opts WriteCom InitialAttribution: opts.InitialAttribution, PromptAttributions: opts.PromptAttributionsJSON, Summary: redactSummary(opts.Summary), - CLIVersion: versioninfo.Version, + CLIVersion: versioninfo.CheckpointVersion(), Kind: opts.Kind, ReviewSkills: opts.ReviewSkills, ReviewPrompt: opts.ReviewPrompt, @@ -503,7 +503,7 @@ func (s *GitStore) writeCheckpointSummary(opts WriteCommittedOptions, basePath s summary := CheckpointSummary{ CheckpointID: opts.CheckpointID, - CLIVersion: versioninfo.Version, + CLIVersion: versioninfo.CheckpointVersion(), Strategy: opts.Strategy, Branch: opts.Branch, CheckpointsCount: checkpointsCount, diff --git a/cmd/entire/cli/versioninfo/versioninfo.go b/cmd/entire/cli/versioninfo/versioninfo.go index 250f701af..218b362ab 100644 --- a/cmd/entire/cli/versioninfo/versioninfo.go +++ b/cmd/entire/cli/versioninfo/versioninfo.go @@ -1,7 +1,73 @@ package versioninfo -// Version and Commit are set at build time via ldflags. +import ( + "runtime/debug" + "strings" +) + +// Version and Commit are set at build time via ldflags for release, nightly, +// and `mise run build` binaries. Plain `go build`/`go install` binaries leave +// these at their defaults and rely on the VCS metadata the Go toolchain embeds. var ( Version = "dev" Commit = "unknown" ) + +// dirtySuffix marks a build produced from a modified working tree. It matches +// the suffix the Go toolchain appends to a module pseudo-version. +const dirtySuffix = "+dirty" + +// CheckpointVersion returns the build identity recorded in checkpoint metadata +// (the cli_version field on entire/checkpoints/v1). It mirrors Go's +// pseudo-version scheme so a checkpoint can be traced to the last known tag, +// the originating commit, and whether the working tree was dirty at build time. +// +// Release, nightly, and mise builds stamp Version via ldflags, so that value is +// authoritative; only a "+dirty" marker is added when the build tree was +// modified. Plain `go build` binaries leave Version at "dev"; for those we fall +// back to the module pseudo-version the Go toolchain embeds (e.g. +// "v0.6.3-...-15d80761c74b+dirty"), which already carries the tag, commit, and +// dirty marker. +func CheckpointVersion() string { + return describe(Version, readBuildInfo()) +} + +// buildInfo is the subset of debug.BuildInfo that describe needs, extracted so +// the formatting logic can be exercised in tests without a real build. +type buildInfo struct { + // pseudoVersion is debug.BuildInfo.Main.Version. It is empty or "(devel)" + // when no module pseudo-version is available (e.g. test binaries). + pseudoVersion string + // modified reports whether vcs.modified was "true" at build time. + modified bool +} + +func readBuildInfo() buildInfo { + bi, ok := debug.ReadBuildInfo() + if !ok { + return buildInfo{} + } + info := buildInfo{pseudoVersion: bi.Main.Version} + for _, s := range bi.Settings { + if s.Key == "vcs.modified" { + info.modified = s.Value == "true" + } + } + return info +} + +func describe(version string, bi buildInfo) string { + // Without an ldflags stamp, defer to the Go-embedded pseudo-version: it + // already encodes the last tag, the commit, and the +dirty marker. + if version == "dev" && hasPseudoVersion(bi.pseudoVersion) { + return bi.pseudoVersion + } + if bi.modified && !strings.HasSuffix(version, dirtySuffix) { + return version + dirtySuffix + } + return version +} + +func hasPseudoVersion(v string) bool { + return v != "" && v != "(devel)" +} diff --git a/cmd/entire/cli/versioninfo/versioninfo_test.go b/cmd/entire/cli/versioninfo/versioninfo_test.go new file mode 100644 index 000000000..c8dd70748 --- /dev/null +++ b/cmd/entire/cli/versioninfo/versioninfo_test.go @@ -0,0 +1,78 @@ +package versioninfo + +import "testing" + +func TestDescribe(t *testing.T) { + t.Parallel() + + tests := []struct { + name string + version string + info buildInfo + want string + }{ + { + name: "release passes through clean", + version: "v0.6.3", + info: buildInfo{pseudoVersion: "v0.6.3", modified: false}, + want: "v0.6.3", + }, + { + name: "nightly passes through clean", + version: "v0.6.3-nightly.202605270736.c94e9573", + info: buildInfo{pseudoVersion: "v0.6.3-...", modified: false}, + want: "v0.6.3-nightly.202605270736.c94e9573", + }, + { + name: "ldflags version gains dirty marker when build tree modified", + version: "v0.6.3-nightly.202605270736.c94e9573-dev-15d80761", + info: buildInfo{pseudoVersion: "v0.6.3-...+dirty", modified: true}, + want: "v0.6.3-nightly.202605270736.c94e9573-dev-15d80761+dirty", + }, + { + name: "dirty marker not duplicated", + version: "v0.6.3+dirty", + info: buildInfo{modified: true}, + want: "v0.6.3+dirty", + }, + { + name: "dev falls back to embedded pseudo-version", + version: "dev", + info: buildInfo{pseudoVersion: "v0.6.3-0.20260527133156-15d80761c74b", modified: false}, + want: "v0.6.3-0.20260527133156-15d80761c74b", + }, + { + name: "dev falls back to dirty pseudo-version verbatim", + version: "dev", + info: buildInfo{pseudoVersion: "v0.6.3-0.20260527133156-15d80761c74b+dirty", modified: true}, + want: "v0.6.3-0.20260527133156-15d80761c74b+dirty", + }, + { + name: "dev with no pseudo-version stays bare when clean", + version: "dev", + info: buildInfo{pseudoVersion: "(devel)", modified: false}, + want: "dev", + }, + { + name: "dev with no pseudo-version gains dirty marker", + version: "dev", + info: buildInfo{pseudoVersion: "(devel)", modified: true}, + want: "dev+dirty", + }, + { + name: "dev with no build info stays bare", + version: "dev", + info: buildInfo{}, + want: "dev", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + t.Parallel() + if got := describe(tt.version, tt.info); got != tt.want { + t.Errorf("describe(%q, %+v) = %q, want %q", tt.version, tt.info, got, tt.want) + } + }) + } +} From ca2c1cf2fd3c4d33fcf05c9626c626020b263941 Mon Sep 17 00:00:00 2001 From: Paulo Gomes Date: Wed, 27 May 2026 15:53:38 +0100 Subject: [PATCH 2/2] Fix test build regressions Entire-Checkpoint: a643cc5a26d4 --- cmd/entire/cli/fetch_no_config_pollution_test.go | 3 +-- cmd/entire/cli/versioninfo/versioninfo_test.go | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cmd/entire/cli/fetch_no_config_pollution_test.go b/cmd/entire/cli/fetch_no_config_pollution_test.go index 2639e9f18..359ef28b1 100644 --- a/cmd/entire/cli/fetch_no_config_pollution_test.go +++ b/cmd/entire/cli/fetch_no_config_pollution_test.go @@ -8,7 +8,6 @@ import ( "strings" "testing" - "github.com/entireio/cli/cmd/entire/cli/checkpoint" "github.com/entireio/cli/cmd/entire/cli/checkpoint/id" "github.com/entireio/cli/cmd/entire/cli/paths" "github.com/entireio/cli/cmd/entire/cli/testutil" @@ -124,7 +123,7 @@ func TestFetchV2MainTreeOnly_DoesNotCreateShallowRepository(t *testing.T) { if err != nil { t.Fatalf("failed to open producer repo: %v", err) } - writeV2CheckpointForExport(t, producerRepo, id.MustCheckpointID("121212121212"), checkpoint.WriteCommittedOptions{ + writeV2CheckpointForExport(t, producerRepo, id.MustCheckpointID("121212121212"), v2CheckpointFixtureOptions{ SessionID: "fetch-v2-shallow-guard", Transcript: redact.AlreadyRedacted([]byte(`{"type":"user","message":{"content":[{"type":"text","text":"hello"}]}}` + "\n")), }) diff --git a/cmd/entire/cli/versioninfo/versioninfo_test.go b/cmd/entire/cli/versioninfo/versioninfo_test.go index c8dd70748..fd49605a5 100644 --- a/cmd/entire/cli/versioninfo/versioninfo_test.go +++ b/cmd/entire/cli/versioninfo/versioninfo_test.go @@ -67,7 +67,8 @@ func TestDescribe(t *testing.T) { }, } - for _, tt := range tests { + for i := range tests { + tt := tests[i] t.Run(tt.name, func(t *testing.T) { t.Parallel() if got := describe(tt.version, tt.info); got != tt.want {