@lumoai/cli 1.41.0 → 1.43.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.
@@ -102,10 +102,14 @@ function formatLineageMarkdown(data) {
102
102
  lines.push(`**Status**: ${data.task.status}`);
103
103
  lines.push('');
104
104
  if (data.groups.length === 0) {
105
- lines.push('_No lineage edges recorded yet. Lineage is captured when a ' +
106
- "bound session consumes this task's context; once that happens " +
107
- '(and a PR merges / the task closes), the causal trail and its cost ' +
108
- 'will appear here._');
105
+ // LUM-559: an empty report is fail-closed it means no session ever bound
106
+ // to this task, so neither its cost nor its causal trail could be measured.
107
+ // Say "not measured" explicitly; never let the blank read as "zero cost".
108
+ lines.push('_Cost not measured — no session was ever bound to this task, so its ' +
109
+ 'cost and causal trail could not be recorded. This is "not measured", ' +
110
+ 'not "zero cost". Cost and lineage are captured once a session binds ' +
111
+ '(`lumo session attach <id>`) and consumes the context; bind before ' +
112
+ 'working so future runs are recorded._');
109
113
  lines.push('');
110
114
  return lines.join('\n');
111
115
  }
@@ -146,6 +150,16 @@ function formatLineageMarkdown(data) {
146
150
  }
147
151
  const summary = outcomeSummary(fragmentOutcomeCounts(g.fragments));
148
152
  lines.push(`**Fragments** (${g.fragments.length}${summary ? `: ${summary}` : ''}):`);
153
+ // LUM-559: an edgeless cost group is a session that spent tokens but
154
+ // recorded no fragments (it bound after session-start). Its empty trail is
155
+ // "not captured", NOT "the session used nothing" — say so, and skip the
156
+ // per-fragment usage legend that has nothing to annotate.
157
+ if (g.fragments.length === 0) {
158
+ lines.push('_Causal fragments not captured — this session bound after start, so ' +
159
+ 'its cost is recorded but its fragment trail is not._');
160
+ lines.push('');
161
+ continue;
162
+ }
149
163
  lines.push('_✓ used · · abstained · ✗ unused (manual)_');
150
164
  for (const f of g.fragments) {
151
165
  const tag = f.disclosure === 'INDEX'
@@ -134,6 +134,7 @@ function formatTaskStatus(data, extras = {}) {
134
134
  }
135
135
  }
136
136
  }
137
+ pushStruggleTrail(lines, data);
137
138
  lines.push('');
