@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
@@ -1,28 +1,10 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.outcomeRecord = outcomeRecord;
4
3
  exports.outcomeShow = outcomeShow;
5
4
  exports.outcomeRate = outcomeRate;
6
5
  const config_1 = require("../lib/config");
7
6
  const api_1 = require("../lib/api");
8
7
  const sanitize_1 = require("../lib/sanitize");
9
- /**
10
- * `lumo outcome` — the post-hoc outcome well (LUM-598).
11
- *
12
- * The well is the single external oracle for correctness + fidelity: it records
13
- * the real-world fate a delivery met AFTER it shipped. It is a FALSIFIER, not a
14
- * verifier — it only ever reads REJECTED (reality revoked/redid/bypassed the
15
- * work) or INCONCLUSIVE (no rejection on record). There is deliberately no
16
- * "mark satisfied": the absence of a rejection is not a pass.
17
- */
18
- const MANUAL_KINDS = [
19
- 'REVERTED',
20
- 'ROLLED_BACK',
21
- 'CI_REGRESSION',
22
- 'DOWNSTREAM_REDIRECT',
23
- 'BYPASSED',
24
- 'MANUAL',
25
- ];
26
8
  function authBase() {
27
9
  const creds = (0, config_1.readCredentials)();
28
10
  if (!creds)
@@ -36,65 +18,6 @@ function authBase() {
36
18
  headers['X-Lumo-Session-Id'] = sessionId;
37
19
  return { base, headers };
38
20
  }
39
- /**
40
- * `lumo outcome record <task> --note "<what reality did>" [--kind <kind>]` —
41
- * record a human-observed post-hoc REJECTION of a delivery. Append-only. The
42
- * note is mandatory (the observed referent); the default kind is MANUAL.
43
- */
44
- async function outcomeRecord(taskId, options = {}) {
45
- if (!taskId || taskId.trim() === '') {
46
- console.error('Error: a task is required: lumo outcome record <task> --note "…"');
47
- return 1;
48
- }
49
- const note = options.note?.trim();
50
- if (!note) {
51
- console.error('Error: --note "<what reality did>" is required (the observed rejection, e.g. "reverted in #812 after prod incident").');
52
- return 1;
53
- }
54
- const kind = (options.kind?.trim().toUpperCase() ||
55
- 'MANUAL');
56
- if (!MANUAL_KINDS.includes(kind)) {
57
- console.error(`Error: --kind must be one of: ${MANUAL_KINDS.map(k => k.toLowerCase()).join(', ')}`);
58
- return 1;
59
- }
60
- const auth = authBase();
61
- if ('error' in auth) {
62
- console.error(`Error: ${auth.error}`);
63
- return 1;
64
- }
65
- const payload = { kind, note };
66
- if (options.occurredAt?.trim())
67
- payload.occurredAt = options.occurredAt.trim();
68
- let res;
69
- try {
70
- res = await fetch(`${auth.base}/api/tasks/${encodeURIComponent(taskId)}/outcome-signals`, {
71
- method: 'POST',
72
- headers: { ...auth.headers, 'Content-Type': 'application/json' },
73
- body: JSON.stringify(payload),
74
- });
75
- }
76
- catch (err) {
77
- const msg = err instanceof Error ? err.message : String(err);
78
- console.error(`Error: could not reach Lumo API (${msg})`);
79
- return 1;
80
- }
81
- if (res.status === 401) {
82
- console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
83
- return 1;
84
- }
85
- if (!res.ok) {
86
- const errBody = (await res.json().catch(() => null));
87
- const detail = errBody && typeof errBody.error === 'string'
88
- ? (0, sanitize_1.sanitizeField)(errBody.error)
89
- : '';
90
- console.error(`Error: outcome not recorded (HTTP ${res.status})${detail ? ` — ${detail}` : ''}`);
91
- return 1;
92
- }
93
- process.stdout.write(`✓ Recorded a ${(0, sanitize_1.sanitizeField)(kind.toLowerCase())} rejection on ${(0, sanitize_1.sanitizeField)(taskId)}.\n` +
94
- ' The well is a falsifier — this marks the delivery REJECTED by reality; ' +
95
- 'it is append-only and there is no "satisfied" counterpart.\n');
96
- return;
97
- }
98
21
  /**
99
22
  * `lumo outcome show <task>` — read the well for a task: the falsifier verdict
100
23
  * plus its backing rejection signals.
@@ -23,7 +23,7 @@ const memory_auto_1 = require("../lib/memory-auto");
23
23
  * a different binding requires a brand-new Claude Code session.
24
24
  *
25
25
  * A STEWARD session plans and accepts only: task create/update, criteria
26
- * set, deps confirm/dismiss, verdict --fail, milestone add/remove and all
26
+ * set, deps confirm/dismiss, milestone add/remove and all
27
27
  * reads pass; `lumo verify` and self-dispatch are refused with 409, and
28
28
  * implementation artifacts (commits/PRs) are recorded as boundary crossings.
29
29
  */
@@ -131,6 +131,12 @@ async function sessionAttach(identifier, options) {
131
131
  console.log('');
132
132
  console.log((0, sanitize_1.sanitizeField)(body.blockerWarningSection));
133
133
  }
134
+ // LUM-737: security findings next — same slot as the hook envelope (after
135
+ // the blocker, before the contract).
136
+ if (body.securityFindingsSection) {
137
+ console.log('');
138
+ console.log((0, sanitize_1.sanitizeField)(body.securityFindingsSection));
139
+ }
134
140
  // Contract next: it's what the upcoming work is judged against (LUM-342).
135
141
  if (body.criteriaSection) {
136
142
  console.log('');
@@ -239,7 +245,7 @@ async function stewardAttach(args) {
239
245
  console.log(`Attached session ${sessionId} as STEWARD of milestone "${(0, sanitize_1.sanitizeField)(body.milestoneName)}"`);
240
246
  console.log('');
241
247
  console.log('Governance role — plan & accept only (lifetime lock):');
242
- console.log(' allowed: task create/update · criteria set (incl. --human) · deps confirm/dismiss · verdict --fail · milestone add/remove · all reads');
248
+ console.log(' allowed: task create/update · criteria set (incl. --human) · deps confirm/dismiss · milestone add/remove · all reads');
243
249
  console.log(' blocked: lumo verify (409, no round burned) · moving a task to in_progress assigned to yourself · attaching a task');
244
250
  console.log(' note: commits/PRs produced in this session are recorded as boundary crossings (human-reviewed on the web)');
245
251
  // LUM-647: prior-milestone learnings distilled from this project's earlier
@@ -2,12 +2,14 @@
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.buildCloseDecisions = buildCloseDecisions;
4
4
  exports.formatUnfinishedRefusal = formatUnfinishedRefusal;
5
+ exports.describeSprintClose = describeSprintClose;
5
6
  exports.sprintClose = sprintClose;
6
7
  const config_1 = require("../lib/config");
7
8
  const api_1 = require("../lib/api");
8
9
  const resolve_1 = require("../lib/resolve");
9
10
  const format_1 = require("../lib/format");
10
11
  const sanitize_1 = require("../lib/sanitize");
12
+ const confirmation_1 = require("../lib/confirmation");
11
13
  /**
12
14
  * Build the decisions map for POST /api/sprints/<id>/close.
13
15
  * Only non-DONE tasks get a decision entry; DONE tasks are excluded entirely.
@@ -34,24 +36,34 @@ function formatUnfinishedRefusal(number, name, tasks) {
34
36
  (0, format_1.formatTaskListTable)(unfinished),
35
37
  '',
36
38
  'To resolve, pass one of:',
37
- ' --move-all --yes move all unfinished tasks to the next sprint',
38
- ' --backlog-all --yes return all unfinished tasks to the backlog',
39
- ' Web UI decide task-by-task at lumo.so',
39
+ ' --move-all --confirm move all unfinished tasks to the next sprint',
40
+ ' --backlog-all --confirm return all unfinished tasks to the backlog',
41
+ ' Web UI decide task-by-task at lumo.so',
40
42
  ];
41
43
  return lines.join('\n');
42
44
  }
45
+ /**
46
+ * The changes[] block for the `--move-all` / `--backlog-all` confirmation
47
+ * envelope (LUM-755): the sprint, and every unfinished task with where it goes.
48
+ */
49
+ function describeSprintClose(number, name, tasks, mode) {
50
+ const unfinished = tasks.filter(t => t.status !== 'DONE');
51
+ const lines = [`Will close sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}"`];
52
+ if (unfinished.length === 0) {
53
+ lines.push('No unfinished tasks — nothing is moved');
54
+ return lines;
55
+ }
56
+ const ids = unfinished.map(t => `${t.teamIdentifier}-${t.number}`).join(', ');
57
+ const dest = mode === 'move' ? 'move to the next sprint' : 'return to the backlog';
58
+ lines.push((0, sanitize_1.sanitizeField)(`${unfinished.length} unfinished task${unfinished.length === 1 ? '' : 's'} ${dest}: ${ids}`));
59
+ return lines;
60
+ }
43
61
  async function sprintClose(identifier, opts) {
44
62
  // CLI-side mutex: both flags together is an error.
45
63
  if (opts.moveAll && opts.backlogAll) {
46
64
  console.error('Error: --move-all and --backlog-all are mutually exclusive. Pick one.');
47
65
  return 1;
48
66
  }
49
- // CLI-side: mode flags require --yes.
50
- if ((opts.moveAll || opts.backlogAll) && !opts.yes) {
51
- const flag = opts.moveAll ? '--move-all' : '--backlog-all';
52
- console.error(`Error: ${flag} requires --yes to confirm. Re-run with --yes.`);
53
- return 1;
54
- }
55
67
  const creds = (0, config_1.readCredentials)();
56
68
  if (!creds) {
57
69
  console.error('Error: not logged in. Run `lumo auth login` first.');
@@ -110,6 +122,14 @@ async function sprintClose(identifier, opts) {
110
122
  }
111
123
  // All tasks done — post with empty decisions.
112
124
  }
125
+ // LUM-755: a mode flag without --confirm → envelope listing every task
126
+ // that would move, computed from the real sprint state; nothing is posted.
127
+ if ((opts.moveAll || opts.backlogAll) && !(0, confirmation_1.isConfirmed)(opts)) {
128
+ return (0, confirmation_1.emitConfirmation)({
129
+ command: 'sprint close',
130
+ changes: describeSprintClose(sprint.number, sprint.name, tasks, opts.moveAll ? 'move' : 'backlog'),
131
+ });
132
+ }
113
133
  // Build decisions map.
114
134
  const decisions = opts.moveAll || opts.backlogAll
115
135
  ? buildCloseDecisions(tasks, opts.moveAll ? 'move' : 'backlog')
@@ -1,17 +1,18 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.formatDeleteRefusal = formatDeleteRefusal;
3
+ exports.describeSprintDelete = describeSprintDelete;
4
4
  exports.sprintDelete = sprintDelete;
5
5
  const config_1 = require("../lib/config");
6
6
  const api_1 = require("../lib/api");
7
7
  const resolve_1 = require("../lib/resolve");
8
8
  const sanitize_1 = require("../lib/sanitize");
9
- function formatDeleteRefusal(number, name, taskCount) {
10
- const head = `Refusing to delete sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}" without --yes.`;
11
- const tail = `Re-run with --yes to confirm.`;
12
- if (taskCount === 0)
13
- return `${head}\n${tail}`;
14
- return `${head}\n${taskCount} tasks under it will keep their data; only sprintId is cleared.\n${tail}`;
9
+ const confirmation_1 = require("../lib/confirmation");
10
+ /** The changes[] block for the confirmation envelope (LUM-755). */
11
+ function describeSprintDelete(number, name, taskCount) {
12
+ const lines = [`Will delete sprint #${number} "${(0, sanitize_1.sanitizeField)(name)}"`];
13
+ if (taskCount > 0)
14
+ lines.push(`${taskCount} tasks under it keep their data; only sprintId is cleared`);
15
+ return lines;
15
16
  }
16
17
  async function sprintDelete(identifier, opts) {
17
18
  const creds = (0, config_1.readCredentials)();
@@ -51,9 +52,11 @@ async function sprintDelete(identifier, opts) {
51
52
  return 1;
52
53
  }
53
54
  const { sprint, progress } = (await showRes.json());
54
- if (!opts.yes) {
55
- console.error(formatDeleteRefusal(sprint.number, sprint.name, progress.total));
56
- return 1;
55
+ if (!(0, confirmation_1.isConfirmed)(opts)) {
56
+ return (0, confirmation_1.emitConfirmation)({
57
+ command: 'sprint delete',
58
+ changes: describeSprintDelete(sprint.number, sprint.name, progress.total),
59
+ });
57
60
  }
58
61
  let res;
59
62
  try {
@@ -18,7 +18,7 @@ function blockerLine(label, items) {
18
18
  .join(', ');
19
19
  return ` ${label.padEnd(11)}${rendered}`;
20
20
  }
21
- function formatSprintShow(s, progress, tasks, risk, topBlockers) {
21
+ function formatSprintShow(s, progress, tasks, topBlockers) {
22
22
  const lines = [
23
23
  `Sprint: #${s.number} ${(0, sanitize_1.sanitizeField)(s.name)}`,
24
24
  `Status: ${s.status}`,
@@ -30,12 +30,6 @@ function formatSprintShow(s, progress, tasks, risk, topBlockers) {
30
30
  ``,
31
31
  `Progress: ${progress.done} / ${progress.total}`,
32
32
  ];
33
- if (risk) {
34
- lines.push(`Health: ${risk.level.toUpperCase()}`);
35
- for (const r of risk.reasons) {
36
- lines.push(` - ${(0, sanitize_1.sanitizeField)(r.detail)}`);
37
- }
38
- }
39
33
  if (topBlockers) {
40
34
  const blockerLines = [
41
35
  blockerLine('Overdue:', topBlockers.overdueTaskIds),
@@ -104,7 +98,7 @@ async function sprintShow(identifier, opts) {
104
98
  console.error(`Error: sprint tasks failed (HTTP ${tasksRes.status})`);
105
99
  return 1;
106
100
  }
107
- const { sprint, progress, risk, topBlockers } = (await sprintRes.json());
101
+ const { sprint, progress, topBlockers } = (await sprintRes.json());
108
102
  const { tasks } = (await tasksRes.json());
109
- process.stdout.write(formatSprintShow(sprint, progress, tasks, risk, topBlockers) + '\n');
103
+ process.stdout.write(formatSprintShow(sprint, progress, tasks, topBlockers) + '\n');
110
104
  }
@@ -1,20 +1,36 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeArtifactRm = describeArtifactRm;
3
4
  exports.taskArtifactRm = taskArtifactRm;
4
5
  const config_1 = require("../lib/config");
5
6
  const api_1 = require("../lib/api");
6
7
  const sanitize_1 = require("../lib/sanitize");
8
+ const confirmation_1 = require("../lib/confirmation");
9
+ /** Best-effort `{ error }` extraction, else null. */
10
+ async function serverError(res) {
11
+ try {
12
+ const errBody = (await res.json());
13
+ return typeof errBody.error === 'string' ? errBody.error : null;
14
+ }
15
+ catch {
16
+ return null;
17
+ }
18
+ }
19
+ /** The changes[] block for the confirmation envelope. */
20
+ function describeArtifactRm(identifier, artifact) {
21
+ const title = (0, sanitize_1.sanitizeField)(artifact.title).replace(/"/g, '\\"');
22
+ return [
23
+ `Will delete artifact ${artifact.id} "${title}" (${artifact.kind}) from ${identifier}`,
24
+ 'Artifact deletion is irreversible',
25
+ ];
26
+ }
7
27
  async function taskArtifactRm(identifier, artifactId, options) {
8
28
  if (!identifier) {
9
- console.error('Error: missing <task>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --yes');
29
+ console.error('Error: missing <task>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --confirm');
10
30
  return 1;
11
31
  }
12
32
  if (!artifactId) {
13
- console.error('Error: missing <artifact-id>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --yes');
14
- return 1;
15
- }
16
- if (!options.yes) {
17
- console.error('Error: --yes is required (artifact deletion is irreversible).');
33
+ console.error('Error: missing <artifact-id>. Usage: lumo task artifact rm <LUM-42> <artifact-id> --confirm');
18
34
  return 1;
19
35
  }
20
36
  const creds = (0, config_1.readCredentials)();
@@ -24,12 +40,41 @@ async function taskArtifactRm(identifier, artifactId, options) {
24
40
  }
25
41
  const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
26
42
  const base = (0, api_1.trimTrailingSlash)(apiUrl);
43
+ const url = `${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts/${encodeURIComponent(artifactId)}`;
44
+ const headers = { Authorization: `Bearer ${creds.token}` };
45
+ // LUM-755: without --confirm, look the artifact up so the envelope names
46
+ // what would be deleted, then stop — no DELETE is sent.
47
+ if (!(0, confirmation_1.isConfirmed)(options)) {
48
+ let showRes;
49
+ try {
50
+ showRes = await fetch(url, { headers });
51
+ }
52
+ catch (err) {
53
+ const msg = err instanceof Error ? err.message : String(err);
54
+ console.error(`Error: could not reach Lumo API at ${apiUrl} (${msg})`);
55
+ return 1;
56
+ }
57
+ if (showRes.status === 401) {
58
+ console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
59
+ return 1;
60
+ }
61
+ if (!showRes.ok) {
62
+ const msg = (await serverError(showRes)) ??
63
+ (showRes.status === 404
64
+ ? `task ${identifier} or artifact ${artifactId} not found in workspace ${creds.workspaceSlug}`
65
+ : `artifact lookup failed (HTTP ${showRes.status})`);
66
+ console.error(`Error: ${(0, sanitize_1.sanitizeField)(msg)}`);
67
+ return 1;
68
+ }
69
+ const { artifact } = (await showRes.json());
70
+ return (0, confirmation_1.emitConfirmation)({
71
+ command: 'task artifact rm',
72
+ changes: describeArtifactRm(identifier, artifact),
73
+ });
74
+ }
27
75
  let res;
28
76
  try {
29
- res = await fetch(`${base}/api/tasks/${encodeURIComponent(identifier)}/artifacts/${encodeURIComponent(artifactId)}`, {
30
- method: 'DELETE',
31
- headers: { Authorization: `Bearer ${creds.token}` },
32
- });
77
+ res = await fetch(url, { method: 'DELETE', headers });
33
78
  }
34
79
  catch (err) {
35
80
  const msg = err instanceof Error ? err.message : String(err);
@@ -41,28 +86,13 @@ async function taskArtifactRm(identifier, artifactId, options) {
41
86
  return 1;
42
87
  }
43
88
  if (res.status === 404) {
44
- let serverMsg = `task ${identifier} or artifact ${artifactId} not found in workspace ${creds.workspaceSlug}`;
45
- try {
46
- const errBody = (await res.json());
47
- if (typeof errBody.error === 'string')
48
- serverMsg = errBody.error;
49
- }
50
- catch {
51
- /* not JSON */
52
- }
89
+ const serverMsg = (await serverError(res)) ??
90
+ `task ${identifier} or artifact ${artifactId} not found in workspace ${creds.workspaceSlug}`;
53
91
  console.error(`Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`);
54
92
  return 1;
55
93
  }
56
94
  if (res.status !== 204) {
57
- let serverMsg = null;
58
- try {
59
- const errBody = (await res.json());
60
- if (typeof errBody.error === 'string')
61
- serverMsg = errBody.error;
62
- }
63
- catch {
64
- /* not JSON */
65
- }
95
+ const serverMsg = await serverError(res);
66
96
  console.error(serverMsg
67
97
  ? `Error: ${(0, sanitize_1.sanitizeField)(serverMsg)}`
68
98
  : `Error: artifact rm failed (HTTP ${res.status})`);
@@ -72,11 +72,8 @@ async function taskCriteriaList(identifier) {
72
72
  }
73
73
  const data = (await res.json());
74
74
  if (data.criteria.length === 0) {
75
- process.stdout.write(`No acceptance criteria on ${identifier} — draft 3–7 and submit with lumo task criteria set ${identifier} --file <criteria.json>\n`);
75
+ process.stdout.write(`No acceptance criteria on ${identifier} — draft them and submit with lumo task criteria set ${identifier} --file <criteria.json>\n`);
76
76
  return;
77
77
  }
78
78
  process.stdout.write(formatCriteriaRows(data.criteria));
79
- if (data.warning) {
80
- process.stdout.write(`⚠ ${(0, sanitize_1.sanitizeField)(data.warning)}\n`);
81
- }
82
79
  }
@@ -27,9 +27,8 @@ function verifyReadback(submitted, stored) {
27
27
  if ((got.checkpointer ?? null) !== wantCk) {
28
28
  issues.push(`checkpointer corrupted in landing for "${s.statement.slice(0, 40)}": sent ${JSON.stringify(wantCk)}, stored ${JSON.stringify(got.checkpointer ?? null)}`);
29
29
  }
30
- if (s.referentKind != null && got.referentKind !== s.referentKind) {
31
- issues.push(`referentKind corrupted in landing for "${s.statement.slice(0, 40)}": sent ${s.referentKind}, stored ${got.referentKind}`);
32
- }
30
+ // LUM-733: referentKind is derived server-side from the checkpointer, so a
31
+ // submitted declaration is advisory and never compared on read-back.
33
32
  }
34
33
  return issues;
35
34
  }
@@ -54,7 +53,7 @@ function parseCriteriaJson(raw) {
54
53
  if (!Array.isArray(parsed) || parsed.length === 0) {
55
54
  return {
56
55
  ok: false,
57
- error: 'expected a non-empty JSON array of criteria, e.g. [{"statement":"…","verifierType":"MACHINE","checkpointer":"npx jest …","referentKind":"AGENT_CONSTRUCTED_STATE"}]',
56
+ error: 'expected a non-empty JSON array of criteria, e.g. [{"statement":"…","verifierType":"MACHINE","checkpointer":"npx jest …"}]',
58
57
  };
59
58
  }
60
59
  return { ok: true, items: parsed };
@@ -191,17 +190,9 @@ async function taskCriteriaSet(identifier, options) {
191
190
  ? `Recorded HUMAN_EDIT contract revision on ${identifier} (${data.criteria.length} criteria)\n`
192
191
  : `Acceptance contract drafted on ${identifier} (${data.criteria.length} criteria) — locked for agent edits; human revisions via --human\n`);
193
192
  process.stdout.write((0, task_criteria_list_1.formatCriteriaRows)(data.criteria));
194
- if (data.warning) {
195
- process.stdout.write(`⚠ ${(0, sanitize_1.sanitizeField)(data.warning)}\n`);
196
- }
197
193
  if (data.judgeStepsWarning) {
198
194
  process.stdout.write(`⚠ ${(0, sanitize_1.sanitizeField)(data.judgeStepsWarning)}\n`);
199
195
  }
200
- // LUM-605: EXTERNAL_FACT criteria whose checkpointer doesn't ground external
201
- // — stored, but the verify loop will refuse a PASS on them until re-grounded.
202
- if (data.referentWarning) {
203
- process.stdout.write(`⚠ ${(0, sanitize_1.sanitizeField)(data.referentWarning)}\n`);
204
- }
205
196
  const readbackIssues = verifyReadback(criteriaItems, data.criteria);
206
197
  if (readbackIssues.length > 0) {
207
198
  for (const issue of readbackIssues) {
@@ -6,10 +6,12 @@ exports.taskDepsList = taskDepsList;
6
6
  exports.taskDepsAdd = taskDepsAdd;
7
7
  exports.taskDepsConfirm = taskDepsConfirm;
8
8
  exports.taskDepsDismiss = taskDepsDismiss;
9
+ exports.describeDepsRm = describeDepsRm;
9
10
  exports.taskDepsRm = taskDepsRm;
10
11
  const config_1 = require("../lib/config");
11
12
  const api_1 = require("../lib/api");
12
13
  const sanitize_1 = require("../lib/sanitize");
14
+ const confirmation_1 = require("../lib/confirmation");
13
15
  /** Short id = first 8 chars; used for both display and selectors. */
14
16
  const shortId = (id) => id.slice(0, 8);
15
17
  /**
@@ -264,20 +266,32 @@ async function taskDepsDismiss(identifier, selector) {
264
266
  return res;
265
267
  console.log((0, sanitize_1.sanitizeField)(`Dismissed: [${shortId(edge.id)}] ${edge.other.identifier} "${edge.other.title}" (won't be suggested again)`));
266
268
  }
267
- /** `lumo task deps rm <LUM-N> <edge> --yes` */
269
+ /** The changes[] block for the `task deps rm` confirmation envelope. */
270
+ function describeDepsRm(identifier, edge) {
271
+ const dir = edge.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks';
272
+ return [
273
+ (0, sanitize_1.sanitizeField)(`Will delete dependency edge [${shortId(edge.id)}] from ${identifier}: ${dir} ${edge.other.identifier} "${edge.other.title}" (${edge.status}, ${edge.source})`),
274
+ 'The edge row is hard-deleted; both tasks are otherwise untouched',
275
+ ];
276
+ }
277
+ /** `lumo task deps rm <LUM-N> <edge> --confirm` */
268
278
  async function taskDepsRm(identifier, selector, opts) {
269
279
  if (!identifier || !selector) {
270
- console.error('Error: usage: lumo task deps rm <LUM-42> <edge> --yes');
271
- return 1;
272
- }
273
- if (!opts.yes) {
274
- console.error('Error: refusing to delete without --yes. Re-run with --yes to confirm.');
280
+ console.error('Error: usage: lumo task deps rm <LUM-42> <edge> --confirm');
275
281
  return 1;
276
282
  }
277
283
  const resolved = await resolveTaskAndEdge(identifier, selector);
278
284
  if (typeof resolved === 'number')
279
285
  return resolved;
280
286
  const { ctx, task, edge } = resolved;
287
+ // LUM-755: the edge is resolved first so the envelope can say exactly
288
+ // which row would go; without --confirm nothing is sent.
289
+ if (!(0, confirmation_1.isConfirmed)(opts)) {
290
+ return (0, confirmation_1.emitConfirmation)({
291
+ command: 'task deps rm',
292
+ changes: describeDepsRm(task.identifier, edge),
293
+ });
294
+ }
281
295
  const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies/${encodeURIComponent(edge.id)}`, { method: 'DELETE' }, 'dependency delete');
282
296
  if (typeof res === 'number')
283
297
  return res;