@lumoai/cli 1.10.0 → 1.15.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/assets/skill.md CHANGED
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: lumo
3
- description: 'Use the Lumo CLI to load task context, manage session bindings, inspect projects and milestones, and create/update/list/show/comment on tasks from the terminal. Activate when: user mentions a Lumo task identifier (LUM-42, LUM-12, etc.), asks to load task background or context, wants to bind, check, or detach a Claude Code session''s task binding, is about to start development work on a specific task, wants to create a new task, list their tasks, view a task, comment on a task, list projects, list milestones, attach a task to a milestone, or update a task''s status/title/description/priority/assignee/milestone. Triggers on: "LUM-", "task context", "load context", "session start", "session attach", "session status", "session detach", "bind session", "unbind session", "which task", "what task am I on", "work on LUM", "create task", "new task", "add task", "file a task", "log a task", "list tasks", "my tasks", "show task", "view task", "comment on task", "leave a comment", "list projects", "what projects", "update task", "change task status", "rename task", "reassign task", "mark task as done", "milestone", "里程碑", "list milestones", "set milestone", "挂到 milestone", "attach milestone", "unbind milestone", "create milestone", "new milestone", "update milestone", "change milestone status", "delete milestone", "show milestone", "view milestone", "tasks in milestone", "milestone tasks", "新建里程碑", "更新里程碑", "删除里程碑", "查看里程碑", "milestone summary", "milestone retro", "summarize milestone", "里程碑总结", "里程碑复盘", "milestone add", "milestone remove", "add tasks to milestone", "remove tasks from milestone", "batch milestone", "bulk milestone", "挂任务到里程碑", "批量挂里程碑", "从里程碑移除任务", "auth login", "log in", "login", "auth logout", "log out", "logout", "sign out", "switch account", "switch identity", "whoami", "who am I", "current identity", "current user", "current workspace", "登录", "登出", "切换账号", "当前身份", "create doc", "new doc", "new document", "write doc", "写文档", "新建文档", "update doc", "edit doc", "修改文档", "更新文档", "list docs", "my docs", "我的文档", "show doc", "view doc", "查看文档", "delete doc", "删除文档", "bind doc", "attach doc to task", "把文档关联到任务", "文档绑定到任务", "unbind doc", "解绑文档", "personal doc", "workspace doc", "个人文档", "workspace 文档", "doc scope", "tag", "add tag", "remove tag", "标签", "添加标签", "移除标签", "doc tag", "task tag", "share doc", "doc share", "share document", "分享文档", "文档分享", "unshare doc", "remove share", "取消分享", "share list", "list doc shares", "who has access", "viewer", "editor", "manager", "shared with", "doc tree", "doc move", "move doc", "reparent doc", "文档树", "移动文档", "sprint", "start sprint", "close sprint", "add to sprint", "active sprints", "冲刺", "迭代", "开始冲刺", "关闭冲刺", "create sprint", "new sprint", "list sprints", "show sprint", "update sprint", "delete sprint", "sprint summary", "sprint retro", "把任务挂到冲刺", "冲刺里有什么", "lumo update", "update cli", "upgrade lumo", "update lumo", "upgrade cli", "升级 lumo", "更新 cli", "new lumo version", "是否有新版本", "lumo setup", "install lumo skill", "install lumo hooks", "wire up lumo", "set up lumo", "onboard lumo", "npx @lumoai/cli", "安装 lumo", "配置 lumo", "lumo 初始化", "task artifact", "artifact add", "artifact list", "artifact show", "artifact rm", "artifact delete", "artifact update", "update artifact", "edit artifact", "change artifact kind", "change artifact source", "remove artifact", "delete artifact", "spec artifact", "record spec", "attach spec", "attach plan", "记录 spec", "挂 spec", "查看 artifact", "编辑 artifact", "修改 artifact", "删除 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design, "memory", "记忆", "remember", "record a memory", "记一条", "promote memory", "promote to project", "沉淀", "task memory", "project memory", "lumo memory", "retrieval", "取全文", "load full content", "拉全文", "task slack show", "看 thread", "看 slack thread", "show slack thread", "slack 全文", "task web show", "show web link body", "web 正文", "抓网页正文", "task figma context", "figma metadata", "figma 元数据", "figma 设计上下文", "task comments list", "list comments", "看评论", "评论流", "task pr show", "查看 PR", "show pr", "PR 详情", "pr metadata", "import google doc", "sync google doc", "google drive", "doc import-gdoc", "doc sync", "导入 google 文档", "同步 google 文档", "session wrap", "wrap up session", "收尾", "post progress", "把进度发出去", "progress comment", "进度评论".'
3
+ description: 'Use the Lumo CLI to load task context, manage session bindings, inspect projects and milestones, and create/update/list/show/comment on tasks from the terminal. Activate when: user mentions a Lumo task identifier (LUM-42, LUM-12, etc.), asks to load task background or context, wants to bind, check, or detach a Claude Code session''s task binding, is about to start development work on a specific task, wants to create a new task, list their tasks, view a task, comment on a task, list projects, list milestones, attach a task to a milestone, or update a task''s status/title/description/priority/assignee/milestone. Triggers on: "LUM-", "task context", "load context", "session start", "session attach", "session status", "session detach", "bind session", "unbind session", "which task", "what task am I on", "work on LUM", "create task", "new task", "add task", "file a task", "log a task", "list tasks", "my tasks", "show task", "view task", "comment on task", "leave a comment", "list projects", "what projects", "update task", "change task status", "rename task", "reassign task", "mark task as done", "milestone", "里程碑", "list milestones", "set milestone", "挂到 milestone", "attach milestone", "unbind milestone", "create milestone", "new milestone", "update milestone", "change milestone status", "delete milestone", "show milestone", "view milestone", "tasks in milestone", "milestone tasks", "search milestones", "find milestone", "filter milestones", "milestone search", "搜索里程碑", "查找里程碑", "milestone health", "milestone risk", "risk light", "on-track", "at-risk", "overdue", "里程碑健康度", "健康度", "风险灯", "延期风险", "新建里程碑", "更新里程碑", "删除里程碑", "查看里程碑", "archive milestone", "unarchive milestone", "restore milestone", "archived milestones", "归档里程碑", "恢复里程碑", "取消归档", "milestone summary", "milestone retro", "summarize milestone", "里程碑总结", "里程碑复盘", "milestone reorder", "milestone move", "reorder milestones", "move milestone", "排序里程碑", "调整里程碑顺序", "里程碑调序", "move milestone before", "move milestone after", "milestone add", "milestone remove", "add tasks to milestone", "remove tasks from milestone", "batch milestone", "bulk milestone", "挂任务到里程碑", "批量挂里程碑", "从里程碑移除任务", "auth login", "log in", "login", "auth logout", "log out", "logout", "sign out", "switch account", "switch identity", "whoami", "who am I", "current identity", "current user", "current workspace", "登录", "登出", "切换账号", "当前身份", "create doc", "new doc", "new document", "write doc", "写文档", "新建文档", "update doc", "edit doc", "修改文档", "更新文档", "list docs", "my docs", "我的文档", "show doc", "view doc", "查看文档", "delete doc", "删除文档", "bind doc", "attach doc to task", "把文档关联到任务", "文档绑定到任务", "unbind doc", "解绑文档", "personal doc", "workspace doc", "个人文档", "workspace 文档", "doc scope", "tag", "add tag", "remove tag", "标签", "添加标签", "移除标签", "doc tag", "task tag", "share doc", "doc share", "share document", "分享文档", "文档分享", "unshare doc", "remove share", "取消分享", "share list", "list doc shares", "who has access", "viewer", "editor", "manager", "shared with", "doc tree", "doc move", "move doc", "reparent doc", "文档树", "移动文档", "sprint", "start sprint", "close sprint", "add to sprint", "active sprints", "冲刺", "迭代", "开始冲刺", "关闭冲刺", "create sprint", "new sprint", "list sprints", "show sprint", "update sprint", "delete sprint", "sprint summary", "sprint retro", "把任务挂到冲刺", "冲刺里有什么", "lumo update", "update cli", "upgrade lumo", "update lumo", "upgrade cli", "升级 lumo", "更新 cli", "new lumo version", "是否有新版本", "lumo setup", "install lumo skill", "install lumo hooks", "wire up lumo", "set up lumo", "onboard lumo", "npx @lumoai/cli", "安装 lumo", "配置 lumo", "lumo 初始化", "task artifact", "artifact add", "artifact list", "artifact show", "artifact rm", "artifact delete", "artifact update", "update artifact", "edit artifact", "change artifact kind", "change artifact source", "remove artifact", "delete artifact", "spec artifact", "record spec", "attach spec", "attach plan", "记录 spec", "挂 spec", "查看 artifact", "编辑 artifact", "修改 artifact", "删除 artifact", figma, attach figma, figma link, 关联 figma, 设计稿, figma design, "memory", "记忆", "remember", "record a memory", "记一条", "promote memory", "promote to project", "沉淀", "task memory", "project memory", "lumo memory", "retrieval", "取全文", "load full content", "拉全文", "task slack show", "看 thread", "看 slack thread", "show slack thread", "slack 全文", "task web show", "show web link body", "web 正文", "抓网页正文", "task figma context", "figma metadata", "figma 元数据", "figma 设计上下文", "task comments list", "list comments", "看评论", "评论流", "task pr show", "查看 PR", "show pr", "PR 详情", "pr metadata", "import google doc", "sync google doc", "google drive", "doc import-gdoc", "doc sync", "导入 google 文档", "同步 google 文档", "session wrap", "wrap up session", "收尾", "post progress", "把进度发出去", "progress comment", "进度评论", "mark blocked", "blocked tag", "标 blocked", "标记 blocked", "卡住检测", "反复失败", "stuck", "repeatedly failing", "lumo next", "next task", "what''s next", "what should I work on", "recommend a task", "推荐下一个任务", "pick my next task".'
4
4
  ---