138
139
  if (data.nextActions.length === 0) {
139
140
  lines.push(data.currentRound > 0
@@ -165,6 +166,79 @@ function formatTaskStatus(data, extras = {}) {
165
166
  pushOpenCrossings(lines, extras);
166
167
  return lines.join('\n') + '\n';
167
168
  }
169
+ /**
170
+ * Append the honest "Struggle / rework / outstanding" section (LUM-561) — the
171
+ * anti-mum-and-deaf block (kills a silent "Nothing outstanding"). It is ALWAYS
172
+ * rendered when the contract exists, even on a clean 0-unmet task: a passing
173
+ * task that bounced, was sent back, or left a follow-up behind still shows its
174
+ * scars. When the trail is genuinely empty the block states the *basis* for
175
+ * that ("none — N rounds run, 0 FAIL …"), or, when nothing has been verified,
176
+ * that absence cannot be confirmed — never a bare clean slate. Skipped only
177
+ * when the server didn't emit the field (older server).
178
+ */
179
+ function pushStruggleTrail(lines, data) {
180
+ const trail = data.struggleTrail;
181
+ if (!trail)
182
+ return; // older server: can't fabricate, so don't claim "none".
183
+ lines.push('');
184
+ lines.push('Struggle / rework / outstanding:');
185
+ // A single PR is the happy path; >1 PR is the iteration signal. Reopens (a
186
+ // bounce after IN_REVIEW/DONE) always count. These two catch the rework that
187
+ // lives in PR cycles / status flips rather than in FAIL verdicts (LUM-561
188
+ // follow-up — without them a 10-PR task like LUM-557 reads as one hiccup).
189
+ const prIterated = trail.pullRequests.length > 1;
190
+ const empty = trail.reworkRounds.length === 0 &&
191
+ trail.sendBacks.length === 0 &&
192
+ trail.followUps.length === 0 &&
193
+ trail.reopens.length === 0 &&
194
+ !prIterated;
195
+ if (empty) {
196
+ if (data.currentRound === 0) {
197
+ // Honest fail-open: nothing was verified, so we cannot claim there were
198
+ // no difficulties — say so rather than render an implicitly-clean slate.
199
+ lines.push(' No verification has run yet — cannot confirm there were no difficulties. Run `lumo verify`.');
200
+ }
201
+ else {
202
+ const rounds = `${data.currentRound} verification round${data.currentRound === 1 ? '' : 's'}`;
203
+ lines.push(` None recorded — ${rounds} run, 0 FAIL, no send-backs, no reopens, no leftover follow-ups.`);
204
+ }
205
+ return;
206
+ }
207
+ if (trail.reworkRounds.length > 0) {
208
+ const parts = trail.reworkRounds.map(r => `round ${r.round} (${r.failed} FAIL)`);
209
+ lines.push(` Rework rounds: ${parts.join(', ')}`);
210
+ }
211
+ if (trail.sendBacks.length > 0) {
212
+ lines.push(' Send-backs:');
213
+ for (const s of trail.sendBacks) {
214
+ const lifecycle = s.status === 'resolved'
215
+ ? `resolved (sent back r${s.failedAtRound}${s.resolvedAtRound != null ? ` → r${s.resolvedAtRound}` : ''})`
216
+ : `open (sent back r${s.failedAtRound})`;
217
+ lines.push(` • ${(0, sanitize_1.sanitizeField)(s.statement)} — ${lifecycle}`);
218
+ }
219
+ }
220
+ if (trail.followUps.length > 0) {
221
+ lines.push(' Follow-ups left behind:');
222
+ for (const f of trail.followUps) {
223
+ lines.push(` • ${(0, sanitize_1.sanitizeField)(f.statement)} — flagged r${f.round}`);
224
+ }
225
+ }
226
+ // PR iteration — the dominant rework signal when the verify loop only ran
227
+ // once but the work churned across many PRs (LUM-557). Only when >1 PR.
228
+ if (prIterated) {
229
+ const PR_CAP = 12;
230
+ const nums = trail.pullRequests.map(p => `#${p.number}`);
231
+ const shown = nums.slice(0, PR_CAP).join(', ');
232
+ const overflow = nums.length > PR_CAP ? `, +${nums.length - PR_CAP} more` : '';
233
+ lines.push(` PR iterations: ${trail.pullRequests.length} PRs (${shown}${overflow})`);
234
+ }
235
+ if (trail.reopens.length > 0) {
236
+ lines.push(` Reopened ${trail.reopens.length}× (bounced back after review/done):`);
237
+ for (const r of trail.reopens) {
238
+ lines.push(` • ${r.from} → ${r.to}`);
239
+ }
240
+ }
241
+ }
168
242
  /**
169
243
  * Append the OPEN boundary-crossings safety block (LUM-448) — a count, one line
170
244
  * per crossing with its severity + category + clipped detail, and a pointer to
@@ -70,6 +70,7 @@ const memory_rm_1 = require("./commands/memory-rm");
70
70
  const memory_show_1 = require("./commands/memory-show");
71
71
  const memory_sync_1 = require("./commands/memory-sync");
72
72
  const memory_push_1 = require("./commands/memory-push");
73
+ const memory_fold_1 = require("./commands/memory-fold");
73
74
  const task_artifact_add_1 = require("./commands/task-artifact-add");
74
75
  const task_criteria_set_1 = require("./commands/task-criteria-set");
75
76
  const task_criteria_list_1 = require("./commands/task-criteria-list");
@@ -533,6 +534,11 @@ memoryCmd
533
534
  .option('--dir <path>', 'Memory dir (default: ~/.claude/projects/<cwd>/memory)')
534
535
  .option('--dry-run', 'List what would be pushed without sending')
535
536
  .action(wrap((opts) => (0, memory_push_1.memoryPush)(opts)));
537
+ memoryCmd
538
+ .command('fold [project-ref]')
539
+ .description('Preview the autonomous topic-fold pass (folding itself runs daily, automatically). Requires --dry-run.')
540
+ .option('--dry-run', 'preview proposed subsystem cards + which fine cards they fold; writes nothing')
541
+ .action(wrap((ref, opts) => (0, memory_fold_1.memoryFold)(ref, opts)));
536
542
  const milestoneCmd = program
537
543
  .command('milestone')
538
544
  .description('Inspect milestones from the terminal');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.41.0",
3
+ "version": "1.43.0",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",