@lumoai/cli 1.35.0 → 1.36.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.
@@ -51,7 +51,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
51
51
  - `lumo task figma context <id> <linkId>` — Figma link metadata (v1)
52
52
  - `lumo task comments list <id>` — comment thread, capped to the output budget (`--full` prints every comment; read-only; ≠ `task comment`)
53
53
  - `lumo task pr show <id> <number>` — synced PR metadata (v1)
54
- - `lumo task lineage <id>` — show the causal trail: fragments that fed the task + each one's outcome + the run's token/loop cost (read-only audit view); `lumo task lineage <id> --signal` also appends workspace-level usage signal-health (used distribution, per-session variance, used-vs-base merge rate)
54
+ - `lumo task lineage <id>` — show the causal trail: fragments that fed the task + each one's outcome + the run's token/loop cost (read-only audit view); `lumo task lineage <id> --signal` also appends workspace-level usage signal-health (used distribution, per-session variance, used-vs-base merge rate via iteration-taint fold — tasks with a send-back/reopen/PR-close count as the negative class even if later merged; shows negative-class size per side; prints "metric cannot discriminate" when no failure outcomes exist yet)
55
55
 
56
56
  **Tasks** — see [tasks.md](references/tasks.md)
57
57
 
@@ -126,7 +126,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
126
126
 
127
127
  - `lumo session attach <id>` — bind this session to a task (then run `task context`). **Lifetime lock**: re-attaching to the same task is a no-op; attaching to a _different_ task is refused with 409 — start a new Claude Code session instead. No `--force`, no `session detach`.
128
128
  - `lumo session status` — show current binding
129
- - `lumo session wrap [--yes] [--dry-run] [--used <indices>]` — end-of-session panel: progress comment + memory review + fragment-usage vote (`--used`, LUM-300) + blocked-tag prompt, then a read-only reminder when the bound task has ≥1 OPEN boundary crossing still undispositioned (silent only on a genuine empty read — no wrap-up noise; a crossings-check failure prints a "could not confirm" warning instead of staying silent, LUM-480; pointer is web + human-only, LUM-448). Usage is now also audited automatically when a task reaches DONE (evidence-gated, true-only — confident fragments marked used, the rest left NULL); `session wrap --used` remains the manual override and takes precedence for a session.
129
+ - `lumo session wrap [--yes] [--dry-run] [--used <indices>]` — end-of-session panel: memory review + fragment-usage vote (`--used`, LUM-300) + blocked-tag prompt, then a read-only reminder when the bound task has ≥1 OPEN boundary crossing still undispositioned (silent only on a genuine empty read — no wrap-up noise; a crossings-check failure prints a "could not confirm" warning instead of staying silent, LUM-480; pointer is web + human-only, LUM-448). Usage is now also audited automatically when a task reaches DONE (evidence-gated, true-only — confident fragments marked used, the rest left NULL); `session wrap --used` remains the manual override and takes precedence for a session.
130
130
  - Git-suggest at session start (suggests `session attach`, never auto-binds) + Layer-2 project-memory review — see the reference
131
131
 
132
132
  **Worktrees (local dev tooling)** — see [worktree.md](references/worktree.md)
@@ -125,18 +125,11 @@ lumo session status
125
125
 
126
126
  When to suggest: the user asks "which task am I on", "what's this session bound to", or you need to decide whether to suggest `session attach` for a mentioned task ID.
127
127
 
128
- ### `lumo session wrap [--yes] [--dry-run] [--used <indices>]` — wrap-up panel: progress comment + memory review + fragment-usage vote + blocked-tag prompt
128
+ ### `lumo session wrap [--yes] [--dry-run] [--used <indices>]` — wrap-up panel: memory review + fragment-usage vote + blocked-tag prompt
129
129
 
130
- Session-end wrap-up panel with **four sections, run in order**:
130
+ Session-end wrap-up panel with **three sections, run in order**:
131
131
 