5
5
 
6
6
  ## Prerequisites
@@ -343,7 +343,7 @@ Task LUM-48 has no sprint binding # noop (already unbound)
343
343
 
344
344
  The CLI does **not** currently update due date or parent task. Those need to be edited in the web UI.
345
345
 
346
- Milestone updates (`--milestone`) and sprint binding (`--sprint`) both work. Full milestone CRUD is available via `lumo milestone create / show / update / delete`, and tasks can be bound/unbound in bulk via `lumo milestone add / remove <identifier> <task...>` (see below). Full sprint CRUD is available via `lumo sprint create / show / update / delete / start / close / add / remove` (see below).
346
+ 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).
347
347
 
348
348
  ### `lumo task list [flags]` — list tasks assigned to you
349
349
 
@@ -370,6 +370,44 @@ Filtering is currently client-side — the server returns the full "my tasks" se
370
370
  - The user asks "what am I working on", "what tasks do I have", "list my tasks", "show me my queue".
371
371
  - Before suggesting a status change ("mark something as done"), if no task ID is in context — run `task list` first to surface candidates.
372
372
 
373
+ ### `lumo next [--count <N>]` — recommend the next task to work on
374
+
375
+ Ranks the tasks assigned to you and prints the top N (default 3), each with a
376
+ one-line reason. Read-only — it does **not** bind or load context. Pick one from
377
+ the list, then run `lumo session attach <LUM-N>` + `lumo task context <LUM-N>`.
378
+
379
+ Ranking is lexicographic: **priority** (URGENT→LOW) first, then **active-sprint
380
+ membership**, then **due date** (earlier first), then in-flight status
381
+ (IN_PROGRESS / IN_REVIEW ahead of TODO). DONE tasks are excluded. The active
382
+ sprint lookup is best-effort — if it fails the command still recommends, just
383
+ without the sprint boost.
384
+
385
+ | Flag | Type | Notes |
386
+ | ----------------- | ------- | ----------------------------------------------------------------------- |
387
+ | `-n, --count <N>` | integer | How many tasks to recommend. Defaults to 3. Must be a positive integer. |
388
+
389
+ ```bash
390
+ lumo next
391
+ lumo next --count 1
392
+ ```
393
+
394
+ Output:
395
+
396
+ ```
397
+ Top 3 recommended tasks (of 12 open):
398
+
399
+ 1. LUM-42 IN_PROGRESS URGENT Fix Slack OAuth redirect
400
+ ↳ URGENT · active sprint · due 2026-06-03 (overdue) · in progress
401
+ 2. LUM-48 TODO HIGH Investigate slow query
402
+ ↳ HIGH · active sprint
403
+ 3. LUM-12 TODO MEDIUM Add rate limiting
404
+ ↳ MEDIUM · due 2026-06-10
405
+
406
+ Next: lumo session attach LUM-42 && lumo task context LUM-42
407
+ ```
408
+
409
+ 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.
410
+
373
411
  ### `lumo task show <identifier>` — print one task's detail
