From 7e7940db9a988a50679709021ad7cadb05d74c7e Mon Sep 17 00:00:00 2001 From: Eric Rakestraw Date: Wed, 7 Oct 2026 13:51:52 +0000 Subject: [PATCH] Add ReleaseKit version reporting --- docs/roadmap/implementation.md | 10 ++++- go.mod | 1 + go.sum | 2 + internal/cli/run.go | 48 +++++++++++++++++++++++ internal/cli/version_test.go | 72 ++++++++++++++++++++++++++++++++++ 5 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 internal/cli/version_test.go diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index dafc4b7..fdb3071 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -1,7 +1,7 @@ # Implementation Plan: ReleaseKit Adoption **Last updated:** 2026-10-07 -**Status:** Ready for implementation; no stages completed. +**Status:** Stage 1 complete; Stages 2–5 pending. **Audience:** Luna coding agent. ## Scope and Execution @@ -64,7 +64,7 @@ Gitea with delivery credentials, or wait for CI. Deployment observations belong to the first adopting release and its separate delivery status. No extra release gates or platform guarantees are introduced by these adoption checks. -## Stage 1 — Add the Dependency and Version Command +## Stage 1 — Add the Dependency and Version Command [x] **Primary files:** `go.mod`, `go.sum`, `internal/cli/run.go`, and focused tests in `internal/cli`. @@ -92,6 +92,12 @@ gates or platform guarantees are introduced by these adoption checks. module diff, and commit. Real release-version injection is verified in Stage 5; no test should hardcode an upstream private linker symbol. +**Completed 2026-10-07:** Added ReleaseKit v0.1.0 and the configuration-free +`audita version` command with root/help routing and usage-error handling. +Checks passed: `go test ./internal/cli ./cmd/audita`, +`go build -o ./bin/audita ./cmd/audita`, `./bin/audita version`, and +`git diff --check`. + ## Stage 2 — Introduce the Product Profile and Thin Local Wrapper **Primary files:** new `releasekit.json`, `scripts/release.sh`, and diff --git a/go.mod b/go.mod index e37181e..e81a71e 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,7 @@ go 1.25.5 require ( gitea.maximumdirect.net/eric/ConfigKit v0.5.0 + gitea.maximumdirect.net/eric/ReleaseKit v0.1.0 gitea.maximumdirect.net/eric/promptkit v0.12.0 gopkg.in/yaml.v3 v3.0.1 ) diff --git a/go.sum b/go.sum index cb485d6..2e596fd 100644 --- a/go.sum +++ b/go.sum @@ -1,5 +1,7 @@ gitea.maximumdirect.net/eric/ConfigKit v0.5.0 h1:2KwG10+kApWM3avOoYA+p768xcbHUp7tQ2rqizBNpyg= gitea.maximumdirect.net/eric/ConfigKit v0.5.0/go.mod h1:tYWpsrbLyu05r8+1MapWp5fN6ZrX9I8hU2xEPedYmYk= +gitea.maximumdirect.net/eric/ReleaseKit v0.1.0 h1:Ejk85+9Of+A8M7Mod91iB3KxrrQYgnJcz9hPA6fL1a4= +gitea.maximumdirect.net/eric/ReleaseKit v0.1.0/go.mod h1:KmH607Vc/RK/zKrCB96cWh0QZYvkbxBFvtqV+BkKaxs= gitea.maximumdirect.net/eric/promptkit v0.12.0 h1:oIFWEg6GPxtmX3Plqv0euP+FnK1LCk/t4xDy6oKCpP4= gitea.maximumdirect.net/eric/promptkit v0.12.0/go.mod h1:pFOOy5v1zH17stEi9rmHYmfbuLjnfVMcUhfmk+4VtJY= gitea.maximumdirect.net/eric/promptkit-backend-openrouter/v3 v3.20260930.0 h1:lzfCDbzJQRGzIlRPRprnAV9hVVL5zUQy80VMQ0kbYa0= diff --git a/internal/cli/run.go b/internal/cli/run.go index f89dc88..b532aeb 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -2,9 +2,11 @@ package cli import ( "context" + "flag" "fmt" "io" + "gitea.maximumdirect.net/eric/ReleaseKit/buildinfo" "gitea.maximumdirect.net/eric/audita/internal/core/schema" "gitea.maximumdirect.net/eric/audita/internal/framework/llm" ) @@ -42,6 +44,9 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int { case "config": writeConfigUsage(stdout) return 0 + case "version": + writeVersionUsage(stdout) + return 0 } } @@ -56,12 +61,47 @@ func Run(ctx context.Context, args []string, stdout, stderr io.Writer) int { if args[0] == "config" { return runConfig(ctx, args[1:], stdout, stderr) } + if args[0] == "version" { + return runVersion(args[1:], stdout, stderr) + } fmt.Fprintf(stderr, "audita: unknown command %q\n\n", args[0]) writeRootUsage(stderr) return 2 } +func runVersion(args []string, stdout, stderr io.Writer) int { + if len(args) == 1 && (args[0] == "-h" || args[0] == "--help") { + writeVersionUsage(stdout) + return 0 + } + for _, arg := range args { + if arg == "-h" || arg == "--help" { + fmt.Fprintln(stderr, "audita version: help flags cannot be combined with other arguments") + fmt.Fprintln(stderr) + writeVersionUsage(stderr) + return 2 + } + } + flags := flag.NewFlagSet("version", flag.ContinueOnError) + flags.SetOutput(stderr) + flags.Usage = func() { writeVersionUsage(stderr) } + if err := flags.Parse(args); err != nil { + if err == flag.ErrHelp { + writeVersionUsage(stdout) + return 0 + } + return 2 + } + if positional := flags.Args(); len(positional) > 0 { + fmt.Fprintf(stderr, "audita version: unexpected argument %q\n\n", positional[0]) + writeVersionUsage(stderr) + return 2 + } + fmt.Fprintf(stdout, "audita %s\n", buildinfo.Version()) + return 0 +} + func runConfig(ctx context.Context, args []string, stdout, stderr io.Writer) int { if len(args) == 0 || isHelpCommand(args) || args[0] == "--help" || args[0] == "-h" { writeConfigUsage(stdout) @@ -177,11 +217,19 @@ func writeRootUsage(w io.Writer) { fmt.Fprintln(w, "Commands:") fmt.Fprintln(w, " process Process a transcript JSON file") fmt.Fprintln(w, " config Validate and inspect config") + fmt.Fprintln(w, " version Print the Audita version") fmt.Fprintln(w) fmt.Fprintln(w, "Example:") fmt.Fprintln(w, " audita process transcript.json --glossary glossary.yaml --output corrected.json") } +func writeVersionUsage(w io.Writer) { + fmt.Fprintln(w, "Print the Audita version.") + fmt.Fprintln(w) + fmt.Fprintln(w, "Usage:") + fmt.Fprintln(w, " audita version") +} + func writeConfigUsage(w io.Writer) { fmt.Fprintln(w, "Validate and inspect Audita config.") fmt.Fprintln(w) diff --git a/internal/cli/version_test.go b/internal/cli/version_test.go new file mode 100644 index 0000000..c3ecf73 --- /dev/null +++ b/internal/cli/version_test.go @@ -0,0 +1,72 @@ +package cli + +import ( + "bytes" + "context" + "fmt" + "strings" + "testing" + + "gitea.maximumdirect.net/eric/ReleaseKit/buildinfo" +) + +func TestVersionCommandRunsWithoutApplicationSetup(t *testing.T) { + t.Chdir(t.TempDir()) + var stdout, stderr bytes.Buffer + if code := Run(context.Background(), []string{"version"}, &stdout, &stderr); code != 0 { + t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String()) + } + if got, want := stdout.String(), fmt.Sprintf("audita %s\n", buildinfo.Version()); got != want { + t.Fatalf("stdout=%q, want %q", got, want) + } + if got := stderr.String(); got != "" { + t.Fatalf("stderr=%q, want empty", got) + } +} + +func TestVersionCommandHelp(t *testing.T) { + for _, test := range []struct { + name string + args []string + want string + }{ + {name: "root help", args: []string{"--help"}, want: "version Print the Audita version"}, + {name: "help version", args: []string{"help", "version"}, want: "audita version"}, + {name: "short flag", args: []string{"version", "-h"}, want: "audita version"}, + {name: "long flag", args: []string{"version", "--help"}, want: "audita version"}, + } { + t.Run(test.name, func(t *testing.T) { + var stdout, stderr bytes.Buffer + if code := Run(context.Background(), test.args, &stdout, &stderr); code != 0 { + t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String()) + } + if !strings.Contains(stdout.String(), test.want) { + t.Fatalf("stdout=%q, want %q", stdout.String(), test.want) + } + if got := stderr.String(); got != "" { + t.Fatalf("stderr=%q, want empty", got) + } + }) + } +} + +func TestVersionCommandRejectsUnexpectedArguments(t *testing.T) { + for _, args := range [][]string{ + {"version", "extra"}, + {"version", "--unknown"}, + {"version", "--help", "extra"}, + } { + t.Run(strings.Join(args, " "), func(t *testing.T) { + var stdout, stderr bytes.Buffer + if code := Run(context.Background(), args, &stdout, &stderr); code != 2 { + t.Fatalf("exit=%d, stdout=%q, stderr=%q", code, stdout.String(), stderr.String()) + } + if got := stdout.String(); got != "" { + t.Fatalf("stdout=%q, want empty", got) + } + if got := stderr.String(); got == "" { + t.Fatal("expected usage error on stderr") + } + }) + } +}