132
- **1. Progress comment** — reads back the current Claude Code session's per-turn
133
- `turnSummary` rows (the one-line summaries written each STOP), aggregates
134
- every turn **since the last progress comment** into one bulleted body, and — after
135
- a `[y] post / [e] edit / [s] skip` confirmation — posts it as a comment on the
136
- session's bound task. A server-side watermark (`Session.lastProgressCommentAt`)
137
- means re-running never re-posts the same turns.
138
-
139
- **2. Memory review** — lists the Layer1 memories this session sedimented since the
132
+ **1. Memory review** — lists the Layer1 memories this session sedimented since the
140
133
  last review (deduped by a per-session watermark `Session.lastMemoryReviewAt`).
141
134
  Each new memory is shown as `[SCOPE] CATEGORY headline`, numbered from 1. You
142
135
  curate with a single line: `d 1,3` deletes rows 1 and 3, `p 2` promotes row 2 to
@@ -147,7 +140,7 @@ Out-of-range indices are ignored. Deletes/promotes run server-side, scoped to
147
140
  memories this session created (you can't touch other sessions' memories through
148
141
  this panel). With no new memories the section prints "(no content)" and does nothing.
149
142
 
150
- **3. Fragment-usage vote (LUM-300)** — lists the context
143
+ **2. Fragment-usage vote (LUM-300)** — lists the context
151
144
  fragments this session **consumed** (its lineage edges: memory / slack / web /
152
145
  figma / PR / review-todo / session), numbered from 1 with a content snippet
153
146
  label. The agent records which it **actually used** via
@@ -161,7 +154,7 @@ upgrades the flywheel signal from "co-loaded" (constant, no information) to
161
154
  fragment's usage-based merge rate, falling back to the weaker presence rate when
162
155
  usage samples are thin. With no consumed fragments the section prints "(no content)".
163
156
 
164
- **4. Blocked check (blocked-tag prompt, LUM-153)** — if the **same kind of failure
157
+ **3. Blocked check (blocked-tag prompt, LUM-153)** — if the **same kind of failure
165
158
  recurred ≥ 3 times** in this session (server-aggregated from
166
159
  `POST_TOOL_USE_FAILURE` events grouped by tool name, plus `STOP_FAILURE`
167
160
  turn-level failures), the section surfaces the dominant failure (`This session looks repeatedly stuck on <tool> (N failures).` + last error summary) and prompts `[y] tag / [s] skip` whether to
@@ -177,7 +170,7 @@ shared board requires an interactive `y`, so `--yes` (and non-TTY) prints the
177
170
  suggestion and moves on rather than silently flipping board state. When there's
178
171
  nothing to prompt, the section prints "(no content)".
179
172
 
180
- **After the panel — open-crossings reminder (LUM-448).** Once the four sections
173
+ **After the panel — open-crossings reminder (LUM-448).** Once the three sections
181
174
  finish, `session wrap` prints a one-shot read-only reminder **if** the bound
182
175
  task has ≥1 OPEN (undispositioned) boundary crossing: `⚠ N open boundary
183
176
  crossing(s) on LUM-N still undispositioned:` then a line per crossing `• [SEVERITY]
@@ -194,31 +187,27 @@ clear its own crossing from the terminal.
194
187
 
195
188
  ```bash
196
189
  lumo session wrap # interactive: preview each section, choose per-section
197
- lumo session wrap --yes # progress posted + memories kept; blocked tag NOT auto-applied (needs interactive y)
190
+ lumo session wrap --yes # memories kept; blocked tag NOT auto-applied (needs interactive y)
198
191
  lumo session wrap --yes --used 1,3 # also record fragments 1 & 3 as used (the rest used=false)
199
192
  lumo session wrap --used none # record that none of the injected fragments were used