374
412
 
375
413
  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.
@@ -521,13 +559,35 @@ Prints `<slug> <Display Name>` lines. The slug column matches the `--project <r
521
559
  lumo project list
522
560
  ```
523
561
 
524
- ### `lumo milestone list [--project <ref>]` — list milestones in a project
562
+ ### `lumo milestone list [--project <ref>] [--archived] [--all] [--search <text>]` — list milestones in a project
563
+
564
+ Prints fixed-width rows: `<STATUS> <HEALTH> <target-date or -> <name>`, sorted by target date asc (nulls last) then created asc.
525
565
 
526
- Prints fixed-width rows: `<STATUS> <target-date or -> <name>`, sorted by target date asc (nulls last) then created asc.
566
+ By default, only **non-archived** milestones are listed. Use `--archived` to show **only** archived milestones, or `--all` to show **both** archived and non-archived. Archived rows are marked with a ` (archived)` suffix on the name.
567
+
568
+ Use `--search <text>` to filter to milestones whose **name or description** contains the text (case-insensitive substring match). It applies **on top of** the archive filter (e.g. `--all --search q3`). A blank/whitespace-only value is ignored (no filtering). The matched text is bounded to 120 chars server-side.
569
+
570
+ | Flag | Type | Notes |
571
+ | ----------------- | ------- | -------------------------------------------------------------------------------------- |
572
+ | `--project <ref>` | string | Required when the workspace has more than one project. Name or slug, case-insensitive. |
573
+ | `--archived` | boolean | Show **only** archived milestones (instead of the default non-archived set). |
574
+ | `--all` | boolean | Show **both** archived and non-archived milestones. |
575
+ | `--search <text>` | string | Filter by **name/description** case-insensitive substring. Combines with the archive filter; blank value ignored. |
576
+
577
+ `HEALTH` is the target-date risk light, server-computed from the milestone's target date + task progress:
578
+
579
+ - `ON-TRACK` — on schedule (or all tasks done)
580
+ - `AT-RISK` — completion lags elapsed time, or (no start date) the target is within ~7 days with work remaining
581
+ - `OVERDUE` — past the target date with tasks still open
582
+ - `-` — no light applies (status `COMPLETED`/`CANCELLED`, or no target date)
527
583
 
