@lumoai/cli 1.11.0 → 1.17.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +13 -13
- package/assets/skill/SKILL.md +111 -0
- package/assets/skill/references/artifacts-figma.md +124 -0
- package/assets/skill/references/docs.md +306 -0
- package/assets/skill/references/memory.md +69 -0
- package/assets/skill/references/milestones.md +244 -0
- package/assets/skill/references/onboarding.md +102 -0
- package/assets/skill/references/sessions.md +142 -0
- package/assets/skill/references/sprints.md +157 -0
- package/assets/skill/references/task-context.md +109 -0
- package/assets/skill/references/tasks.md +205 -0
- package/dist/cli/src/commands/milestone-archive.js +60 -0
- package/dist/cli/src/commands/milestone-list.js +24 -5
- package/dist/cli/src/commands/milestone-move.js +84 -0
- package/dist/cli/src/commands/milestone-reorder.js +72 -0
- package/dist/cli/src/commands/milestone-show.js +35 -0
- package/dist/cli/src/commands/milestone-unarchive.js +60 -0
- package/dist/cli/src/commands/session-wrap.js +5 -2
- package/dist/cli/src/commands/setup.js +50 -22
- package/dist/cli/src/commands/sprint-show.js +32 -3
- package/dist/cli/src/commands/task-context.js +4 -0
- package/dist/cli/src/commands/task-update.js +12 -4
- package/dist/cli/src/commands/wrap/blocked-prompt-section.js +64 -0
- package/dist/cli/src/index.js +31 -2
- package/dist/cli/src/lib/failure-summary-api.js +43 -0
- package/dist/cli/src/lib/hook-runner.js +1 -0
- package/dist/cli/src/lib/milestone-reorder.js +92 -0
- package/dist/cli/src/lib/resolve.js +17 -6
- package/package.json +1 -1
- package/assets/skill.md +0 -1333
|
@@ -0,0 +1,157 @@
|
|
|
1
|
+
# Sprint Management
|
|
2
|
+
|
|
3
|
+
## Sprint Management
|
|
4
|
+
|
|
5
|
+
### `lumo sprint list [flags]` — list sprints in a team
|
|
6
|
+
|
|
7
|
+
Prints fixed-width rows: `<NUMBER> <STATUS> <start> <end> <name>`, sorted newest-first (server sort).
|
|
8
|
+
|
|
9
|
+
| Flag | Type | Notes |
|
|
10
|
+
| ---------------------- | ------- | ----------------------------------------------------- |
|
|
11
|
+
| `--team <ref>` | string | Team name or slug. Required in multi-team workspaces. |
|
|
12
|
+
| `-s, --status <value>` | enum | `draft \| active \| closed`. |
|
|
13
|
+
| `-n, --limit <count>` | integer | Cap output to the first N rows. |
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
lumo sprint list
|
|
17
|
+
lumo sprint list --status active
|
|
18
|
+
lumo sprint list --team backend --limit 5
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
When to suggest: user asks "what sprints do we have", "which sprint is active", "list sprints", "show me the current sprint", "active sprints".
|
|
22
|
+
|
|
23
|
+
### `lumo sprint create [flags]` — create a sprint
|
|
24
|
+
|
|
25
|
+
| Flag | Type | Notes |
|
|
26
|
+
| ---------------- | ------ | ----------------------------------------------------- |
|
|
27
|
+
| `--team <ref>` | string | Team name or slug. Required in multi-team workspaces. |
|
|
28
|
+
| `--start <date>` | string | **Required.** YYYY-MM-DD. |
|
|
29
|
+
| `--end <date>` | string | **Required.** YYYY-MM-DD. |
|
|
30
|
+
| `-n, --name <>` | string | Optional. Server fills a default name when omitted. |
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
lumo sprint create --start 2026-06-01 --end 2026-06-14
|
|
34
|
+
lumo sprint create --name "Sprint 4" --start 2026-06-01 --end 2026-06-14 --team backend
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
On success: `Created sprint #<number> "<name>" <id>`.
|
|
38
|
+
|
|
39
|
+
When to suggest: user says "create a sprint", "new sprint", "新建冲刺", "start a new iteration".
|
|
40
|
+
|
|
41
|
+
### `lumo sprint show <identifier> [--team <ref>]` — show sprint detail + task table
|
|
42
|
+
|
|
43
|
+
`<identifier>` accepts a sprint number (e.g. `3`) or a UUID. `--team` is required when using a number in a multi-team workspace.
|
|
44
|
+
|
|
45
|
+
Output: key:value header (number, name, status, dates, team), then a `Progress:` line, then a **`Health:` line** with the sprint's risk level (`HEALTHY` / `WATCH` / `AT-RISK`), then a task table listing every task in the sprint.
|
|
46
|
+
|
|
47
|
+
The risk level reuses the same engine as project/workspace stats, fed the sprint's task set (thresholds come from the workspace risk config — sprints have no per-sprint overrides). When the engine flags reasons they print as `- <detail>` lines under `Health:`, and a `Blockers:` section lists the top offenders per category (`Overdue` / `Stalled` / `Agent fail` / `Stale PRs`). A healthy sprint with no blockers shows just the `Health:` line.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
Progress: 4 / 10
|
|
51
|
+
Health: AT-RISK
|
|
52
|
+
- 4/10 tasks overdue (40%)
|
|
53
|
+
- 3 tasks with no progress in 7 days
|
|
54
|
+
Blockers:
|
|
55
|
+
Overdue: LUM-1 Fix login, LUM-8 Wire API
|
|
56
|
+
Stalled: LUM-5 Add tests
|
|
57
|
+
Stale PRs: #12, #15
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
lumo sprint show 3
|
|
62
|
+
lumo sprint show 3 --team backend
|
|
63
|
+
lumo sprint show 11111111-2222-3333-4444-555555555555
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
When to suggest: user asks "what's in sprint 3", "show me the current sprint", "冲刺里有什么", "what tasks are in this sprint", "is this sprint at risk", "冲刺风险", "sprint health".
|
|
67
|
+
|
|
68
|
+
### `lumo sprint update <identifier> [flags]` — patch a sprint
|
|
69
|
+
|
|
70
|
+
Updates sprint metadata. At least one flag required. **No `--status` flag** — use `lumo sprint start` / `lumo sprint close` to transition status.
|
|
71
|
+
|
|
72
|
+
| Flag | Type | Notes |
|
|
73
|
+
| ---------------- | ------ | --------------------------------------------------------------- |
|
|
74
|
+
| `--team <ref>` | string | Required when identifier is a number in a multi-team workspace. |
|
|
75
|
+
| `-n, --name <>` | string | New name. Cannot be empty. |
|
|
76
|
+
| `--start <date>` | string | YYYY-MM-DD. `--start ""` clears. |
|
|
77
|
+
| `--end <date>` | string | YYYY-MM-DD. `--end ""` clears. |
|
|
78
|
+
|
|
79
|
+
```bash
|
|
80
|
+
lumo sprint update 3 --name "Sprint 4 (extended)"
|
|
81
|
+
lumo sprint update 3 --end 2026-06-21
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
When to suggest: user wants to rename a sprint, extend dates, or fix sprint metadata.
|
|
85
|
+
|
|
86
|
+
### `lumo sprint delete <identifier> --yes` — delete a sprint (DRAFT only)
|
|
87
|
+
|
|
88
|
+
Requires `--yes`; no interactive prompt (agent-friendly). Server rejects with an error if the sprint is ACTIVE or CLOSED.
|
|
89
|
+
|
|
90
|
+
```bash
|
|
91
|
+
lumo sprint delete 3 --yes
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
When to suggest: user wants to remove a draft sprint that was created by mistake.
|
|
95
|
+
|
|
96
|
+
### `lumo sprint start <identifier>` — transition DRAFT → ACTIVE
|
|
97
|
+
|
|
98
|
+
No additional flags. Fails if the sprint is already ACTIVE or CLOSED.
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
lumo sprint start 3
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
When to suggest: user says "start the sprint", "开始冲刺", "kick off sprint 3", "activate sprint".
|
|
105
|
+
|
|
106
|
+
### `lumo sprint close <identifier> [flags]` — transition ACTIVE → CLOSED
|
|
107
|
+
|
|
108
|
+
Handles unfinished tasks based on flags. Without flags: closes only if all tasks are done; otherwise prints a list of unfinished tasks and refuses.
|
|
109
|
+
|
|
110
|
+
| Flag | Type | Notes |
|
|
111
|
+
| --------------- | ------- | -------------------------------------------------------------------------------- |
|
|
112
|
+
| `--move-all` | boolean | Move all unfinished tasks to the next sprint. Requires `--yes`. |
|
|
113
|
+
| `--backlog-all` | boolean | Remove all unfinished tasks from the sprint (send to backlog). Requires `--yes`. |
|
|
114
|
+
| `--yes` | boolean | Required when `--move-all` or `--backlog-all` is given. |
|
|
115
|
+
|
|
116
|
+
```bash
|
|
117
|
+
lumo sprint close 3 # fails if unfinished tasks exist
|
|
118
|
+
lumo sprint close 3 --move-all --yes # move unfinished to next sprint
|
|
119
|
+
lumo sprint close 3 --backlog-all --yes # send unfinished to backlog
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
When to suggest: user says "close the sprint", "关闭冲刺", "end sprint 3", "wrap up the sprint". If they haven't decided what to do with unfinished tasks, ask before adding `--move-all` or `--backlog-all`.
|
|
123
|
+
|
|
124
|
+
### `lumo sprint summary <identifier> [--retry]` — fetch AI-generated sprint retro
|
|
125
|
+
|
|
126
|
+
Prints the AI-generated retrospective summary for the sprint. A 404 response means no summary has been generated yet ("no summary yet").
|
|
127
|
+
|
|
128
|
+
| Flag | Type | Notes |
|
|
129
|
+
| --------- | ------- | ------------------------------------------------- |
|
|
130
|
+
| `--retry` | boolean | Queue a regeneration (async, server returns 202). |
|
|
131
|
+
|
|
132
|
+
```bash
|
|
133
|
+
lumo sprint summary 3
|
|
134
|
+
lumo sprint summary 3 --retry
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
When to suggest: user asks "summarize the sprint", "sprint retro", "give me a summary of sprint 3", "冲刺总结".
|
|
138
|
+
|
|
139
|
+
### `lumo sprint add <identifier> <task>` — bind a task to a sprint
|
|
140
|
+
|
|
141
|
+
Adds `<task>` (e.g. `LUM-48`) to the sprint. Same-team check applies — the task's team must match the sprint's team.
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
lumo sprint add 3 LUM-48
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
When to suggest: user says "add LUM-48 to sprint 3", "把任务挂到冲刺", "put this task in the sprint".
|
|
148
|
+
|
|
149
|
+
### `lumo sprint remove <identifier> <task>` — unbind a task from a sprint
|
|
150
|
+
|
|
151
|
+
Removes `<task>` from the sprint. Idempotent — if the task is not in the sprint, the server returns success.
|
|
152
|
+
|
|
153
|
+
```bash
|
|
154
|
+
lumo sprint remove 3 LUM-48
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
When to suggest: user says "remove LUM-48 from the sprint", "take this task out of sprint 3", "move task to backlog".
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
# Task Context Loading & Context Retrieval
|
|
2
|
+
|
|
3
|
+
## Task Context Loading
|
|
4
|
+
|
|
5
|
+
When the user mentions a task identifier or asks for task background, load the context:
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
lumo task context <identifier>
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Example: `lumo task context LUM-42`
|
|
12
|
+
|
|
13
|
+
### Reading the output
|
|
14
|
+
|
|
15
|
+
The command prints a markdown document to stdout containing:
|
|
16
|
+
|
|
17
|
+
1. **Task header** — identifier, title, status, description
|
|
18
|
+
2. **Memory section** — cross-session learnings accumulated over prior sessions; treat as trusted background context
|
|
19
|
+
3. **Inline source cards** — Slack / web / Figma / artifacts / documents / comments / Pull Requests (see "Context Retrieval" below)
|
|
20
|
+
4. **PR Review 待办** — mirrored PR review comments as a checkbox todo list: each line-level reviewer comment (shown as `` `file:line` `` + the reviewer's ask + a link to the GitHub comment) and each `changes_requested` review summary (shown as "🛑 整体要求改动"). Present only when the task's PR(s) have review comments. This same block is **auto-injected at session start** (alongside the memory section) when the session is bound to a task — so reviewer asks surface without re-running `task context`.
|
|
21
|
+
- The **inline source cards** (Slack / web / Figma / PR) are likewise **auto-injected at session start** when the session is bound, under a single global token budget shared with the memory section (priority: memory > PR > Slack > Figma > web). Cards that don't fit the budget are degraded to a one-line manifest carrying just the title and its `lumo task … show` retrieval command — so you still know they exist and can pull the full content on demand.
|
|
22
|
+
5. **Previous sessions** — ordered newest-first, each with:
|
|
23
|
+
- A headline summary of what was done
|
|
24
|
+
- Unresolved items (carry-over TODOs from that session)
|
|
25
|
+
|
|
26
|
+
### How to use the context
|
|
27
|
+
|
|
28
|
+
- **Unresolved items** from the most recent session are the highest-priority carry-overs — address them before starting new work unless the user says otherwise
|
|
29
|
+
- **PR Review 待办** items are reviewer-requested changes — treat each unchecked box as a TODO to resolve, then reply on the PR (a Lumo comment mirrors back to GitHub)
|
|
30
|
+
- **Memory section** provides validated context that persists across sessions — use it to avoid re-learning decisions or constraints
|
|
31
|
+
- Focus on the **most recent 1–2 sessions** for relevant state; older sessions are for historical reference only
|
|
32
|
+
- If there are **no prior sessions**, this is a fresh start — read the task description carefully and ask clarifying questions if needed
|
|
33
|
+
|
|
34
|
+
## Context Retrieval (按需取全文)
|
|
35
|
+
|
|
36
|
+
LUM-122 split task context injection into tiers. `lumo task context <LUM-N>`
|
|
37
|
+
now emits a **cheap inline card** for each source — a short summary or just
|
|
38
|
+
metadata — instead of dumping full bodies. Slack, docs, artifacts, and comments
|
|
39
|
+
get an **LLM summary**; web links, Figma, and PRs get **metadata only**. Each
|
|
40
|
+
card ends with the **retrieval command** you run to pull the heavy content on
|
|
41
|
+
demand.
|
|
42
|
+
|
|
43
|
+
**How to use it:** when the inline card is not enough and you need the full
|
|
44
|
+
Slack thread, the web page body, the Figma metadata, the entire comment thread,
|
|
45
|
+
or the PR detail — run the matching command below. Pass the same identifier
|
|
46
|
+
(`LUM-N`) plus the id the card shows for that source (a Slack `contextId`, a web
|
|
47
|
+
`linkId`, a Figma `linkId`, or a PR `number`).
|
|
48
|
+
|
|
49
|
+
All five are **read-only** (no live Slack/GitHub/Figma calls except the web body
|
|
50
|
+
fetch). Web/Figma/PR are v1 metadata-degraded: they print a `note:` explaining
|
|
51
|
+
that live content needs an external integration.
|
|
52
|
+
|
|
53
|
+
### `lumo task slack show <identifier> <contextId>` — full Slack thread snapshot
|
|
54
|
+
|
|
55
|
+
Prints the **stored** thread snapshot (no live Slack call), one line per message
|
|
56
|
+
as `author: text`. Author falls back to `@<userId>` when the display name is
|
|
57
|
+
missing. Empty snapshot prints `(no messages in stored snapshot)`.
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
lumo task slack show LUM-42 ctx_abc123
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### `lumo task web show <identifier> <linkId>` — fetched web link body
|
|
64
|
+
|
|
65
|
+
Fetches the page body on demand behind the SSRF guard (cached after first read)
|
|
66
|
+
and prints it as plain text. Empty body prints `(empty body)`. Fetch failures
|
|
67
|
+
(blocked host, timeout) print the server's error message.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
lumo task web show LUM-42 wl_abc123
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `lumo task figma context <identifier> <linkId>` — Figma link metadata
|
|
74
|
+
|
|
75
|
+
**v1 metadata fallback.** Prints the cached design metadata as `file:` /
|
|
76
|
+
`frame:` / `url:` / `synced:` (and `syncError:` if the last sync failed) lines.
|
|
77
|
+
Live design context (layers, variables, code connect) requires the Figma MCP
|
|
78
|
+
server, so the command ends with a `note:` saying so.
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
lumo task figma context LUM-42 cfl_abc123
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
### `lumo task comments list <identifier>` — full comment thread
|
|
85
|
+
|
|
86
|
+
Prints the **entire** comment thread: each comment as `author · createdAt`
|
|
87
|
+
followed by its plain-text body (comment bodies are stored as HTML and stripped
|
|
88
|
+
to text). Replies are indented two spaces under their parent. Author falls back
|
|
89
|
+
to `unknown`. No comments prints `(no comments)`.
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
lumo task comments list LUM-42
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Plural, and distinct from `task comment`.** `task comments list` _reads_ the
|
|
96
|
+
whole thread (this retrieval command). `task comment <identifier> <body>`
|
|
97
|
+
_writes_ a single new comment (see Task Management). Don't confuse the two —
|
|
98
|
+
the plural `comments` is read-only.
|
|
99
|
+
|
|
100
|
+
### `lumo task pr show <identifier> <number>` — synced PR metadata
|
|
101
|
+
|
|
102
|
+
**v1 metadata fallback.** Prints the synced PR record: a `#<number> (repo)
|
|
103
|
+
title` header, then `state:` (with ` · draft` when draft), `ci:`, `author:`,
|
|
104
|
+
`branch: <head> → <base>`, and `url:` lines. The live diff + review comments
|
|
105
|
+
require the GitHub integration, so the command ends with a `note:` saying so.
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
lumo task pr show LUM-42 128
|
|
109
|
+
```
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# Task Management
|
|
2
|
+
|
|
3
|
+
## Task Management
|
|
4
|
+
|
|
5
|
+
### `lumo task create <title> [flags]` — create a new task
|
|
6
|
+
|
|
7
|
+
Use this when the user wants to file a new task from the terminal. Title is positional and required (non-empty).
|
|
8
|
+
|
|
9
|
+
| Flag | Type | Notes |
|
|
10
|
+
| ------------------------ | ------ | ----------------------------------------------------------------------------------- |
|
|
11
|
+
| `-d, --description <>` | string | Optional task description. |
|
|
12
|
+
| `-p, --priority <level>` | enum | `low \| medium \| high \| urgent` (case-insensitive). Defaults to `low`. |
|
|
13
|
+
| `-a, --assignee <ref>` | string | `me`, an email, or a member name. Defaults to `me`. |
|
|
14
|
+
| `--project <ref>` | string | Project name or slug. **Required when the workspace has more than one project.** |
|
|
15
|
+
| `--milestone <ref>` | string | Milestone name (case-insensitive) within the resolved project. Omit for no binding. |
|
|
16
|
+
| `--sprint <ref>` | string | Sprint number or UUID to bind the task to on creation. Omit for no sprint binding. |
|
|
17
|
+
| `--tag <name>` | string | Attach a tag by name (repeatable). The name is resolved to an id server-side. |
|
|
18
|
+
| `--tag-id <cuid>` | string | Attach a tag by cuid (repeatable). Combines with `--tag`. |
|
|
19
|
+
|
|
20
|
+
Examples:
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
lumo task create "Fix Slack OAuth redirect bug"
|
|
24
|
+
lumo task create "Refactor task service" --priority high --description "Split into smaller files"
|
|
25
|
+
lumo task create "Investigate slow query" --assignee teammate@example.com --project backend
|
|
26
|
+
lumo task create "Plan API design" --milestone "v1.0"
|
|
27
|
+
lumo task create "Sprint task" --sprint 3
|
|
28
|
+
lumo task create "Add rate limiting" --tag bug --tag urgent
|
|
29
|
+
lumo task create "Spike: evaluate caching options" --tag rfc --tag-id clm_abc123
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
On success the command prints one or two lines to stdout:
|
|
33
|
+
|
|
34
|
+
```
|
|
35
|
+
Created LUM-55 "Title" https://www.uselumo.ai/workspace/<slug>/my-tasks/LUM-55
|
|
36
|
+
Tags: bug, urgent
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
The `Tags:` line is omitted when no tags were attached.
|
|
40
|
+
|
|
41
|
+
If the workspace has multiple projects and `--project` is omitted, the server will return an error naming available projects — re-run with `--project <slug>`.
|
|
42
|
+
|
|
43
|
+
### When to suggest `task create`
|
|
44
|
+
|
|
45
|
+
- The user describes wanting a new task ("create a task to ...", "file a bug for ...", "log a TODO that ...", "add a task: ...").
|
|
46
|
+
- After a discussion that ends in "let's track that separately" — offer to create the task with a sensible title and priority.
|
|
47
|
+
- For follow-ups discovered mid-work that shouldn't bloat the current change — propose `task create` rather than silently expanding scope.
|
|
48
|
+
|
|
49
|
+
### `lumo task update <identifier> [flags]` — patch a task
|
|
50
|
+
|
|
51
|
+
Pure flag-driven update. Provide at least one of:
|
|
52
|
+
|
|
53
|
+
| Flag | Type | Notes |
|
|
54
|
+
| ------------------------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
55
|
+
| `-t, --title <text>` | string | Cannot be empty. |
|
|
56
|
+
| `-d, --description <>` | string | `--description ""` clears the field. |
|
|
57
|
+
| `-s, --status <value>` | enum | `todo \| in_progress \| in_review \| done` (case-insensitive). |
|
|
58
|
+
| `-p, --priority <lvl>` | enum | `low \| medium \| high \| urgent`. |
|
|
59
|
+
| `-a, --assignee <ref>` | string | `me`, an email, or a member name. `--assignee ""` clears the field. |
|
|
60
|
+
| `--milestone <ref>` | string | Milestone name (case-insensitive) within the task's project. `--milestone ""` unbinds. |
|
|
61
|
+
| `--sprint <ref>` | string | Sprint number or UUID to bind the task to. `--sprint ""` clears the current sprint binding (idempotent when already unbound). |
|
|
62
|
+
| `--tag <name>` | string (repeatable) | **Bulk replace** the tag set by name. Creates tag if missing. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
63
|
+
| `--tag-id <cuid>` | string (repeatable) | **Bulk replace** the tag set by id. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
64
|
+
| `--add-tag <name>` | string (repeatable) | Attach tag by name (find-or-create). Max 20. |
|
|
65
|
+
| `--add-tag-id <cuid>` | string (repeatable) | Attach tag by id. Max 20. |
|
|
66
|
+
| `--remove-tag <name>` | string (repeatable) | Detach tag by name. `--remove-tag <name>` resolves the name via find-or-create on the workspace. If the name was unknown, a new Tag row is created (orphan, no attachments) before the detach runs as a no-op. Use `--remove-tag-id <cuid>` to avoid orphans. Max 20. |
|
|
67
|
+
| `--remove-tag-id <cuid>` | string (repeatable) | Detach tag by id. Unknown ids are a no-op (no side effects). Max 20. |
|
|
68
|
+
|
|
69
|
+
`--tag` / `--tag-id` (bulk replace) are mutually exclusive with `--add-tag` / `--add-tag-id` / `--remove-tag` / `--remove-tag-id`. The CLI errors before any network call if both families are mixed.
|
|
70
|
+
|
|
71
|
+
Examples:
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
lumo task update LUM-48 --status in_progress
|
|
75
|
+
lumo task update LUM-48 --title "Update task from CLI" --priority high
|
|
76
|
+
lumo task update LUM-48 --assignee me
|
|
77
|
+
lumo task update LUM-48 --description "" # clear description
|
|
78
|
+
lumo task update LUM-48 --assignee "" # unassign
|
|
79
|
+
lumo task update LUM-48 --milestone "v1.0" # attach to milestone
|
|
80
|
+
lumo task update LUM-48 --milestone "" # unbind milestone
|
|
81
|
+
lumo task update LUM-48 --sprint 3 # bind to sprint #3
|
|
82
|
+
lumo task update LUM-48 --sprint "" # unbind from current sprint
|
|
83
|
+
lumo task update LUM-42 --add-tag urgent --remove-tag draft
|
|
84
|
+
lumo task update LUM-42 --tag final --tag approved # replace entire tag set
|
|
85
|
+
lumo task update LUM-42 --tag final --add-tag oops
|
|
86
|
+
# Error: --tag/--tag-id are mutually exclusive with --add-tag/--add-tag-id/--remove-tag/--remove-tag-id
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
On success the command prints one or two lines to stdout:
|
|
90
|
+
|
|
91
|
+
```
|
|
92
|
+
Updated LUM-48 "Title" https://www.uselumo.ai/workspace/<slug>/my-tasks/LUM-48
|
|
93
|
+
Tags: urgent
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
The `Tags:` line is omitted when the resulting tag set is empty.
|
|
97
|
+
|
|
98
|
+
### When to suggest `task update`
|
|
99
|
+
|
|
100
|
+
- The user describes a state change in natural language (e.g. "mark LUM-48 as in progress", "rename LUM-12 to ...", "assign LUM-30 to me", "bump the priority on LUM-7").
|
|
101
|
+
- After the agent finishes a task and the user confirms — offer to move it to `done`.
|
|
102
|
+
- Multiple status changes in a row should each be a separate `update` invocation rather than batched.
|
|
103
|
+
|
|
104
|
+
### Sprint output format
|
|
105
|
+
|
|
106
|
+
`--sprint <ref>` prints a `Sprint:` line after the update line showing old → new binding:
|
|
107
|
+
|
|
108
|
+
```
|
|
109
|
+
Sprint: - → #3 # newly bound (was backlog)
|
|
110
|
+
Sprint: #2 → #3 # rebound (moved from sprint 2 to 3)
|
|
111
|
+
Sprint: #3 → - # unbound
|
|
112
|
+
Task LUM-48 already in sprint #3 # noop (same sprint)
|
|
113
|
+
Task LUM-48 has no sprint binding # noop (already unbound)
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`--sprint` can be combined with other update flags. The PATCH (field updates) runs first; the sprint operation runs after. Failures on either step pass through independently without rollback.
|
|
117
|
+
|
|
118
|
+
### Out of scope
|
|
119
|
+
|
|
120
|
+
The CLI does **not** currently update due date or parent task. Those need to be edited in the web UI.
|
|
121
|
+
|
|
122
|
+
Milestone updates (`--milestone`) and sprint binding (`--sprint`) both work. Full milestone CRUD is available via `lumo milestone create / show / update / delete`, tasks can be bound/unbound in bulk via `lumo milestone add / remove <identifier> <task...>`, and milestones can be manually reordered via `lumo milestone reorder <ref...>` / `lumo milestone move <ref> --before|--after <ref>` (see below). Full sprint CRUD is available via `lumo sprint create / show / update / delete / start / close / add / remove` (see below).
|
|
123
|
+
|
|
124
|
+
### `lumo task list [flags]` — list tasks assigned to you
|
|
125
|
+
|
|
126
|
+
Default behavior: prints every task currently assigned to the authenticated user (no project / status filter), one per line, as a fixed-width table:
|
|
127
|
+
|
|
128
|
+
```
|
|
129
|
+
LUM-42 IN_PROGRESS HIGH web Fix Slack OAuth redirect
|
|
130
|
+
LUM-48 TODO MEDIUM backend Investigate slow query
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
| Flag | Type | Notes |
|
|
134
|
+
| ----------------------- | ------- | -------------------------------------------------------------------------------- |
|
|
135
|
+
| `-s, --status <value>` | enum | Filter by status: `todo \| in_progress \| in_review \| done` (case-insensitive). |
|
|
136
|
+
| `-p, --project <ref>` | string | Filter by project name (case-insensitive match against display name). |
|
|
137
|
+
| `-m, --milestone <ref>` | string | Filter to my tasks under this milestone (name or UUID). |
|
|
138
|
+
| `-n, --limit <count>` | integer | Cap output to the first N rows. |
|
|
139
|
+
|
|
140
|
+
When `--milestone` is set, the CLI calls `GET /api/milestones/<id>/tasks?assigned=me` instead of `/api/tasks/me`. Other filters (`--status`, `--limit`) still apply client-side to the milestone-scoped result.
|
|
141
|
+
|
|
142
|
+
Filtering is currently client-side — the server returns the full "my tasks" set and the CLI slices it. This is fine for typical workspaces (dozens of tasks); revisit if a workspace has hundreds.
|
|
143
|
+
|
|
144
|
+
### When to suggest `task list`
|
|
145
|
+
|
|
146
|
+
- The user asks "what am I working on", "what tasks do I have", "list my tasks", "show me my queue".
|
|
147
|
+
- Before suggesting a status change ("mark something as done"), if no task ID is in context — run `task list` first to surface candidates.
|
|
148
|
+
|
|
149
|
+
### `lumo next [--count <N>]` — recommend the next task to work on
|
|
150
|
+
|
|
151
|
+
Ranks the tasks assigned to you and prints the top N (default 3), each with a
|
|
152
|
+
one-line reason. Read-only — it does **not** bind or load context. Pick one from
|
|
153
|
+
the list, then run `lumo session attach <LUM-N>` + `lumo task context <LUM-N>`.
|
|
154
|
+
|
|
155
|
+
Ranking is lexicographic: **priority** (URGENT→LOW) first, then **active-sprint
|
|
156
|
+
membership**, then **due date** (earlier first), then in-flight status
|
|
157
|
+
(IN_PROGRESS / IN_REVIEW ahead of TODO). DONE tasks are excluded. The active
|
|
158
|
+
sprint lookup is best-effort — if it fails the command still recommends, just
|
|
159
|
+
without the sprint boost.
|
|
160
|
+
|
|
161
|
+
| Flag | Type | Notes |
|
|
162
|
+
| ----------------- | ------- | ----------------------------------------------------------------------- |
|
|
163
|
+
| `-n, --count <N>` | integer | How many tasks to recommend. Defaults to 3. Must be a positive integer. |
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
lumo next
|
|
167
|
+
lumo next --count 1
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
Output:
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
Top 3 recommended tasks (of 12 open):
|
|
174
|
+
|
|
175
|
+
1. LUM-42 IN_PROGRESS URGENT Fix Slack OAuth redirect
|
|
176
|
+
↳ URGENT · active sprint · due 2026-06-03 (overdue) · in progress
|
|
177
|
+
2. LUM-48 TODO HIGH Investigate slow query
|
|
178
|
+
↳ HIGH · active sprint
|
|
179
|
+
3. LUM-12 TODO MEDIUM Add rate limiting
|
|
180
|
+
↳ MEDIUM · due 2026-06-10
|
|
181
|
+
|
|
182
|
+
Next: lumo session attach LUM-42 && lumo task context LUM-42
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
When to suggest: the user asks "what should I work on", "what's next", "推荐下一个任务", "pick my next task", or starts a session without a task in mind. After they choose, run `session attach` + `task context` for the picked task.
|
|
186
|
+
|
|
187
|
+
### `lumo task show <identifier>` — print one task's detail
|
|
188
|
+
|
|
189
|
+
Returns a key:value block for a single task — title, status, priority, project, assignee (with display name from Clerk), URL, and the full description below. Lighter than `task context` because it does not load prior session summaries or memory.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
lumo task show LUM-42
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Use when the user wants the current state of a task without the agent-handoff payload.
|
|
196
|
+
|
|
197
|
+
### `lumo task comment <identifier> <body>` — leave a comment
|
|
198
|
+
|
|
199
|
+
Two-step under the hood (resolve LUM-N → POST comment). Body is plain text; quote it to embed spaces or newlines.
|
|
200
|
+
|
|
201
|
+
```bash
|
|
202
|
+
lumo task comment LUM-42 "Reproduced the redirect bug on staging — Safari only, not Chrome."
|
|
203
|
+
```
|
|
204
|
+
|
|
205
|
+
The CLI does not support @-mention chip syntax. If the user wants to ping someone, they should comment from the web UI.
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatArchiveResult = formatArchiveResult;
|
|
4
|
+
exports.milestoneArchive = milestoneArchive;
|
|
5
|
+
const config_1 = require("../lib/config");
|
|
6
|
+
const api_1 = require("../lib/api");
|
|
7
|
+
const resolve_1 = require("../lib/resolve");
|
|
8
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
9
|
+
function formatArchiveResult(name) {
|
|
10
|
+
return `Archived "${(0, sanitize_1.sanitizeField)(name)}"`;
|
|
11
|
+
}
|
|
12
|
+
async function milestoneArchive(identifier, opts) {
|
|
13
|
+
const creds = (0, config_1.readCredentials)();
|
|
14
|
+
if (!creds) {
|
|
15
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
16
|
+
return 1;
|
|
17
|
+
}
|
|
18
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
19
|
+
const base = (0, api_1.trimTrailingSlash)(apiUrl);
|
|
20
|
+
let milestoneId;
|
|
21
|
+
try {
|
|
22
|
+
const resolved = await (0, resolve_1.resolveMilestoneId)(base, creds.token, identifier, opts.project);
|
|
23
|
+
milestoneId = resolved.id;
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
|
|
27
|
+
return 1;
|
|
28
|
+
}
|
|
29
|
+
let res;
|
|
30
|
+
try {
|
|
31
|
+
res = await fetch(`${base}/api/milestones/${milestoneId}/archive`, {
|
|
32
|
+
method: 'POST',
|
|
33
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
catch (err) {
|
|
37
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
38
|
+
console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
if (res.status === 401) {
|
|
42
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
if (!res.ok) {
|
|
46
|
+
let errMsg = `milestone archive failed (HTTP ${res.status})`;
|
|
47
|
+
try {
|
|
48
|
+
const body = (await res.json());
|
|
49
|
+
if (body.error)
|
|
50
|
+
errMsg = body.error;
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// non-JSON body; keep the status-only message
|
|
54
|
+
}
|
|
55
|
+
console.error(`Error: ${(0, sanitize_1.sanitizeField)(errMsg)}`);
|
|
56
|
+
return 1;
|
|
57
|
+
}
|
|
58
|
+
const { milestone } = (await res.json());
|
|
59
|
+
process.stdout.write(formatArchiveResult(milestone.name) + '\n');
|
|
60
|
+
}
|
|
@@ -6,6 +6,14 @@ const config_1 = require("../lib/config");
|
|
|
6
6
|
const api_1 = require("../lib/api");
|
|
7
7
|
const resolve_1 = require("../lib/resolve");
|
|
8
8
|
const sanitize_1 = require("../lib/sanitize");
|
|
9
|
+
const HEALTH_LABEL = {
|
|
10
|
+
'on-track': 'ON-TRACK',
|
|
11
|
+
'at-risk': 'AT-RISK',
|
|
12
|
+
overdue: 'OVERDUE',
|
|
13
|
+
};
|
|
14
|
+
function formatHealth(health) {
|
|
15
|
+
return health ? HEALTH_LABEL[health] : '-';
|
|
16
|
+
}
|
|
9
17
|
function formatDate(iso) {
|
|
10
18
|
if (!iso)
|
|
11
19
|
return '-';
|
|
@@ -13,7 +21,10 @@ function formatDate(iso) {
|
|
|
13
21
|
}
|
|
14
22
|
/**
|
|
15
23
|
* Render milestones as fixed-width rows:
|
|
16
|
-
* <STATUS> <target-date or -> <name>
|
|
24
|
+
* <STATUS> <HEALTH> <target-date or -> <name>
|
|
25
|
+
*
|
|
26
|
+
* HEALTH is the target-date risk light (ON-TRACK / AT-RISK / OVERDUE), or `-`
|
|
27
|
+
* when no light applies (terminal status or no target date).
|
|
17
28
|
*
|
|
18
29
|
* Sorted server-side by targetDate asc nulls last, createdAt asc.
|
|
19
30
|
*/
|
|
@@ -21,9 +32,10 @@ function formatMilestoneList(rows) {
|
|
|
21
32
|
if (rows.length === 0)
|
|
22
33
|
return 'No milestones.';
|
|
23
34
|
const statusW = Math.max(...rows.map(r => r.status.length));
|
|
35
|
+
const healthW = Math.max(...rows.map(r => formatHealth(r.health).length));
|
|
24
36
|
const dateW = Math.max(...rows.map(r => formatDate(r.targetDate).length));
|
|
25
37
|
return rows
|
|
26
|
-
.map(r => `${r.status.padEnd(statusW)} ${formatDate(r.targetDate).padEnd(dateW)} ${(0, sanitize_1.sanitizeField)(r.name)}`)
|
|
38
|
+
.map(r => `${r.status.padEnd(statusW)} ${formatHealth(r.health).padEnd(healthW)} ${formatDate(r.targetDate).padEnd(dateW)} ${r.archivedAt ? `${(0, sanitize_1.sanitizeField)(r.name)} (archived)` : (0, sanitize_1.sanitizeField)(r.name)}`)
|
|
27
39
|
.join('\n');
|
|
28
40
|
}
|
|
29
41
|
async function milestoneList(options) {
|
|
@@ -43,9 +55,16 @@ async function milestoneList(options) {
|
|
|
43
55
|
console.error(`Error: ${msg}`);
|
|
44
56
|
return 1;
|
|
45
57
|
}
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
58
|
+
if (options.archived && options.all) {
|
|
59
|
+
console.error('Error: --archived and --all are mutually exclusive.');
|
|
60
|
+
return 1;
|
|
61
|
+
}
|
|
62
|
+
const filter = options.all ? 'all' : options.archived ? 'archived' : 'active';
|
|
63
|
+
const query = new URLSearchParams({ filter });
|
|
64
|
+
const term = options.search?.trim();
|
|
65
|
+
if (term)
|
|
66
|
+
query.set('search', term);
|
|
67
|
+
const res = await fetch(`${base}/api/projects/${projectId}/milestones?${query.toString()}`, { headers: { Authorization: `Bearer ${creds.token}` } });
|
|
49
68
|
if (!res.ok) {
|
|
50
69
|
console.error(`Error: milestone list failed (HTTP ${res.status})`);
|
|
51
70
|
return 1;
|