200
- lumo session wrap --dry-run # print all drafts only; never posts, never mutates, never advances watermarks
193
+ lumo session wrap --dry-run # print all drafts only; never mutates, never advances watermarks
201
194
  ```
202
195
 
203
196
  The usage vote is a two-step flow for agents: run `lumo session wrap` once to
204
197
  see the numbered fragment list, decide which you actually used, then re-run with
205
198
  `--used <indices>`. Re-running is safe — the other sections are watermark-guarded
206
- (progress won't double-post, reviewed memories won't re-list).
199
+ (reviewed memories won't re-list).
207
200
 
208
201
  - Requires `$CLAUDE_CODE_SESSION_ID` (must run inside Claude Code) and a bound
209
- task (`lumo session attach <LUM-N>` first). With no bound task or no new turn
210
- summaries, the Progress comment section prints "(no content)" and posts nothing.
211
- - `[e] edit` (Progress comment) opens `$EDITOR` (fallback vi/nano) on the drafted body;
212
- the edited text is posted and the watermark still advances to the turns the
213
- draft covered.
214
- - `--yes` posts the progress comment AND keeps all memories (no
215
- deletes/promotes) while advancing the memory-review watermark; for the
216
- blocked-tag section it prints the suggestion but does **not** apply the tag.
217
- - `--dry-run` prints all drafts; never posts, never mutates memories/tags, never
218
- advances either watermark.
219
- - Non-TTY without `--yes`: prints the drafts and does **not** post, mutate, or
220
- tag (safe default).
221
-
222
- When to suggest: at the end of a working session on a bound task, to record what
223
- was done as a progress comment — offer `lumo session wrap` rather than composing
224
- a `task comment` by hand.
202
+ task (`lumo session attach <LUM-N>` first).
203
+ - `--yes` keeps all memories (no deletes/promotes) while advancing the
204
+ memory-review watermark; for the blocked-tag section it prints the suggestion
205
+ but does **not** apply the tag.
206
+ - `--dry-run` prints all drafts; never mutates memories/tags, never advances the
207
+ memory-review watermark.
208
+ - Non-TTY without `--yes`: prints the drafts and does **not** mutate or tag (safe
209
+ default).
210
+
211
+ When to suggest: at the end of a working session on a bound task, to review the
212
+ memories it sedimented, vote which injected fragments were actually used, and
213
+ flag the task `blocked` if it got repeatedly stuck — offer `lumo session wrap`.
@@ -132,7 +132,7 @@ identifier (`LUM-N`), prints the causal trail:
132
132
 
133
133
  ```bash
134
134
  lumo task lineage LUM-42 # per-session causal trail + cost
