@lumoai/cli 1.24.0 → 1.25.1

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.
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lumo
3
- description: 'Use the Lumo CLI to load task context, manage session bindings, and run tasks / projects / milestones / sprints / docs / memory from the terminal. Activate when: the user mentions a Lumo task identifier (LUM-42 etc.), asks to load task background/context, wants to bind/check/detach a Claude Code session''s task, is about to start work on a task, or wants to create/update/list/show/comment on tasks, projects, milestones, sprints, documents, artifacts, Figma links, or memory. Triggers on: "LUM-", "task context", "load context", "session start", "session attach", "session status", "session detach", "which task", "what task am I on", "work on LUM", "session wrap", "wrap up session", "进度评论", "卡住检测", "fragment usage vote", "mark used fragments", "which fragments did I use", "--used", "上下文使用投票", "标记用过的记忆", "create task", "new task", "file a task", "list tasks", "my tasks", "show task", "view task", "comment on task", "update task", "change task status", "rename task", "reassign task", "mark task as done", "lumo next", "next task", "what should I work on", "推荐下一个任务", "list projects", "what projects", "milestone", "里程碑", "list/create/update/delete/show milestone", "set milestone", "attach/unbind milestone", "tasks in milestone", "search milestones", "find milestone", "milestone health", "at-risk", "overdue", "archive/unarchive milestone", "归档里程碑", "milestone summary", "里程碑复盘", "reorder/move milestone", "排序里程碑", "milestone add/remove", "挂任务到里程碑", "auth login", "log in", "logout", "sign out", "switch account", "whoami", "who am I", "current workspace", "登录", "切换账号", "create/update/list/show/delete doc", "write doc", "写文档", "新建文档", "修改文档", "查看文档", "bind/unbind doc", "把文档关联到任务", "doc scope", "personal/workspace doc", "tag", "add/remove tag", "标签", "share/unshare doc", "分享文档", "doc share-list", "viewer/editor/manager", "doc tree", "doc move", "move/reparent doc", "移动文档", "sprint", "冲刺", "迭代", "create/list/show/update/delete sprint", "start/close sprint", "开始/关闭冲刺", "add to sprint", "active sprints", "sprint summary", "冲刺总结", "把任务挂到冲刺", "sprint health", "sprint risk", "is this sprint at risk", "冲刺风险", "冲刺健康度", "sprint blockers", "冲刺阻塞", "lumo update", "upgrade lumo", "升级 lumo", "new lumo version", "lumo setup", "install lumo skill/hooks", "wire up lumo", "set up lumo", "安装 lumo", "配置 lumo", "task artifact", "artifact add/list/show/update/rm", "spec artifact", "record/attach spec", "attach plan", "记录 spec", "查看 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design, "memory", "记忆", "remember", "record a memory", "记一条", "promote memory", "promote to project", "沉淀", "task/project memory", "retrieval", "取全文", "拉全文", "task slack show", "看 thread", "show slack thread", "task web show", "web 正文", "task figma context", "figma metadata", "task comments list", "list comments", "看评论", "task pr show", "查看 PR", "show pr", "PR 详情", "task deps", "dependency", "dependencies", "依赖", "依赖边", "blocked by", "blocker", "confirm dependency", "dismiss dependency", "确认依赖", "忽略依赖", "import google doc", "sync google doc", "google drive", "doc import-gdoc", "doc sync", "导入/同步 google 文档", "mark blocked", "blocked tag", "标记 blocked", "stuck", "repeatedly failing", "worktree", "git worktree", "并行 worktree", "scaffold worktree", "新建 worktree", "node_modules 软链", "worktree 隔离", "lumo worktree add/rm/list", "task criteria", "criteria set", "criteria list", "acceptance criteria", "验收标准", "验收合约", "draft criteria", "草拟验收", "definition of done", "lumo verify", "verify task", "machine verification", "verification round", "机器验收", "自验", "验收轮", "claim done", "宣称完成", "--cause", "contract drift", "合约漂移", "task lineage", "lineage", "causal trail", "审计", "因果链", "成本归因", "trace context", "--signal", "usage signal health", "auto usage audit", "自动使用审计", "signal-health".'
3
+ description: 'Use the Lumo CLI to work with Lumo (project management for dev teams) from the terminal: load task context, bind/wrap Claude Code sessions, and create/update/list/show/comment on tasks, projects, milestones, sprints, documents, artifacts, Figma links, dependencies, and team memory plus acceptance criteria, machine verification (verify / task status), lineage audit, worktree scaffolding, and CLI setup/auth/update. Activate when the user mentions a Lumo task identifier (LUM-N) or the lumo CLI; asks to load task background or bind/check/wrap a session; manages any of the resources above in Lumo; is starting, resuming, or about to claim completion of a task; or asks what to work on next. Key triggers: "LUM-", "lumo", "task context", "session attach", "session wrap", "验收", "verify", "task status", "milestone", "里程碑", "sprint", "冲刺", "文档", "记忆", "memory", "依赖", "deps", "lineage", "worktree", "下一个任务", "what should I work on", "设计稿", "验收标准", "机器验收", "恢复任务".'
4
4
  ---