528
584
  ```bash
529
- lumo milestone list # one-project workspace
585
+ lumo milestone list # one-project workspace (non-archived only)
530
586
  lumo milestone list --project lumo # multi-project workspace
587
+ lumo milestone list --archived # only archived milestones
588
+ lumo milestone list --all # archived + non-archived (archived marked "(archived)")
589
+ lumo milestone list --search q3 # name/description contains "q3" (case-insensitive)
590
+ lumo milestone list --all --search launch # search across archived + non-archived
531
591
  ```
532
592
 
533
593
  `--project <ref>` is required when the workspace has more than one project (consistent with `task create --project`). Match is by project name or slug, case-insensitive.
@@ -536,6 +596,7 @@ lumo milestone list --project lumo # multi-project workspace
536
596
 
537
597
  - Before suggesting `task create --milestone <ref>` or `task update --milestone <ref>` to confirm the milestone exists under the expected name.
538
598
  - When the user asks "what milestones do we have", "what's on v1.0", or similar.
599
+ - When the user wants to **find/search milestones by keyword** ("find the launch milestone", "搜索里程碑", "which milestones mention X") — use `--search <text>` rather than listing all and eyeballing.
539
600
 
540
601
  When to suggest: before `task create --project <ref>` when the workspace has more than one project and the user hasn't specified which one.
541
602
 
@@ -561,7 +622,7 @@ On success: `Created milestone "Q3 Launch" <id>`.
561
622
 
562
623
  Accepts UUID or name. With a name, `--project <ref>` is required when the workspace has >1 project.
563
624
 
564
- Prints a key:value header (name, status, dates, project, description), task counts, and the full task table under the milestone.
625
+ Prints a key:value header (name, status, **health**, dates, project, description), task counts, and the full task table under the milestone. The `Health:` line shows the same target-date risk light as `milestone list` (`ON-TRACK` / `AT-RISK` / `OVERDUE`, or `-` when none applies).
565
626
 
566
627
  ```bash
567
628
  lumo milestone show "Q3 Launch"
@@ -595,6 +656,23 @@ Requires `--yes`. No interactive prompt — CLI is agent-friendly. Tasks under t
595
656
  lumo milestone delete "Q3 Launch" --yes
596
657
  ```
597
658
 
659
+ ### `lumo milestone archive <identifier>` — soft-archive a milestone
660
+
661
+ Soft-archives a milestone by setting `archivedAt`. The milestone is **hidden from `milestone list` by default** (use `--archived` or `--all` to see it), but its **history and task links are preserved**, and the action is **reversible** via `milestone unarchive`. This is distinct from `milestone delete`, which is a **hard delete**. While archived, the milestone **rejects edits** (`milestone update`) and **new task bindings** (`task --milestone`, `milestone add`) with a 409 until it is restored. `<identifier>` accepts a UUID or name; `--project <ref>` is required when the identifier is a name and the workspace has >1 project.
662
+
663
+ ```bash
664
+ lumo milestone archive "Q3 Launch"
665
+ lumo milestone archive 11111111-2222-3333-4444-555555555555
666
+ ```
667
+
668
+ ### `lumo milestone unarchive <identifier>` — restore an archived milestone
669
+
670
+ Restores an archived milestone by clearing `archivedAt`. It reappears in `milestone list` and can be edited and bound to tasks again. Idempotent — unarchiving an already-active milestone is a no-op. Same identifier / `--project` rules as `milestone archive`: `<identifier>` accepts a UUID or name; `--project <ref>` is required when the identifier is a name and the workspace has >1 project.
671
+
672
+ ```bash
673
+ lumo milestone unarchive "Q3 Launch"
674
+ ```
675
+
598
676
  ### `lumo milestone add <identifier> <task...>` — bind tasks to a milestone (batch)
599
677
 
600
678
  Binds **one or more** tasks to a milestone in a single call — the batch counterpart of `task update --milestone <ref>` (which only takes one task at a time). `<identifier>` accepts a milestone name or UUID; each `<task>` accepts `LUM-N` or a task UUID. `--project <ref>` is required when the identifier is a name and the workspace has >1 project.
