1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
|
# Plan: `ask` as UUID-only Taskwarrior Wrapper
## Goal
Rewrite the `ask` command from a thin pass-through proxy into a **subcommand-based CLI** that wraps Taskwarrior. The wrapper never exposes numeric task IDs to the caller — only UUIDs. Output is minimal and machine-friendly for coding agents.
The existing `project:<repo> +agent` auto-injection is preserved.
## Subcommands
| Subcommand | Example | Taskwarrior equivalent |
|---|---|---|
| `ask add "Implement X"` | create task | `task project:P +agent add "Implement X"` |
| `ask add priority:H +cli "Fix bug"` | create with priority & tag | same + `priority:H +cli` |
| `ask list` | list pending tasks | `task project:P +agent status:pending export` → reformat |
| `ask info <uuid>` | show one task | `task uuid:<uuid> export` → filtered fields |
| `ask annotate <uuid> "note"` | add annotation | `task uuid:<uuid> annotate "note"` |
| `ask start <uuid>` | start work | `task uuid:<uuid> start` |
| `ask stop <uuid>` | stop work | `task uuid:<uuid> stop` |
| `ask done <uuid>` | mark complete | `task uuid:<uuid> done` |
| `ask priority <uuid> H` | set priority | `task uuid:<uuid> modify priority:H` |
| `ask tag <uuid> +foo` | add tag | `task uuid:<uuid> modify +foo` |
| `ask tag <uuid> -foo` | remove tag | `task uuid:<uuid> modify -foo` |
| `ask dep add <uuid> <dep-uuid>` | add dependency | `task uuid:<uuid> modify depends:<dep-uuid>` |
| `ask dep rm <uuid> <dep-uuid>` | remove dependency | `task uuid:<uuid> modify depends:-<dep-uuid>` |
| `ask dep list <uuid>` | show dependencies | `task uuid:<uuid> export` → `depends` field |
| `ask urgency` | list by urgency | `task project:P +agent export` → sort by urgency |
| `ask delete <uuid>` | delete task | `task uuid:<uuid> delete` |
| `ask export` | raw JSON dump | `task project:P +agent export` → pass through |
### List filters, sort, and limit
`ask list` accepts optional filters, sort, and limit arguments:
| Example | Taskwarrior equivalent |
|---|---|
| `ask list` | `task project:P +agent status:pending export` (default sort: priority-, urgency-) |
| `ask list +READY` | `task project:P +agent +READY export` |
| `ask list +BLOCKED` | `task project:P +agent +BLOCKED export` |
| `ask list +frontend` | `task project:P +agent +frontend export` |
| `ask list started` | `task project:P +agent start.any: export` |
| `ask list limit:3` | show only first 3 results |
| `ask list +READY limit:1` | next ready task |
## Data Retrieval: `task export`
All read operations use `task <filter> export` which returns a JSON array. This avoids fragile text parsing. Write operations (`add`, `done`, `start`, `stop`, `annotate`, `modify`) call Taskwarrior directly and capture stdout to extract the created UUID.
### TaskExport struct
```go
type TaskExport struct {
UUID string `json:"uuid"`
Description string `json:"description"`
Status string `json:"status"`
Priority string `json:"priority"`
Tags []string `json:"tags"`
Urgency float64 `json:"urgency"`
Depends []string `json:"depends"`
Annotations []struct {
Description string `json:"description"`
Entry string `json:"entry"`
} `json:"annotations"`
}
```
## Output Formatting
- **`list` / `urgency`**: Compact table — `UUID | Priority | Status | Tags | Description | Urgency`. No numeric ID column.
- **`info`**: UUID, description, status, priority, tags, annotations, dependencies (as UUIDs), urgency.
- **`add`**: Print only the UUID of the created task (parse from Taskwarrior stdout).
- **All other write commands**: Print success/failure + UUID. Suppress Taskwarrior decorative output.
## ID Rejection
If an argument looks like a bare numeric ID where a UUID is expected, reject with: `"use UUID, not numeric task ID"`.
## Package Layout
```
cmd/ask/main.go — parse subcommand, dispatch to askcli
internal/askcli/ — NEW package
├── dispatch.go — subcommand router (switch args[0])
├── taskexec.go — wraps Taskwarrior execution (binary lookup, repo detection, run)
├── taskexport.go — TaskExport struct + JSON parse helper
├── formatter.go — shared UUID-only table/info formatting
├── command_add.go — add logic + UUID extraction from stdout
├── command_list.go — list via export + reformat
├── command_info.go — info via export + field filter
├── command_annotate.go — annotate
├── command_start.go — start
├── command_stop.go — stop
├── command_done.go — done
├── command_priority.go — set priority
├── command_tag.go — add/remove tags
├── command_dep.go — dep add/rm/list
├── command_urgency.go — urgency-sorted list
├── command_delete.go — delete task
└── command_export.go — raw JSON export
```
Each `command_*.go` file gets a corresponding `command_*_test.go`.
## Changes to Existing Code
- **`cmd/ask/main.go`** — stops calling `taskproxy.Runner.Run` directly; delegates to `askcli.Dispatch()`.
- **`internal/taskproxy/`** — reused by `askcli/taskexec.go` for binary lookup (`findTaskBinary`) and repo root detection (`detectRepoRoot`). The `Runner.Run` pass-through method becomes unused and can be removed.
## Task Breakdown
1. Scaffold `internal/askcli/` — dispatch, taskexec, taskexport, formatter
2. Implement `ask add` (UUID extraction from Taskwarrior stdout)
3. Implement `ask list` (export → UUID-only table)
4. Implement `ask info <uuid>` (export → filtered fields)
5. Implement `ask annotate <uuid> "note"`
6. Implement `ask start <uuid>` / `ask stop <uuid>`
7. Implement `ask done <uuid>`
8. Implement `ask priority <uuid> <P>`
9. Implement `ask tag <uuid> +/-tag`
10. Implement `ask dep add/rm/list`
11. Implement `ask urgency`
12. Implement `ask delete <uuid>`
13. Implement `ask export` (raw JSON)
14. Add filter/sort/limit support to `ask list` (+READY, +BLOCKED, +tag, started, limit:N)
15. Wire `cmd/ask/main.go` to `askcli.Dispatch`, remove old pass-through
16. Update docs and README
|