diff options
| author | Paul Buetow <paul@buetow.org> | 2026-04-07 09:01:46 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2026-04-07 09:01:46 +0300 |
| commit | 627485ecf12991c08340c69c999a117dfb24eebb (patch) | |
| tree | 36c374ac0781261f9417f581b81668a43a97bed5 /internal/askcli/task_alias_cache.go | |
| parent | 51d278935b8c0b07faf075d1d4e45bb2a48249a2 (diff) | |
feat: reverse alias IDs for better shell tab-completion
Alias IDs are now stored in reversed form (e.g. id=37 → "10" instead
of "01") so that the first character varies quickly across consecutive
IDs, making shell auto-completion more effective. The counter logic is
unchanged; only the string representation is reversed via a new
reverseString helper in encodeTaskAliasID/decodeTaskAliasID.
The cache file is bumped to task-aliases-v2.json so existing mappings
are abandoned and all aliases are regenerated with the new format.
Also fix TestDispatcher_CompleteUUIDsSubcommand to use an isolated temp
dir for the alias cache, preventing flakiness from cross-test pollution.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Diffstat (limited to 'internal/askcli/task_alias_cache.go')
| -rw-r--r-- | internal/askcli/task_alias_cache.go | 33 |
1 files changed, 29 insertions, 4 deletions
diff --git a/internal/askcli/task_alias_cache.go b/internal/askcli/task_alias_cache.go index 3ea5dff..21967d8 100644 --- a/internal/askcli/task_alias_cache.go +++ b/internal/askcli/task_alias_cache.go @@ -107,7 +107,10 @@ func taskAliasCachePath() (string, error) { if err != nil { return "", fmt.Errorf("resolve cache dir: %w", err) } - return filepath.Join(dir, "ask", "task-aliases-v1.json"), nil + // 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, "ask", "task-aliases-v2.json"), nil } func (c *taskAliasCache) validate() error { @@ -273,6 +276,21 @@ func (e taskAliasCacheEntry) lastTouchedAt() time.Time { return e.CreatedAt } +// reverseString returns s with its bytes in reverse order. Alias strings only +// ever contain ASCII characters so byte-level reversal is correct. +func reverseString(s string) string { + b := []byte(s) + for i, j := 0, len(b)-1; i < j; i, j = i+1, j-1 { + b[i], b[j] = b[j], b[i] + } + return string(b) +} + +// encodeTaskAliasID converts a monotonically-increasing counter to a short +// alphanumeric string and then reverses it. The reversal ensures that the +// first character of the alias varies as quickly as possible, which makes +// shell tab-completion more effective (e.g. "1", "2", ... "z", "00" becomes +// "1", "2", ..., "z", "00"; then "10", "20", ... instead of "00", "10", ...). func encodeTaskAliasID(id uint64) string { const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz" @@ -290,9 +308,14 @@ func encodeTaskAliasID(id uint64) string { buf[i] = alphabet[remaining%uint64(len(alphabet))] remaining /= uint64(len(alphabet)) } - return string(buf) + // Reverse so that the least-significant digit comes first, keeping the + // leading character diverse across consecutive IDs. + return reverseString(string(buf)) } +// decodeTaskAliasID is the inverse of encodeTaskAliasID. It reverses the alias +// string to restore the canonical (most-significant-digit-first) form before +// decoding. func decodeTaskAliasID(alias string) (uint64, bool) { const alphabet = "0123456789abcdefghijklmnopqrstuvwxyz" @@ -300,7 +323,9 @@ func decodeTaskAliasID(alias string) (uint64, bool) { return 0, false } - width := len(alias) + // Reverse the alias back to the canonical form before decoding. + canonical := reverseString(alias) + width := len(canonical) var id uint64 blockSize := uint64(len(alphabet)) for i := 1; i < width; i++ { @@ -309,7 +334,7 @@ func decodeTaskAliasID(alias string) (uint64, bool) { } var value uint64 - for _, r := range alias { + for _, r := range canonical { index := int64(-1) for i, candidate := range alphabet { if r == candidate { |
