@lumoai/cli 1.22.0 → 1.23.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/SKILL.md +10 -1
- package/assets/skill/references/sessions.md +40 -0
- package/assets/skill/references/tasks.md +130 -0
- package/dist/cli/src/commands/session-attach.js +6 -1
- package/dist/cli/src/commands/task-deps.js +285 -0
- package/dist/cli/src/index.js +27 -0
- package/dist/cli/src/lib/hook-runner.js +10 -6
- package/package.json +1 -1
package/assets/skill/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, 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 详情", "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", "task lineage", "lineage", "causal trail", "审计", "因果链", "成本归因", "trace context", "--signal", "usage signal health", "auto usage audit", "自动使用审计", "signal-health".'
|
|
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", "task lineage", "lineage", "causal trail", "审计", "因果链", "成本归因", "trace context", "--signal", "usage signal health", "auto usage audit", "自动使用审计", "signal-health".'
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
## Prerequisites
|
|
@@ -61,6 +61,15 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
61
61
|
- `lumo task show <id>` — print one task's detail
|
|
62
62
|
- `lumo task comment <id> <body>` — leave a comment
|
|
63
63
|
|
|
64
|
+
**Task dependencies**
|
|
65
|
+
|
|
66
|
+
- `lumo task deps list <id>` — list dependency edges in both directions, grouped CONFIRMED / SUGGESTED(待确认) / DISMISSED; each row shows a short edge id `[xxxxxxxx]`, the other task, and detected evidence (`shared_files(N 个共享文件: …)` / `task_mention(…)`); SUGGESTED rows include a copy-pasteable confirm hint
|
|
67
|
+
- `lumo task deps add <id> --blocked-by <LUM-N>` — declare a manual hard dependency (created CONFIRMED, source MANUAL; if a SUGGESTED/DISMISSED edge already exists in the same direction, it is confirmed in place rather than creating a new row)
|
|
68
|
+
- `lumo task deps confirm <id> <edge> [--reverse]` — confirm a detected candidate; `<edge>` is a short edge-id prefix (≥6 chars) or the other task's identifier (case-insensitive); `--reverse` flips the direction when the detector guessed it backwards
|
|
69
|
+
- `lumo task deps dismiss <id> <edge>` — dismiss a candidate (never re-suggested)
|
|
70
|
+
- `lumo task deps rm <id> <edge> --yes` — delete an edge (refuses without `--yes`)
|
|
71
|
+
- On an ambiguous/unknown `<edge>` selector the CLI prints all candidates with short ids and exits 1 — retry with one of them
|
|
72
|
+
|
|
64
73
|
**Acceptance criteria(验收合约)** — see [criteria.md](references/criteria.md)
|
|
65
74
|
|
|
66
75
|
- `lumo task criteria set <task> --file <criteria.json> [--human]` — submit the whole contract: default = initial agent draft (AGENT_DRAFT, locked once submitted); `--human` = a HUMAN_EDIT revision transcribed from the conversation (desired final list; items with `id` keep/update, missing ones are deleted)
|
|
@@ -35,6 +35,46 @@ When the session is bound, session-start may inject a **"🆕 待核对:上次
|
|
|
35
35
|
|
|
36
36
|
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.
|
|
37
37
|
|
|
38
|
+
### Blocker alert injected at `session attach` / session-start
|
|
39
|
+
|
|
40
|
+
When the bound task has live blockers or SUGGESTED dependency candidates, the attach output and session-start hook context inject a dependency warning block. The warning is built by `buildBlockerWarningSectionForTask` and is **omitted entirely** (empty string) when nothing is actionable — no output appears when there are no live blockers and no SUGGESTED candidates.
|
|
41
|
+
|
|
42
|
+
**Trigger conditions (OR — either or both may appear):**
|
|
43
|
+
1. At least one CONFIRMED "blocked by" edge where the blocking task's status is not DONE.
|
|
44
|
+
2. At least one SUGGESTED (unconfirmed) dependency candidate on the task.
|
|
45
|
+
|
|
46
|
+
**Output form A — live blockers exist (with or without SUGGESTED candidates):**
|
|
47
|
+
|
|
48
|
+
Starts with the `## ⚠ 依赖告警` header, followed by one line per live blocker, then the advice line. If there are also SUGGESTED candidates the candidate-hint line is appended after the advice line.
|
|
49
|
+
|
|
50
|
+
```
|
|
51
|
+
## ⚠ 依赖告警
|
|
52
|
+
|
|
53
|
+
- 本任务被 LUM-9「Fix auth token expiry」阻塞(状态 IN_PROGRESS)。
|
|
54
|
+
- 本任务被 LUM-15「Upgrade Postgres driver」阻塞(状态 IN_REVIEW,PR #42 未 merge)。
|
|
55
|
+
|
|
56
|
+
建议先等 blocker 合并再开工,避免白跑 run;如边已过时,用 `lumo task deps rm/dismiss` 清理。
|
|
57
|
+
检测到 3 条候选依赖待确认:运行 `lumo task deps list LUM-42`。
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
**Output form B — NO live blockers, but SUGGESTED candidates exist:**
|
|
61
|
+
|
|
62
|
+
Output is **only** the hint line — no `## ⚠ 依赖告警` header, no advice line. Design rationale: 纯候选是提示不是告警,不戴 ⚠ 头,避免稀释真告警。
|
|
63
|
+
|
|
64
|
+
```
|
|
65
|
+
检测到 3 条候选依赖待确认:运行 `lumo task deps list LUM-42`。
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
- Each blocker line shows: identifier, title, current status. If the blocking task has an open pull request, a `,PR #N` note is appended (no space after the comma) — `,PR #N (draft) 未 merge` for draft PRs.
|
|
69
|
+
- The guidance line ("建议先等 blocker 合并…") appears only when there is at least one live blocker (form A).
|
|
70
|
+
- The function never throws — if it fails it silently returns an empty string, leaving the session start unaffected.
|
|
71
|
+
|
|
72
|
+
**Agent guidance — watch for EITHER the `## ⚠ 依赖告警` header (form A) OR the standalone hint line (form B):**
|
|
73
|
+
- **Form A — live blockers:** Evaluate whether to wait. If the blocking task's work overlaps with yours (same files, same API surface), starting immediately risks rework. Read the blocker's status and open PR note before deciding.
|
|
74
|
+
- **Stale or wrong edge?** Run `lumo task deps list <LUM-N>` to inspect the full edge list, then `lumo task deps rm <LUM-N> <edge> --yes` (if manually added and now obsolete) or `lumo task deps dismiss <LUM-N> <edge>` (if a false positive from detection).
|
|
75
|
+
- **Candidate hint present (form A or B)?** Run `lumo task deps list <LUM-N>` and review each SUGGESTED edge — confirm real ones, dismiss false positives. Leaving SUGGESTED edges unreviewed means repeated hints every session.
|
|
76
|
+
- Do **not** blindly start work on a task whose live blocker is still IN_PROGRESS or IN_REVIEW unless the user explicitly decides to proceed in parallel.
|
|
77
|
+
|
|
38
78
|
### `lumo session attach <identifier>` — bind the current session to a task
|
|
39
79
|
|
|
40
80
|
Use this whenever the user mentions a task ID. The command is the only way to bind a session to a task.
|
|
@@ -203,3 +203,133 @@ lumo task comment LUM-42 "Reproduced the redirect bug on staging — Safari only
|
|
|
203
203
|
```
|
|
204
204
|
|
|
205
205
|
The CLI does not support @-mention chip syntax. If the user wants to ping someone, they should comment from the web UI.
|
|
206
|
+
|
|
207
|
+
---
|
|
208
|
+
|
|
209
|
+
## Task Dependencies (`lumo task deps …`)
|
|
210
|
+
|
|
211
|
+
### `lumo task deps list <LUM-N>` — show all dependency edges
|
|
212
|
+
|
|
213
|
+
Prints the task's dependency edges grouped into three sections: **CONFIRMED**, **SUGGESTED(待确认)**, and **DISMISSED**. Each row includes a short 8-character edge id in square brackets, the direction (`blocked by` / `blocks`), the other task's identifier and title, the other task's current status, the source (`MANUAL` or `DETECTED`), and inline evidence for detected edges.
|
|
214
|
+
|
|
215
|
+
```bash
|
|
216
|
+
lumo task deps list LUM-42
|
|
217
|
+
```
|
|
218
|
+
|
|
219
|
+
Example output:
|
|
220
|
+
|
|
221
|
+
```
|
|
222
|
+
Dependencies for LUM-42 (3)
|
|
223
|
+
|
|
224
|
+
CONFIRMED
|
|
225
|
+
[a1b2c3d4] blocked by LUM-9「Fix auth token expiry」 IN_PROGRESS · MANUAL
|
|
226
|
+
|
|
227
|
+
SUGGESTED(待确认)
|
|
228
|
+
[e5f6a7b8] blocks LUM-55「Migrate DB schema」 TODO · shared_files(4 个共享文件: src/db/schema.ts, src/db/migrate.ts, ...)
|
|
229
|
+
确认: lumo task deps confirm LUM-42 e5f6a7b8(方向反了加 --reverse;误报: dismiss)
|
|
230
|
+
[c9d0e1f2] blocked by LUM-38「Add OAuth scopes」 IN_REVIEW · task_mention(description)
|
|
231
|
+
确认: lumo task deps confirm LUM-42 c9d0e1f2(方向反了加 --reverse;误报: dismiss)
|
|
232
|
+
|
|
233
|
+
DISMISSED
|
|
234
|
+
[b3c4d5e6] blocks LUM-12 · 已忽略
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
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
|
+
|
|
239
|
+
When there are no edges at all the output is:
|
|
240
|
+
```
|
|
241
|
+
Dependencies for LUM-42: 无依赖边。
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
**Evidence fields by detection signal:**
|
|
245
|
+
- `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
|
+
- `task_mention` — `task_mention(description)` or `task_mention(comment)` — the surface where the mention appeared.
|
|
247
|
+
|
|
248
|
+
CONFIRMED rows also show `source`: `MANUAL` (user-declared via `deps add`) or `DETECTED` (auto-found then confirmed via `deps confirm`).
|
|
249
|
+
|
|
250
|
+
### `lumo task deps add <LUM-N> --blocked-by <LUM-M>` — declare a manual hard dependency
|
|
251
|
+
|
|
252
|
+
Asserts that LUM-N is blocked by LUM-M. The edge is created as CONFIRMED + MANUAL, meaning it is immediately in effect (no confirmation step required).
|
|
253
|
+
|
|
254
|
+
```bash
|
|
255
|
+
lumo task deps add LUM-42 --blocked-by LUM-9
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
Both `<LUM-N>` and `--blocked-by` are required. The command errors on usage if either is missing.
|
|
259
|
+
|
|
260
|
+
**Service semantics (read before using):**
|
|
261
|
+
- **Self-edge** → 400 ("A task cannot depend on itself").
|
|
262
|
+
- **CONFIRMED edge in the same direction already exists** → 409 ("Dependency already exists").
|
|
263
|
+
- **CONFIRMED edge in the reverse direction already exists** → the cycle guard fires and returns 409 ("Dependency would create a cycle").
|
|
264
|
+
- **Pair has a SUGGESTED or DISMISSED edge** in the same direction → `add` confirms it in place, preserving the original DETECTED source (so the edge transitions to CONFIRMED while keeping its auto-detected provenance). No new row is created.
|
|
265
|
+
- **Cycle guard** — the service runs a DFS over all CONFIRMED BLOCKS edges in the workspace before writing. If adding the edge would create a cycle → 409 ("Dependency would create a cycle").
|
|
266
|
+
|
|
267
|
+
### `lumo task deps confirm <LUM-N> <edge> [--reverse]` — confirm a detected candidate
|
|
268
|
+
|
|
269
|
+
Promotes a SUGGESTED edge to CONFIRMED. `<edge>` is a selector for the specific edge on LUM-N's edge list.
|
|
270
|
+
|
|
271
|
+
```bash
|
|
272
|
+
lumo task deps confirm LUM-42 e5f6a7b8 # confirm as detected direction
|
|
273
|
+
lumo task deps confirm LUM-42 LUM-55 # selector by other task's identifier
|
|
274
|
+
lumo task deps confirm LUM-42 e5f6a7b8 --reverse # flip direction before confirming
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
**Edge selector semantics** (shared by `confirm`, `dismiss`, `rm`):
|
|
278
|
+
- **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
|
+
- **Edge-id prefix** — at least 6 characters of the short id (e.g., `e5f6a7`). Must match exactly one edge.
|
|
280
|
+
- 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
|
+
|
|
282
|
+
**`--reverse` semantics:**
|
|
283
|
+
- 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
|
+
- 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
|
+
|
|
286
|
+
### `lumo task deps dismiss <LUM-N> <edge>` — dismiss a candidate (immune to re-detection)
|
|
287
|
+
|
|
288
|
+
Marks the edge DISMISSED. The row is kept in the database — this is the key difference from `rm`. Because the row exists (in either direction), the detection service will never re-suggest this pair.
|
|
289
|
+
|
|
290
|
+
```bash
|
|
291
|
+
lumo task deps dismiss LUM-42 e5f6a7b8
|
|
292
|
+
lumo task deps dismiss LUM-42 LUM-38
|
|
293
|
+
```
|
|
294
|
+
|
|
295
|
+
Output: `Dismissed: [e5f6a7b8] LUM-38「Add OAuth scopes」(不再建议)`
|
|
296
|
+
|
|
297
|
+
Use `dismiss` for false positives. Use `rm` only when you want the pair to be eligible for re-detection in the future (the detection service can re-suggest pairs with no existing row).
|
|
298
|
+
|
|
299
|
+
### `lumo task deps rm <LUM-N> <edge> --yes` — delete an edge
|
|
300
|
+
|
|
301
|
+
Hard-deletes the edge row. **Requires `--yes`** — the CLI refuses without it (no interactive prompt exists).
|
|
302
|
+
|
|
303
|
+
```bash
|
|
304
|
+
lumo task deps rm LUM-42 a1b2c3d4 --yes
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
Output: `Removed [a1b2c3d4] from LUM-42`
|
|
308
|
+
|
|
309
|
+
**`rm` vs `dismiss`:** deleting removes the immunity — the detection service may re-suggest this pair the next time a shared-files sweep or task-mention event fires. Prefer `dismiss` for detection false positives; use `rm` to remove an incorrectly-declared MANUAL edge that you want fully erased.
|
|
310
|
+
|
|
311
|
+
---
|
|
312
|
+
|
|
313
|
+
### Detection red lines
|
|
314
|
+
|
|
315
|
+
- The detection service **never creates CONFIRMED edges automatically**. All auto-detected candidates are SUGGESTED; a human must `confirm` or `add` for an edge to become CONFIRMED.
|
|
316
|
+
- **Dismiss is pair-wise immunity**: once any edge exists between task A and task B (in either direction, regardless of status including DISMISSED), the detection service will not create a new candidate for that pair.
|
|
317
|
+
- **`rm` lifts the immunity**: after deleting the only edge between A and B, the detector may re-suggest them on the next event or sweep.
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
### Detection signals
|
|
322
|
+
|
|
323
|
+
**Signal 1 — `task_mention`**: fires when a task's description is updated or a comment is created. If the updated HTML contains @-mentions of other tasks, the mentioning task is recorded as depending on the mentioned task (direction heuristic: mentioner blocked by mentioned). Triggers immediately on write events; no cron needed.
|
|
324
|
+
|
|
325
|
+
**Signal 2 — `shared_files`**: hourly cron sweep. Looks at write-tool hook events (file edits, creates, etc.) in the past 14 days. For every pair of open (non-DONE) tasks that share **≥ 3 written files**, a SUGGESTED edge is created. Direction heuristic: older task blocks newer task. Parent–child task pairs are skipped (they share files by design). Edges are not created across different workspaces.
|
|
326
|
+
|
|
327
|
+
---
|
|
328
|
+
|
|
329
|
+
### When to suggest `task deps` commands
|
|
330
|
+
|
|
331
|
+
- **After `session attach` output shows a blocker warning or candidate-count hint** → run `lumo task deps list <LUM-N>` to review the full edge list, then `confirm` or `dismiss` each SUGGESTED candidate.
|
|
332
|
+
- **User says "X needs to wait for Y" or "LUM-42 is blocked by LUM-9"** → run `lumo task deps add LUM-42 --blocked-by LUM-9`.
|
|
333
|
+
- **Agent sees a `## ⚠ 依赖告警` block (form A — live blockers) at session-start** → evaluate whether to wait for the blocker to merge before starting work; if the edge is stale or wrong, clean it with `deps rm` or `deps dismiss`.
|
|
334
|
+
- **Agent sees only a standalone hint line `检测到 N 条候选依赖待确认…` (form B — no live blockers)** → no immediate blocker, but run `lumo task deps list <LUM-N>` to review and confirm/dismiss SUGGESTED candidates. See [sessions.md](sessions.md) for the full alert format.
|
|
335
|
+
- **User reports a false positive dependency suggestion** → `lumo task deps dismiss <LUM-N> <edge>` to permanently suppress it for this pair.
|
|
@@ -112,7 +112,12 @@ async function sessionAttach(identifier, options = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
console.log(`Attached session ${sessionId} to ${body.taskIdentifier} "${(0, sanitize_1.sanitizeField)(body.taskTitle)}"`);
|
|
114
114
|
console.log(`Re-tagged ${body.retaggedEventCount} previously-untagged event${body.retaggedEventCount === 1 ? '' : 's'} in this session.`);
|
|
115
|
-
//
|
|
115
|
+
// 告警先于 contract/memory:与 hook 注入顺序一致,短且可操作的信息优先。
|
|
116
|
+
if (body.blockerWarningSection) {
|
|
117
|
+
console.log('');
|
|
118
|
+
console.log((0, sanitize_1.sanitizeField)(body.blockerWarningSection));
|
|
119
|
+
}
|
|
120
|
+
// Contract next: it's what the upcoming work is judged against (LUM-342).
|
|
116
121
|
if (body.criteriaSection) {
|
|
117
122
|
console.log('');
|
|
118
123
|
console.log((0, sanitize_1.sanitizeField)(body.criteriaSection));
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.resolveEdgeSelector = resolveEdgeSelector;
|
|
4
|
+
exports.formatDepsList = formatDepsList;
|
|
5
|
+
exports.taskDepsList = taskDepsList;
|
|
6
|
+
exports.taskDepsAdd = taskDepsAdd;
|
|
7
|
+
exports.taskDepsConfirm = taskDepsConfirm;
|
|
8
|
+
exports.taskDepsDismiss = taskDepsDismiss;
|
|
9
|
+
exports.taskDepsRm = taskDepsRm;
|
|
10
|
+
const config_1 = require("../lib/config");
|
|
11
|
+
const api_1 = require("../lib/api");
|
|
12
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
13
|
+
/** 短 id = 前 8 位,展示与 selector 都用它。 */
|
|
14
|
+
const shortId = (id) => id.slice(0, 8);
|
|
15
|
+
/**
|
|
16
|
+
* Resolve a user-supplied edge selector against the task's edge list. Accepts
|
|
17
|
+
* either the other task's identifier (case-insensitive, exact) or an edge-id
|
|
18
|
+
* prefix of at least 6 chars. Returns null when nothing (or more than one
|
|
19
|
+
* edge) matches — callers print the candidate list and bail.
|
|
20
|
+
*/
|
|
21
|
+
function resolveEdgeSelector(edges, selector) {
|
|
22
|
+
const s = selector.trim();
|
|
23
|
+
const byIdent = edges.filter(e => e.other.identifier.toUpperCase() === s.toUpperCase());
|
|
24
|
+
if (byIdent.length === 1)
|
|
25
|
+
return byIdent[0] ?? null;
|
|
26
|
+
const byPrefix = edges.filter(e => e.id.startsWith(s));
|
|
27
|
+
if (s.length >= 6 && byPrefix.length === 1)
|
|
28
|
+
return byPrefix[0] ?? null;
|
|
29
|
+
return null;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Render the dependency edges of a task grouped by status. SUGGESTED rows get
|
|
33
|
+
* a copy-pasteable confirm hint with the short edge id; detected evidence
|
|
34
|
+
* (shared files / task mention) is summarized inline.
|
|
35
|
+
*/
|
|
36
|
+
function formatDepsList(identifier, edges) {
|
|
37
|
+
if (edges.length === 0)
|
|
38
|
+
return `Dependencies for ${identifier}: 无依赖边。`;
|
|
39
|
+
const lines = [
|
|
40
|
+
`Dependencies for ${identifier} (${edges.length})`,
|
|
41
|
+
'',
|
|
42
|
+
];
|
|
43
|
+
const dirLabel = (e) => e.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks';
|
|
44
|
+
const evidence = (e) => {
|
|
45
|
+
if (e.reason === 'shared_files')
|
|
46
|
+
return ` · shared_files(${e.detail?.count ?? '?'} 个共享文件${e.detail?.sample?.length ? ': ' + e.detail.sample.join(', ') : ''})`;
|
|
47
|
+
if (e.reason === 'task_mention')
|
|
48
|
+
return ` · task_mention(${e.detail?.surface ?? ''})`;
|
|
49
|
+
return '';
|
|
50
|
+
};
|
|
51
|
+
const section = (title, rows, render) => {
|
|
52
|
+
if (rows.length === 0)
|
|
53
|
+
return;
|
|
54
|
+
lines.push(title);
|
|
55
|
+
for (const e of rows)
|
|
56
|
+
lines.push(...render(e));
|
|
57
|
+
lines.push('');
|
|
58
|
+
};
|
|
59
|
+
const confirmed = edges.filter(e => e.status === 'CONFIRMED');
|
|
60
|
+
const suggested = edges.filter(e => e.status === 'SUGGESTED');
|
|
61
|
+
const dismissed = edges.filter(e => e.status === 'DISMISSED');
|
|
62
|
+
section('CONFIRMED', confirmed, e => [
|
|
63
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier}「${e.other.title}」 ${e.other.status} · ${e.source}${evidence(e)}`,
|
|
64
|
+
]);
|
|
65
|
+
section('SUGGESTED(待确认)', suggested, e => [
|
|
66
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier}「${e.other.title}」 ${e.other.status}${evidence(e)}`,
|
|
67
|
+
` 确认: lumo task deps confirm ${identifier} ${shortId(e.id)}(方向反了加 --reverse;误报: dismiss)`,
|
|
68
|
+
]);
|
|
69
|
+
section('DISMISSED', dismissed, e => [
|
|
70
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier} · 已忽略`,
|
|
71
|
+
]);
|
|
72
|
+
return lines.join('\n').trimEnd();
|
|
73
|
+
}
|
|
74
|
+
function getCtx() {
|
|
75
|
+
const creds = (0, config_1.readCredentials)();
|
|
76
|
+
if (!creds) {
|
|
77
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
78
|
+
return 1;
|
|
79
|
+
}
|
|
80
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
81
|
+
return {
|
|
82
|
+
apiUrl,
|
|
83
|
+
base: (0, api_1.trimTrailingSlash)(apiUrl),
|
|
84
|
+
token: creds.token,
|
|
85
|
+
workspaceSlug: creds.workspaceSlug,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
/** Resolve LUM-N → DB id (the dependencies endpoints take the DB id). */
|
|
89
|
+
async function resolveTask(ctx, identifier) {
|
|
90
|
+
let res;
|
|
91
|
+
try {
|
|
92
|
+
res = await fetch(`${ctx.base}/api/tasks/resolve/${encodeURIComponent(identifier)}`, { headers: { Authorization: `Bearer ${ctx.token}` } });
|
|
93
|
+
}
|
|
94
|
+
catch (err) {
|
|
95
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
96
|
+
console.error(`Error: could not reach Lumo API at ${ctx.apiUrl} (${msg})`);
|
|
97
|
+
return 1;
|
|
98
|
+
}
|
|
99
|
+
if (res.status === 401) {
|
|
100
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
101
|
+
return 1;
|
|
102
|
+
}
|
|
103
|
+
if (res.status === 404) {
|
|
104
|
+
console.error(`Error: task ${identifier} not found in workspace ${ctx.workspaceSlug}`);
|
|
105
|
+
return 1;
|
|
106
|
+
}
|
|
107
|
+
if (!res.ok) {
|
|
108
|
+
console.error(`Error: resolve failed (HTTP ${res.status})`);
|
|
109
|
+
return 1;
|
|
110
|
+
}
|
|
111
|
+
return (await res.json());
|
|
112
|
+
}
|
|
113
|
+
/** Best-effort `{ error }` body extraction for non-2xx responses. */
|
|
114
|
+
async function readErrorMessage(res) {
|
|
115
|
+
try {
|
|
116
|
+
const body = (await res.json());
|
|
117
|
+
return typeof body.error === 'string' ? body.error : null;
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
return null;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
async function depsFetch(ctx, path, init, label) {
|
|
124
|
+
let res;
|
|
125
|
+
try {
|
|
126
|
+
res = await fetch(`${ctx.base}${path}`, {
|
|
127
|
+
...init,
|
|
128
|
+
headers: {
|
|
129
|
+
Authorization: `Bearer ${ctx.token}`,
|
|
130
|
+
...(init.body ? { 'Content-Type': 'application/json' } : {}),
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
136
|
+
console.error(`Error: could not reach Lumo API at ${ctx.apiUrl} (${msg})`);
|
|
137
|
+
return 1;
|
|
138
|
+
}
|
|
139
|
+
if (res.status === 401) {
|
|
140
|
+
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
141
|
+
return 1;
|
|
142
|
+
}
|
|
143
|
+
if (!res.ok) {
|
|
144
|
+
const serverMsg = await readErrorMessage(res);
|
|
145
|
+
console.error(serverMsg
|
|
146
|
+
? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
|
|
147
|
+
: `Error: ${label} failed (HTTP ${res.status})`);
|
|
148
|
+
return 1;
|
|
149
|
+
}
|
|
150
|
+
return res;
|
|
151
|
+
}
|
|
152
|
+
async function fetchDeps(ctx, taskDbId) {
|
|
153
|
+
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(taskDbId)}/dependencies`, {}, 'dependencies list');
|
|
154
|
+
if (typeof res === 'number')
|
|
155
|
+
return res;
|
|
156
|
+
const { dependencies } = (await res.json());
|
|
157
|
+
return dependencies ?? [];
|
|
158
|
+
}
|
|
159
|
+
/** Selector miss: print all candidates with short ids so the user can retry. */
|
|
160
|
+
function printSelectorCandidates(edges, selector) {
|
|
161
|
+
console.error(`Error: no unique dependency edge matches "${selector}". Candidates:`);
|
|
162
|
+
if (edges.length === 0) {
|
|
163
|
+
console.error(' (无依赖边)');
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
for (const e of edges) {
|
|
167
|
+
console.error((0, sanitize_1.sanitizeField)(` [${shortId(e.id)}] ${e.status} ${e.other.identifier}「${e.other.title}」`));
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Shared front half of confirm/dismiss/rm: resolve the task, fetch its edge
|
|
172
|
+
* list, and resolve the selector. Returns the task + edge on success, or an
|
|
173
|
+
* exit code after printing the candidate list.
|
|
174
|
+
*/
|
|
175
|
+
async function resolveTaskAndEdge(identifier, selector) {
|
|
176
|
+
const ctx = getCtx();
|
|
177
|
+
if (typeof ctx === 'number')
|
|
178
|
+
return ctx;
|
|
179
|
+
const task = await resolveTask(ctx, identifier);
|
|
180
|
+
if (typeof task === 'number')
|
|
181
|
+
return task;
|
|
182
|
+
const edges = await fetchDeps(ctx, task.id);
|
|
183
|
+
if (typeof edges === 'number')
|
|
184
|
+
return edges;
|
|
185
|
+
const edge = resolveEdgeSelector(edges, selector);
|
|
186
|
+
if (!edge) {
|
|
187
|
+
printSelectorCandidates(edges, selector);
|
|
188
|
+
return 1;
|
|
189
|
+
}
|
|
190
|
+
return { ctx, task, edge };
|
|
191
|
+
}
|
|
192
|
+
// ---------------------------------------------------------------------------
|
|
193
|
+
// Command actions
|
|
194
|
+
// ---------------------------------------------------------------------------
|
|
195
|
+
/** `lumo task deps list <LUM-N>` */
|
|
196
|
+
async function taskDepsList(identifier) {
|
|
197
|
+
if (!identifier) {
|
|
198
|
+
console.error('Error: usage: lumo task deps list <LUM-42>');
|
|
199
|
+
return 1;
|
|
200
|
+
}
|
|
201
|
+
const ctx = getCtx();
|
|
202
|
+
if (typeof ctx === 'number')
|
|
203
|
+
return ctx;
|
|
204
|
+
const task = await resolveTask(ctx, identifier);
|
|
205
|
+
if (typeof task === 'number')
|
|
206
|
+
return task;
|
|
207
|
+
const edges = await fetchDeps(ctx, task.id);
|
|
208
|
+
if (typeof edges === 'number')
|
|
209
|
+
return edges;
|
|
210
|
+
// Whole-block sanitization (session-attach style): edge titles, reasons and
|
|
211
|
+
// sample paths are server-controlled free text.
|
|
212
|
+
console.log((0, sanitize_1.sanitizeField)(formatDepsList(task.identifier, edges)));
|
|
213
|
+
}
|
|
214
|
+
/** `lumo task deps add <LUM-N> --blocked-by <LUM-M>` */
|
|
215
|
+
async function taskDepsAdd(identifier, opts) {
|
|
216
|
+
if (!identifier || !opts.blockedBy) {
|
|
217
|
+
console.error('Error: usage: lumo task deps add <LUM-42> --blocked-by <LUM-9>');
|
|
218
|
+
return 1;
|
|
219
|
+
}
|
|
220
|
+
const ctx = getCtx();
|
|
221
|
+
if (typeof ctx === 'number')
|
|
222
|
+
return ctx;
|
|
223
|
+
const task = await resolveTask(ctx, identifier);
|
|
224
|
+
if (typeof task === 'number')
|
|
225
|
+
return task;
|
|
226
|
+
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies`, { method: 'POST', body: JSON.stringify({ blockedBy: opts.blockedBy }) }, 'dependency add');
|
|
227
|
+
if (typeof res === 'number')
|
|
228
|
+
return res;
|
|
229
|
+
console.log(`Added: ${task.identifier} blocked by ${opts.blockedBy}`);
|
|
230
|
+
}
|
|
231
|
+
/** `lumo task deps confirm <LUM-N> <edge> [--reverse]` */
|
|
232
|
+
async function taskDepsConfirm(identifier, selector, opts) {
|
|
233
|
+
if (!identifier || !selector) {
|
|
234
|
+
console.error('Error: usage: lumo task deps confirm <LUM-42> <edge> [--reverse]');
|
|
235
|
+
return 1;
|
|
236
|
+
}
|
|
237
|
+
const resolved = await resolveTaskAndEdge(identifier, selector);
|
|
238
|
+
if (typeof resolved === 'number')
|
|
239
|
+
return resolved;
|
|
240
|
+
const { ctx, task, edge } = resolved;
|
|
241
|
+
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies/${encodeURIComponent(edge.id)}`, {
|
|
242
|
+
method: 'PATCH',
|
|
243
|
+
body: JSON.stringify({
|
|
244
|
+
action: 'confirm',
|
|
245
|
+
reverse: opts.reverse ?? false,
|
|
246
|
+
}),
|
|
247
|
+
}, 'dependency confirm');
|
|
248
|
+
if (typeof res === 'number')
|
|
249
|
+
return res;
|
|
250
|
+
console.log((0, sanitize_1.sanitizeField)(`Confirmed: [${shortId(edge.id)}] ${task.identifier} ${edge.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks'} ${edge.other.identifier}「${edge.other.title}」${opts.reverse ? ' (reversed)' : ''}`));
|
|
251
|
+
}
|
|
252
|
+
/** `lumo task deps dismiss <LUM-N> <edge>` */
|
|
253
|
+
async function taskDepsDismiss(identifier, selector) {
|
|
254
|
+
if (!identifier || !selector) {
|
|
255
|
+
console.error('Error: usage: lumo task deps dismiss <LUM-42> <edge>');
|
|
256
|
+
return 1;
|
|
257
|
+
}
|
|
258
|
+
const resolved = await resolveTaskAndEdge(identifier, selector);
|
|
259
|
+
if (typeof resolved === 'number')
|
|
260
|
+
return resolved;
|
|
261
|
+
const { ctx, task, edge } = resolved;
|
|
262
|
+
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies/${encodeURIComponent(edge.id)}`, { method: 'PATCH', body: JSON.stringify({ action: 'dismiss' }) }, 'dependency dismiss');
|
|
263
|
+
if (typeof res === 'number')
|
|
264
|
+
return res;
|
|
265
|
+
console.log((0, sanitize_1.sanitizeField)(`Dismissed: [${shortId(edge.id)}] ${edge.other.identifier}「${edge.other.title}」(不再建议)`));
|
|
266
|
+
}
|
|
267
|
+
/** `lumo task deps rm <LUM-N> <edge> --yes` */
|
|
268
|
+
async function taskDepsRm(identifier, selector, opts) {
|
|
269
|
+
if (!identifier || !selector) {
|
|
270
|
+
console.error('Error: usage: lumo task deps rm <LUM-42> <edge> --yes');
|
|
271
|
+
return 1;
|
|
272
|
+
}
|
|
273
|
+
if (!opts.yes) {
|
|
274
|
+
console.error('Error: refusing to delete without --yes. Re-run with --yes to confirm.');
|
|
275
|
+
return 1;
|
|
276
|
+
}
|
|
277
|
+
const resolved = await resolveTaskAndEdge(identifier, selector);
|
|
278
|
+
if (typeof resolved === 'number')
|
|
279
|
+
return resolved;
|
|
280
|
+
const { ctx, task, edge } = resolved;
|
|
281
|
+
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies/${encodeURIComponent(edge.id)}`, { method: 'DELETE' }, 'dependency delete');
|
|
282
|
+
if (typeof res === 'number')
|
|
283
|
+
return res;
|
|
284
|
+
console.log(`Removed [${shortId(edge.id)}] from ${task.identifier}`);
|
|
285
|
+
}
|
package/dist/cli/src/index.js
CHANGED
|
@@ -74,6 +74,7 @@ const task_slack_show_1 = require("./commands/task-slack-show");
|
|
|
74
74
|
const task_web_show_1 = require("./commands/task-web-show");
|
|
75
75
|
const task_figma_context_1 = require("./commands/task-figma-context");
|
|
76
76
|
const task_comment_list_1 = require("./commands/task-comment-list");
|
|
77
|
+
const task_deps_1 = require("./commands/task-deps");
|
|
77
78
|
const task_pr_show_1 = require("./commands/task-pr-show");
|
|
78
79
|
const task_lineage_1 = require("./commands/task-lineage");
|
|
79
80
|
const project_list_1 = require("./commands/project-list");
|
|
@@ -307,6 +308,32 @@ taskComments
|
|
|
307
308
|
.command('list <identifier>')
|
|
308
309
|
.description('List the full task comment thread')
|
|
309
310
|
.action(wrap(id => (0, task_comment_list_1.taskCommentList)(id)));
|
|
311
|
+
const taskDeps = task
|
|
312
|
+
.command('deps')
|
|
313
|
+
.description('Task dependency edges — detected candidates + confirmed blockers');
|
|
314
|
+
taskDeps
|
|
315
|
+
.command('list <identifier>')
|
|
316
|
+
.description('List dependency edges (both directions)')
|
|
317
|
+
.action(wrap(id => (0, task_deps_1.taskDepsList)(id)));
|
|
318
|
+
taskDeps
|
|
319
|
+
.command('add <identifier>')
|
|
320
|
+
.requiredOption('--blocked-by <blocker>', 'blocker task (LUM-N)')
|
|
321
|
+
.description('Declare a manual hard dependency (CONFIRMED)')
|
|
322
|
+
.action(wrap((id, opts) => (0, task_deps_1.taskDepsAdd)(id, opts)));
|
|
323
|
+
taskDeps
|
|
324
|
+
.command('confirm <identifier> <edge>')
|
|
325
|
+
.option('--reverse', 'flip direction when confirming')
|
|
326
|
+
.description('Confirm a detected candidate edge')
|
|
327
|
+
.action(wrap((id, edge, opts) => (0, task_deps_1.taskDepsConfirm)(id, edge, opts)));
|
|
328
|
+
taskDeps
|
|
329
|
+
.command('dismiss <identifier> <edge>')
|
|
330
|
+
.description('Dismiss a candidate (never re-suggested)')
|
|
331
|
+
.action(wrap((id, edge) => (0, task_deps_1.taskDepsDismiss)(id, edge)));
|
|
332
|
+
taskDeps
|
|
333
|
+
.command('rm <identifier> <edge>')
|
|
334
|
+
.option('--yes', 'skip confirmation')
|
|
335
|
+
.description('Delete a dependency edge')
|
|
336
|
+
.action(wrap((id, edge, opts) => (0, task_deps_1.taskDepsRm)(id, edge, opts)));
|
|
310
337
|
const taskPr = task.command('pr').description('Inspect linked PRs');
|
|
311
338
|
taskPr
|
|
312
339
|
.command('show <identifier> <number>')
|
|
@@ -107,15 +107,19 @@ function formatHookStdoutLines(path, responseBody, now = new Date()) {
|
|
|
107
107
|
else if (tb && tb.bound === false) {
|
|
108
108
|
lines.push(unboundPromptLine(sessionId));
|
|
109
109
|
}
|
|
110
|
-
// Recovery card + memory + linked resources + PR-review
|
|
111
|
-
// additionalContext block so Claude Code injects a single
|
|
112
|
-
// payload at session start. The card slots in first so it's
|
|
113
|
-
// the model reads
|
|
110
|
+
// Recovery card + blocker warning + memory + linked resources + PR-review
|
|
111
|
+
// todos share one additionalContext block so Claude Code injects a single
|
|
112
|
+
// coherent context payload at session start. The card slots in first so it's
|
|
113
|
+
// the first thing the model reads; the dependency blocker warning (LUM-172)
|
|
114
|
+
// comes right after so it stays prominent, ahead of the memory section.
|
|
114
115
|
const card = renderRecoveryCard(body.previousSession, tb?.taskIdentifier ?? '', now);
|
|
115
116
|
const envelope = sessionContextEnvelope([
|
|
116
117
|
card,
|
|
117
|
-
//
|
|
118
|
-
//
|
|
118
|
+
// Blocker warning right after the card: it can preempt the session's
|
|
119
|
+
// work entirely (wait for the blocker instead of starting) — LUM-172.
|
|
120
|
+
body.blockerWarningSection,
|
|
121
|
+
// Acceptance contract next: it's what the session's work is judged
|
|
122
|
+
// against (LUM-342).
|
|
119
123
|
body.criteriaSection,
|
|
120
124
|
body.memorySection,
|
|
121
125
|
body.linkedResourcesSection,
|