@lumoai/cli 1.58.0 โ†’ 1.60.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.
Files changed (47) hide show
  1. package/assets/skill/SKILL.md +15 -17
  2. package/assets/skill/references/artifacts-figma.md +4 -3
  3. package/assets/skill/references/confirmation.md +133 -0
  4. package/assets/skill/references/criteria.md +12 -21
  5. package/assets/skill/references/doc-editing.md +11 -9
  6. package/assets/skill/references/docs.md +4 -3
  7. package/assets/skill/references/memory.md +4 -2
  8. package/assets/skill/references/milestones.md +3 -2
  9. package/assets/skill/references/outcome.md +1 -14
  10. package/assets/skill/references/plan-runs.md +5 -0
  11. package/assets/skill/references/sessions.md +4 -4
  12. package/assets/skill/references/sprints.md +18 -17
  13. package/assets/skill/references/task-deps.md +4 -3
  14. package/assets/skill/references/tasks.md +34 -2
  15. package/assets/skill/references/verify.md +71 -71
  16. package/assets/skill/references/worktree.md +13 -7
  17. package/dist/cli/src/commands/crossing-disposition.js +342 -0
  18. package/dist/cli/src/commands/crossing-explain.js +10 -21
  19. package/dist/cli/src/commands/doc-delete.js +35 -23
  20. package/dist/cli/src/commands/doc-rebuild-source.js +16 -4
  21. package/dist/cli/src/commands/memory-rm.js +68 -10
  22. package/dist/cli/src/commands/milestone-delete.js +13 -10
  23. package/dist/cli/src/commands/outcome.js +0 -77
  24. package/dist/cli/src/commands/session-attach.js +8 -2
  25. package/dist/cli/src/commands/sprint-close.js +29 -9
  26. package/dist/cli/src/commands/sprint-delete.js +13 -10
  27. package/dist/cli/src/commands/sprint-show.js +3 -9
  28. package/dist/cli/src/commands/task-artifact-rm.js +58 -28
  29. package/dist/cli/src/commands/task-criteria-list.js +1 -4
  30. package/dist/cli/src/commands/task-criteria-set.js +3 -12
  31. package/dist/cli/src/commands/task-deps.js +20 -6
  32. package/dist/cli/src/commands/task-status.js +196 -111
  33. package/dist/cli/src/commands/task-update.js +129 -0
  34. package/dist/cli/src/commands/verify.js +22 -13
  35. package/dist/cli/src/commands/worktree-rm.js +35 -7
  36. package/dist/cli/src/index.js +60 -48
  37. package/dist/cli/src/lib/blocked-error.js +183 -0
  38. package/dist/cli/src/lib/bound-task.js +32 -0
  39. package/dist/cli/src/lib/confirmation.js +119 -0
  40. package/dist/cli/src/lib/hook-runner.js +23 -11
  41. package/dist/cli/src/lib/open-crossings.js +6 -6
  42. package/dist/shared/src/referent-kind.js +31 -1
  43. package/dist/shared/src/security-scan.js +125 -0
  44. package/package.json +1 -1
  45. package/assets/skill/references/fidelity.md +0 -32
  46. package/dist/cli/src/commands/fidelity.js +0 -108
  47. package/dist/cli/src/commands/verdict.js +0 -189
@@ -36,7 +36,7 @@ When the session is bound, session-start may inject a **"๐Ÿ†• Review needed: pro
36
36
 
37
37
  #### When to suggest
38
38
 
39
- 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.
39
+ Briefly sanity-check each listed memory against the codebase/context. If one is wrong or over-generalized, remove it with `lumo memory rm <id>` โ€” it exits 4 with a confirmation envelope; show it to the user and re-run with `--confirm` only on their approval ([confirmation.md](confirmation.md)). If they all look right, ignore the section and continue.
40
40
 
41
41
  ### Blocker alert injected at `session attach` / session-start
42
42
 
@@ -73,7 +73,7 @@ Detected 3 candidate dependencies awaiting confirmation: run `lumo task deps lis
73
73
  Watch for EITHER the `## โš  Dependency alerts` header (form A) OR the standalone hint line (form B).
74
74
 
75
75
  - `lumo task deps list <LUM-N>` โ€” inspect the full edge list (run for any candidate hint, form A or B; confirm real SUGGESTED edges, dismiss false positives โ€” unreviewed edges mean repeated hints every session).
76
- - `lumo task deps rm <LUM-N> <edge> --yes` โ€” drop a manually-added, now-obsolete edge.
76
+ - `lumo task deps rm <LUM-N> <edge> --confirm` โ€” drop a manually-added, now-obsolete edge (exit 4 + envelope without `--confirm`).
77
77
  - `lumo task deps dismiss <LUM-N> <edge>` โ€” dismiss a false positive from detection.
78
78
  - Form A live blockers: evaluate whether to wait โ€” overlapping work (same files, same API surface) risks rework; read the blocker's status and open-PR note before deciding.
79
79
  - 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.
@@ -134,7 +134,7 @@ lumo session attach --steward cmxyz123... # by milestone id
134
134
  - **Multiple stewards per milestone are allowed** (acceptance is multi-round by nature); sessions don't conflict.
135
135
  - **Prior-milestone learnings injected at attach (LUM-647):** the attach prints a `## Prior-milestone learnings (this project)` section โ€” the **MILESTONE-scope** memories distilled from this project's earlier milestone-close retros (a cross-milestone learning unit), wrapped in the `<untrusted-team-memory>` boundary. Empty when the project has no prior distilled learnings; the current milestone's own rows are excluded. A resumed steward session re-gets it via the session-start hook. These memories are written automatically when a milestone closes and its retro is generated โ€” there is no manual command to author them.
136
136
 
137
- **Allowed (governance โ€” pass through unchanged, with the session recorded as provenance):** `task create` / `task update`, `task criteria set` (incl. `--human`), `task deps confirm/dismiss`, `verdict --fail` (the send-back rows carry `sourceSessionId`), `milestone add/remove`, and **all read operations**.
137
+ **Allowed (governance โ€” pass through unchanged, with the session recorded as provenance):** `task create` / `task update`, `task criteria set` (incl. `--human`), `task deps confirm/dismiss`, `milestone add/remove`, and **all read operations**.
138
138
 
139
139
  **Blocked (implementation โ€” first layer, API gate):**
140
140
 
@@ -142,7 +142,7 @@ lumo session attach --steward cmxyz123... # by milestone id
142
142
  - Moving a task to `in_progress` with **yourself as the effective assignee** (whether already assigned to you or assigned in the same call) โ†’ 409. Dispatching to _someone else_ is planning and passes.
143
143
  - Red line unchanged: a steward writes no PASS of any kind; DONE stays human-only.
144
144
 
