Skip to content

Commit 025586e

Browse files
steveyeggeclaude
andcommitted
feat(stale): add gt stale command for binary staleness check
Exposes CheckStaleBinary() via CLI for scripting. Supports --json for machine-readable output and --quiet for exit-code-only mode (0=stale, 1=fresh, 2=error). Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
1 parent b990094 commit 025586e

1 file changed

Lines changed: 122 additions & 0 deletions

File tree

internal/cmd/stale.go

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
package cmd
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"os"
7+
8+
"github.com/spf13/cobra"
9+
"github.com/steveyegge/gastown/internal/style"
10+
"github.com/steveyegge/gastown/internal/version"
11+
)
12+
13+
var staleJSON bool
14+
var staleQuiet bool
15+
16+
var staleCmd = &cobra.Command{
17+
Use: "stale",
18+
GroupID: GroupDiag,
19+
Short: "Check if the gt binary is stale",
20+
Long: `Check if the gt binary was built from an older commit than the current repo HEAD.
21+
22+
This command compares the commit hash embedded in the binary at build time
23+
with the current HEAD of the gastown repository.
24+
25+
Examples:
26+
gt stale # Human-readable output
27+
gt stale --json # Machine-readable JSON output
28+
gt stale --quiet # Exit code only (0=stale, 1=fresh)
29+
30+
Exit codes:
31+
0 - Binary is stale (needs rebuild)
32+
1 - Binary is fresh (up to date)
33+
2 - Error (could not determine staleness)`,
34+
RunE: runStale,
35+
}
36+
37+
func init() {
38+
staleCmd.Flags().BoolVar(&staleJSON, "json", false, "Output as JSON")
39+
staleCmd.Flags().BoolVarP(&staleQuiet, "quiet", "q", false, "Exit code only (0=stale, 1=fresh)")
40+
rootCmd.AddCommand(staleCmd)
41+
}
42+
43+
// StaleOutput represents the JSON output structure.
44+
type StaleOutput struct {
45+
Stale bool `json:"stale"`
46+
BinaryCommit string `json:"binary_commit"`
47+
RepoCommit string `json:"repo_commit"`
48+
CommitsBehind int `json:"commits_behind,omitempty"`
49+
Error string `json:"error,omitempty"`
50+
}
51+
52+
func runStale(cmd *cobra.Command, args []string) error {
53+
// Find the gastown repo
54+
repoRoot, err := version.GetRepoRoot()
55+
if err != nil {
56+
if staleQuiet {
57+
os.Exit(2)
58+
}
59+
if staleJSON {
60+
return outputStaleJSON(StaleOutput{Error: err.Error()})
61+
}
62+
return fmt.Errorf("cannot find gastown repo: %w", err)
63+
}
64+
65+
// Check staleness
66+
info := version.CheckStaleBinary(repoRoot)
67+
68+
// Handle errors
69+
if info.Error != nil {
70+
if staleQuiet {
71+
os.Exit(2)
72+
}
73+
if staleJSON {
74+
return outputStaleJSON(StaleOutput{Error: info.Error.Error()})
75+
}
76+
return fmt.Errorf("staleness check failed: %w", info.Error)
77+
}
78+
79+
// Quiet mode: just exit with appropriate code
80+
if staleQuiet {
81+
if info.IsStale {
82+
os.Exit(0)
83+
}
84+
os.Exit(1)
85+
}
86+
87+
// Build output
88+
output := StaleOutput{
89+
Stale: info.IsStale,
90+
BinaryCommit: info.BinaryCommit,
91+
RepoCommit: info.RepoCommit,
92+
CommitsBehind: info.CommitsBehind,
93+
}
94+
95+
if staleJSON {
96+
return outputStaleJSON(output)
97+
}
98+
99+
return outputStaleText(output)
100+
}
101+
102+
func outputStaleJSON(output StaleOutput) error {
103+
enc := json.NewEncoder(os.Stdout)
104+
enc.SetIndent("", " ")
105+
return enc.Encode(output)
106+
}
107+
108+
func outputStaleText(output StaleOutput) error {
109+
if output.Stale {
110+
fmt.Printf("%s Binary is stale\n", style.Warning.Render("⚠"))
111+
fmt.Printf(" Binary: %s\n", version.ShortCommit(output.BinaryCommit))
112+
fmt.Printf(" Repo: %s\n", version.ShortCommit(output.RepoCommit))
113+
if output.CommitsBehind > 0 {
114+
fmt.Printf(" %s\n", style.Dim.Render(fmt.Sprintf("(%d commits behind)", output.CommitsBehind)))
115+
}
116+
fmt.Printf("\n Run 'go install ./cmd/gt' to rebuild\n")
117+
} else {
118+
fmt.Printf("%s Binary is fresh\n", style.Success.Render("✓"))
119+
fmt.Printf(" Commit: %s\n", version.ShortCommit(output.BinaryCommit))
120+
}
121+
return nil
122+
}

0 commit comments

Comments
 (0)