From 69d3ec004b8de3b9f7cfeb34686b9c344c787db4 Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sat, 11 Apr 2026 22:20:57 +0300 Subject: Rename task CLI binary from do back to ask - Move cmd/do to cmd/ask; mage builds and installs ask; Fish completions to ask.fish - Update askcli help text, errors, executor default label, and Fish script (__ask_*) - Task alias cache subdirectory under XDG cache: hexai/ask/ - Rename integration test files and helpers; refresh README and docs - Rename plan-do-uuid-wrapper.md to plan-ask-uuid-wrapper.md Made-with: Cursor --- internal/askcli/command_add.go | 8 +- internal/askcli/command_delete.go | 2 +- internal/askcli/command_dep.go | 8 +- internal/askcli/command_fish.go | 4 +- internal/askcli/command_info_add_test.go | 2 +- internal/askcli/command_write.go | 16 ++-- internal/askcli/completion.go | 132 ++++++++++++++++++------------- internal/askcli/completion_test.go | 44 +++++------ internal/askcli/dispatch.go | 52 ++++++------ internal/askcli/dispatch_test.go | 18 ++--- internal/askcli/formatter.go | 2 +- internal/askcli/task_alias_cache.go | 2 +- internal/askcli/taskexec.go | 6 +- internal/askcli/taskexec_test.go | 26 +++--- 14 files changed, 173 insertions(+), 149 deletions(-) (limited to 'internal/askcli') diff --git a/internal/askcli/command_add.go b/internal/askcli/command_add.go index d2fb2ea..b94fe89 100644 --- a/internal/askcli/command_add.go +++ b/internal/askcli/command_add.go @@ -10,7 +10,7 @@ import ( func (d *Dispatcher) handleAdd(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do add requires a description\n") + _, _ = io.WriteString(stderr, "error: ask add requires a description\n") return 1, nil } modifiers, description, dependencySelectors, err := parseAddArgs(args[1:]) @@ -19,7 +19,7 @@ func (d *Dispatcher) handleAdd(ctx context.Context, args []string, stdout, stder return 1, nil } if strings.TrimSpace(description) == "" { - _, _ = io.WriteString(stderr, "error: do add requires a description\n") + _, _ = io.WriteString(stderr, "error: ask add requires a description\n") return 1, nil } dependencyUUIDs, code, err := d.resolveAddDependencyUUIDs(ctx, dependencySelectors, stderr) @@ -112,14 +112,14 @@ func parseAddArgs(args []string) (modifiers []string, description string, depend func parseAddDependencySelectors(arg string) ([]string, error) { raw := strings.TrimSpace(strings.TrimPrefix(arg, "depends:")) if raw == "" { - return nil, fmt.Errorf("do add depends:[,...] requires at least one dependency ID or UUID") + return nil, fmt.Errorf("ask add depends:[,...] requires at least one dependency ID or UUID") } parts := strings.Split(raw, ",") selectors := make([]string, 0, len(parts)) for _, part := range parts { selector := strings.TrimSpace(part) if selector == "" { - return nil, fmt.Errorf("do add dependency selector list contains an empty item") + return nil, fmt.Errorf("ask add dependency selector list contains an empty item") } selectors = append(selectors, selector) } diff --git a/internal/askcli/command_delete.go b/internal/askcli/command_delete.go index 7356753..801d4cf 100644 --- a/internal/askcli/command_delete.go +++ b/internal/askcli/command_delete.go @@ -8,7 +8,7 @@ import ( func (d *Dispatcher) handleDelete(ctx context.Context, args []string, stdin io.Reader, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do delete requires an ID or UUID argument\n") + _, _ = io.WriteString(stderr, "error: ask delete requires an ID or UUID argument\n") return 1, nil } resolved, _, code, err := d.resolveTaskSelector(ctx, args[1], stderr) diff --git a/internal/askcli/command_dep.go b/internal/askcli/command_dep.go index b6fb2b9..aa28df8 100644 --- a/internal/askcli/command_dep.go +++ b/internal/askcli/command_dep.go @@ -10,7 +10,7 @@ import ( func (d *Dispatcher) handleDep(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do dep requires an operation (add/rm/list) and arguments\n") + _, _ = io.WriteString(stderr, "error: ask dep requires an operation (add/rm/list) and arguments\n") return 1, nil } op := args[1] @@ -20,14 +20,14 @@ func (d *Dispatcher) handleDep(ctx context.Context, args []string, stdout, stder case "list": return d.handleDepList(ctx, args, stdout, stderr) default: - fmt.Fprintf(stderr, "error: do dep: unknown operation %q (use add, rm, or list)\n", op) + fmt.Fprintf(stderr, "error: ask dep: unknown operation %q (use add, rm, or list)\n", op) return 1, nil } } func (d *Dispatcher) handleDepAddRm(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 4 { - _, _ = io.WriteString(stderr, "error: do dep add/rm requires \n") + _, _ = io.WriteString(stderr, "error: ask dep add/rm requires \n") return 1, nil } resolved, _, code, err := d.resolveTaskSelector(ctx, args[2], stderr) @@ -59,7 +59,7 @@ func (d *Dispatcher) handleDepAddRm(ctx context.Context, args []string, stdout, func (d *Dispatcher) handleDepList(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do dep list requires \n") + _, _ = io.WriteString(stderr, "error: ask dep list requires \n") return 1, nil } _, tasks, code, err := d.resolveTaskSelector(ctx, args[2], stderr) diff --git a/internal/askcli/command_fish.go b/internal/askcli/command_fish.go index 401a9e2..f4ca9e0 100644 --- a/internal/askcli/command_fish.go +++ b/internal/askcli/command_fish.go @@ -10,12 +10,12 @@ import ( func (d *Dispatcher) handleFish(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { _ = ctx if len(args) != 1 { - fmt.Fprintln(stderr, "usage: do fish") + fmt.Fprintln(stderr, "usage: ask fish") return 1, nil } binaryPath, err := os.Executable() if err != nil { - binaryPath = "do" + binaryPath = "ask" } if _, err := io.WriteString(stdout, FishCompletionFor(binaryPath)); err != nil { return 1, err diff --git a/internal/askcli/command_info_add_test.go b/internal/askcli/command_info_add_test.go index 8ca14a2..0e5b366 100644 --- a/internal/askcli/command_info_add_test.go +++ b/internal/askcli/command_info_add_test.go @@ -383,7 +383,7 @@ func TestHandleAdd_DependsModifierWithoutSelectors(t *testing.T) { if code != 1 { t.Fatalf("add code = %d, want 1", code) } - if got := stderr.String(); !strings.Contains(got, "do add depends:[,...] requires at least one dependency ID or UUID") { + if got := stderr.String(); !strings.Contains(got, "ask add depends:[,...] requires at least one dependency ID or UUID") { t.Fatalf("stderr = %q, want depends: selector error", got) } } diff --git a/internal/askcli/command_write.go b/internal/askcli/command_write.go index 31a3f39..d8dbf5b 100644 --- a/internal/askcli/command_write.go +++ b/internal/askcli/command_write.go @@ -31,7 +31,7 @@ func (d *Dispatcher) runSingleTaskCommand( func (d *Dispatcher) handleDenotate(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do denotate requires an ID or UUID and text argument\n") + _, _ = io.WriteString(stderr, "error: ask denotate requires an ID or UUID and text argument\n") return 1, nil } text := args[2] @@ -42,7 +42,7 @@ func (d *Dispatcher) handleDenotate(ctx context.Context, args []string, stdout, func (d *Dispatcher) handleModify(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do modify requires an ID or UUID and modification args\n") + _, _ = io.WriteString(stderr, "error: ask modify requires an ID or UUID and modification args\n") return 1, nil } modArgs := args[2:] @@ -53,7 +53,7 @@ func (d *Dispatcher) handleModify(ctx context.Context, args []string, stdout, st func (d *Dispatcher) handleAnnotate(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do annotate requires an ID or UUID and note argument\n") + _, _ = io.WriteString(stderr, "error: ask annotate requires an ID or UUID and note argument\n") return 1, nil } note := strings.Join(args[2:], " ") @@ -64,7 +64,7 @@ func (d *Dispatcher) handleAnnotate(ctx context.Context, args []string, stdout, func (d *Dispatcher) handleStart(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do start requires an ID or UUID argument\n") + _, _ = io.WriteString(stderr, "error: ask start requires an ID or UUID argument\n") return 1, nil } return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { @@ -76,7 +76,7 @@ func (d *Dispatcher) handleStart(ctx context.Context, args []string, stdout, std func (d *Dispatcher) handleStop(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do stop requires an ID or UUID argument\n") + _, _ = io.WriteString(stderr, "error: ask stop requires an ID or UUID argument\n") return 1, nil } return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { @@ -86,7 +86,7 @@ func (d *Dispatcher) handleStop(ctx context.Context, args []string, stdout, stde func (d *Dispatcher) handleDone(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 2 { - _, _ = io.WriteString(stderr, "error: do done requires an ID or UUID argument\n") + _, _ = io.WriteString(stderr, "error: ask done requires an ID or UUID argument\n") return 1, nil } return d.runSingleTaskCommand(ctx, args[1], stdout, stderr, func(resolved resolvedTaskSelector) []string { @@ -96,7 +96,7 @@ func (d *Dispatcher) handleDone(ctx context.Context, args []string, stdout, stde func (d *Dispatcher) handlePriority(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do priority requires an ID or UUID and priority (H/M/L)\n") + _, _ = io.WriteString(stderr, "error: ask priority requires an ID or UUID and priority (H/M/L)\n") return 1, nil } priority := args[2] @@ -107,7 +107,7 @@ func (d *Dispatcher) handlePriority(ctx context.Context, args []string, stdout, func (d *Dispatcher) handleTag(ctx context.Context, args []string, stdout, stderr io.Writer) (int, error) { if len(args) < 3 { - _, _ = io.WriteString(stderr, "error: do tag requires an ID or UUID and +/-tag\n") + _, _ = io.WriteString(stderr, "error: ask tag requires an ID or UUID and +/-tag\n") return 1, nil } tag := args[2] diff --git a/internal/askcli/completion.go b/internal/askcli/completion.go index 4d4b5d3..79dd923 100644 --- a/internal/askcli/completion.go +++ b/internal/askcli/completion.go @@ -1,6 +1,7 @@ package askcli import ( + "path/filepath" "strings" ) @@ -54,43 +55,60 @@ func fishAddDependencyModifierCompletionContext(positional []string, current str return current == "depends" || strings.HasPrefix(current, "depends:") } -// FishCompletion returns the default Fish completion script for the do CLI. +// FishCompletion returns the default Fish completion script for the ask CLI. func FishCompletion() string { - return FishCompletionFor("do") + return FishCompletionFor("ask") +} + +// fishShellCommandName returns the argv0 basename used for `complete -c` wiring. +func fishShellCommandName(binaryPath string) string { + first := strings.TrimSpace(binaryPath) + if i := strings.IndexByte(first, ' '); i >= 0 { + first = first[:i] + } + return filepath.Base(first) } // FishCompletionFor returns a Fish completion script that points to the provided binary path. func FishCompletionFor(binaryPath string) string { + cmd := fishShellCommandName(binaryPath) var b strings.Builder - writeFishPreamble(&b) + writeFishPreamble(&b, cmd) writeFishContextFunctions(&b) writeFishTaskSelectorFunction(&b, binaryPath) writeFishAddDependencyModifierFunction(&b) - b.WriteString("complete -c do -f\n") - b.WriteString("complete -c do -s j -l json -d 'Emit JSON output'\n") + b.WriteString("complete -c ") + b.WriteString(cmd) + b.WriteString(" -f\n") + b.WriteString("complete -c ") + b.WriteString(cmd) + b.WriteString(" -s j -l json -d 'Emit JSON output'\n") for _, item := range []fishCompletionItem{ {name: "na", description: "Run against project tasks without +agent"}, {name: "no-agent", description: "Run against project tasks without +agent"}, {name: "proj:", description: "Run against an explicit project"}, } { - writeFishCompletionLine(&b, "__do_needs_root_completion", item) + writeFishCompletionLine(&b, cmd, "__ask_needs_root_completion", item) } for _, entry := range commandRegistry.rootCompletionEntries() { item := fishCompletionItem{name: entry.name, description: entry.description} - writeFishCompletionLine(&b, "__do_needs_command_completion", item) + writeFishCompletionLine(&b, cmd, "__ask_needs_command_completion", item) } for _, item := range askDepCompletionItems { - writeFishCompletionLine(&b, "__do_in_dep_context", item) + writeFishCompletionLine(&b, cmd, "__ask_in_dep_context", item) } - writeFishUUIDCompletionLine(&b, "__do_in_uuid_context", "Task selector") - writeFishUUIDCompletionLine(&b, "__do_in_dep_uuid_context", "Task selector") - writeFishFunctionCompletionLine(&b, "__do_in_add_dep_modifier_context", "__do_add_dependency_modifiers", "Task dependency") + writeFishUUIDCompletionLine(&b, cmd, "__ask_in_uuid_context", "Task selector") + writeFishUUIDCompletionLine(&b, cmd, "__ask_in_dep_uuid_context", "Task selector") + writeFishFunctionCompletionLine(&b, cmd, "__ask_in_add_dep_modifier_context", "__ask_add_dependency_modifiers", "Task dependency") return b.String() } -func writeFishPreamble(b *strings.Builder) { - b.WriteString("# Fish completion for do.\n") - b.WriteString("# Source with: do fish | source\n\n") +func writeFishPreamble(b *strings.Builder, cmd string) { + b.WriteString("# Fish completion for ") + b.WriteString(cmd) + b.WriteString(".\n# Source with: ") + b.WriteString(cmd) + b.WriteString(" fish | source\n\n") } func writeFishContextFunctions(b *strings.Builder) { @@ -106,7 +124,7 @@ func writeFishContextFunctions(b *strings.Builder) { } func writeFishPositionalTokensFunction(b *strings.Builder) { - b.WriteString("function __do_positional_tokens\n") + b.WriteString("function __ask_positional_tokens\n") b.WriteString(" set -l tokens (commandline -opc)\n") b.WriteString(" set -l positional\n") b.WriteString(" for token in $tokens[2..-1]\n") @@ -122,11 +140,11 @@ func writeFishPositionalTokensFunction(b *strings.Builder) { } func writeFishCommandPositionalsFunction(b *strings.Builder) { - b.WriteString("function __do_command_positionals\n") - b.WriteString(" set -l positional (__do_positional_tokens)\n") + b.WriteString("function __ask_command_positionals\n") + b.WriteString(" set -l positional (__ask_positional_tokens)\n") b.WriteString(" while test (count $positional) -gt 0\n") b.WriteString(" switch $positional[1]\n") - b.WriteString(" case na no-agent proj:*\n") + b.WriteString(" case na no-agent 'proj:*'\n") b.WriteString(" set positional $positional[2..-1]\n") b.WriteString(" case '*'\n") b.WriteString(" break\n") @@ -139,11 +157,11 @@ func writeFishCommandPositionalsFunction(b *strings.Builder) { } func writeFishScopePrefixFunction(b *strings.Builder) { - b.WriteString("function __do_scope_prefix\n") - b.WriteString(" set -l positional (__do_positional_tokens)\n") + b.WriteString("function __ask_scope_prefix\n") + b.WriteString(" set -l positional (__ask_positional_tokens)\n") b.WriteString(" while test (count $positional) -gt 0\n") b.WriteString(" switch $positional[1]\n") - b.WriteString(" case na no-agent proj:*\n") + b.WriteString(" case na no-agent 'proj:*'\n") b.WriteString(" printf '%s\\n' $positional[1]\n") b.WriteString(" set positional $positional[2..-1]\n") b.WriteString(" case '*'\n") @@ -155,8 +173,8 @@ func writeFishScopePrefixFunction(b *strings.Builder) { } func writeFishNeedsRootCompletionFunction(b *strings.Builder) { - b.WriteString("function __do_needs_root_completion\n") - b.WriteString(" set -l positional (__do_positional_tokens)\n") + b.WriteString("function __ask_needs_root_completion\n") + b.WriteString(" set -l positional (__ask_positional_tokens)\n") b.WriteString(" if test (count $positional) -eq 0\n") b.WriteString(" return 0\n") b.WriteString(" end\n") @@ -165,14 +183,14 @@ func writeFishNeedsRootCompletionFunction(b *strings.Builder) { } func writeFishNeedsCommandCompletionFunction(b *strings.Builder) { - b.WriteString("function __do_needs_command_completion\n") - b.WriteString(" set -l positional (__do_positional_tokens)\n") + b.WriteString("function __ask_needs_command_completion\n") + b.WriteString(" set -l positional (__ask_positional_tokens)\n") b.WriteString(" if test (count $positional) -eq 0\n") b.WriteString(" return 0\n") b.WriteString(" end\n") b.WriteString(" if test (count $positional) -eq 1\n") b.WriteString(" switch $positional[1]\n") - b.WriteString(" case na no-agent proj:*\n") + b.WriteString(" case na no-agent 'proj:*'\n") b.WriteString(" return 0\n") b.WriteString(" end\n") b.WriteString(" end\n") @@ -181,8 +199,8 @@ func writeFishNeedsCommandCompletionFunction(b *strings.Builder) { } func writeFishDepContextFunction(b *strings.Builder) { - b.WriteString("function __do_in_dep_context\n") - b.WriteString(" set -l positional (__do_command_positionals)\n") + b.WriteString("function __ask_in_dep_context\n") + b.WriteString(" set -l positional (__ask_command_positionals)\n") b.WriteString(" if test (count $positional) -lt 1\n") b.WriteString(" return 1\n") b.WriteString(" end\n") @@ -194,8 +212,8 @@ func writeFishDepContextFunction(b *strings.Builder) { } func writeFishUUIDContextFunction(b *strings.Builder) { - b.WriteString("function __do_in_uuid_context\n") - b.WriteString(" set -l positional (__do_command_positionals)\n") + b.WriteString("function __ask_in_uuid_context\n") + b.WriteString(" set -l positional (__ask_command_positionals)\n") b.WriteString(" if test (count $positional) -eq 0\n") b.WriteString(" return 1\n") b.WriteString(" end\n") @@ -215,8 +233,8 @@ func writeFishUUIDContextFunction(b *strings.Builder) { } func writeFishDepUUIDContextFunction(b *strings.Builder) { - b.WriteString("function __do_in_dep_uuid_context\n") - b.WriteString(" set -l positional (__do_command_positionals)\n") + b.WriteString("function __ask_in_dep_uuid_context\n") + b.WriteString(" set -l positional (__ask_command_positionals)\n") b.WriteString(" if test (count $positional) -lt 2\n") b.WriteString(" return 1\n") b.WriteString(" end\n") @@ -240,8 +258,8 @@ func writeFishDepUUIDContextFunction(b *strings.Builder) { } func writeFishAddDependencyModifierContextFunction(b *strings.Builder) { - b.WriteString("function __do_in_add_dep_modifier_context\n") - b.WriteString(" set -l positional (__do_command_positionals)\n") + b.WriteString("function __ask_in_add_dep_modifier_context\n") + b.WriteString(" set -l positional (__ask_command_positionals)\n") b.WriteString(" if test (count $positional) -lt 1\n") b.WriteString(" return 1\n") b.WriteString(" end\n") @@ -257,38 +275,38 @@ func writeFishAddDependencyModifierContextFunction(b *strings.Builder) { } func writeFishTaskSelectorFunction(b *strings.Builder, binaryPath string) { - b.WriteString("function __do_task_selectors\n") - b.WriteString(" set -l do_bin ") + b.WriteString("function __ask_task_selectors\n") + b.WriteString(" set -l ask_bin ") b.WriteString(quoteFishString(binaryPath)) b.WriteString("\n") - b.WriteString(" set -l scope_prefix (__do_scope_prefix)\n") + b.WriteString(" set -l scope_prefix (__ask_scope_prefix)\n") b.WriteString(" set -l cache_key default\n") b.WriteString(" if test (count $scope_prefix) -gt 0\n") b.WriteString(" set cache_key (string join ' ' $scope_prefix)\n") b.WriteString(" end\n") b.WriteString(" set -l now (date +%s)\n") - b.WriteString(" if set -q __do_task_selector_cache_until; and test $__do_task_selector_cache_until -ge $now; and set -q __do_task_selector_cache_key; and test \"$__do_task_selector_cache_key\" = \"$cache_key\"\n") - b.WriteString(" printf '%s\\n' $__do_task_selector_cache\n") + b.WriteString(" if set -q __ask_task_selector_cache_until; and test $__ask_task_selector_cache_until -ge $now; and set -q __ask_task_selector_cache_key; and test \"$__ask_task_selector_cache_key\" = \"$cache_key\"\n") + b.WriteString(" printf '%s\\n' $__ask_task_selector_cache\n") b.WriteString(" return 0\n") b.WriteString(" end\n") b.WriteString(" set -l selectors\n") b.WriteString(" if test (count $scope_prefix) -gt 0\n") - b.WriteString(" set selectors (command $do_bin $scope_prefix complete-aliases 2>/dev/null)\n") + b.WriteString(" set selectors (command $ask_bin $scope_prefix complete-aliases 2>/dev/null)\n") b.WriteString(" else\n") - b.WriteString(" set selectors (command $do_bin complete-aliases 2>/dev/null)\n") + b.WriteString(" set selectors (command $ask_bin complete-aliases 2>/dev/null)\n") b.WriteString(" end\n") b.WriteString(" if test $status -ne 0\n") b.WriteString(" return 1\n") b.WriteString(" end\n") - b.WriteString(" set -g __do_task_selector_cache $selectors\n") - b.WriteString(" set -g __do_task_selector_cache_until (math $now + 2)\n") - b.WriteString(" set -g __do_task_selector_cache_key $cache_key\n") + b.WriteString(" set -g __ask_task_selector_cache $selectors\n") + b.WriteString(" set -g __ask_task_selector_cache_until (math $now + 2)\n") + b.WriteString(" set -g __ask_task_selector_cache_key $cache_key\n") b.WriteString(" printf '%s\\n' $selectors\n") b.WriteString("end\n\n") } func writeFishAddDependencyModifierFunction(b *strings.Builder) { - b.WriteString("function __do_add_dependency_modifiers\n") + b.WriteString("function __ask_add_dependency_modifiers\n") b.WriteString(" set -l current (commandline -ct)\n") b.WriteString(" if test $current = depends\n") b.WriteString(" printf '%s\\n' 'depends:'\n") @@ -307,9 +325,9 @@ func writeFishAddDependencyModifierFunction(b *strings.Builder) { b.WriteString(" set chosen $pieces[1..-2]\n") b.WriteString(" end\n") b.WriteString(" end\n") - // Each item from __do_task_selectors is "selector\tdescription"; extract + // Each item from __ask_task_selectors is "selector\tdescription"; extract // just the selector (before the tab) for matching and output purposes. - b.WriteString(" for item in (__do_task_selectors)\n") + b.WriteString(" for item in (__ask_task_selectors)\n") b.WriteString(" set -l selector (string split -m1 '\\t' -- $item)[1]\n") b.WriteString(" if contains -- $selector $chosen\n") b.WriteString(" continue\n") @@ -326,8 +344,10 @@ func writeFishAddDependencyModifierFunction(b *strings.Builder) { b.WriteString("end\n\n") } -func writeFishCompletionLine(b *strings.Builder, condition string, item fishCompletionItem) { - b.WriteString("complete -c do -n '") +func writeFishCompletionLine(b *strings.Builder, cmd, condition string, item fishCompletionItem) { + b.WriteString("complete -c ") + b.WriteString(cmd) + b.WriteString(" -n '") b.WriteString(condition) b.WriteString("' -a '") b.WriteString(item.name) @@ -336,16 +356,20 @@ func writeFishCompletionLine(b *strings.Builder, condition string, item fishComp b.WriteString("'\n") } -func writeFishUUIDCompletionLine(b *strings.Builder, condition, description string) { - b.WriteString("complete -c do -n '") +func writeFishUUIDCompletionLine(b *strings.Builder, cmd, condition, description string) { + b.WriteString("complete -c ") + b.WriteString(cmd) + b.WriteString(" -n '") b.WriteString(condition) - b.WriteString("' -a '(__do_task_selectors)' -d '") + b.WriteString("' -a '(__ask_task_selectors)' -d '") b.WriteString(strings.ReplaceAll(description, "'", "\\'")) b.WriteString("'\n") } -func writeFishFunctionCompletionLine(b *strings.Builder, condition, functionName, description string) { - b.WriteString("complete -c do -n '") +func writeFishFunctionCompletionLine(b *strings.Builder, cmd, condition, functionName, description string) { + b.WriteString("complete -c ") + b.WriteString(cmd) + b.WriteString(" -n '") b.WriteString(condition) b.WriteString("' -a '(") b.WriteString(functionName) diff --git a/internal/askcli/completion_test.go b/internal/askcli/completion_test.go index 7d85c7b..d3f08e5 100644 --- a/internal/askcli/completion_test.go +++ b/internal/askcli/completion_test.go @@ -18,40 +18,40 @@ func TestFishCompletion_IncludesCommandsAndExcludesExport(t *testing.T) { } } for _, line := range []string{ - "# Source with: do fish | source", - "complete -c do -n '__do_in_dep_context' -a 'add' -d 'Add a dependency'", - "complete -c do -n '__do_in_dep_context' -a 'rm' -d 'Remove a dependency'", - "complete -c do -n '__do_in_dep_context' -a 'list' -d 'List dependencies'", - "function __do_command_positionals", - "function __do_scope_prefix", - "function __do_task_selectors", - "function __do_add_dependency_modifiers", - `set -l do_bin "do"`, + "# Source with: ask fish | source", + "complete -c ask -n '__ask_in_dep_context' -a 'add' -d 'Add a dependency'", + "complete -c ask -n '__ask_in_dep_context' -a 'rm' -d 'Remove a dependency'", + "complete -c ask -n '__ask_in_dep_context' -a 'list' -d 'List dependencies'", + "function __ask_command_positionals", + "function __ask_scope_prefix", + "function __ask_task_selectors", + "function __ask_add_dependency_modifiers", + `set -l ask_bin "ask"`, "set -l selectors", - "set selectors (command $do_bin complete-aliases 2>/dev/null)", - "set selectors (command $do_bin $scope_prefix complete-aliases 2>/dev/null)", - "case na no-agent proj:*", + "set selectors (command $ask_bin complete-aliases 2>/dev/null)", + "set selectors (command $ask_bin $scope_prefix complete-aliases 2>/dev/null)", + "case na no-agent 'proj:*'", "set cache_key (string join ' ' $scope_prefix)", - "complete -c do -n '__do_in_uuid_context' -a '(__do_task_selectors)' -d 'Task selector'", - "complete -c do -n '__do_in_dep_uuid_context' -a '(__do_task_selectors)' -d 'Task selector'", - "complete -c do -n '__do_in_add_dep_modifier_context' -a '(__do_add_dependency_modifiers)' -d 'Task dependency'", + "complete -c ask -n '__ask_in_uuid_context' -a '(__ask_task_selectors)' -d 'Task selector'", + "complete -c ask -n '__ask_in_dep_uuid_context' -a '(__ask_task_selectors)' -d 'Task selector'", + "complete -c ask -n '__ask_in_add_dep_modifier_context' -a '(__ask_add_dependency_modifiers)' -d 'Task dependency'", // The dep modifier function must extract just the selector (before the // tab) from each tab-separated "selector\tdescription" completion item. - "for item in (__do_task_selectors)", + "for item in (__ask_task_selectors)", "set -l selector (string split -m1 '\\t' -- $item)[1]", } { if !strings.Contains(script, line) { t.Fatalf("script missing dep completion line %q", line) } } - if strings.Contains(script, "do export") { + if strings.Contains(script, "ask export") { t.Fatalf("script should not advertise non-existent export command") } - if strings.Contains(script, "assets/do.fish") { + if strings.Contains(script, "assets/ask.fish") { t.Fatalf("script should not reference a static asset") } for _, name := range []string{"info", "annotate", "start", "stop", "done", "priority", "tag", "modify", "denotate", "delete"} { - if strings.Contains(script, "complete -c do -n '__do_in_uuid_context' -a '"+name+"'") { + if strings.Contains(script, "complete -c ask -n '__ask_in_uuid_context' -a '"+name+"'") { t.Fatalf("script should not hard-code UUID completion item %q", name) } } @@ -141,10 +141,10 @@ func TestFishAddDependencyModifierCompletionContext(t *testing.T) { } func TestFishCompletionFor_EmbedsBinaryPath(t *testing.T) { - script := FishCompletionFor(`/tmp/do "$HOME"`) + script := FishCompletionFor(`/tmp/ask "$HOME"`) for _, line := range []string{ - `set -l do_bin "/tmp/do \"\$HOME\""`, - "set selectors (command $do_bin complete-aliases 2>/dev/null)", + `set -l ask_bin "/tmp/ask \"\$HOME\""`, + "set selectors (command $ask_bin complete-aliases 2>/dev/null)", } { if !strings.Contains(script, line) { t.Fatalf("script missing %q", line) diff --git a/internal/askcli/dispatch.go b/internal/askcli/dispatch.go index 69310c7..e326017 100644 --- a/internal/askcli/dispatch.go +++ b/internal/askcli/dispatch.go @@ -6,7 +6,7 @@ import ( "io" ) -// Runner performs CLI work that would otherwise be handled by the do CLI itself. +// Runner performs CLI work that would otherwise be handled by the ask CLI itself. // // The interface is implemented by the executor that ultimately proxies commands to Taskwarrior. type Runner interface { @@ -22,7 +22,7 @@ type Dispatcher struct { // NewDispatcher creates a Dispatcher backed by the provided Runner or a default executor when nil. func NewDispatcher(runner Runner) *Dispatcher { if runner == nil { - e := NewExecutor("do") + e := NewExecutor("ask") runner = &e } return &Dispatcher{runner: runner} @@ -68,36 +68,36 @@ func (d *Dispatcher) Dispatch(ctx context.Context, args []string, stdin io.Reade } func (d *Dispatcher) help(w io.Writer) (int, error) { - _, _ = io.WriteString(w, "do - task management CLI\n") + _, _ = io.WriteString(w, "ask - task management CLI\n") _, _ = io.WriteString(w, "\nProject prefixes:\n") - _, _ = io.WriteString(w, " do proj: Run a subcommand against an explicit project\n") + _, _ = io.WriteString(w, " ask proj: Run a subcommand against an explicit project\n") _, _ = io.WriteString(w, "\nScope prefixes:\n") - _, _ = io.WriteString(w, " do na Run a subcommand against project tasks without +agent\n") - _, _ = io.WriteString(w, " do no-agent Alias for do na\n") + _, _ = io.WriteString(w, " ask na Run a subcommand against project tasks without +agent\n") + _, _ = io.WriteString(w, " ask no-agent Alias for ask na\n") _, _ = io.WriteString(w, "\nSubcommands:\n") - _, _ = io.WriteString(w, " do add [mods...] [depends:,...] Create a new task and print created task \n") - _, _ = io.WriteString(w, " do list [filters] List active tasks (default)\n") - _, _ = io.WriteString(w, " do ready List READY tasks (not blocked)\n") - _, _ = io.WriteString(w, " do all [filters] List all tasks including completed/deleted\n") - _, _ = io.WriteString(w, " do info [id|uuid] Show task details or current started task\n") - _, _ = io.WriteString(w, " do annotate \"note\" Add annotation to task\n") - _, _ = io.WriteString(w, " do start Start working on task\n") - _, _ = io.WriteString(w, " do stop Stop work on a task\n") - _, _ = io.WriteString(w, " do done Mark task complete\n") - _, _ = io.WriteString(w, " do priority

Set priority (H/M/L)\n") - _, _ = io.WriteString(w, " do tag +/- Add or remove tag\n") - _, _ = io.WriteString(w, " do dep add Add dependency\n") - _, _ = io.WriteString(w, " do dep rm Remove dependency\n") - _, _ = io.WriteString(w, " do dep list List dependencies\n") - _, _ = io.WriteString(w, " do urgency List tasks sorted by urgency\n") - _, _ = io.WriteString(w, " do modify Modify task fields\n") - _, _ = io.WriteString(w, " do denotate \"text\" Remove annotation\n") - _, _ = io.WriteString(w, " do delete Delete a task\n") - _, _ = io.WriteString(w, " do fish Emit Fish shell completion script\n") + _, _ = io.WriteString(w, " ask add [mods...] [depends:,...] Create a new task and print created task \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") + _, _ = io.WriteString(w, " ask info [id|uuid] Show task details or current started task\n") + _, _ = io.WriteString(w, " ask annotate \"note\" Add annotation to task\n") + _, _ = io.WriteString(w, " ask start Start working on task\n") + _, _ = io.WriteString(w, " ask stop Stop work on a task\n") + _, _ = io.WriteString(w, " ask done Mark task complete\n") + _, _ = io.WriteString(w, " ask priority

Set priority (H/M/L)\n") + _, _ = io.WriteString(w, " ask tag +/- Add or remove tag\n") + _, _ = io.WriteString(w, " ask dep add Add dependency\n") + _, _ = io.WriteString(w, " ask dep rm Remove dependency\n") + _, _ = io.WriteString(w, " ask dep list List dependencies\n") + _, _ = io.WriteString(w, " ask urgency List tasks sorted by urgency\n") + _, _ = io.WriteString(w, " ask modify Modify task fields\n") + _, _ = io.WriteString(w, " ask denotate \"text\" Remove annotation\n") + _, _ = io.WriteString(w, " ask delete Delete a task\n") + _, _ = io.WriteString(w, " ask fish Emit Fish shell completion script\n") return 0, nil } func (d *Dispatcher) unknownCommand(w io.Writer, subcommand string) (int, error) { - fmt.Fprintf(w, "do: unknown subcommand %q\n", subcommand) + fmt.Fprintf(w, "ask: unknown subcommand %q\n", subcommand) return 1, nil } diff --git a/internal/askcli/dispatch_test.go b/internal/askcli/dispatch_test.go index 68f1e3b..d8f1458 100644 --- a/internal/askcli/dispatch_test.go +++ b/internal/askcli/dispatch_test.go @@ -23,22 +23,22 @@ func TestDispatcher_Help(t *testing.T) { t.Fatalf("help returned error: %v", err) } output := stdout.String() - if !strings.Contains(output, "do - task management CLI") { + if !strings.Contains(output, "ask - task management CLI") { t.Fatalf("help missing title: %s", output) } - if !strings.Contains(output, "do proj: ") { + if !strings.Contains(output, "ask proj: ") { t.Fatalf("help missing project prefix: %s", output) } - if !strings.Contains(output, "do na ") || !strings.Contains(output, "do no-agent ") { + if !strings.Contains(output, "ask na ") || !strings.Contains(output, "ask no-agent ") { t.Fatalf("help missing no-agent scope prefixes: %s", output) } - if !strings.Contains(output, "do list") { + if !strings.Contains(output, "ask list") { t.Fatalf("help missing list subcommand: %s", output) } - if !strings.Contains(output, "do all") { + if !strings.Contains(output, "ask all") { t.Fatalf("help missing all subcommand: %s", output) } - if !strings.Contains(output, "do fish") { + if !strings.Contains(output, "ask fish") { t.Fatalf("help missing fish subcommand: %s", output) } } @@ -131,8 +131,8 @@ func TestDispatcher_LongHelp(t *testing.T) { d.Dispatch(context.Background(), []string{"help"}, nil, &stdout, io.Discard) output := stdout.String() for _, sub := range []string{"add", "list", "all", "ready", "info", "annotate", "start", "stop", "done", "priority", "tag", "dep", "urgency", "modify", "denotate", "delete", "fish"} { - if !strings.Contains(output, "do "+sub) { - t.Errorf("help missing subcommand: do %s", sub) + if !strings.Contains(output, "ask "+sub) { + t.Errorf("help missing subcommand: ask %s", sub) } } } @@ -172,7 +172,7 @@ func TestDispatcher_FishSubcommandRejectsExtraArgs(t *testing.T) { if stdout.Len() != 0 { t.Fatalf("fish extra args wrote unexpected stdout: %q", stdout.String()) } - if got := stderr.String(); !strings.Contains(got, "usage: do fish") { + if got := stderr.String(); !strings.Contains(got, "usage: ask fish") { t.Fatalf("fish extra args stderr = %q, want usage", got) } } diff --git a/internal/askcli/formatter.go b/internal/askcli/formatter.go index 5d2ed76..7f9b846 100644 --- a/internal/askcli/formatter.go +++ b/internal/askcli/formatter.go @@ -176,7 +176,7 @@ func FormatSuccess(alias string) string { return fmt.Sprintf("ok %s\n", alias) } -// FormatCreatedTask returns the success string written to stdout after do add creates a task. +// 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) } diff --git a/internal/askcli/task_alias_cache.go b/internal/askcli/task_alias_cache.go index ff682d6..e89dbb5 100644 --- a/internal/askcli/task_alias_cache.go +++ b/internal/askcli/task_alias_cache.go @@ -114,7 +114,7 @@ func taskAliasCachePath() (string, error) { // v2 uses reversed alias strings (e.g. "10" instead of "01") so that the // first character varies more often, improving shell auto-completion. The // old v1 file is intentionally abandoned so the mapping starts fresh. - return filepath.Join(dir, "do", "task-aliases-v2.json"), nil + return filepath.Join(dir, "ask", "task-aliases-v2.json"), nil } func (c *taskAliasCache) validate() error { diff --git a/internal/askcli/taskexec.go b/internal/askcli/taskexec.go index d479c34..0b68e3b 100644 --- a/internal/askcli/taskexec.go +++ b/internal/askcli/taskexec.go @@ -16,7 +16,7 @@ type repoTopLevelDetector func(context.Context) (string, error) type commandRunner func(context.Context, string, []string, io.Reader, io.Writer, io.Writer) error -// Executor encapsulates how the do CLI communicates with the Taskwarrior binary. +// Executor encapsulates how the ask CLI communicates with the Taskwarrior binary. type Executor struct { commandName string findBinary binaryFinder @@ -97,14 +97,14 @@ func (e Executor) Run(ctx context.Context, args []string, stdin io.Reader, stdou func (e Executor) label() string { label := strings.TrimSpace(e.commandName) if label == "" { - return "do" + return "ask" } return label } func normalizeExecutor(e Executor) Executor { if e.commandName == "" { - e.commandName = "do" + e.commandName = "ask" } if e.findBinary == nil { e.findBinary = findTaskBinary diff --git a/internal/askcli/taskexec_test.go b/internal/askcli/taskexec_test.go index 0d744dc..2236866 100644 --- a/internal/askcli/taskexec_test.go +++ b/internal/askcli/taskexec_test.go @@ -12,7 +12,7 @@ import ( ) func TestExecutorTaskArgs(t *testing.T) { - exec_ := NewExecutor("do") + exec_ := NewExecutor("ask") args, err := exec_.taskArgs(context.Background(), "/tmp/work/hexai", []string{"list", "limit:1"}) if err != nil { t.Fatalf("taskArgs returned error: %v", err) @@ -24,7 +24,7 @@ func TestExecutorTaskArgs(t *testing.T) { } func TestExecutorTaskArgs_NoAgentScope(t *testing.T) { - exec_ := NewExecutor("do") + exec_ := NewExecutor("ask") ctx := contextWithTaskScope(context.Background(), taskScopeNoAgent) args, err := exec_.taskArgs(ctx, "/tmp/work/hexai", []string{"list", "limit:1"}) if err != nil { @@ -37,7 +37,7 @@ func TestExecutorTaskArgs_NoAgentScope(t *testing.T) { } func TestExecutorTaskArgs_ProjectOverride(t *testing.T) { - exec_ := NewExecutor("do") + exec_ := NewExecutor("ask") ctx := contextWithTaskProject(context.Background(), "alpha") args, err := exec_.taskArgs(ctx, "", []string{"list", "limit:1"}) if err != nil { @@ -50,7 +50,7 @@ func TestExecutorTaskArgs_ProjectOverride(t *testing.T) { } func TestExecutorTaskArgs_AddDefaultScope(t *testing.T) { - exec_ := NewExecutor("do") + exec_ := NewExecutor("ask") args, err := exec_.taskArgs(context.Background(), "/tmp/work/hexai", []string{"add", "rc.verbose=nothing", "rc.verbose=new-uuid", "new task"}) if err != nil { t.Fatalf("taskArgs returned error: %v", err) @@ -62,7 +62,7 @@ func TestExecutorTaskArgs_AddDefaultScope(t *testing.T) { } func TestExecutorTaskArgs_AddNoAgentScope(t *testing.T) { - exec_ := NewExecutor("do") + exec_ := NewExecutor("ask") ctx := contextWithTaskScope(context.Background(), taskScopeNoAgent) args, err := exec_.taskArgs(ctx, "/tmp/work/hexai", []string{"add", "rc.verbose=nothing", "rc.verbose=new-uuid", "new task"}) if err != nil { @@ -78,7 +78,7 @@ func TestExecutorRun_InjectsProjectFilterAndAgentTag(t *testing.T) { var gotName string var gotArgs []string exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil }, runCommand: func(_ context.Context, name string, args []string, stdin io.Reader, stdout, stderr io.Writer) error { @@ -107,7 +107,7 @@ func TestExecutorRun_InjectsProjectFilterAndAgentTag(t *testing.T) { func TestExecutorRun_InjectsProjectFilterAndNoAgentTag(t *testing.T) { var gotArgs []string exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil }, runCommand: func(_ context.Context, name string, args []string, stdin io.Reader, stdout, stderr io.Writer) error { @@ -133,7 +133,7 @@ func TestExecutorRun_InjectsProjectFilterAndNoAgentTag(t *testing.T) { func TestExecutorRun_ProjectOverrideSkipsRepoDetection(t *testing.T) { var gotArgs []string exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { t.Fatal("detectRepoRoot should not be called when project override is set") @@ -161,7 +161,7 @@ func TestExecutorRun_ProjectOverrideSkipsRepoDetection(t *testing.T) { func TestExecutorRun_OutsideGitRepo_IsActionable(t *testing.T) { exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "", errors.New("git failed") }, runCommand: func(context.Context, string, []string, io.Reader, io.Writer, io.Writer) error { @@ -181,7 +181,7 @@ func TestExecutorRun_OutsideGitRepo_IsActionable(t *testing.T) { func TestExecutorRun_PreservesTaskwarriorExitCode(t *testing.T) { exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil }, runCommand: func(context.Context, string, []string, io.Reader, io.Writer, io.Writer) error { @@ -202,7 +202,7 @@ func TestExecutorRun_PreservesStdoutAndStderr(t *testing.T) { var stdout bytes.Buffer var stderr bytes.Buffer exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "/tmp/work/hexai", nil }, runCommand: func(_ context.Context, name string, args []string, stdin io.Reader, out, errOut io.Writer) error { @@ -229,7 +229,7 @@ func TestExecutorRun_PreservesStdoutAndStderr(t *testing.T) { func TestExecutorRun_TaskLookupFailure_IsActionable(t *testing.T) { exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "", errors.New("not found") }, } @@ -244,7 +244,7 @@ func TestExecutorRun_TaskLookupFailure_IsActionable(t *testing.T) { func TestExecutorRun_EmptyRepoName_IsActionable(t *testing.T) { exec_ := Executor{ - commandName: "do", + commandName: "ask", findBinary: func() (string, error) { return "/usr/bin/task", nil }, detectRepoRoot: func(context.Context) (string, error) { return "/", nil }, } -- cgit v1.2.3