@@ -643,9 +721,9 @@ Prints the AI-generated retrospective summary for a milestone (mirrors `sprint s
643
721
 
644
722
  A summary is generated automatically when a milestone transitions to `COMPLETED` (e.g. via `lumo milestone update <id> --status completed`). The generated report has sections `## Summary`, `## Delivered`, `## Outstanding` plus a one-line `tldr`. Use `--retry` to queue regeneration (e.g. after a failed generation) before fetching — regeneration is async, so the printed result may still be the previous summary or `(no summary generated yet)`.
645
723
 
646
- | Flag | Type | Notes |
647
- | ----------------- | ------- | ---------------------------------------------------------------- |
648
- | `--project <ref>` | string | Project name or slug. Required when identifier is a name and the workspace has >1 project. |
724
+ | Flag | Type | Notes |
725
+ | ----------------- | ------- | ------------------------------------------------------------------------------------------------------ |
726
+ | `--project <ref>` | string | Project name or slug. Required when identifier is a name and the workspace has >1 project. |
649
727
  | `--retry` | boolean | Queue a regeneration (async, server returns 202) before fetching. Only valid on a COMPLETED milestone. |
650
728
 
651
729
  ```bash
@@ -656,6 +734,50 @@ lumo milestone summary 11111111-2222-3333-4444-555555555555
656
734
 
657
735
  When to suggest: user asks "summarize the milestone", "milestone retro", "give me a summary of the Q3 milestone", "里程碑总结", "里程碑复盘".
658
736
 
737
+ ### `lumo milestone reorder <ref...> [--project <ref>]` — set the full milestone order
738
+
739
+ Reorders a project's milestones. Pass **every** milestone in the project (by name, case-insensitive, or its cuid) in the desired order — the command rewrites each milestone's `sortOrder` to match. An incomplete list (not every milestone named), an unknown ref, or a duplicate is rejected **before any network mutation**, with a message naming the offending / missing milestones.
740
+
741
+ ```bash
742
+ lumo milestone reorder "Q3 Launch" "Beta" "Alpha"
743
+ lumo milestone reorder "Q3 Launch" "Beta" "Alpha" --project backend
744
+ ```
745
+
746
+ `--project <ref>` is required when the workspace has more than one project.
747
+
748
+ Output:
749
+
750
+ ```
751
+ Reordered 3 milestones:
752
+ 1. Q3 Launch
753
+ 2. Beta
754
+ 3. Alpha
755
+ ```
756
+
757
+ ### `lumo milestone move <ref> --before <ref> | --after <ref> [--project <ref>]` — move one milestone
758
+
759
+ Moves a single milestone immediately before or after a target milestone, leaving the rest in their current relative order. `--before` and `--after` are **mutually exclusive and exactly one is required** (the CLI errors before any network call if both or neither is given). Refs resolve by cuid or case-insensitive name.
760
+
761
+ ```bash
762
+ lumo milestone move "Alpha" --before "Q3 Launch"
763
+ lumo milestone move "Alpha" --after "Beta" --project backend
764
+ ```
765
+
766
+ Output:
767
+
768
+ ```
769
+ Moved "Alpha" before "Q3 Launch". New order:
770
+ 1. Alpha
771
+ 2. Q3 Launch
772
+ 3. Beta
773
+ ```
774
+
775
+ Both commands resolve to a full ordered list client-side and call the same `PATCH /api/projects/<id>/milestones/reorder` endpoint (which requires the list to name every milestone exactly once). New milestones created via `milestone create` are appended to the bottom of the order.
776
+
777
+ **Ambiguous names:** milestone names are not unique within a project (only the slug is). A ref whose name matches more than one milestone is **rejected** with an `ambiguous milestone name … re-run with the id` error listing the candidate cuids — pass the cuid instead. This applies to every name-based milestone ref (`reorder`, `move`, and also `show` / `update` / `delete` / `summary` / `add` / `remove`).
778
+
779
+ When to suggest: user asks to "reorder milestones", "排序里程碑", "调整里程碑顺序", "move milestone X before/after Y", "把里程碑 X 移到 Y 前面/后面", "put this milestone first".
780
+
659
781
  ## Document Management
660
782
 
661
783
  ### `lumo doc create [title] [flags]` — create a new document
@@ -1195,6 +1317,16 @@ auto-bind line, run `lumo session detach` to clear the binding (then `session
1195
1317
  attach <LUM-N>` if they name the right one). No detach is needed when the
1196
1318
  auto-bound task is correct.
1197
1319
 
1320
+ ### Layer 2 project-memory review at session start
1321
+
1322
+ When the session is bound, session-start may inject a **"🆕 待核对:上次会话自动合并的项目级记忆"** section alongside the memory / PR-review blocks (LUM-165). It lists the **PROJECT-scope** memories that the member's **immediately-preceding session** auto-consolidated (Layer 2 runs asynchronously when a task is marked `done`). Each item shows its `id`.
1323
+
1324
+ - **Why it's here:** Layer 2 promotions land async, so they can't be reviewed in the synchronous `session wrap` panel — they're surfaced at the _next_ session-start instead, when they've definitely landed.
1325
+ - **Show-once:** the section appears only at the session that immediately follows the one that produced the memories. It does **not** re-nag on later sessions, so act on it now or it scrolls off.
1326
+ - **Agent guidance:** briefly sanity-check each listed memory against the codebase/context. If one is wrong or over-generalized, remove it with `lumo memory rm <id> --yes` (ideally confirm with the user first). If they all look right, ignore the section and continue.
1327
+
1328
+ Attribution requires the CC session id to reach the server: `lumo task update <id> --status done` automatically sends `CLAUDE_CODE_SESSION_ID` (via an `X-Lumo-Session-Id` header) so the resulting Layer 2 memories are attributed to the session. Marking a task done from the **web UI** leaves them unattributed (they won't surface for review) — that's expected.
1329
+
1198
1330
  ### `lumo session attach <identifier>` — bind the current session to a task
1199
1331
 
1200
1332
  Use this whenever the user mentions a task ID. The command is the only way to bind a session to a task.
@@ -1235,27 +1367,64 @@ lumo session detach
1235
1367
 
1236
1368
  When to suggest: the user wants to stop tagging the current session with the active task (e.g., switching to unrelated exploratory work without binding to a different task).
1237
1369
 
1238
- ### `lumo session wrap [--yes] [--dry-run]` — draft + post a progress comment at wrap-up
1370
+ ### `lumo session wrap [--yes] [--dry-run]` — wrap-up panel: progress comment + memory review + blocked-tag prompt
1371
+
1372
+ Session-end wrap-up panel with **three sections, run in order**:
1239
1373
 
1240
- Session-end wrap-up panel. Reads back the current Claude Code session's per-turn
1374
+ **1. 进度评论** reads back the current Claude Code session's per-turn
1241
1375
  `turnSummary` rows (the one-line Chinese summaries written each STOP), aggregates
1242
1376
  every turn **since the last progress comment** into one bulleted body, and — after
1243
1377
  a `[y] 发送 / [e] 编辑 / [s] 跳过` confirmation — posts it as a comment on the
1244
1378
  session's bound task. A server-side watermark (`Session.lastProgressCommentAt`)
1245
1379
  means re-running never re-posts the same turns.
1246
1380
 
1381
+ **2. 记忆审阅** — lists the Layer1 memories this session sedimented since the
1382
+ last review (deduped by a per-session watermark `Session.lastMemoryReviewAt`).
1383
+ Each new memory is shown as `[SCOPE] CATEGORY headline`, numbered from 1. You
1384
+ curate with a single line: `d 1,3` deletes rows 1 and 3, `p 2` promotes row 2 to
1385
+ project scope, and they combine (`d 1,3 p 2`). **回车 (empty) keeps all**; `s`
1386
+ skips the section. Keeping all (回车 or `--yes`) still **advances the watermark**
1387
+ so the next wrap won't re-list reviewed memories; `s` leaves them for next time.
1388
+ Out-of-range indices are ignored. Deletes/promotes run server-side, scoped to
1389
+ memories this session created (you can't touch other sessions' memories through
1390
+ this panel). With no new memories the section prints "(无内容)" and does nothing.
1391
+
1392
+ **3. 卡住检测 (blocked-tag prompt, LUM-153)** — if the **same kind of failure
1393
+ recurred ≥ 3 times** in this session (server-aggregated from
1394
+ `POST_TOOL_USE_FAILURE` events grouped by tool name, plus `STOP_FAILURE`
1395
+ turn-level failures), the section surfaces the dominant failure (`卡在 <tool>
1396
+ (N 次失败)` + last error summary) and prompts `[y] 标记 / [s] 跳过` whether to
1397
+ flag the bound task with a **`blocked` tag**. **Prompt-only — never auto-flips
1398
+ status.** It uses a plain tag (no `TaskStatus` enum, no board column, **no
1399
+ schema migration**). The prompt is **suppressed** when: there's no bound task,
1400
+ the threshold isn't met, or the task **already** carries a `blocked` tag (the
1401
+ idempotent gate — there's no watermark, the existing tag is what prevents
1402
+ re-nagging). The default on empty input / `s` is **do nothing** (tagging is
1403
+ opt-in), so a stray Enter never tags the task. Confirming with an explicit `y`
1404
+ attaches the tag idempotently. **`--yes` does NOT auto-tag** — tagging the
1405
+ shared board requires an interactive `y`, so `--yes` (and non-TTY) prints the
1406
+ suggestion and moves on rather than silently flipping board state. When there's
1407
+ nothing to prompt, the section prints "(无内容)".
1408
+
1247
1409
  ```bash
1248
- lumo session wrap # interactive: preview draft, choose y / e / s
1249
- lumo session wrap --yes # post the drafted body without prompting (agent-friendly)
1250
- lumo session wrap --dry-run # print the draft only; never posts, never advances watermark
1410
+ lumo session wrap # interactive: preview each section, choose per-section
1411
+ lumo session wrap --yes # progress posted + memories kept; blocked tag NOT auto-applied (needs interactive y)
1412
+ lumo session wrap --dry-run # print all drafts only; never posts, never mutates, never advances watermarks
1251
1413
  ```
1252
1414
 
1253
1415
  - Requires `$CLAUDE_CODE_SESSION_ID` (must run inside Claude Code) and a bound
1254
1416
  task (`lumo session attach <LUM-N>` first). With no bound task or no new turn
1255
- summaries, the panel prints "(无内容)" and posts nothing.
1256
- - `[e] 编辑` opens `$EDITOR` (fallback vi/nano) on the drafted body; the edited
1257
- text is posted and the watermark still advances to the turns the draft covered.
1258
- - Non-TTY without `--yes`: prints the draft and does **not** post (safe default).
1417
+ summaries, the 进度评论 section prints "(无内容)" and posts nothing.
1418
+ - `[e] 编辑` (进度评论) opens `$EDITOR` (fallback vi/nano) on the drafted body;
1419
+ the edited text is posted and the watermark still advances to the turns the
1420
+ draft covered.
1421
+ - `--yes` posts the progress comment AND keeps all memories (no
1422
+ deletes/promotes) while advancing the memory-review watermark; for the
1423
+ blocked-tag section it prints the suggestion but does **not** apply the tag.
1424
+ - `--dry-run` prints all drafts; never posts, never mutates memories/tags, never
1425
+ advances either watermark.
1426
+ - Non-TTY without `--yes`: prints the drafts and does **not** post, mutate, or
1427
+ tag (safe default).
1259
1428
 
1260
1429
  When to suggest: at the end of a working session on a bound task, to record what
1261
1430
  was done as a progress comment — offer `lumo session wrap` rather than composing
@@ -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
- const res = await fetch(`${base}/api/projects/${projectId}/milestones`, {
47
- headers: { Authorization: `Bearer ${creds.token}` },
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;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.milestoneMove = milestoneMove;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_1 = require("../lib/resolve");
7
+ const sanitize_1 = require("../lib/sanitize");
8
+ const milestone_reorder_1 = require("../lib/milestone-reorder");
9
+ async function milestoneMove(reference, opts) {
10
+ if (!reference) {
11
+ console.error('Error: usage: lumo milestone move <ref> --before <ref> | --after <ref>');
12
+ return 1;
13
+ }
14
+ const hasBefore = typeof opts.before === 'string' && opts.before.length > 0;
15
+ const hasAfter = typeof opts.after === 'string' && opts.after.length > 0;
16
+ if (hasBefore && hasAfter) {
17
+ console.error('Error: --before and --after are mutually exclusive');
18
+ return 1;
19
+ }
20
+ if (!hasBefore && !hasAfter) {
21
+ console.error('Error: specify --before <ref> or --after <ref>');
22
+ return 1;
23
+ }
24
+ const creds = (0, config_1.readCredentials)();
25
+ if (!creds) {
26
+ console.error('Error: not logged in. Run `lumo auth login` first.');
27
+ return 1;
28
+ }
29
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
30
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
31
+ let projectId;
32
+ try {
33
+ projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, opts.project);
34
+ }
35
+ catch (err) {
36
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
37
+ return 1;
38
+ }
39
+ const listRes = await fetch(`${base}/api/projects/${projectId}/milestones`, {
40
+ headers: { Authorization: `Bearer ${creds.token}` },
41
+ });
42
+ if (!listRes.ok) {
43
+ console.error(`Error: milestone list failed (HTTP ${listRes.status})`);
44
+ return 1;
45
+ }
46
+ const { milestones } = (await listRes.json());
47
+ const refRows = milestones.map(m => ({
48
+ id: m.id,
49
+ name: m.name,
50
+ sortOrder: m.sortOrder,
51
+ }));
52
+ const position = hasBefore ? 'before' : 'after';
53
+ const targetRef = (hasBefore ? opts.before : opts.after);
54
+ const resolved = (0, milestone_reorder_1.computeMoveOrder)(reference, targetRef, position, refRows);
55
+ if (!resolved.ok) {
56
+ console.error(`Error: ${resolved.error}`);
57
+ return 1;
58
+ }
59
+ const patchRes = await fetch(`${base}/api/projects/${projectId}/milestones/reorder`, {
60
+ method: 'PATCH',
61
+ headers: {
62
+ Authorization: `Bearer ${creds.token}`,
63
+ 'Content-Type': 'application/json',
64
+ },
65
+ body: JSON.stringify({ orderedIds: resolved.orderedIds }),
66
+ });
67
+ if (!patchRes.ok) {
68
+ const text = await patchRes.text();
69
+ let msg = text;
70
+ try {
71
+ const json = JSON.parse(text);
72
+ if (json.error)
73
+ msg = json.error;
74
+ }
75
+ catch { }
76
+ console.error(`Error: ${patchRes.status} ${patchRes.statusText}: ${(0, sanitize_1.sanitizeField)(msg)}`);
77
+ return 1;
78
+ }
79
+ const byId = new Map(refRows.map(m => [m.id, m.name]));
80
+ console.log(`Moved "${(0, sanitize_1.sanitizeField)(reference)}" ${position} "${(0, sanitize_1.sanitizeField)(targetRef)}". New order:`);
81
+ resolved.orderedIds.forEach((id, i) => {
82
+ console.log(` ${i + 1}. ${(0, sanitize_1.sanitizeField)(byId.get(id) ?? id)}`);
83
+ });
84
+ }
@@ -0,0 +1,72 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.milestoneReorder = milestoneReorder;
4
+ const config_1 = require("../lib/config");
5
+ const api_1 = require("../lib/api");
6
+ const resolve_1 = require("../lib/resolve");
7
+ const sanitize_1 = require("../lib/sanitize");
8
+ const milestone_reorder_1 = require("../lib/milestone-reorder");
9
+ async function milestoneReorder(refs, opts) {
10
+ if (!refs || refs.length === 0) {
11
+ console.error('Error: usage: lumo milestone reorder <ref...> [--project <ref>]');
12
+ return 1;
13
+ }
14
+ const creds = (0, config_1.readCredentials)();
15
+ if (!creds) {
16
+ console.error('Error: not logged in. Run `lumo auth login` first.');
17
+ return 1;
18
+ }
19
+ const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
20
+ const base = (0, api_1.trimTrailingSlash)(apiUrl);
21
+ let projectId;
22
+ try {
23
+ projectId = await (0, resolve_1.resolveProjectId)(base, creds.token, opts.project);
24
+ }
25
+ catch (err) {
26
+ console.error(`Error: ${err instanceof Error ? err.message : String(err)}`);
27
+ return 1;
28
+ }
29
+ const listRes = await fetch(`${base}/api/projects/${projectId}/milestones`, {
30
+ headers: { Authorization: `Bearer ${creds.token}` },
31
+ });
32
+ if (!listRes.ok) {
33
+ console.error(`Error: milestone list failed (HTTP ${listRes.status})`);
34
+ return 1;
35
+ }
36
+ const { milestones } = (await listRes.json());
37
+ const refRows = milestones.map(m => ({
38
+ id: m.id,
39
+ name: m.name,
40
+ sortOrder: m.sortOrder,
41
+ }));
42
+ const resolved = (0, milestone_reorder_1.resolveOrderedIds)(refs, refRows);
43
+ if (!resolved.ok) {
44
+ console.error(`Error: ${resolved.error}`);
45
+ return 1;
46
+ }
47
+ const patchRes = await fetch(`${base}/api/projects/${projectId}/milestones/reorder`, {
48
+ method: 'PATCH',
49
+ headers: {
50
+ Authorization: `Bearer ${creds.token}`,
51
+ 'Content-Type': 'application/json',
52
+ },
53
+ body: JSON.stringify({ orderedIds: resolved.orderedIds }),
54
+ });
55
+ if (!patchRes.ok) {
56
+ const text = await patchRes.text();
57
+ let msg = text;
58
+ try {
59
+ const json = JSON.parse(text);
60
+ if (json.error)
61
+ msg = json.error;
62
+ }
63
+ catch { }
64
+ console.error(`Error: ${patchRes.status} ${patchRes.statusText}: ${(0, sanitize_1.sanitizeField)(msg)}`);
65
+ return 1;
66
+ }
67
+ const byId = new Map(refRows.map(m => [m.id, m.name]));
68
+ console.log(`Reordered ${resolved.orderedIds.length} milestones:`);
69
+ resolved.orderedIds.forEach((id, i) => {
70
+ console.log(` ${i + 1}. ${(0, sanitize_1.sanitizeField)(byId.get(id) ?? id)}`);
71
+ });
72
+ }
@@ -7,9 +7,17 @@ const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
8
  const format_1 = require("../lib/format");
9
9
  const sanitize_1 = require("../lib/sanitize");
10
+ const HEALTH_LABEL = {
11
+ 'on-track': 'ON-TRACK',
12
+ 'at-risk': 'AT-RISK',
13
+ overdue: 'OVERDUE',
14
+ };
10
15
  function fmtDate(iso) {
11
16
  return iso ? iso.slice(0, 10) : '-';
12
17
  }
18
+ function fmtHealth(health) {
19
+ return health ? HEALTH_LABEL[health] : '-';
20
+ }
13
21
  function formatMilestoneShow(m, tasks) {
14
22
  const total = m.taskCounts.TODO +
15
23
  m.taskCounts.IN_PROGRESS +
@@ -18,6 +26,8 @@ function formatMilestoneShow(m, tasks) {
18
26
  const lines = [
19
27
  `Milestone: ${(0, sanitize_1.sanitizeField)(m.name)}`,
20
28
  `Status: ${m.status}`,
29
+ `Archived: ${m.archivedAt ? m.archivedAt.slice(0, 10) : 'no'}`,
30
+ `Health: ${fmtHealth(m.health)}`,
21
31
  `Start: ${fmtDate(m.startDate)}`,
22
32
  `Target: ${fmtDate(m.targetDate)}`,
23
33
  `Project: ${(0, sanitize_1.sanitizeField)(m.projectName)}`,
@@ -99,8 +109,10 @@ async function milestoneShow(identifier, opts) {
99
109
  status: milestone.status,
100
110
  startDate: milestone.startDate,
101
111
  targetDate: milestone.targetDate,
112
+ archivedAt: milestone.archivedAt,
102
113
  description: milestone.description,
103
114
  projectName,
104
115
  taskCounts: milestone.taskCounts,
116
+ health: milestone.health,
105
117
  }, tasks) + '\n');
106
118
  }