@lumoai/cli 1.57.0 → 1.59.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/skill/SKILL.md +52 -130
- package/assets/skill/references/artifacts-figma.md +4 -3
- package/assets/skill/references/confirmation.md +131 -0
- package/assets/skill/references/criteria.md +60 -20
- package/assets/skill/references/doc-editing.md +11 -9
- package/assets/skill/references/docs.md +4 -3
- package/assets/skill/references/ideas.md +82 -0
- package/assets/skill/references/initiatives.md +28 -0
- package/assets/skill/references/memory.md +4 -2
- package/assets/skill/references/milestones.md +3 -2
- package/assets/skill/references/outcome.md +1 -14
- package/assets/skill/references/plan-runs.md +32 -0
- package/assets/skill/references/sessions.md +7 -5
- package/assets/skill/references/sprints.md +18 -17
- package/assets/skill/references/task-context.md +1 -1
- package/assets/skill/references/task-deps.md +4 -3
- package/assets/skill/references/tasks.md +34 -2
- package/assets/skill/references/verify.md +46 -68
- package/assets/skill/references/worktree.md +13 -7
- package/dist/cli/src/commands/doc-delete.js +35 -23
- package/dist/cli/src/commands/doc-rebuild-source.js +16 -4
- package/dist/cli/src/commands/memory-rm.js +68 -10
- package/dist/cli/src/commands/milestone-delete.js +13 -10
- package/dist/cli/src/commands/outcome.js +0 -77
- package/dist/cli/src/commands/session-attach.js +8 -2
- package/dist/cli/src/commands/sprint-close.js +29 -9
- package/dist/cli/src/commands/sprint-delete.js +13 -10
- package/dist/cli/src/commands/sprint-show.js +3 -9
- package/dist/cli/src/commands/task-artifact-rm.js +58 -28
- package/dist/cli/src/commands/task-criteria-list.js +1 -4
- package/dist/cli/src/commands/task-criteria-set.js +3 -12
- package/dist/cli/src/commands/task-deps.js +20 -6
- package/dist/cli/src/commands/task-status.js +165 -110
- package/dist/cli/src/commands/task-update.js +129 -0
- package/dist/cli/src/commands/verify.js +22 -13
- package/dist/cli/src/commands/worktree-rm.js +35 -7
- package/dist/cli/src/index.js +47 -47
- package/dist/cli/src/lib/blocked-error.js +178 -0
- package/dist/cli/src/lib/confirmation.js +89 -0
- package/dist/cli/src/lib/hook-runner.js +23 -11
- package/dist/shared/src/referent-kind.js +31 -1
- package/dist/shared/src/security-scan.js +29 -0
- package/package.json +1 -1
- package/assets/skill/references/fidelity.md +0 -32
- package/dist/cli/src/commands/fidelity.js +0 -108
- 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,
|
|
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 ·
|
|
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 --
|
|
38
|
-
' --backlog-all --
|
|
39
|
-
' Web UI
|
|
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.
|
|
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
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
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 (!
|
|
55
|
-
|
|
56
|
-
|
|
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,
|
|
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,
|
|
101
|
+
const { sprint, progress, topBlockers } = (await sprintRes.json());
|
|
108
102
|
const { tasks } = (await tasksRes.json());
|
|
109
|
-
process.stdout.write(formatSprintShow(sprint, progress, tasks,
|
|
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> --
|
|
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> --
|
|
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(
|
|
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
|
-
|
|
45
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
31
|
-
|
|
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 …"
|
|
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
|
-
/** `
|
|
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> --
|
|
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;
|