summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorPaul Buetow <paul@buetow.org>2026-03-27 12:20:09 +0200
committerPaul Buetow <paul@buetow.org>2026-03-27 12:20:09 +0200
commit2ddb334fa671b9c425ca43c8c673c6b36c3ad0ab (patch)
treecfff7b2b8397ebf4fad9b81a91d54563ea66d208 /internal
parentc8c57f0d5821ae0542cb613c87e6ed2ea66e1e0a (diff)
release: v0.27.1v0.27.1
Diffstat (limited to 'internal')
-rw-r--r--internal/askcli/command_add.go2
-rw-r--r--internal/askcli/command_info.go14
-rw-r--r--internal/askcli/command_info_add_test.go56
-rw-r--r--internal/askcli/dispatch.go2
-rw-r--r--internal/askcli/formatter.go5
-rw-r--r--internal/askcli/formatter_test.go7
-rw-r--r--internal/askcli/render_task_list.go13
-rw-r--r--internal/askcli/render_task_list_test.go17
-rw-r--r--internal/askcli/taskexport.go16
-rw-r--r--internal/version.go2
10 files changed, 111 insertions, 23 deletions
diff --git a/internal/askcli/command_add.go b/internal/askcli/command_add.go
index c28650e..b94fe89 100644
--- a/internal/askcli/command_add.go
+++ b/internal/askcli/command_add.go
@@ -50,7 +50,7 @@ func (d *Dispatcher) handleAdd(ctx context.Context, args []string, stdout, stder
fmt.Fprintf(stderr, "error: failed to assign task alias: %v\n", err)
return 1, nil
}
- _, _ = io.WriteString(stdout, displayTaskAlias(uuid, aliases)+"\n")
+ _, _ = io.WriteString(stdout, FormatCreatedTask(displayTaskAlias(uuid, aliases)))
return 0, nil
}
diff --git a/internal/askcli/command_info.go b/internal/askcli/command_info.go
index 646ff8a..ba2e37d 100644
--- a/internal/askcli/command_info.go
+++ b/internal/askcli/command_info.go
@@ -15,8 +15,14 @@ func (d *Dispatcher) handleInfo(ctx context.Context, args []string, stdout, stde
writeInfoError(stderr, err)
return code, nil
}
+ allUUIDs := append([]string{tasks[0].UUID}, tasks[0].Depends...)
+ aliases, err := ensureTaskAliasesForUUIDs(allUUIDs)
+ if err != nil {
+ fmt.Fprintf(stderr, "error: failed to load task aliases: %v\n", err)
+ return 1, nil
+ }
if d.jsonOutput {
- data, err := json.Marshal(tasks)
+ data, err := json.Marshal(withTaskIDs(tasks, aliases))
if err != nil {
fmt.Fprintf(stderr, "error: failed to marshal JSON: %v\n", err)
return 1, nil
@@ -24,12 +30,6 @@ func (d *Dispatcher) handleInfo(ctx context.Context, args []string, stdout, stde
_, _ = stdout.Write(data)
_, _ = io.WriteString(stdout, "\n")
} else {
- allUUIDs := append([]string{tasks[0].UUID}, tasks[0].Depends...)
- aliases, err := ensureTaskAliasesForUUIDs(allUUIDs)
- if err != nil {
- fmt.Fprintf(stderr, "error: failed to load task aliases: %v\n", err)
- return 1, nil
- }
_, _ = io.WriteString(stdout, FormatTaskInfo(tasks[0], displayTaskAlias(tasks[0].UUID, aliases), aliases))
}
return 0, nil
diff --git a/internal/askcli/command_info_add_test.go b/internal/askcli/command_info_add_test.go
index 3dc83ed..74b2380 100644
--- a/internal/askcli/command_info_add_test.go
+++ b/internal/askcli/command_info_add_test.go
@@ -3,6 +3,7 @@ package askcli
import (
"bytes"
"context"
+ "encoding/json"
"io"
"path/filepath"
"strings"
@@ -136,6 +137,53 @@ func TestHandleInfo_AliasSelector(t *testing.T) {
}
}
+func TestHandleInfo_JSONIncludesAliasID(t *testing.T) {
+ dir := t.TempDir()
+ oldRoot := taskAliasCacheRoot
+ oldNow := nowTaskAliasCache
+ taskAliasCacheRoot = func() (string, error) { return filepath.Join(dir, "hexai"), nil }
+ nowTaskAliasCache = func() time.Time { return time.Date(2026, 3, 26, 12, 0, 0, 0, time.UTC) }
+ defer func() {
+ taskAliasCacheRoot = oldRoot
+ nowTaskAliasCache = oldNow
+ }()
+
+ writeTaskAliasCacheForTest(t, taskAliasCache{
+ NextID: 1,
+ Entries: []taskAliasCacheEntry{
+ {UUID: "test-uuid", Alias: "0", CreatedAt: nowTaskAliasCache()},
+ },
+ })
+
+ jsonData := `[{"uuid":"test-uuid","description":"Test task","status":"pending","priority":"H","tags":["cli"],"urgency":15.0,"depends":[]}]`
+ d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
+ if len(args) > 0 && strings.HasPrefix(args[0], "uuid:test-uuid") {
+ _, _ = io.WriteString(stdout, jsonData)
+ }
+ return 0, nil
+ }})
+
+ var stdout, stderr bytes.Buffer
+ code, _ := d.Dispatch(context.Background(), []string{"--json", "info", "0"}, nil, &stdout, &stderr)
+ if code != 0 {
+ t.Fatalf("info code = %d, want 0", code)
+ }
+
+ var parsed []map[string]any
+ if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), &parsed); err != nil {
+ t.Fatalf("failed to parse JSON output: %v", err)
+ }
+ if len(parsed) != 1 {
+ t.Fatalf("parsed len = %d, want 1", len(parsed))
+ }
+ if got := parsed[0]["id"]; got != "0" {
+ t.Fatalf("json id = %#v, want 0", got)
+ }
+ if got := parsed[0]["uuid"]; got != "test-uuid" {
+ t.Fatalf("json uuid = %#v, want test-uuid", got)
+ }
+}
+
func TestHandleInfo_NumericID(t *testing.T) {
d := NewDispatcher(&spyRunner{runFn: func(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) {
return 0, nil
@@ -231,8 +279,8 @@ func TestHandleAdd_Success(t *testing.T) {
if code != 0 {
t.Fatalf("add code = %d, want 0", code)
}
- if got := strings.TrimSpace(stdout.String()); got != "1" {
- t.Fatalf("stdout = %q, want alias 1", stdout.String())
+ if got := strings.TrimSpace(stdout.String()); got != "created task 1" {
+ t.Fatalf("stdout = %q, want created task 1", stdout.String())
}
cache := readTaskAliasCacheSnapshot(t)
entry := findTaskAliasEntry(t, cache, "abc-123-def")
@@ -406,8 +454,8 @@ func TestHandleAdd_WithDependencies(t *testing.T) {
if code != 0 {
t.Fatalf("add code = %d, want 0: stderr=%s", code, stderr.String())
}
- if got := strings.TrimSpace(stdout.String()); got != "2" {
- t.Fatalf("stdout = %q, want alias 2", stdout.String())
+ if got := strings.TrimSpace(stdout.String()); got != "created task 2" {
+ t.Fatalf("stdout = %q, want created task 2", stdout.String())
}
if len(capturedAddArgs) < 6 {
t.Fatalf("capturedAddArgs = %v, want add invocation with dependency modifier", capturedAddArgs)
diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go
index c2c7aab..d03c340 100644
--- a/internal/askcli/dispatch.go
+++ b/internal/askcli/dispatch.go
@@ -60,7 +60,7 @@ func (d *Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reade
func (d *Dispatcher) help(w io.Writer) (int, error) {
_, _ = io.WriteString(w, "ask - task management CLI\n")
_, _ = io.WriteString(w, "\nSubcommands:\n")
- _, _ = io.WriteString(w, " ask add [mods...] [depends:<id|uuid>,...] <description...> Create a new task and print its ID\n")
+ _, _ = io.WriteString(w, " ask add [mods...] [depends:<id|uuid>,...] <description...> Create a new task and print created task <id>\n")
_, _ = io.WriteString(w, " ask list [filters] List active tasks (default)\n")
_, _ = io.WriteString(w, " ask ready List READY tasks (not blocked)\n")
_, _ = io.WriteString(w, " ask all [filters] List all tasks including completed/deleted\n")
diff --git a/internal/askcli/formatter.go b/internal/askcli/formatter.go
index d4c1e27..734b4a5 100644
--- a/internal/askcli/formatter.go
+++ b/internal/askcli/formatter.go
@@ -168,6 +168,11 @@ func FormatSuccess(alias string) string {
return fmt.Sprintf("ok %s\n", alias)
}
+// FormatCreatedTask returns the success string written to stdout after ask add creates a task.
+func FormatCreatedTask(alias string) string {
+ return fmt.Sprintf("created task %s\n", alias)
+}
+
// FormatError formats error output using the optional task identifier when available.
func FormatError(err error, taskID string) string {
if taskID != "" {
diff --git a/internal/askcli/formatter_test.go b/internal/askcli/formatter_test.go
index 2921555..3a9239a 100644
--- a/internal/askcli/formatter_test.go
+++ b/internal/askcli/formatter_test.go
@@ -195,6 +195,13 @@ func TestFormatSuccess(t *testing.T) {
}
}
+func TestFormatCreatedTask(t *testing.T) {
+ output := FormatCreatedTask("sp")
+ if output != "created task sp\n" {
+ t.Fatalf("FormatCreatedTask = %q, want created task message", output)
+ }
+}
+
func TestFormatError(t *testing.T) {
err := &testError{msg: "something went wrong"}
output := FormatError(err, "0")
diff --git a/internal/askcli/render_task_list.go b/internal/askcli/render_task_list.go
index 33487e5..177bd81 100644
--- a/internal/askcli/render_task_list.go
+++ b/internal/askcli/render_task_list.go
@@ -9,8 +9,14 @@ import (
var taskListAliasLoader = ensureTaskAliases
func renderTaskList(tasks []TaskExport, stdout, stderr io.Writer, jsonOutput bool) (int, error) {
+ aliases, err := taskListAliasLoader(tasks)
+ if err != nil {
+ fmt.Fprintf(stderr, "error: failed to load task aliases: %v\n", err)
+ return 1, nil
+ }
+
if jsonOutput {
- data, err := json.Marshal(tasks)
+ data, err := json.Marshal(withTaskIDs(tasks, aliases))
if err != nil {
fmt.Fprintf(stderr, "error: failed to marshal JSON: %v\n", err)
return 1, nil
@@ -20,11 +26,6 @@ func renderTaskList(tasks []TaskExport, stdout, stderr io.Writer, jsonOutput boo
return 0, nil
}
- aliases, err := taskListAliasLoader(tasks)
- if err != nil {
- fmt.Fprintf(stderr, "error: failed to load task aliases: %v\n", err)
- return 1, nil
- }
_, _ = io.WriteString(stdout, FormatTaskListForWidth(tasks, aliases, detectTaskListTerminalWidth(stdout)))
return 0, nil
}
diff --git a/internal/askcli/render_task_list_test.go b/internal/askcli/render_task_list_test.go
index 623055e..ff47153 100644
--- a/internal/askcli/render_task_list_test.go
+++ b/internal/askcli/render_task_list_test.go
@@ -10,6 +10,13 @@ import (
)
func TestRenderTaskList_JSONOutput(t *testing.T) {
+ oldLoader := taskListAliasLoader
+ defer func() { taskListAliasLoader = oldLoader }()
+
+ taskListAliasLoader = func(tasks []TaskExport) (map[string]string, error) {
+ return map[string]string{"uuid-json": "sq"}, nil
+ }
+
tasks := []TaskExport{{
UUID: "uuid-json",
Description: "JSON task",
@@ -26,12 +33,16 @@ func TestRenderTaskList_JSONOutput(t *testing.T) {
if code != 0 {
t.Fatalf("renderTaskList code = %d, want 0", code)
}
- var parsed []TaskExport
+ var parsed []taskExportWithID
if err := json.Unmarshal(bytes.TrimSpace(stdout.Bytes()), &parsed); err != nil {
t.Fatalf("failed to parse JSON output: %v", err)
}
- if !reflect.DeepEqual(parsed, tasks) {
- t.Fatalf("rendered tasks = %#v, want %#v", parsed, tasks)
+ want := []taskExportWithID{{
+ ID: "sq",
+ TaskExport: tasks[0],
+ }}
+ if !reflect.DeepEqual(parsed, want) {
+ t.Fatalf("rendered tasks = %#v, want %#v", parsed, want)
}
if stderr.Len() != 0 {
t.Fatalf("unexpected stderr = %q", stderr.String())
diff --git a/internal/askcli/taskexport.go b/internal/askcli/taskexport.go
index d633d3c..be24117 100644
--- a/internal/askcli/taskexport.go
+++ b/internal/askcli/taskexport.go
@@ -34,3 +34,19 @@ func ParseTaskExport(r io.Reader) ([]TaskExport, error) {
}
return tasks, nil
}
+
+type taskExportWithID struct {
+ ID string `json:"id,omitempty"`
+ TaskExport
+}
+
+func withTaskIDs(tasks []TaskExport, aliases map[string]string) []taskExportWithID {
+ withIDs := make([]taskExportWithID, len(tasks))
+ for i := range withIDs {
+ withIDs[i] = taskExportWithID{
+ ID: displayTaskAlias(tasks[i].UUID, aliases),
+ TaskExport: tasks[i],
+ }
+ }
+ return withIDs
+}
diff --git a/internal/version.go b/internal/version.go
index 99fe0c9..5b94bc7 100644
--- a/internal/version.go
+++ b/internal/version.go
@@ -1,4 +1,4 @@
// Package internal provides the Hexai semantic version identifier used by CLI and LSP binaries.
package internal
-const Version = "0.27.0"
+const Version = "0.27.1"