diff --git a/AGENTS.md b/AGENTS.md index 43955ed..5b84e98 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -218,3 +218,26 @@ When behavior changes: If a product decision would materially affect persistence compatibility, GitHub writes, AI data exposure, destructive behavior, or default keybindings, ask the repository owner rather than guessing. + +## Semantic versioning + +The application version lives in `version.go` and must use Semantic Versioning +in `MAJOR.MINOR.PATCH` form. + +- Keep the major version at `0` until the repository owner explicitly requests + a `1.0.0` or later release. +- Increment `MINOR` and reset `PATCH` to zero for a completed new + backward-compatible feature. During `0.x`, also use a minor increment for an + intentional breaking change and document that break. +- Increment `PATCH` for a completed backward-compatible bug fix, refinement, + or other non-feature change. +- Apply one version increment per complete logical change, not per user prompt. + Follow-up questions and refinements that finish the same feature share that + feature's single version increment. +- Before incrementing, determine the logical change boundary from the + conversation and current work. When the boundary is unclear and Jujutsu is + available, inspect the current change and its parent read-only; an empty + described parent may identify the feature being developed. Never mutate + Jujutsu history merely to determine a version. +- If the current logical change already contains the appropriate version + increment, do not increment it again for another prompt in that same change. diff --git a/README.md b/README.md index 424d79d..b56e762 100644 --- a/README.md +++ b/README.md @@ -134,6 +134,7 @@ Adjust polling or inspect all command-line options: ```sh diple --poll 15s +diple --version diple --help ``` diff --git a/cli.go b/cli.go index 640570e..a362ef9 100644 --- a/cli.go +++ b/cli.go @@ -8,6 +8,17 @@ import ( var completionShells = []string{"bash", "zsh", "fish"} +func handleVersionCommand(args []string, output io.Writer) (bool, error) { + if len(args) == 0 || args[0] != "--version" { + return false, nil + } + if len(args) != 1 { + return true, fmt.Errorf("usage: diple --version") + } + _, err := fmt.Fprintf(output, "diple %s\n", dipleVersion) + return true, err +} + func handleCompletionCommand(args []string, output io.Writer) (bool, error) { if len(args) == 0 || args[0] != "completion" { return false, nil @@ -55,6 +66,7 @@ func writeCLIHelp(output io.Writer, defaults Config, configPath string) { Usage: diple [options] + diple --version diple completion diple help @@ -92,6 +104,7 @@ Local state: Other: -h, --help Show this help and exit. + --version Show the application version and exit. Boolean options accept explicit values, for example --cache=false. Command-line options override TOML settings. GH_REPO is used only when @@ -158,7 +171,7 @@ _diple_completion() { ;; esac - local options="--repo --all --limit --poll --endpoint --theme --dashboard-mode --thread-list-width --fold-resolved --compact-reviews --path-scroll --path-scroll-interval --editor-mode --config --cache --cache-max-age --cache-dir --help -h" + local options="--repo --all --limit --poll --endpoint --theme --dashboard-mode --thread-list-width --fold-resolved --compact-reviews --path-scroll --path-scroll-interval --editor-mode --config --cache --cache-max-age --cache-dir --version --help -h" COMPREPLY=($(compgen -W "${options}" -- "${current}")) } complete -F _diple_completion diple @@ -202,6 +215,7 @@ _diple() { '--cache=[enable local read cache]:boolean:(true false)' \ '--cache-max-age[maximum offline cache age]:duration:' \ '--cache-dir[local read-cache directory]:directory:_directories' \ + '--version[show application version]' \ '(-h --help)'{-h,--help}'[show help]' } @@ -234,5 +248,6 @@ complete -c diple -l config -r -F -d 'TOML configuration file' complete -c diple -l cache -d 'Enable local read cache' complete -c diple -l cache-max-age -x -d 'Maximum offline cache age' complete -c diple -l cache-dir -r -a '(__fish_complete_directories)' -d 'Local read-cache directory' +complete -c diple -l version -d 'Show application version' complete -c diple -s h -l help -d 'Show help' ` diff --git a/cli_test.go b/cli_test.go index cee0dd5..0f954fa 100644 --- a/cli_test.go +++ b/cli_test.go @@ -2,10 +2,34 @@ package main import ( "bytes" + "regexp" "strings" "testing" ) +func TestVersionCommandPrintsSemanticVersion(t *testing.T) { + var output bytes.Buffer + handled, err := handleVersionCommand([]string{"--version"}, &output) + if err != nil { + t.Fatal(err) + } + if !handled || output.String() != "diple "+dipleVersion+"\n" { + t.Fatalf("version output = %q, handled=%t", output.String(), handled) + } + if !regexp.MustCompile(`^0\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)$`).MatchString(dipleVersion) { + t.Fatalf("version %q is not a pre-1.0 semantic version", dipleVersion) + } +} + +func TestVersionCommandRejectsAdditionalArguments(t *testing.T) { + handled, err := handleVersionCommand( + []string{"--version", "--help"}, &bytes.Buffer{}, + ) + if !handled || err == nil || !strings.Contains(err.Error(), "usage: diple --version") { + t.Fatalf("handled=%t error=%v", handled, err) + } +} + func TestCompletionCommandGeneratesSupportedShells(t *testing.T) { for _, shell := range completionShells { t.Run(shell, func(t *testing.T) { @@ -65,6 +89,7 @@ func TestCLIHelpIsGroupedAndActionable(t *testing.T) { "Appearance and navigation:", "Local state:", "diple completion ", + "--version", "--repo OWNER/REPOSITORY", "--cache=false", "gh auth login", diff --git a/main.go b/main.go index 9cf46c3..fea7e86 100644 --- a/main.go +++ b/main.go @@ -11,6 +11,12 @@ import ( ) func main() { + if handled, err := handleVersionCommand(os.Args[1:], os.Stdout); handled { + if err != nil { + exitf("%v", err) + } + return + } if handled, err := handleCompletionCommand(os.Args[1:], os.Stdout); handled { if err != nil { exitf("%v", err) diff --git a/version.go b/version.go new file mode 100644 index 0000000..4a7e09e --- /dev/null +++ b/version.go @@ -0,0 +1,3 @@ +package main + +const dipleVersion = "0.1.0"