@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 +187 -18
- 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 +12 -0
- package/dist/cli/src/commands/milestone-unarchive.js +60 -0
- package/dist/cli/src/commands/next.js +103 -0
- package/dist/cli/src/commands/session-wrap.js +13 -5
- 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/commands/wrap/memory-review-section.js +81 -0
- package/dist/cli/src/index.js +36 -1
- 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/memory-content.js +7 -0
- package/dist/cli/src/lib/milestone-reorder.js +92 -0
- package/dist/cli/src/lib/rank-tasks.js +80 -0
- package/dist/cli/src/lib/resolve.js +17 -6
- package/dist/cli/src/lib/session-memory-api.js +47 -0
- package/package.json +1 -1
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`,
|
|
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
|
-
|
|
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]` —
|
|
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
|
-
|
|
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
|
|
1249
|
-
lumo session wrap --yes #
|
|
1250
|
-
lumo session wrap --dry-run # print
|
|
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
|
|
1256
|
-
- `[e] 编辑` opens `$EDITOR` (fallback vi/nano) on the drafted body;
|
|
1257
|
-
text is posted and the watermark still advances to the turns the
|
|
1258
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -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
|
}
|