5
5
 
6
6
  ## Prerequisites
@@ -26,7 +26,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
26
26
  | `task create/update/list/show/comment`, `next` | [references/tasks.md](references/tasks.md) |
27
27
  | `task artifact*`, `task figma*` | [references/artifacts-figma.md](references/artifacts-figma.md) |
28
28
  | `task criteria set/list`, drafting the acceptance contract | [references/criteria.md](references/criteria.md) |
29
- | `verify` — machine verification loop, claim-done flow | [references/verify.md](references/verify.md) |
29
+ | `verify`, `task status` — machine verification loop, claim-done flow, 自查/恢复 | [references/verify.md](references/verify.md) |
30
30
  | `project list`, `milestone*` | [references/milestones.md](references/milestones.md) |
31
31
  | `doc*` | [references/docs.md](references/docs.md) |
32
32
  | `sprint*` | [references/sprints.md](references/sprints.md) |
@@ -79,6 +79,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
79
79
  **Verification(机器验收循环)** — see [verify.md](references/verify.md)
80
80
 
81
81
  - `lumo verify [task] [--timeout <seconds>]` — run every MACHINE criterion's checkpointer locally, report one structured PASS/FAIL verdict per criterion to the server, print next actions. Defaults to the session-bound task. Round cap 3: an all-pass round moves the task to IN_REVIEW (agent stops there); a round-3 fail escalates to a human (stop retrying). **Run this before claiming a task is done.**
82
+ - `lumo task status [task] [--json]` — read-only acceptance self-check (no LLM, milliseconds): the contract with each criterion's latest verdict (REVIEW_ADDED provenance visible), verification history, current round, last round's failure reasons, and `nextActions` = the unmet criteria (the declarative "what's next" — no separate plan). Defaults to the session-bound task; `--json` emits a versioned payload (`version` field). **Run it first when resuming a task in a new session or after a verification round was rejected.**
82
83
 
83
84
  **Artifacts & Figma** — see [artifacts-figma.md](references/artifacts-figma.md)
84
85
 
@@ -127,6 +128,20 @@ The command catalog below is a **map**: it lists every command grouped by domain
127
128
  - `lumo worktree rm <LUM-N> --yes` — remove a worktree (keeps the branch unless `--delete-branch`)
128
129
  - `lumo worktree list` — list `.worktrees/` worktrees (task id, branch, dirty, node_modules link)
129
130
 