135
- lumo task lineage LUM-42 --signal # append workspace-level usage signal-health
135
+ lumo task lineage LUM-42 --signal # append workspace-level usage signal-health; used-vs-base merge rate uses iteration-taint fold (send-back/reopen/PR-close = negative class even if later merged); shows negative-class size per side; prints "metric cannot discriminate" when no failure outcomes exist yet
136
136
  ```
137
137
 
138
138
  - **Totals banner** — distinct sessions, fragment count, edge count,
@@ -63,6 +63,18 @@ only).
63
63
  them; the server rejects partial rounds.
64
64
  - Criteria added during review (`REVIEW_ADDED`) appear in the contract and
65
65
  are picked up automatically by the next round.
66
+ - **Session bound to a different task (LUM-459)** → the server returns 409,
67
+ which the command surfaces as an error. No advisory is printed; the verify
68
+ round is rejected outright.
69
+ - **Provably-unbound session** → the server includes `bindingAdvisory: 'unbound'`
70
+ in the round response, and the command prints:
71
+ `⚠ Working unbound — this verify ran from a Claude Code session not attached to the task.`
72
+ The run is recorded as a `SESSION_BINDING_MISSING` boundary crossing visible in
73
+ `lumo task status` open crossings. Run `lumo session attach <LUM-N>` before the
74
+ next verify to bind the session.
75
+ - **Unconfirmed session binding** → `bindingAdvisory: 'unconfirmed'` causes a
76
+ softer advisory: `⚠ Could not confirm this session is attached to the task.`
77
+ Same remediation: `lumo session attach <LUM-N>`.
66
78
 
67
79
  ## Round discipline
68
80
 
@@ -3,7 +3,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.sessionWrap = sessionWrap;
4
4
  const config_1 = require("../lib/config");
5
5
  const wrap_panel_1 = require("../lib/wrap-panel");
6
- const progress_comment_section_1 = require("./wrap/progress-comment-section");
7
6
  const memory_review_section_1 = require("./wrap/memory-review-section");
8
7
  const fragment_usage_section_1 = require("./wrap/fragment-usage-section");
9
8
  const blocked_prompt_section_1 = require("./wrap/blocked-prompt-section");
@@ -11,13 +10,12 @@ const crossings_reminder_1 = require("./wrap/crossings-reminder");
11
10
  /**
12
11
  * `lumo session wrap [--yes] [--dry-run]`
13
12
  *
14
- * Session-end wrap-up panel with four sections, run in order: (1) draft a
15
- * progress comment from this session's unposted turnSummaries and post it
16
- * (after y/e/s confirmation) to the bound task; (2) review the Layer1 memories
17
- * this session sedimented keep/delete/promote, deduped by a per-session
18
- * watermark; (3) vote which injected context fragments were actually used
19
- * (LUM-300, via `--used`); (4) if the session repeatedly hit the same failure,
20
- * prompt whether to flag the bound task with a `blocked` tag (LUM-153).
13
+ * Session-end wrap-up panel with three sections, run in order: (1) review the
14
+ * Layer1 memories this session sedimented keep/delete/promote, deduped by a
15
+ * per-session watermark; (2) vote which injected context fragments were
16
+ * actually used (LUM-300, via `--used`); (3) if the session repeatedly hit the
17
+ * same failure, prompt whether to flag the bound task with a `blocked` tag
18
+ * (LUM-153).
21
19
  */
22
20
  async function sessionWrap(options) {
23
21
  const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
@@ -32,7 +30,6 @@ async function sessionWrap(options) {
32
30
  return 1;
33
31
  }
34
32
  const sections = [
35
- new progress_comment_section_1.ProgressCommentSection({ creds, sessionId }),
36
33
  new memory_review_section_1.MemoryReviewSection({ creds, sessionId }),
37
34
  new fragment_usage_section_1.FragmentUsageSection({ creds, sessionId, used: options.used }),
38
35
  new blocked_prompt_section_1.BlockedPromptSection({ creds, sessionId }),
@@ -141,7 +141,14 @@ function formatSignalHealth(h) {
141
141
  lines.push(`- Distribution: used ${h.distribution.used} · null ${h.distribution.abstained} · false ${h.distribution.unused}`);
142
142
  lines.push(`- Per-session variance: ${h.perSessionVariance.toFixed(2)} (${h.votedSessions} voted sessions)`);
143
143
  if (h.usedMergeRate !== null && h.baseMergeRate !== null) {
144
- lines.push(`- Used × outcome: merge-rate(used) ${Math.round(h.usedMergeRate * 100)}% vs base ${Math.round(h.baseMergeRate * 100)}%`);
144
+ if (h.baseFailedTasks === 0) {
145
+ // No failure outcomes exist yet, so any rate is non-discriminating by
146
+ // construction — say so honestly instead of printing a misleading 100%.
147
+ lines.push('- Used × outcome: no failure outcomes yet — metric cannot discriminate');
148
+ }
149
+ else {
150
+ lines.push(`- Used × outcome: merge-rate(used) ${Math.round(h.usedMergeRate * 100)}% (${h.usedResolvedTasks} resolved, ${h.usedFailedTasks} failed) vs base ${Math.round(h.baseMergeRate * 100)}% (${h.baseResolvedTasks} resolved, ${h.baseFailedTasks} failed)`);
151
+ }
145
152
  }
146
153
  else {
147
154
  lines.push('- Used × outcome: insufficient resolved tasks');
@@ -181,6 +181,14 @@ async function verify(identifier, options = {}) {
181
181
  }
182
182
  const outcome = (await res.json());
183
183
  process.stdout.write(`\nRound ${outcome.round}/${outcome.maxRounds} recorded.\n`);
184
+ if (outcome.bindingAdvisory === 'unbound') {
185
+ process.stdout.write('⚠ Working unbound — this verify ran from a Claude Code session not attached to the task. ' +
186
+ 'Run `lumo session attach <LUM-N>` to bind (recorded as a boundary crossing).\n');
187
+ }
188
+ else if (outcome.bindingAdvisory === 'unconfirmed') {
189
+ process.stdout.write('⚠ Could not confirm this session is attached to the task. ' +
190
+ 'If you are working with Claude Code, run `lumo session attach <LUM-N>`.\n');
191
+ }
184
192
  if (outcome.allPassed) {
185
193
  process.stdout.write(`✓ All MACHINE criteria passed — task is now ${outcome.taskStatus}.\n` +
186
194
  `Stop here: human adjudication (and any HUMAN criteria) take over from this point.\n`);
@@ -247,9 +247,9 @@ session
247
247
  .action(wrap(() => (0, session_status_1.sessionStatus)()));
248
248
  session
249
249
  .command('wrap')
250
- .description("Session-end wrap-up: draft a progress comment from this session's turn summaries and post it to the bound task after confirmation.")
251
- .option('-y, --yes', 'Post the drafted comment without prompting (agent-friendly)')
252
- .option('--dry-run', 'Print the draft but do not post or advance the watermark')
250
+ .description('Session-end wrap-up: review the memories sedimented this session, vote which injected context fragments were actually used, and optionally flag the bound task blocked.')
251
+ .option('-y, --yes', 'Keep all memories without prompting (agent-friendly); does not auto-apply the blocked tag')
252
+ .option('--dry-run', 'Print the section drafts but do not mutate memories/tags or advance watermarks')
253
253
  .option('--used <indices>', 'Mark which injected context fragments you actually used (1-based indices, comma/space separated; "none" for all-unused). Omit to skip recording.')
254
254
  .action(wrap(options => (0, session_wrap_1.sessionWrap)(options)));
255
255
  const task = program
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.35.0",
3
+ "version": "1.36.0",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",
@@ -1,81 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.ProgressCommentSection = void 0;
4
- exports.formatProgressBody = formatProgressBody;
5
- const sanitize_1 = require("../../lib/sanitize");
6
- const line_prompt_1 = require("../../lib/line-prompt");
7
- const editor_1 = require("../../lib/editor");
8
- const progress_comment_api_1 = require("../../lib/progress-comment-api");
9
- const HEADER = 'Session progress';
10
- /** Join turn summaries into a bulleted progress comment body under a header. */
11
- function formatProgressBody(summaries) {
12
- return [HEADER, ...summaries.map(s => `- ${s}`)].join('\n');
13
- }
14
- /**
15
- * Wrap-panel section that drafts a progress comment from the session's
16
- * unposted turnSummaries and posts it after y/e/s confirmation. Holds its own
17
- * draft + body state between prepare() and run().
18
- */
19
- class ProgressCommentSection {
20
- deps;
21
- title = 'Progress comment';
22
- draft = null;
23
- body = '';
24
- constructor(deps) {
25
- this.deps = deps;
26
- }
27
- async prepare() {
28
- this.draft = await (0, progress_comment_api_1.fetchProgressDraft)(this.deps.creds, this.deps.sessionId);
29
- if (!this.draft.taskIdentifier || this.draft.summaries.length === 0) {
30
- return false;
31
- }
32
- this.body = formatProgressBody(this.draft.summaries.map(s => s.turnSummary));
33
- return true;
34
- }
35
- async run(opts) {
36
- const draft = this.draft;
37
- if (!draft || !draft.watermark)
38
- return;
39
- // Preview: sanitize the server free-text before it hits the terminal.
40
- process.stdout.write(`Will post to ${draft.taskIdentifier} "${(0, sanitize_1.sanitizeField)(draft.taskTitle ?? '')}":\n`);
41
- process.stdout.write(`${(0, sanitize_1.sanitizeField)(this.body)}\n`);
42
- if (opts.dryRun) {
43
- process.stdout.write('(dry-run, not posted)\n');
44
- return;
45
- }
46
- if (opts.yes) {
47
- await this.post(draft.watermark, this.body);
48
- return;
49
- }
50
- const choice = (await (0, line_prompt_1.promptLine)('[y] post [e] edit [s] skip > ')).toLowerCase();
51
- if (choice === 's' || choice === '') {
52
- process.stdout.write('Skipped.\n');
53
- return;
54
- }
55
- if (choice === 'e') {
56
- const edited = (await (0, editor_1.editInEditor)(this.body)).trim();
57
- if (edited.length === 0) {
58
- process.stdout.write('Empty body — skipped.\n');
59
- return;
60
- }
61
- process.stdout.write(`${(0, sanitize_1.sanitizeField)(edited)}\n`);
62
- const confirm = (await (0, line_prompt_1.promptLine)('[y] post [s] skip > ')).toLowerCase();
63
- if (confirm !== 'y') {
64
- process.stdout.write('Skipped.\n');
65
- return;
66
- }
67
- await this.post(draft.watermark, edited);
68
- return;
69
- }
70
- if (choice === 'y') {
71
- await this.post(draft.watermark, this.body);
72
- return;
73
- }
74
- process.stdout.write('Unrecognized choice — skipped.\n');
75
- }
76
- async post(watermark, body) {
77
- const { commentId } = await (0, progress_comment_api_1.postProgressComment)(this.deps.creds, this.deps.sessionId, { body, watermark });
78
- process.stdout.write(`Posted progress comment (comment ${commentId})\n`);
79
- }
80
- }
81
- exports.ProgressCommentSection = ProgressCommentSection;
@@ -1,47 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.fetchProgressDraft = fetchProgressDraft;
4
- exports.postProgressComment = postProgressComment;
5
- const api_1 = require("./api");
6
- function base(creds) {
7
- return (0, api_1.trimTrailingSlash)((0, api_1.resolveAuthedApiUrl)(creds.apiUrl));
8
- }
9
- /** GET the unposted progress draft for the session. Throws on transport / non-200. */
10
- async function fetchProgressDraft(creds, sessionId) {
11
- const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/turn-summaries`;
12
- const res = await fetch(url, {
13
- headers: { Authorization: `Bearer ${creds.token}` },
14
- });
15
- if (res.status === 401)
16
- throw new Error('API key invalid or revoked. Run `lumo auth login`.');
17
- if (!res.ok)
18
- throw new Error(`progress draft fetch failed (HTTP ${res.status})`);
19
- return (await res.json());
20
- }
21
- /** POST the (possibly edited) body + watermark. Throws the server message on non-201. */
22
- async function postProgressComment(creds, sessionId, payload) {
23
- const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/progress-comment`;
24
- const res = await fetch(url, {
25
- method: 'POST',
26
- headers: {
27
- Authorization: `Bearer ${creds.token}`,
28
- 'Content-Type': 'application/json',
29
- },
30
- body: JSON.stringify(payload),
31
- });
32
- if (res.status === 401)
33
- throw new Error('API key invalid or revoked. Run `lumo auth login`.');
34
- if (res.status !== 201) {
35
- let serverMsg = null;
36
- try {
37
- const errBody = (await res.json());
38
- if (typeof errBody.error === 'string')
39
- serverMsg = errBody.error;
40
- }
41
- catch {
42
- // body wasn't JSON
43
- }
44
- throw new Error(serverMsg ?? `progress comment failed (HTTP ${res.status})`);
45
- }
46
- return (await res.json());
47
- }