145
- **Backstop (second layer, detector):** commits / PR creations produced during a steward session anyway (the CLI can't stop local git) are recorded as **`STEWARD_IMPLEMENTATION_ARTIFACT` boundary crossings against the milestone** โ€” fail-closed (an unparseable Bash payload records an _unconfirmed_ crossing rather than nothing), disposition stays **web + human-only**, and there is **no CLI clear path** (`crossing explain` remains an appeal, never a clear).
145
+ **Backstop (second layer, detector):** commits / PR creations produced during a steward session anyway (the CLI can't stop local git) are recorded as **`STEWARD_IMPLEMENTATION_ARTIFACT` boundary crossings against the milestone** โ€” fail-closed (an unparseable Bash payload records an _unconfirmed_ crossing rather than nothing), disposition stays **web + human-only** โ€” `lumo crossing disposition` (LUM-769) is task-scoped and cannot reach a milestone crossing โ€” and there is **no CLI clear path** (`crossing explain` remains an appeal, never a clear).
146
146
 
147
147
  #### When to suggest
148
148
 
@@ -42,15 +42,12 @@ When to suggest: user says "create a sprint", "new sprint", "start a new iterati
42
42
 
43
43
  `<identifier>` accepts a sprint number (e.g. `3`) or a UUID. `--team` is required when using a number in a multi-team workspace.
44
44
 
45
- Output: key:value header (number, name, status, dates, team), then a `Progress:` line, then a **`Health:` line** with the sprint's risk level (`HEALTHY` / `WATCH` / `AT-RISK`), then a task table listing every task in the sprint.
45
+ Output: key:value header (number, name, status, dates, team), then a `Progress:` line, then a `Blockers:` section, then a task table listing every task in the sprint.
46
46
 
47
- The risk level reuses the same engine as project/workspace stats, fed the sprint's task set (thresholds come from the workspace risk config โ€” sprints have no per-sprint overrides). When the engine flags reasons they print as `- <detail>` lines under `Health:`, and a `Blockers:` section lists the top offenders per category (`Overdue` / `Stalled` / `Agent fail` / `Stale PRs`). A healthy sprint with no blockers shows just the `Health:` line.
47
+ `Blockers:` lists the top offenders per category (`Overdue` / `Stalled` / `Agent fail` / `Stale PRs`) โ€” a task counts as stalled after 7 days untouched, a PR as stale after 3 days awaiting review. A sprint with nothing in any category prints no `Blockers:` section at all. (There is no risk score or `Health:` line โ€” LUM-709 removed the risk-assessment layer.)
48
48
 
49
49
  ```
50
50
  Progress: 4 / 10
51
- Health: AT-RISK
52
- - 4/10 tasks overdue (40%)
53
- - 3 tasks with no progress in 7 days
54
51
  Blockers:
55
52
  Overdue: LUM-1 Fix login, LUM-8 Wire API
56
53
  Stalled: LUM-5 Add tests
@@ -63,7 +60,7 @@ lumo sprint show 3 --team backend
63
60
  lumo sprint show 11111111-2222-3333-4444-555555555555
64
61
  ```
65
62
 
66
- When to suggest: user asks "what's in sprint 3", "show me the current sprint", "what tasks are in this sprint", "is this sprint at risk", "sprint health".
63
+ When to suggest: user asks "what's in sprint 3", "show me the current sprint", "what tasks are in this sprint", "what's blocking this sprint".
67
64
 
68
65
  ### `lumo sprint update <identifier> [flags]` โ€” patch a sprint
69
66
 
@@ -83,12 +80,13 @@ lumo sprint update 3 --end 2026-06-21
83
80
 
84
81
  When to suggest: user wants to rename a sprint, extend dates, or fix sprint metadata.
85
82
 
86
- ### `lumo sprint delete <identifier> --yes` โ€” delete a sprint (DRAFT only)
83
+ ### `lumo sprint delete <identifier> --confirm` โ€” delete a sprint (DRAFT only)
87
84
 
88
- Requires `--yes`; no interactive prompt (agent-friendly). Server rejects with an error if the sprint is ACTIVE or CLOSED.
85
+ Without `--confirm` the CLI exits **4** with a confirmation envelope (`Will delete sprint #3 "Sprint 3"` ยท `N tasks under it keep their data; only sprintId is cleared`) and deletes nothing โ€” relay it to the user, re-run `confirmCommand` on approval ([confirmation.md](confirmation.md)). `--yes` is a legacy alias. Server rejects with an error if the sprint is ACTIVE or CLOSED.
89
86
 
90
87
  ```bash
91
- lumo sprint delete 3 --yes
88
+ lumo sprint delete 3 # exit 4 + envelope
89
+ lumo sprint delete 3 --confirm # after the user approved
92
90
  ```
93
91
 
94
92
  When to suggest: user wants to remove a draft sprint that was created by mistake.
@@ -107,18 +105,21 @@ When to suggest: user says "start the sprint", "kick off sprint 3", "activate sp
107
105
 
108
106
  Handles unfinished tasks based on flags. Without flags: closes only if all tasks are done; otherwise prints a list of unfinished tasks and refuses.
109
107
 
110
- | Flag | Type | Notes |
111
- | --------------- | ------- | -------------------------------------------------------------------------------- |
112
- | `--move-all` | boolean | Move all unfinished tasks to the next sprint. Requires `--yes`. |
113
- | `--backlog-all` | boolean | Remove all unfinished tasks from the sprint (send to backlog). Requires `--yes`. |
114
- | `--yes` | boolean | Required when `--move-all` or `--backlog-all` is given. |
108
+ | Flag | Type | Notes |
109
+ | --------------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
110
+ | `--move-all` | boolean | Move all unfinished tasks to the next sprint. Gated by `--confirm`. |
111
+ | `--backlog-all` | boolean | Remove all unfinished tasks from the sprint (send to backlog). Gated by `--confirm`. |
112
+ | `--confirm` | boolean | The user approved the envelope. Without it a mode flag exits 4 with a confirmation envelope listing every task that would move (`--yes` is a legacy alias). |
115
113
 
116
114
  ```bash
117
- lumo sprint close 3 # fails if unfinished tasks exist
118
- lumo sprint close 3 --move-all --yes # move unfinished to next sprint
119
- lumo sprint close 3 --backlog-all --yes # send unfinished to backlog
115
+ lumo sprint close 3 # fails (exit 1) if unfinished tasks exist โ€” pick a mode
116
+ lumo sprint close 3 --move-all # exit 4 + envelope: "2 unfinished tasks move to the next sprint: LUM-3, LUM-4"
117
+ lumo sprint close 3 --move-all --confirm # move unfinished to next sprint (after the user approved)
118
+ lumo sprint close 3 --backlog-all --confirm # send unfinished to backlog
120
119
  ```
121
120
 
121
+ See [confirmation.md](confirmation.md) for the envelope shape and the relay rules.
122
+
122
123
  When to suggest: user says "close the sprint", "end sprint 3", "wrap up the sprint". If they haven't decided what to do with unfinished tasks, ask before adding `--move-all` or `--backlog-all`.
123
124
 
124
125
  ### `lumo sprint summary <identifier> [--retry]` โ€” fetch AI-generated sprint retro
@@ -82,12 +82,13 @@ lumo task deps dismiss LUM-42 e5f6a7b8
82
82
 
83
83
  Use `dismiss` for false positives. Use `rm` only when you want the pair eligible for re-detection again.
84
84
 
85
- ## `lumo task deps rm <LUM-N> <edge> --yes` โ€” delete an edge
85
+ ## `lumo task deps rm <LUM-N> <edge> --confirm` โ€” delete an edge
86
86
 
87
- Hard-deletes the edge row. **Requires `--yes`** โ€” the CLI refuses without it (no interactive prompt exists).
87
+ Hard-deletes the edge row. Without `--confirm` it resolves the edge, then exits **4** with a confirmation envelope describing it (`Will delete dependency edge [a1b2c3d4] from LUM-42: blocked by LUM-9 "โ€ฆ" (CONFIRMED, manual)`) and sends nothing โ€” relay `changes` to the user and re-run the envelope's `confirmCommand` on approval (see [confirmation.md](confirmation.md)). `--yes` is a legacy alias of `--confirm`.
88
88
 
89
89
  ```bash
90
- lumo task deps rm LUM-42 a1b2c3d4 --yes
90
+ lumo task deps rm LUM-42 a1b2c3d4 # exit 4 + envelope, nothing deleted
91
+ lumo task deps rm LUM-42 a1b2c3d4 --confirm # after the user approved
91
92
  # Removed [a1b2c3d4] from LUM-42
92
93
  ```
93
94
 
@@ -120,14 +120,46 @@ The server's transition matrix (`lib/task/state-machine.ts`):
120
120
 
121
121
  Practical rules:
122
122
 
123
- - **One call suffices.** `--status done` straight from TODO or IN_PROGRESS is legal โ€” never walk `in_progress โ†’ in_review โ†’ done` as a ritual; it just wastes calls.
123
+ - **One call suffices.** `--status done` straight from TODO or IN_PROGRESS is legal โ€” never walk `in_progress โ†’ in_review โ†’ done` as a ritual; it just wastes calls. That one call **walks the confirmation protocol** (see below): without `--confirm` it exits 4 with an envelope, never a prompt.
124
124
  - **Under the verify flow you don't set `in_review`/`done` at all** โ€” `lumo verify` moves the task to IN_REVIEW on all-pass and the DONE adjudication is human-only.
125
125
  - **A DONE task can be reopened** โ€” to IN_REVIEW, IN_PROGRESS, or TODO. Reopening is a plain status change and does not alter any recorded acceptance verdict; verdict adjudication is human-only and has no CLI path. To attach context without reopening, `lumo task comment` works.
126
126
 
127
+ ### `--status done` requires `--confirm` โ€” the confirmation envelope (LUM-755)
128
+
129
+ DONE is the one status move that needs a human's sign-off. It no longer prompts (LUM-731's interactive yes/no question is gone): run without `--confirm`, the CLI looks the task up, prints a **confirmation envelope** on stdout and exits **4** with nothing sent โ€” no PATCH, and no tag resolution either. The envelope's `changes` carry everything the old prompt showed, including a trailing `โš ` line when a linked PR has not merged (that line replaces the old second prompt โ€” the protocol is one step, and you relay the whole block). Full protocol: [confirmation.md](confirmation.md).
130
+
131
+ ```bash
132
+ lumo task update LUM-48 --status done
133
+ # exit 4, stdout (JSON when piped; the same lines as text on a TTY):
134
+ # {
135
+ # "status": "confirmation_required",
136
+ # "command": "task update",
137
+ # "changes": [
138
+ # "Will move LUM-48 \"Title\" to DONE",
139
+ # "Status: IN_REVIEW โ†’ DONE",
140
+ # "#926 merged refactor: panel https://github.com/o/r/pull/926",
141
+ # "#930 open feat: wip https://github.com/o/r/pull/930",
142
+ # "โš  1 linked pull request not merged: #930 open"
143
+ # ],
144
+ # "confirmCommand": "lumo task update LUM-48 --status done --confirm"
145
+ # }
146
+ ```
147
+
148
+ With no linked PR the PR lines collapse to `Pull requests: none linked` and there is no `โš ` line. Other flags on the same call (`--title`, `--add-tag`โ€ฆ) are preserved in `confirmCommand`.
149
+
150
+ - **Show `changes` to the user and wait for an explicit yes**, then run `confirmCommand` unchanged. `--confirm` means the user confirmed โ€” never add it on your own.
151
+ - With `--confirm` the PATCH goes out directly. The server's own DONE gates still apply on top โ€” an unresolved send-back, an undispositioned boundary crossing or a blocking security finding refuses with **409**, which the CLI turns into a structured **`DONE_BLOCKED` error on stdout with exit 5**: the 409 text verbatim, a `blockers[]` with ids (built from `task status` + the crossings read model), and `remediation` lines that only name human-side or user-approved paths. Nothing on `task update` clears any of them โ€” relay, don't retry. A boundary crossing is the one blocker with a terminal path, `lumo crossing disposition <id> โ€ฆ` (itself exit-4: the user rules, never you); once every blocker is cleared, re-run this `--confirm` call. See [confirmation.md](confirmation.md) "When the gate is human-only". Any other 409 stays plain text, exit 1.
152
+ - `--confirm` is the only flag: there is no `--yes` or `--force` on `task update`. Every other status (`todo` / `in_progress` / `in_review`) PATCHes directly and ignores `--confirm`.
153
+ - A failed lookup (404, network) exits 1 before any envelope.
154
+
155
+ ```bash
156
+ lumo task update LUM-48 --status done --confirm # only after the user approved the envelope
157
+ ```
158
+
127
159
  ### When to suggest `task update`
128
160
 
129
161
  - The user describes a state change in natural language (e.g. "mark LUM-48 as in progress", "rename LUM-12 to ...", "assign LUM-30 to me", "bump the priority on LUM-7").
130
- - After the agent finishes a task and the user confirms โ€” offer to move it to `done`.
162
+ - After the agent finishes a task and the user confirms โ€” tell the user to run `lumo task update LUM-N --status done` in their terminal (it prompts for confirmation; an agent shell is non-TTY and is refused).
131
163
  - Multiple status changes in a row should each be a separate `update` invocation rather than batched.
132
164
 
133
165
  ### Sprint output format
@@ -21,22 +21,19 @@ lumo verify LUM-42 --note "โ€ฆ" # explicit task (overrides the sessio
21
21
  lumo verify --note "โ€ฆ" --timeout 900 # per-checkpointer timeout in seconds (default 600)
22
22
  ```
23
23
 
24
- **`--note` is required (LUM-597)** whenever a round will actually be posted (the
25
- task has MACHINE criteria). It is your one-line self-report โ€” what you did and
26
- why it is ready ("ๆˆ‘ๆ”นไบ† X,ๅ› ไธบ Y ๅฏ้ชŒๆ”ถ"). When the round all-passes and the
27
- task flips to IN*REVIEW, the note is frozen as the task's **claim** (provenance
28
- `AGENT` โ€” theๆฑ‡ๆŠฅ่€…'s own voice, not a summarizer paraphrase) and is checked
29
- against the diff for faithfulness. Capturing the claim is deterministic ("ไธๅกซไธ
30
- ่ฎฉ post"); whether it is \_truthful* is the faithfulness audit's job โ€” so don't
31
- under- or over-state it. A missing/blank note is refused **before** the round is
32
- posted, so no round is burned; just re-run with one. (An old CLI that omits it
33
- still verifies โ€” the claim degrades to the synthesized run-summary fallback.)
24
+ **`--note` is optional (LUM-733).** It is your one-line self-report โ€” what
25
+ you did and why it is ready ("ๆˆ‘ๆ”นไบ† X,ๅ› ไธบ Y ๅฏ้ชŒๆ”ถ"). When given and the round
26
+ all-passes into IN_REVIEW, it is frozen as the task's claim (source `AGENT`) and
27
+ judged for faithfulness in the background; when omitted the round still posts
28
+ and the claim degrades to the synthesized run summary. It is never a
29
+ precondition for verifying โ€” don't hold a round back to word it.
34
30
 
35
31
  ## What one round does
36
32
 
37
33
  1. Loads the task's acceptance contract and picks out MACHINE criteria.
38
- 2. Runs each checkpointer locally (shell, cwd = current directory), one at a
39
- time, echoing PASS/FAIL as it goes.
34
+ 2. Runs each checkpointer locally (shell, cwd = current directory, env = the
35
+ CLI's own minus its private `LUMO_NO_HINTS` mute โ€” `--no-hints` never
36
+ reaches a checkpointer), one at a time, echoing PASS/FAIL as it goes.
40
37
  3. POSTs the structured verdicts; the server records one VerificationRun per
41
38
  criterion at round = previous max + 1 and mirrors each verdict as a
42
39
  TaskActivity event.
@@ -64,16 +61,15 @@ errors.
64
61
 
65
62
  ## Edge cases
66
63
 
67
- | Case | Behavior |
68
- | ----------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
69
- | **No contract yet** | Error pointing at `lumo task criteria set`; draft the contract first (criteria.md golden rule). |
70
- | **HUMAN-only contract** (zero MACHINE criteria) | Nothing to run; CLI says so and suggests `lumo task update <id> --status in_review` for human review. No server write happens. |
71
- | **Partial round** | A round must cover every MACHINE criterion; the CLI always runs all of them and the server rejects partial rounds. |
72
- | **`REVIEW_ADDED` criteria** | Criteria added during review appear in the contract and are picked up automatically by the next round. |
73
- | **Session bound to a different task** | Server returns 409, surfaced as an error. No advisory printed; the verify round is rejected outright. |
74
- | **Ungrounded EXTERNAL_FACT PASS** (LUM-605) | A reported PASS on a criterion declared `EXTERNAL_FACT` whose checkpointer doesn't invoke external tooling (e.g. `npx jest โ€ฆ`) is blocked with **422 โ€” no round burned**; the task does **not** move to IN_REVIEW. Fix it for free: re-ground the checkpointer against a real external fact, or honestly re-declare `referentKind` as `AGENT_CONSTRUCTED_STATE` (`lumo task criteria set`), then re-run. See criteria.md "referentKind". |
75
- | **Provably-unbound session** | Response carries `bindingAdvisory: 'unbound'`; prints `โš  Working unbound โ€” this verify ran from a Claude Code session not attached to the task.` Recorded as a `SESSION_BINDING_MISSING` boundary crossing (visible in `lumo task status` open crossings). Run `lumo session attach <LUM-N>` before the next verify. |
76
- | **Unconfirmed session binding** | `bindingAdvisory: 'unconfirmed'` โ†’ softer advisory `โš  Could not confirm this session is attached to the task.` Same remediation: `lumo session attach <LUM-N>`. |
64
+ | Case | Behavior |
65
+ | ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
66
+ | **No contract yet** | Error pointing at `lumo task criteria set`; draft the contract first (criteria.md golden rule). |
67
+ | **HUMAN-only contract** (zero MACHINE criteria) | Nothing to run; CLI says so and suggests `lumo task update <id> --status in_review` for human review. No server write happens. |
68
+ | **Partial round** | A round must cover every MACHINE criterion; the CLI always runs all of them and the server rejects partial rounds. |
69
+ | **`REVIEW_ADDED` criteria** | Criteria added during review appear in the contract and are picked up automatically by the next round. |
70
+ | **Session bound to a different task** | Server returns 409, surfaced as an error. No advisory printed; the verify round is rejected outright. |
71
+ | **Provably-unbound session** | Response carries `bindingAdvisory: 'unbound'`; prints `โš  Working unbound โ€” this verify ran from a Claude Code session not attached to the task.` Recorded as a `SESSION_BINDING_MISSING` boundary crossing (visible in `lumo task status` open crossings). Run `lumo session attach <LUM-N>` before the next verify. |
72
+ | **Unconfirmed session binding** | `bindingAdvisory: 'unconfirmed'` โ†’ softer advisory `โš  Could not confirm this session is attached to the task.` Same remediation: `lumo session attach <LUM-N>`. |
77
73
 
78
74
  ## Round discipline
79
75
 
@@ -105,8 +101,9 @@ nothing and burns no round. Defaults to the session-bound task; an explicit
105
101
  identifier overrides.
106
102
 
107
103
  ```bash
108
- lumo task status # session-bound task
104
+ lumo task status # session-bound task (core: criteria / next actions / open crossings)
109
105
  lumo task status LUM-42 # explicit task
106
+ lumo task status --full # + verification rollup, history, cost, struggle trail, trend
110
107
  lumo task status --json # versioned machine-readable payload
111
108
  ```
112
109
 
@@ -125,19 +122,16 @@ what's unmet and why (the exact failure tails), and how many rounds are left.
125
122
  ### What it prints
126
123
 
127
124
  - **Header** โ€” task identifier/title/status + `verification round N/M` (`M` = the workspace's `verificationMaxRounds`; round 0 = never verified) + an escalation warning when the machine loop is exhausted.
128
- - **Claim vs verification** โ€” ่ง„ๅพ‹ 2 ๅฃฐ็งฐvsๆ ธ้ชŒ: the headline contrast, printed right after the header (whenever the contract exists), so the report shows **both** columns instead of only the verification one. Two sides:
129
- - **Claim** โ€” what the agent _says_ it did: an **unverified self-report** (`agent self-report ยท estimated, not verification`), estimate-tier provenance (ไผฐ, not ๆต‹). Sourced by layering (LUM-597, preference `AGENT > RUN_SUMMARY > DIGEST > null`): if you supplied a `lumo verify --note`, that **own self-report** is the claim (`โ†ณ source: agent self-report (verify --note)`) โ€” theๆฑ‡ๆŠฅ่€…'s voice, what faithfulness judges. Absent a self-report (old CLI / non-verify path) it degrades to the summarizer paraphrase, labelled honestly (`โ†ณ source: synthesized run summary (no self-report)`). **Fail-closed**: when only a raw STOP turn digest exists it prints `generating โ€” the formal run summary is still being synthesized` and **withholds the raw digest**; with no material at all it prints `not generated yet โ€” โ€ฆ`, never a fabricated claim.
130
- - **Verification** โ€” what was actually _confirmed_ (measured): the machine-verification rollup `N machine-verified / M human override (of T MACHINE criteria)` over the active MACHINE criteria (relocated here from its old standalone line under `Criteria`), plus `X of Y criteria met by their latest verdict`. **Fail-closed**: before any round runs it prints `no verification has run yet โ€” the claim is unconfirmed` rather than implying a pass.
131
- - **Faithfulness** โ€” the third state over the two columns: whether the CLAIM itself is _true_, read from the persisted faithfulness verdict (no LLM at read time), printed as `โ–ธ Faithfulness โ€” does the claim match the delivery`. One of `faithful` / `overstated` (claim says more than the diff/PR shows) / `under-reported` (claim says less) / `unjudgeable`, with an `ยท evidence: PR #N ยท <sha>` pointer when the judge cited one. **Fail-closed**: `not yet judged` (no verdict โ€” distinct from a real `unjudgeable` verdict) and `โ€ฆ (stale โ€” re-checked on the next batch)` (judged against an older claim snapshot, the verdict still shown) are surfaced explicitly. Omitted only against an older server that doesn't emit it.
132
- - Carried in `--json` as `claim { text, source }` (`source: 'AGENT' | 'RUN_SUMMARY' | 'DIGEST' | null`; `AGENT` = the verify self-report, `RUN_SUMMARY`/`DIGEST` = the synthesized fallback; `text: null` = none generated) and `faithfulness { state, verdict, evidence, diffShas, prNumbers, judgedAt, judgedClaimAt }` (`state` adds `PENDING`/`STALE` over the four verdicts). Omitted only against an older server that doesn't emit the field. The machine-verification rollup is still carried top-level as `machineVerification`.
125
+ - **Default = the self-check core (LUM-733):** header, Criteria, Last round failures, Next actions, Open boundary crossings. The dashboard sections below (**Verification**, **History**, **Cost**, **Struggle**, **Trend**) print only with `--full` โ€” they are human-dashboard material and cost context. The claim text and faithfulness verdict are no longer rendered in the terminal (`--json` still carries `claim` / `faithfulness` for scripts).
126
+ - **Verification** (`--full`) โ€” the measured rollup `N machine-verified / M human override (of T MACHINE criteria)` plus `X of Y criteria met by their latest verdict`; before any round it prints `no verification has run yet`.
133
127
  - **Criteria** โ€” every criterion as `<glyph> <id> [TYPE] SOURCE@rN statement` (โœ“ latest verdict passed / โœ— failed / โ—‹ no verdict yet) with its checkpointer and latest verdict line (failure tail on fail). `REVIEW_ADDED@rN` provenance is visible per row.
134
128
  - A passing **MACHINE** criterion's verdict line carries a machine-state tag derived from the read model's `machinePassed` flag, NOT the latest verdict: `ยท machine-verified` when a checkpointer actually passed it (even after a human later signs the task off), or `ยท human override (no machine pass)` when it passes only on a human sign-off with no machine run underneath. This keeps the terminal honest with web โ€” a machine-verified criterion that a human co-signed no longer reads as a plain human pass.
135
129
  - A verdict's **evidence is drillable**, rendered as an indented `โ†ณ evidence:` line under the verdict (PASS _and_ FAIL) instead of the inert raw pointer that used to ride the verdict line โ€” so a conclusion points at real proof you can act on, not just the `check:` command: a `cmd:` pointer prints the actual command + exit code (`ran \`โ€ฆ\` โ†’ exit N ยท re-run to reproduce`), a `file:`pointer prints a terminal-clickable`path:line`, and a `commit:` pointer prints a navigable web URL (`<repo>/commit/<hash>`, resolved from the local git `origin`remote) or a`git show <hash>`fallback when no remote resolves. A criterion that **requires evidence but has none recorded yet** (e.g. a HUMAN evidence criterion before sign-off) renders an explicit`โ†ณ evidence: pending โ€” no reference recorded yet`(fail-closed) instead of a bare, dead`[evidence]` tag.
136
130
  - A pass can carry a **`โš  pre-edit version`** note: the criterion was changed after that verdict (reworded, or its checkpointer was swapped so the recorded evidence ran a different command). The pass still counts as met (a stale pass does not block DONE โ€” render-only signal), but it vouches for an older version โ€” **re-run `lumo verify` to re-confirm against the current criterion.** This is the habit whenever you edit a MACHINE criterion's checkpointer mid-task: change the check, then re-verify so the green is honest.
137
- - **History** โ€” one line per recorded round: `rN ยท timestamp ยท X PASS / Y FAIL`.
131
+ - **History** (`--full`) โ€” one line per recorded round: `rN ยท timestamp ยท X PASS / Y FAIL`.
138
132
  - **Last round failures** โ€” the most recent round's FAIL verdicts with their rejection reasons (why the last round bounced).
139
- - **Cost** โ€” ่ง„ๅพ‹ 1: the costs a human should weigh, on the same report as the verdict instead of scattered across the web delivery card and `task lineage`. Three lines: **Tokens** (total input+output+cache across the task's sessions), **Active time** (non-idle agent seconds โ€” ฮฃ per-turn `STOP โˆ’ prompt`), and **Rework rounds** (verify rounds that recorded a FAIL). Read from the **same** server-side source the web delivery card consumes (`retrospectiveRepository.loadActuals`), so the two reports cannot drift. Token cost is **fail-closed**: when no session usage was recorded it prints `Tokens: not recorded (no session usage captured)`, kept distinct from a measured `0` (ๆฒกๆต‹ๅˆฐ vs ่Šฑไบ†0). Carried in `--json` as `cost { tokenCost, activeTimeSec, reworkRounds }` (`tokenCost: null` = not measured). Omitted only against an older server that doesn't emit the field.
140
- - **Struggle / rework / outstanding** โ€” the anti-mum-and-deaf block: **always printed when the contract exists, even on a clean 0-unmet task** so a passing task still shows its scars instead of wiping them to a single PASS count. Lists, when present:
133
+ - **Cost** (`--full`) โ€” ่ง„ๅพ‹ 1: the costs a human should weigh, on the same report as the verdict instead of scattered across the web delivery card and `task lineage`. Three lines: **Tokens** (total input+output+cache across the task's sessions), **Active time** (non-idle agent seconds โ€” ฮฃ per-turn `STOP โˆ’ prompt`), and **Rework rounds** (verify rounds that recorded a FAIL). Read from the **same** server-side source the web delivery card consumes (`retrospectiveRepository.loadActuals`), so the two reports cannot drift. Token cost is **fail-closed**: when no session usage was recorded it prints `Tokens: not recorded (no session usage captured)`, kept distinct from a measured `0` (ๆฒกๆต‹ๅˆฐ vs ่Šฑไบ†0). Carried in `--json` as `cost { tokenCost, activeTimeSec, reworkRounds }` (`tokenCost: null` = not measured). Omitted only against an older server that doesn't emit the field.
134
+ - **Struggle / rework / outstanding** (`--full`) โ€” the anti-mum-and-deaf block: **always printed when the contract exists, even on a clean 0-unmet task** so a passing task still shows its scars instead of wiping them to a single PASS count. Lists, when present:
141
135
  - **rework rounds** โ€” verify rounds that had a FAIL;
142
136
  - **send-backs** โ€” criteria sent back by a human/agent verdict (a MACHINE verify-loop FAIL is not a ๆ‰“ๅ›ž), with their open/resolved lifecycle, preserved even for since-removed criteria;
143
137
  - **leftover follow-ups** โ€” criteria whose latest verdict is `PASS_WITH_FOLLOWUP`;
@@ -146,11 +140,15 @@ what's unmet and why (the exact failure tails), and how many rounds are left.
146
140
 
147
141
  When the trail is genuinely empty it states the **basis** (`None recorded โ€” N rounds run, 0 FAIL, no send-backs, no reopens, no leftover follow-ups`); when nothing has been verified yet it says so (`No verification has run yet โ€” cannot confirm there were no difficulties`) rather than rendering an implicitly-clean slate. Carried in `--json` as `struggleTrail` (incl. `pullRequests` + `reopens`).
148
142
 
149
- - **Trend** โ€” ่ง„ๅพ‹ 7 ่ถ‹ๅŠฟ้žๅฟซ็…ง: the _movement_ of the key quantities across the task's attempts, not a single snapshot. Where History/Cost/Struggle list current values, this shows direction: **Pass rate** across verification rounds (`r1 60% โ†’ r2 100% (โ†‘ +40pts)`), **Cost/session** across the task's sessions (`4.2K โ†’ 1.1K tokens (โ†“), 5.3K total` โ€” per-session spend from the same source as the **Cost** total, so the trajectory's points sum to it), and **Rework** accrual (`3 accrued โ€” 1 FAIL round, 1 reopen, +1 PR cycle (โ†‘ from 0)`). **Honest about a single point:** with only one round and one session every quantity is one data point, so it prints `Single attempt so far โ€” no trajectory yet (a trend needs โ‰ฅ2 rounds or sessions)` rather than drawing a fake arrow off one value. When nothing was verified and no cost was measured it says `No verification rounds or measured cost yet โ€” nothing to trend`. Carried in `--json` as `trend { passRate[], cost[], rework{} }`. Omitted only against an older server.
143
+ - **Trend** (`--full`) โ€” ่ง„ๅพ‹ 7 ่ถ‹ๅŠฟ้žๅฟซ็…ง: the _movement_ of the key quantities across the task's attempts, not a single snapshot. Where History/Cost/Struggle list current values, this shows direction: **Pass rate** across verification rounds (`r1 60% โ†’ r2 100% (โ†‘ +40pts)`), **Cost/session** across the task's sessions (`4.2K โ†’ 1.1K tokens (โ†“), 5.3K total` โ€” per-session spend from the same source as the **Cost** total, so the trajectory's points sum to it), and **Rework** accrual (`3 accrued โ€” 1 FAIL round, 1 reopen, +1 PR cycle (โ†‘ from 0)`). **Honest about a single point:** with only one round and one session every quantity is one data point, so it prints `Single attempt so far โ€” no trajectory yet (a trend needs โ‰ฅ2 rounds or sessions)` rather than drawing a fake arrow off one value. When nothing was verified and no cost was measured it says `No verification rounds or measured cost yet โ€” nothing to trend`. Carried in `--json` as `trend { passRate[], cost[], rework{} }`. Omitted only against an older server.
150
144
 
151
- - **Next actions** โ€” the unmet criteria (latest verdict is not a pass: failed or never verified, HUMAN ones included). This list IS the plan โ€” recomputed from the event log on every read, never maintained separately. Empty + rounds recorded = awaiting human adjudication.
152
- - **Open boundary crossings** โ€” a trailing safety block when the task has โ‰ฅ1 OPEN (undispositioned) forbidden-action crossing: a count, then one line per crossing `โ€ข [SEVERITY] CATEGORY โ€” <clipped detail>` (highest-severity first), each followed by a read-only **attribution** line `โ†ณ by model=<m> ยท agent=<type>[/branch] ยท session=<8-char prefix>` (who/what crossed; any dimension that couldn't be resolved server-side prints `unknown`, never a fabricated value), then a pointer to the web acceptance panel. Silent when there are none, so it never overshadows the criteria.
153
- - **Read-only awareness** โ€” this surfaces crossings detected elsewhere; there is no CLI path to disposition or clear one. Disposition stays web + human-only: an agent/CLI bearer cannot clear its own crossing from the terminal.
145
+ - **Next actions** โ€” the unmet criteria (latest verdict is not a pass: failed or never verified, HUMAN ones included) followed by any **undispositioned PR security findings** (LUM-737) as `โ€ข [SECURITY] [SEVERITY] PROVENANCE ruleId โ€” file:line โ€” title (PR #n ยท blocks DONE | advisory[ โ€” <cause>, downgraded])`. The trailing **downgrade cause** appears only on deterministic `secret:*` rows the scanner lowered to LOW โ€” `placeholder value` (the matched string looks like `โ€ฆEXAMPLE`, `YOUR_โ€ฆ`, `CHANGEME`โ€ฆ) or `test/fixture/doc path`. Downgrading is **not** suppression: the row is still reported so a human decides, and the cause is what lets you tell a recognised test fixture from a real leak without opening the file. Absent on rows scanned before the cause was recorded, and on any row that was not downgraded โ€” silence means "not downgraded", never "cause unknown". This list IS the plan โ€” recomputed from the event log + the latest scan per linked PR on every read, never maintained separately. The header counts them separately: `Next actions (N unmet ยท M security findings)`. A finding is **not** a criterion: fix it and push (a fixed fingerprint disappears from the next scan) or a human dispositions it in the web delivery panel โ€” there is no CLI path to clear one, and a `blocks DONE` finding refuses DONE with 409. Empty + rounds recorded = awaiting human adjudication.
146
+ - **Per-PR scan status** (LUM-735): before the unconfirmed-PR lines, one line per linked PR that has a latest scan (open or closed alike): `PR #945 ยท scan CLEAN ยท Secrets: checked ยท Code scan: checked (2 external findings) ยท Dependencies: checked (1 dependency finding, 1 already on main) ยท AI review: checked ยท Exploit paths: โš  incomplete` โ€” stage segments only for keys present, in secrets/external/supplyChain/judge/hunt order. **The stage keys are internal; what prints is their display label** (LUM-763, `shared/src/security-scan.ts`, shared with the GitHub PR summary so the two cannot drift): `secrets`โ†’Secrets, `external`โ†’Code scan, `supplyChain`โ†’Dependencies, `judge`โ†’AI review, `hunt`โ†’Exploit paths; states render as `RAN`โ†’checked, `SKIPPED`โ†’skipped, `NOT_CONFIGURED`โ†’off, `PENDING`โ†’scanning, `PARTIAL`โ†’โš  incomplete, `SUPERSEDED`โ†’โš  superseded, `FAILED`โ†’โœ— failed. "checked" means the layer completed, **not** that it found nothing โ€” findings are counted in their own segments and in the next-actions list (`hunt` = the L3 vulnerability hunt, LUM-739: absent when `LUMO_SECURITY_HUNT=off`, `SKIPPED` when no trigger fired, `PENDING` while the workflow runs, `RAN` / `PARTIAL` (budget hit, a planned task that never finished, or more surviving candidates than the falsifier cap) / `FAILED`; its findings are `LLM_JUDGE` advisory, never `blocks DONE`); `(N external findings)` (singular at 1) decorates only the Code scan segment, and only when N > 0; `(N dependency findings[, M already on main])` (LUM-738; singular at 1) decorates only the Dependencies segment, only when N > 0, and prints bare `Dependencies: <state>` against an older server without the counts; ` ยท partial` appended when the scan is partial; and, when `stages.external === 'FAILED'` and a **scrubbed scanner reason** was recorded (the `error` column carries the `external: ` provenance prefix), an appended ` โ€” <reason without the prefix, tail 200 chars>` โ€” an unprefixed `error` (stage A's own raw crash text) is never printed here, same P8 rule as the web panel and the PR summary (LUM-756). Fed by the additive `securityFindings.scans` array; omitted entirely (no lines) against an older server that doesn't send it.
147
+ - **Hunt coverage line** (LUM-762): indented under its scan line whenever the server sends the L3 hunt's per-category audit โ€” `Exploit paths 2/3 done ยท t1 idor 41.2s done ยท t2 injection 72.0s done (2 attempts) ยท t3 race skipped`. `<n>/N done` is how many planned tasks finished (where N is the number of tasks the planning step selected for this scan, typically 0โ€“3). Rows persisted before LUM-758 lack a planned-task count and fall back to the historical 6-category denominator (reading as `/6` instead). Each segment is `[taskId ]<category> <elapsed>s <stop>` โ€” the `taskId` prefix (`t1`, `t2`, โ€ฆ) appears only on current rows where the task graph is present (post-LUM-758), and is omitted on pre-task-graph audit rows, so they render exactly as they always did. Elapsed is summed over the task's attempts; `(N attempts)` when the workflow retried it; no elapsed for a `skipped` task (the scan's budget ran out before its turn). Stop values: `done` ยท `steps` ยท `budget` ยท `timeout` ยท `error` ยท `skipped`. Headed by the same display label as the layer's own segment (LUM-763), so the detail line and the line above it never name the layer two different ways. This is the only surface that reads `huntAudit`, so it is where you answer "how long does a healthy task take" and "which tasks never ran" without paging through deploy logs. Absent on an older server and on scans with no usable audit (pre-LUM-739 rows, or a hunt that failed before its first batch) โ€” no line, never a guess.
148
+ - **Persisting findings** (LUM-738): a finding whose fingerprint is already on the repository's default-branch baseline (`persisting`) is not this PR's โ€” it never appears as a next action, is not counted in `openFindings`, and shows muted (_already on the default branch_) in the web panel. Dependency findings (`kind=DEPENDENCY`, from osv-scanner) are advisory: they never block DONE.
149
+ - **Fails closed:** `โš  Security-scan check failed โ€” could not confirm โ€ฆ` when the scan read errored, and one `โš  PR #n: โ€ฆ โ€” could not confirm it is clean.` line per open PR whose latest scan is missing / FAILED / still running. Silence means a successful read with nothing open, never a failed check.
150
+ - **Open boundary crossings** โ€” a trailing safety block when the task has โ‰ฅ1 OPEN (undispositioned) forbidden-action crossing: a count, then one line per crossing `โ€ข [SEVERITY] CATEGORY โ€” <clipped detail>` (highest-severity first), each followed by a read-only **attribution** line `โ†ณ by model=<m> ยท agent=<type>[/branch] ยท session=<8-char prefix>` (who/what crossed; any dimension that couldn't be resolved server-side prints `unknown`, never a fabricated value), then a pointer to `lumo crossing disposition` and the web acceptance panel. Silent when there are none, so it never overshadows the criteria.
151
+ - **Read-only awareness** โ€” this block surfaces crossings detected elsewhere and clears nothing itself. The ruling is the user's: `lumo crossing disposition <id> --false-positive | --confirmed` (exit-4 envelope you relay, LUM-769 โ€” see below) or the web panel. You never self-approve.
154
152
  - **The check fails closed:** if the crossings read itself errors (network / server / parse), the block prints `โš  Boundary-crossing check failed (network/server error) โ€” could not confirm whether any are undispositioned` instead of staying silent. Silence means a successful read with zero open crossings, never a failed check โ€” a hiccup can no longer masquerade as "all clear".
155
153
 
156
154
  ### Responding to an open crossing โ€” `lumo crossing explain`
@@ -166,11 +164,32 @@ lumo crossing explain <id> --note "this was a generated fixture, not a hand-edit
166
164
  This is the **inverse** of dispositioning, but it is the agent/CLI path
167
165
  (bearer-only; a clerk/human caller is refused). Behavior:
168
166
 
169
- - it can **only append** an append-only note โ€” it **never clears the crossing or unblocks Done** (disposition stays web + human-only);
167
+ - it can **only append** an append-only note โ€” it **never clears the crossing or unblocks Done** (the user rules, via `lumo crossing disposition` or the web panel);
170
168
  - the note is shown to the human reviewer at disposition time, kept for later review, and explicitly labeled _agent self-report ยท unverified_;
171
169
  - `<id>` must be a crossing on the **session-bound task** (resolved from `$CLAUDE_CODE_SESSION_ID`; cross-task targets and unbound/mismatched sessions are rejected);
172
170
  - earlier explanations are immutable โ€” a correction is a new note.
173
171
 
172
+ ### Recording the user's ruling โ€” `lumo crossing disposition` (LUM-769)
173
+
174
+ When the user has ruled on a crossing โ€” in chat, after you relayed it โ€” record
175
+ that ruling from the terminal instead of sending them to the web panel:
176
+
177
+ ```bash
178
+ lumo crossing disposition <id> --false-positive --note "covered by the run-outbound tests"
179
+ ```
180
+
181
+ It is a gated mutation on the [confirmation protocol](confirmation.md), as a **three-step** handshake โ€” two envelopes, two separate approvals from the user, and the server enforces the order:
182
+
183
+ - **Step 1 โ€” no step flag.** Reads the crossing, obtains a server-signed stage-1 **read receipt**, and prints an exit-4 envelope โ€” severity, category, detail, `Seen ร—N` when it recurred, every explanation you left (labelled _agent self-report ยท unverified_), the note, a `โš ` line saying that approving clears this crossing's block on DONE (plus `โš  HIGH severity` / `โš  Irreversible category` when they apply), and `Step 1 of 2 โ€” read receipt issued โ€ฆ valid until <time>`. Nothing is written. Relay every line and **ask the user to confirm they have read it** โ€” this envelope's `confirmCommand` ends in `--receipt <r1> --confirm-read`, not `--confirm`;
184
+ - **Step 2 โ€” `--confirm-read --receipt <r1>`**, only after the user says they read it. The server verifies r1 and returns the stage-2 receipt; the CLI prints a second exit-4 envelope for the ruling itself โ€” `Read acknowledged โ€ฆ (shown <t1>, acknowledged <t2>)`, `Step 2 of 2 โ€” record the ruling: OPEN โ†’ FALSE_POSITIVE`, the note, the โš  consequence line. Still nothing written. Relay it and **ask the user to approve the ruling** โ€” its `confirmCommand` ends in `--receipt <r2> --confirm`;
185
+ - **Step 3 โ€” `--confirm --receipt <r2>`**, only after the user approves. POSTs the ruling (`FALSE_POSITIVE` | `CONFIRMED`, optional note, the stage-2 receipt); either value clears that crossing's DONE block, and the audit row records the shown / read-acknowledged / confirmed times. The web panel can still correct it later. **Never add `--confirm-read` or `--confirm` yourself** โ€” each is the user's approval of that step, and the server records the ruling as a CLI-channel disposition (shown as _via CLI_ in the panel) precisely because it cannot verify the in-chat approvals;
186
+ - **the receipts are the server's ordering check** (LUM-769): each is bound to the crossing, the ruling, your session/member and the crossing's state at read time, and expires after 10 minutes. Confirming the ruling with the stage-1 receipt (read not yet acknowledged) is refused โ€” locally before any request, and by the server with 409 โ€” and told to run `--confirm-read` first; a step flag without its receipt, or both step flags at once, is refused locally; a receipt that is forged, expired, for another reader/ruling, or older than the crossing's latest change (a recurrence, a new explanation, a ruling) is refused by the server with 409 and nothing is written โ€” re-run without a step flag for a fresh envelope. If the server cannot issue receipts (no signing secret configured) step 1 fails closed and no envelope is printed;
187
+ - the crossing is read first on both paths: a read that errors **fails closed** (exit 1, nothing written), an id not on the task is exit 1, and an already-matching ruling is a no-op (exit 0);
188
+ - `<id>` resolves against the **session-bound task**; `--task <LUM-N>` overrides the binding (e.g. from a terminal with no session, or after DONE unbinds you);
189
+ - **still web-only:** reverting a ruling to OPEN, repository suppression rules (LUM-751), and steward milestone crossings (`STEWARD_IMPLEMENTATION_ARTIFACT` has no task).
190
+
191
+ After the last blocker is cleared, re-run the original `lumo task update <id> --status done --confirm` โ€” its envelope was already approved.
192
+
174
193
  ### --json contract
175
194
 
176
195
  `--json` emits the full read model with a top-level `version` field (currently
@@ -181,50 +200,33 @@ fields don't. Pin on `version` when scripting against it.
181
200
  - the payload carries a top-level `machineVerification` aggregate `{ total, machineVerified, humanOverridden }` over the active MACHINE criteria โ€” read these, not `latestVerdict` alone, to tell a machine-verified criterion from a human override;
182
201
  - open boundary crossings ride along as an additive top-level `openCrossings`, each entry `{ id, category, severity, detail, attribution }` where `attribution` is `{ workspaceMemberId, sessionId, agent, worktreeBranch, model }` with every field nullable โ€” null = unknown, never fabricated; the array length is the count. Same read-only awareness, no write path;
183
202
  - **`openCrossings` is `null` when the crossings check failed** โ€” distinct from `[]` (a successful read with zero open crossings). Script consumers must treat `null` as "unknown / could not confirm", **not** "safe".
203
+ - `nextActions` entries carry a `kind` discriminator (LUM-737): `CRITERION` (the pre-737 shape: `criterionId`, `statement`, `verifierType`, `checkpointer`, `judgeSteps`, `source`, `addedAtRound`, `rejectionReason`) or `SECURITY_FINDING` (`findingId`, `statement`, `severity`, `provenance`, `blocking`, `prNumber`, `ruleId`, `filePath`, `line`). Additive โ€” a missing `kind` means `CRITERION`; do not index every entry by `criterionId`;
204
+ - a top-level `securityFindings` summary `{ open, blocking, unconfirmedPrs: [{ number, reason: NONE|FAILED|PENDING|RUNNING }], scans?: [{ prNumber, status, stages, partial, error, externalFindings, openFindings }] }`; **`null` means the scan read failed** โ€” treat as "unknown / could not confirm", not "safe". `scans` is additive (LUM-735) โ€” absent against an older server, one entry per linked PR with a latest scan otherwise.
184
205
 
185
206
  `status` reads; `verify` judges. Running status never starts a round, never
186
207
  escalates, and never changes task state โ€” loop rules (the workspace round cap,
187
208
  IN_REVIEW on all-pass, human-only DONE) live entirely in `lumo verify` and the
188
209
  server.
189
210
 
190
- ## lumo verdict โ€” the three verdict channels
191
-
192
- `lumo verify` is the MACHINE channel. `lumo verdict` covers the other two โ€” the
193
- HUMAN pass and the AGENT send-back โ€” under one red line: **no passing data row is
194
- ever agent-produced.**
195
-
196
- ```bash
197
- lumo verdict --pass
198
- lumo verdict LUM-42 --pass
199
- lumo verdict --fail --reason CRITERION_UNMET --note "the retry path is still missing"
200
- lumo verdict LUM-42 --fail --reason scope_mismatch --criterion c-abc123
201
- ```
202
-
203
- ### --pass โ€” a deep link, never a write
204
-
205
- This resolves the task, then opens the browser to its verdict bar focused on
206
- Pass. **The CLI writes nothing** โ€” PASS only ever lands from a human's own click
207
- (Clerk session). Use this to hand a finished task to a human for the final pass;
208
- it carries them one click from recording it.
209
-
210
- ### --fail โ€” the AGENT send-back
211
-
212
- `--fail --reason <enum>` records a real verdict row server-side with
213
- verifierType=AGENT (a channel distinct from MACHINE and HUMAN, so "machine
214
- all-pass but human FAIL" stays an uncontaminated signal). The verdict is
215
- hard-coded FAIL โ€” there is no agent path to a passing verdict. It:
211
+ ## Verdict channels
216
212
 
217
- - `--reason <enum>` required (case-insensitive): `CRITERION_UNMET | EVIDENCE_INSUFFICIENT | CHECK_EXECUTION_ERROR | SCOPE_MISMATCH | OTHER` โ€” the agent pays the structured tax a human send-back is spared;
218
- - `--note <text>` optional, posted as a task comment (@mentions and images for free) and summarized onto the verdict row;
219
- - `--criterion <id>` repeatable, narrows the send-back; omitted, it fans out to the whole contract;
220
- - `round` = the current max (not a new round); bounces the task back to IN_PROGRESS, with the unmet criteria surfacing through `lumo task status`.
213
+ `lumo verify` is the MACHINE channel. The only other channel is the **human**
214
+ one: a person records PASS or a send-back (FAIL) in the web verdict bar (Clerk
215
+ session). There is no agent-facing verdict command โ€” the former
216
+ `lumo verdict --fail` AGENT send-back was removed in LUM-733 (17 rows all-time,
217
+ none in the last 30 days); when the machine loop runs out of rounds the task is
218
+ escalated to a human instead. **No passing data row is ever agent-produced.**
221
219
 
222
220
  ### The DONE gate
223
221
 
224
- Once any criterion's latest verdict is FAIL โ€” machine, AGENT, or human โ€” moving
222
+ Once any criterion's latest verdict is FAIL โ€” machine or human โ€” moving
225
223
  the task to DONE on the agent/CLI path is refused with **409** and the unresolved
226
224
  items listed. Clear the send-back (fix + re-verify, or a human PASS) before
227
- `lumo task update <id> --status done`.
225
+ `lumo task update <id> --status done` โ€” which itself walks the confirmation
226
+ protocol: without `--confirm` it exits 4 with an envelope for the user to
227
+ approve, and when the server gate still refuses it exits 5 with a structured
228
+ `DONE_BLOCKED` error listing every blocker (LUM-755, see [tasks.md](tasks.md)
229
+ and [confirmation.md](confirmation.md)).
228
230
 
229
231
  - A task with no criteria, or whose criteria were never adjudicated, transitions freely โ€” **the gate only blocks an actual send-back, never an un-adjudicated criterion.**
230
232
  - When the machine loop has left a task IN_REVIEW with no send-back standing, the agent may move it to DONE directly; a human-PASS row is a provable manual override, not a required ticket.
@@ -259,9 +261,7 @@ When someone reports a defect in conversation, your action depends on whether th
259
261
  task has **ever entered IN_REVIEW**:
260
262
 
261
263
  - **Not yet** (still your first working pass) โ†’ just fix it and continue. No verdict needed โ€” nothing was claimed complete, so there's nothing to contradict.
262
- - **Already submitted** (entered IN_REVIEW / DONE / merged) โ†’ **do not silently fix and re-pass.** Either:
263
- - record your own send-back `lumo verdict --fail` (noting it was human-reported โ€” this is _your_ honest concurrence, not a forged human verdict), or
264
- - ask the reporter to record a human FAIL via the web UI / Slack (the only channel that can attribute it to a human).
264
+ - **Already submitted** (entered IN_REVIEW / DONE / merged) โ†’ **do not silently fix and re-pass.** Ask the reporter to record a human send-back (FAIL) via the web verdict bar / Slack โ€” the only channel that can attribute it to a human โ€” then fix in place and re-run `lumo verify` so the fix lands as a fresh machine round on the record.
265
265
 
266
266
  If the defect is a **new requirement** not covered by any criterion, first
267
267
  transcribe it with `lumo task criteria set --human`, then proceed. You can never
@@ -55,15 +55,21 @@ Errors if the target dir already exists; reuses the branch if it already exists
55
55
 
56
56
  ## `lumo worktree rm <LUM-N>`
57
57
 
58
- Removes the worktree for a task. Requires `--yes`. Refuses a dirty worktree
59
- unless `--force`. Keeps the branch by default (it may hold unpushed work / an
60
- open PR); `--delete-branch` removes it with `git branch -d` (which itself
61
- refuses an unmerged branch).
58
+ Removes the worktree for a task. Gated by the confirmation protocol
59
+ ([confirmation.md](confirmation.md)): without `--confirm` โ€” or with
60
+ uncommitted changes and no `--force` โ€” it exits **4** with an envelope naming
61
+ the path and branch, a `โš  โ€ฆ uncommitted changes โ€ฆ discarded` line when dirty,
62
+ and whether the branch is kept or deleted; nothing is removed. A dirty tree's
63
+ `confirmCommand` carries `--force --confirm`. `--yes` is a legacy alias of
64
+ `--confirm`. Keeps the branch by default (it may hold unpushed work / an open
65
+ PR); `--delete-branch` removes it with `git branch -d` (which itself refuses
66
+ an unmerged branch).
62
67
 
63
68
  ```bash
64
- lumo worktree rm LUM-267 --yes
65
- lumo worktree rm LUM-267 --yes --force # discard uncommitted changes
66
- lumo worktree rm LUM-267 --yes --delete-branch # also delete lumo/LUM-267โ€ฆ
69
+ lumo worktree rm LUM-267 # exit 4 + envelope
70
+ lumo worktree rm LUM-267 --confirm # after the user approved (clean tree)
71
+ lumo worktree rm LUM-267 --force --confirm # dirty tree: discard uncommitted changes
72
+ lumo worktree rm LUM-267 --confirm --delete-branch # also delete lumo/LUM-267โ€ฆ
67
73
  ```
68
74
 
69
75
  ## `lumo worktree list`