131
+ ## Commands & flags that do NOT exist (common mistakes)
132
+
133
+ Measured from real agent sessions (LUM-392) — don't guess these:
134
+
135
+ - No `lumo session start` — binding is `lumo session attach <LUM-N>`
136
+ - No `lumo task delete` — tasks can't be deleted from the CLI (web UI only)
137
+ - No `lumo task artifact edit` — it's `lumo task artifact update`
138
+ - No `lumo auth status` — identity check is `lumo whoami`
139
+ - No `--body` on `lumo task comment` — the body is a positional arg: `lumo task comment LUM-N "text"`
140
+ - No `--content` on `task/project memory add` — memory is structured fields (`--category` + per-category flags), see [memory.md](references/memory.md)
141
+ - No global `--verbose` flag on any command
142
+ - `lumo task comments list` (plural) **reads** the thread; `lumo task comment` (singular) **writes** one
143
+ - Status updates: `lumo task update LUM-N --status done` is one direct call — do **not** walk `in_progress → in_review → done` step by step (see [tasks.md](references/tasks.md) for the transition matrix; under the verify flow you shouldn't be setting `in_review`/`done` yourself at all)
144
+
130
145
  ## Core workflow
131
146
 
132
147
  Typical flow when a user says "help me with LUM-42":
@@ -138,4 +153,6 @@ Typical flow when a user says "help me with LUM-42":
138
153
  5. Begin working on the task
139
154
  6. **Before claiming the work is done: run `lumo verify`** — the machine half of the acceptance loop. Fix failures and re-run (round cap 3). On all-pass the task moves to IN_REVIEW and you stop; never set DONE yourself after a verify loop — that adjudication is human-only. See [verify.md](references/verify.md)
140
155
 
156
+ **Status-first recovery:** when you pick a task back up — a new session resuming earlier work, or a task that came back after a rejected verification round / review findings — run `lumo task status` **before** re-reading code or planning. It tells you where the loop stands (current round, what passed, what's unmet and why, any REVIEW_ADDED criteria appended during review) so you don't redo finished work or miss the reason it bounced. See [verify.md](references/verify.md)
157
+
141
158
  **Git-suggest at start:** when the session is unbound, session-start may infer the task from the git branch / recent commits and print a suggestion — `检测到 LUM-N … 运行 lumo session attach LUM-N 绑定。` — **without** binding. Confirm it's the right task, then run `lumo session attach <LUM-N>` yourself (binding only happens on an explicit attach). See [sessions.md](references/sessions.md) for the full session-start behavior.
@@ -95,6 +95,23 @@ Tags: urgent
95
95
 
96
96
  The `Tags:` line is omitted when the resulting tag set is empty.
97
97
 
98
+ ### Status transitions — direct moves are legal
99
+
100
+ The server's transition matrix (`lib/task/state-machine.ts`):
101
+
102
+ | From | Allowed targets |
103
+ | ----------- | --------------------------------------------------- |
104
+ | TODO | IN_PROGRESS, IN_REVIEW, DONE |
105
+ | IN_PROGRESS | TODO, IN_REVIEW, DONE |
106
+ | IN_REVIEW | TODO, IN_PROGRESS, DONE |
107
+ | DONE | TODO, IN_PROGRESS (reopen only — **not** IN_REVIEW) |
108
+
109
+ Practical rules:
110
+
111
+ - **One call suffices.** `--status done` straight from TODO or IN_PROGRESS is legal — never walk `in_progress → in_review → done` as a ritual (measured in LUM-392: 70 such chains wasted ~75 calls).
112
+ - **Under the verify flow you don't set `in_review`/`done` at all** — `lumo verify` moves the task to IN_REVIEW on all-pass and the DONE adjudication is human-only.
113
+ - **DONE → IN_REVIEW is rejected (409).** To attach follow-up context to a DONE task, use `lumo task comment` instead of reopening.
114
+
98
115
  ### When to suggest `task update`
99
116
 
100
117
  - 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").
@@ -237,11 +254,13 @@ DISMISSED
237
254
  CONFIRMED and SUGGESTED rows show the other task's identifier, title, current status, and source/evidence. DISMISSED rows render as `[shortId] <direction> <identifier> · 已忽略` only — no title, status, or source.
238
255
 
239
256
  When there are no edges at all the output is:
257
+
240
258
  ```
241
259
  Dependencies for LUM-42: 无依赖边。
242
260
  ```
243
261
 
244
262
  **Evidence fields by detection signal:**
263
+
245
264
  - `shared_files` — `shared_files(N 个共享文件: path1, path2, …)` — number of shared write-touched files in the 14-day window, plus up to 5 sample paths.
246
265
  - `task_mention` — `task_mention(description)` or `task_mention(comment)` — the surface where the mention appeared.
247
266
 
@@ -258,6 +277,7 @@ lumo task deps add LUM-42 --blocked-by LUM-9
258
277
  Both `<LUM-N>` and `--blocked-by` are required. The command errors on usage if either is missing.
259
278
 
260
279
  **Service semantics (read before using):**
280
+
261
281
  - **Self-edge** → 400 ("A task cannot depend on itself").
262
282
  - **CONFIRMED edge in the same direction already exists** → 409 ("Dependency already exists").
263
283
  - **CONFIRMED edge in the reverse direction already exists** → the cycle guard fires and returns 409 ("Dependency would create a cycle").
@@ -275,11 +295,13 @@ lumo task deps confirm LUM-42 e5f6a7b8 --reverse # flip direction before confir
275
295
  ```
276
296
 
277
297
  **Edge selector semantics** (shared by `confirm`, `dismiss`, `rm`):
298
+
278
299
  - **Other task's identifier** (e.g., `LUM-55`) — case-insensitive exact match against the edge's other-task identifier. Resolves unambiguously when there is exactly one edge to that task.
279
300
  - **Edge-id prefix** — at least 6 characters of the short id (e.g., `e5f6a7`). Must match exactly one edge.
280
301
  - If zero or more than one edge matches → prints all candidate edges with short ids and exits 1. Retry with a more specific selector.
281
302
 
282
303
  **`--reverse` semantics:**
304
+
283
305
  - The detector's direction heuristic is best-effort. If the suggested direction is backwards (e.g., the detector says "LUM-42 blocks LUM-55" but actually LUM-55 blocks LUM-42), confirm with `--reverse` to flip before writing.
284
306
  - The service checks that the reversed pair does not already have an edge (→ 409), and re-runs the cycle guard with the flipped direction.
285
307
 
@@ -70,3 +70,55 @@ Rounds are a hard budget of 3, not a retry loop. Between rounds, actually fix
70
70
  the failures — re-running without changes burns a round and (at round 3)
71
71
  pages a human. A FAIL round never changes task status; only an all-pass round
72
72
  moves it (to IN_REVIEW, never further).
73
+
74
+ ## lumo task status — the read half(自查入口)
75
+
76
+ `lumo task status [task] [--json]` is the read-only counterpart of the loop
77
+ (LUM-344): pure read, milliseconds, no LLM, never writes — running it costs
78
+ nothing and burns no round. Defaults to the session-bound task; an explicit
79
+ identifier overrides.
80
+
81
+ ```
82
+ lumo task status # session-bound task
83
+ lumo task status LUM-42 # explicit task
84
+ lumo task status --json # versioned machine-readable payload
85
+ ```
86
+
87
+ ### When to run it
88
+
89
+ **Status-first recovery:** run it FIRST — before re-reading code or
90
+ planning — whenever you:
91
+
92
+ - resume a task in a new session (yours or another agent's earlier work);
93
+ - come back after a verification round was rejected (`lumo verify` failed);
94
+ - were told the task bounced in review (REVIEW_ADDED criteria may have been
95
+ appended at the round they surfaced — they show up here automatically).
96
+
97
+ It answers "where does the loop stand": what already passed (don't redo it),
98
+ what's unmet and why (the exact failure tails), and how many rounds are left.
99
+
100
+ ### What it prints
101
+
102
+ - Header: task identifier/title/status + `verification round N/3` (round 0 =
103
+ never verified) + an escalation warning when the machine loop is exhausted.
104
+ - **Criteria** — every criterion as `<glyph> <id> [TYPE] SOURCE@rN
105
+ statement` (✓ latest verdict passed / ✗ failed / ○ no verdict yet) with its
106
+ checkpointer and latest verdict line (evidence pointer on pass, failure
107
+ tail on fail). `REVIEW_ADDED@rN` provenance is visible per row.
108
+ - **History** — one line per recorded round: `rN · timestamp · X PASS / Y FAIL`.
109
+ - **Last round failures** — the most recent round's FAIL verdicts with their
110
+ rejection reasons (why the last round bounced).
111
+ - **Next actions** — the unmet criteria (latest verdict is not a pass:
112
+ failed or never verified, HUMAN ones included). This list IS the plan —
113
+ it is recomputed from the event log on every read, never maintained
114
+ separately. Empty + rounds recorded = awaiting human adjudication.
115
+
116
+ ### --json contract
117
+
118
+ `--json` emits the full read model with a top-level `version` field
119
+ (currently `1`). The schema is versioned: breaking shape changes bump the
120
+ major; additive fields don't. Pin on `version` when scripting against it.
121
+
122
+ `status` reads; `verify` judges. Running status never starts a round, never
123
+ escalates, and never changes task state — loop rules (cap 3, IN_REVIEW on
124
+ all-pass, human-only DONE) live entirely in `lumo verify` and the server.
@@ -9,6 +9,10 @@ const doc_create_1 = require("./doc-create");
9
9
  const doc_tree_1 = require("../lib/doc-tree");
10
10
  const sanitize_1 = require("../lib/sanitize");
11
11
  function visibilityLabel(v) {
12
+ // Some routes (e.g. /api/tasks/<id>/documents) have returned rows without a
13
+ // visibility field at runtime — fall back instead of crashing on .padEnd.
14
+ if (!v)
15
+ return '-';
12
16
  if (v === 'PRIVATE')
13
17
  return 'PERSONAL';
14
18
  return v;
@@ -20,7 +20,10 @@ async function memoryPromote(memoryId) {
20
20
  try {
21
21
  res = await fetch(`${base}/api/memories/${encodeURIComponent(memoryId)}`, {
22
22
  method: 'PATCH',
23
- headers: { Authorization: `Bearer ${creds.token}`, 'Content-Type': 'application/json' },
23
+ headers: {
24
+ Authorization: `Bearer ${creds.token}`,
25
+ 'Content-Type': 'application/json',
26
+ },
24
27
  body: JSON.stringify({ scope: 'PROJECT' }),
25
28
  });
26
29
  }
@@ -29,7 +32,7 @@ async function memoryPromote(memoryId) {
29
32
  return 1;
30
33
  }
31
34
  if (res.status === 404) {
32
- console.error(`Error: memory ${memoryId} not found`);
35
+ console.error(`Error: memory ${memoryId} not found — pass the full memory id (cuid) from \`lumo task memory list\` / \`lumo project memory list\`; truncated id prefixes are not resolved`);
33
36
  return 1;
34
37
  }
35
38
  if (res.status === 409) {
@@ -43,8 +46,12 @@ async function memoryPromote(memoryId) {
43
46
  if (typeof b.error === 'string')
44
47
  m = b.error;
45
48
  }
46
- catch { /* */ }
47
- console.error(m ? `Error: ${(0, sanitize_1.sanitizeField)(m)}` : `Error: promote failed (HTTP ${res.status})`);
49
+ catch {
50
+ /* */
51
+ }
52
+ console.error(m
53
+ ? `Error: ${(0, sanitize_1.sanitizeField)(m)}`
54
+ : `Error: promote failed (HTTP ${res.status})`);
48
55
  return 1;
49
56
  }
50
57
  process.stdout.write(`Promoted ${memoryId} to PROJECT — every agent on this project now sees it.\n` +
@@ -31,7 +31,7 @@ async function memoryRm(memoryId, options) {
31
31
  return 1;
32
32
  }
33
33
  if (res.status === 404) {
34
- console.error(`Error: memory ${memoryId} not found`);
34
+ console.error(`Error: memory ${memoryId} not found — pass the full memory id (cuid) from \`lumo task memory list\` / \`lumo project memory list\`; truncated id prefixes are not resolved`);
35
35
  return 1;
36
36
  }
37
37
  if (res.status !== 204) {
@@ -44,7 +44,7 @@ async function taskArtifactAdd(identifier, options) {
44
44
  const verdict = (0, path_guard_1.checkArtifactFilePath)(options.file);
45
45
  if (!verdict.ok) {
46
46
  if (verdict.reason === 'unreadable') {
47
- console.error(`Error: could not read file ${options.file}`);
47
+ console.error(`Error: ${(0, doc_input_1.unreadableFileMessage)(options.file)}`);
48
48
  }
49
49
  else {
50
50
  console.error(`Error: refusing to read ${options.file} — ${verdict.detail}. ` +
@@ -57,7 +57,7 @@ async function taskArtifactAdd(identifier, options) {
57
57
  content = await (0, doc_input_1.readFileUtf8)(verdict.resolved);
58
58
  }
59
59
  catch {
60
- console.error(`Error: could not read file ${options.file}`);
60
+ console.error(`Error: ${(0, doc_input_1.unreadableFileMessage)(options.file)}`);
61
61
  return 1;
62
62
  }
63
63
  if (content.trim().length === 0) {
@@ -55,7 +55,7 @@ async function taskCriteriaSet(identifier, options) {
55
55
  const verdict = (0, path_guard_1.checkArtifactFilePath)(options.file);
56
56
  if (!verdict.ok) {
57
57
  if (verdict.reason === 'unreadable') {
58
- console.error(`Error: could not read file ${options.file}`);
58
+ console.error(`Error: ${(0, doc_input_1.unreadableFileMessage)(options.file)}`);
59
59
  }
60
60
  else {
61
61
  console.error(`Error: refusing to read ${options.file} — ${verdict.detail}. ` +
@@ -68,7 +68,7 @@ async function taskCriteriaSet(identifier, options) {
68
68
  raw = await (0, doc_input_1.readFileUtf8)(verdict.resolved);
69
69
  }
70
70
  catch {
71
- console.error(`Error: could not read file ${options.file}`);
71
+ console.error(`Error: ${(0, doc_input_1.unreadableFileMessage)(options.file)}`);
72
72
  return 1;
73
73
  }
74
74
  const parsed = parseCriteriaJson(raw);
@@ -0,0 +1,166 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.formatTaskStatus = formatTaskStatus;
4
+ exports.taskStatus = taskStatus;
5
+ const config_1 = require("../lib/config");
6
+ const api_1 = require("../lib/api");
7
+ const resolve_bound_task_1 = require("../lib/resolve-bound-task");
8
+ const sanitize_1 = require("../lib/sanitize");
9
+ const REASON_TAIL = 400;
10
+ function tail(s, max) {
11
+ return s.length > max ? `…${s.slice(-max)}` : s;
12
+ }
13
+ /**
14
+ * Render the acceptance status as prose. Same row grammar as
15
+ * `criteria list` (`<id> [TYPE] SOURCE@rN statement`) with a verdict
16
+ * glyph in front — ✓ pass, ✗ fail, ○ no verdict yet — so REVIEW_ADDED
17
+ * provenance stays explicitly visible in every row.
18
+ */
19
+ function formatTaskStatus(data) {
20
+ const lines = [];
21
+ const t = data.task;
22
+ lines.push(`${t.identifier} ${(0, sanitize_1.sanitizeField)(t.title)}`);
23
+ const roundLabel = data.currentRound === 0
24
+ ? 'no verification rounds yet'
25
+ : `verification round ${data.currentRound}/${data.maxRounds}`;
26
+ lines.push(`Status: ${t.status} · ${roundLabel}`);
27
+ if (data.escalated) {
28
+ lines.push('⚠ Escalated: the machine loop is exhausted — a human has been paged. Stop retrying lumo verify.');
29
+ }
30
+ if (data.criteria.length === 0) {
31
+ lines.push('');
32
+ lines.push(`No acceptance criteria on ${t.identifier} — draft 3–7 and submit with lumo task criteria set ${t.identifier} --file <criteria.json>`);
33
+ return lines.join('\n') + '\n';
34
+ }
35
+ lines.push('');
36
+ lines.push(`Criteria (${data.criteria.length} total, ${data.nextActions.length} unmet):`);
37
+ for (const c of data.criteria) {
38
+ const glyph = c.latestVerdict == null
39
+ ? '○'
40
+ : c.latestVerdict.verdict === 'FAIL'
41
+ ? '✗'
42
+ : '✓';
43
+ const provenance = `${c.source}@r${c.addedAtRound}`;
44
+ const evidence = c.evidenceRequired ? ' [evidence]' : '';
45
+ lines.push(` ${glyph} ${c.id} [${c.verifierType}] ${provenance}${evidence} ${(0, sanitize_1.sanitizeField)(c.statement)}`);
46
+ if (c.checkpointer) {
47
+ lines.push(` ↳ check: ${(0, sanitize_1.sanitizeField)(c.checkpointer)}`);
48
+ }
49
+ const v = c.latestVerdict;
50
+ if (v == null) {
51
+ lines.push(' (no verdict yet)');
52
+ }
53
+ else if (v.verdict === 'FAIL') {
54
+ const why = v.rejectionReason
55
+ ? ` — ${(0, sanitize_1.sanitizeField)(tail(v.rejectionReason, REASON_TAIL))}`
56
+ : '';
57
+ lines.push(` ✗ FAIL@r${v.round}${why}`);
58
+ }
59
+ else {
60
+ const evidencePart = v.evidencePointer
61
+ ? ` · ${(0, sanitize_1.sanitizeField)(v.evidencePointer)}`
62
+ : '';
63
+ lines.push(` ✓ ${v.verdict}@r${v.round}${evidencePart}`);
64
+ }
65
+ }
66
+ if (data.verificationHistory.length > 0) {
67
+ lines.push('');
68
+ lines.push('History:');
69
+ for (const h of data.verificationHistory) {
70
+ lines.push(` r${h.round} · ${h.recordedAt} · ${h.passed} PASS / ${h.failed} FAIL`);
71
+ }
72
+ }
73
+ if (data.lastRoundFailures.length > 0) {
74
+ lines.push('');
75
+ lines.push(`Last round (r${data.currentRound}) failures:`);
76
+ for (const f of data.lastRoundFailures) {
77
+ lines.push(` • ${(0, sanitize_1.sanitizeField)(f.statement)}`);
78
+ if (f.rejectionReason) {
79
+ lines.push(` why: ${(0, sanitize_1.sanitizeField)(tail(f.rejectionReason, REASON_TAIL))}`);
80
+ }
81
+ }
82
+ }
83
+ lines.push('');
84
+ if (data.nextActions.length === 0) {
85
+ lines.push(data.currentRound > 0
86
+ ? 'All criteria met by their latest verdicts — awaiting human adjudication.'
87
+ : 'Nothing unmet — but no verification has run; run `lumo verify` to judge the contract.');
88
+ }
89
+ else {
90
+ lines.push(`Next actions (${data.nextActions.length} unmet):`);
91
+ for (const a of data.nextActions) {
92
+ lines.push(` • [${a.verifierType}] ${(0, sanitize_1.sanitizeField)(a.statement)}`);
93
+ if (a.checkpointer) {
94
+ lines.push(` check: ${(0, sanitize_1.sanitizeField)(a.checkpointer)}`);
95
+ }
96
+ if (a.rejectionReason) {
97
+ lines.push(` why: ${(0, sanitize_1.sanitizeField)(tail(a.rejectionReason, REASON_TAIL))}`);
98
+ }
99
+ }
100
+ if (data.escalated) {
101
+ lines.push('Wait for human direction before touching these.');
102
+ }
103
+ else {
104
+ const hasMachine = data.nextActions.some(a => a.verifierType === 'MACHINE');
105
+ const left = data.maxRounds - data.currentRound;
106
+ lines.push(hasMachine
107
+ ? `Fix the unmet criteria, then run \`lumo verify\` (${left} round${left === 1 ? '' : 's'} left).`
108
+ : 'Remaining criteria are HUMAN-only — finish the work and hand off for human review.');
109
+ }
110
+ }
111
+ return lines.join('\n') + '\n';
112
+ }
113
+ /**
114
+ * `lumo task status [task] [--json]` — the agent's read-only self-check
115
+ * (LUM-344): contract + latest verdicts + verification history + next
116
+ * actions, straight from the server's derived read model. Run it first when
117
+ * resuming a task in a new session or after a round was rejected. Defaults
118
+ * to the session-bound task; an explicit identifier overrides.
119
+ */
120
+ async function taskStatus(identifier, options = {}) {
121
+ const creds = (0, config_1.readCredentials)();
122
+ if (!creds) {
123
+ console.error('Error: not logged in. Run `lumo auth login` first.');
124
+ return 1;
125
+ }
126
+ const base = (0, api_1.trimTrailingSlash)((0, api_1.resolveAuthedApiUrl)(creds.apiUrl));
127
+ let taskId = identifier;
128
+ if (!taskId) {
129
+ taskId =
130
+ (await (0, resolve_bound_task_1.resolveBoundTaskIdentifier)(base, creds.token)) ?? undefined;
131
+ if (!taskId) {
132
+ console.error('Error: no task given and this session is not bound to one.\n' +
133
+ 'Run `lumo task status <LUM-N>`, or bind first with `lumo session attach <LUM-N>`.');
134
+ return 1;
135
+ }
136
+ }
137
+ let res;
138
+ try {
139
+ res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/status`, { headers: { Authorization: `Bearer ${creds.token}` } });
140
+ }
141
+ catch (err) {
142
+ const msg = err instanceof Error ? err.message : String(err);
143
+ console.error(`Error: could not reach Lumo API (${msg})`);
144
+ return 1;
145
+ }
146
+ if (res.status === 401) {
147
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
148
+ return 1;
149
+ }
150
+ if (res.status === 404) {
151
+ console.error(`Error: task ${taskId} not found in workspace ${creds.workspaceSlug}`);
152
+ return 1;
153
+ }
154
+ if (!res.ok) {
155
+ console.error(`Error: task status failed (HTTP ${res.status})`);
156
+ return 1;
157
+ }
158
+ const data = (await res.json());
159
+ if (options.json) {
160
+ // JSON.stringify escapes control chars (…), so the payload is safe
161
+ // to emit raw — and consumers get byte-faithful server data.
162
+ process.stdout.write(JSON.stringify(data, null, 2) + '\n');
163
+ return;
164
+ }
165
+ process.stdout.write(formatTaskStatus(data));
166
+ }
@@ -53,6 +53,7 @@ const task_create_1 = require("./commands/task-create");
53
53
  const task_update_1 = require("./commands/task-update");
54
54
  const task_list_1 = require("./commands/task-list");
55
55
  const task_show_1 = require("./commands/task-show");
56
+ const task_status_1 = require("./commands/task-status");
56
57
  const task_comment_1 = require("./commands/task-comment");
57
58
  const task_figma_add_1 = require("./commands/task-figma-add");
58
59
  const task_figma_list_1 = require("./commands/task-figma-list");
@@ -171,7 +172,12 @@ function wrap(fn) {
171
172
  const program = new commander_1.Command()
172
173
  .name('lumo')
173
174
  .description('Lumo CLI — manage tasks and sessions from the terminal')
174
- .version(pkg.version);
175
+ .version(pkg.version)
176
+ // Make usage errors actionable for agents: suggest near-miss spellings and
177
+ // point at --help instead of dead-ending on "unknown option". Subcommands
178
+ // created via .command() inherit these settings.
179
+ .showSuggestionAfterError(true)
180
+ .showHelpAfterError('(run the command with --help to list its valid flags and arguments)');
175
181
  const auth = program.command('auth').description('Manage Lumo authentication');
176
182
  auth
177
183
  .command('login')
@@ -249,6 +255,11 @@ task
249
255
  .command('show <identifier>')
250
256
  .description('Show a single task: title, status, priority, assignee, project, URL, description.')
251
257
  .action(wrap(identifier => (0, task_show_1.taskShow)(identifier)));
258
+ task
259
+ .command('status [task]')
260
+ .description('Acceptance self-check (LUM-344): contract with latest verdicts, verification history, current round, and nextActions (unmet criteria). Read-only, no LLM. Defaults to the session-bound task. Run it first when resuming a task or after a verification round was rejected.')
261
+ .option('--json', 'Emit the versioned machine-readable payload (version field; breaking changes bump it)')
262
+ .action(wrap((taskArg, options) => (0, task_status_1.taskStatus)(taskArg, options)));
252
263
  task
253
264
  .command('comment <identifier> <body>')
254
265
  .description('Add a comment to a task. Body is plain text — quote it to pass spaces or newlines.')
@@ -33,6 +33,7 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.unreadableFileMessage = unreadableFileMessage;
36
37
  exports.resolveDocContent = resolveDocContent;
37
38
  exports.readStdinToString = readStdinToString;
38
39
  exports.readFileUtf8 = readFileUtf8;
@@ -45,6 +46,14 @@ const path_guard_1 = require("./path-guard");
45
46
  * mutually exclusive. stdin is only consulted when neither flag is set
46
47
  * and the shell is non-TTY.
47
48
  */
49
+ /**
50
+ * Actionable message for an unreadable --file path: agents most often pass a
51
+ * path that doesn't exist relative to the current working directory.
52
+ */
53
+ function unreadableFileMessage(file) {
54
+ return (`could not read file ${file} (cwd: ${process.cwd()}) — ` +
55
+ `check the path exists; pass a project-local relative path`);
56
+ }
48
57
  async function resolveDocContent(args) {
49
58
  const hasContent = args.content !== undefined;
50
59
  const hasFile = args.file !== undefined && args.file.length > 0;
@@ -62,7 +71,7 @@ async function resolveDocContent(args) {
62
71
  return {
63
72
  kind: 'error',
64
73
  message: check.reason === 'unreadable'
65
- ? `could not read file ${args.file}`
74
+ ? unreadableFileMessage(args.file)
66
75
  : `refusing to read ${args.file} — ${check.detail}. ` +
67
76
  `--file must be a non-sensitive path inside the project directory.`,
68
77
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.24.0",
3
+ "version": "1.25.1",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",