@lumoai/cli 1.10.0 → 1.15.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.md +187 -18
- package/dist/cli/src/commands/milestone-archive.js +60 -0
- package/dist/cli/src/commands/milestone-list.js +24 -5
- package/dist/cli/src/commands/milestone-move.js +84 -0
- package/dist/cli/src/commands/milestone-reorder.js +72 -0
- package/dist/cli/src/commands/milestone-show.js +12 -0
- package/dist/cli/src/commands/milestone-unarchive.js +60 -0
- package/dist/cli/src/commands/next.js +103 -0
- package/dist/cli/src/commands/session-wrap.js +13 -5
- package/dist/cli/src/commands/task-context.js +4 -0
- package/dist/cli/src/commands/task-update.js +12 -4
- package/dist/cli/src/commands/wrap/blocked-prompt-section.js +64 -0
- package/dist/cli/src/commands/wrap/memory-review-section.js +81 -0
- package/dist/cli/src/index.js +36 -1
- package/dist/cli/src/lib/failure-summary-api.js +43 -0
- package/dist/cli/src/lib/hook-runner.js +1 -0
- package/dist/cli/src/lib/memory-content.js +7 -0
- package/dist/cli/src/lib/milestone-reorder.js +92 -0
- package/dist/cli/src/lib/rank-tasks.js +80 -0
- package/dist/cli/src/lib/resolve.js +17 -6
- package/dist/cli/src/lib/session-memory-api.js +47 -0
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Pure ranking logic for `lumo next`. Given the caller's open tasks plus the
|
|
4
|
+
* set of ACTIVE sprint ids, produce a recommendation order and a per-task list
|
|
5
|
+
* of human-readable reason factors.
|
|
6
|
+
*
|
|
7
|
+
* Ordering is lexicographic (explainable over a tunable magic score), honoring
|
|
8
|
+
* "priority first": priority → active-sprint membership → dueDate → in-flight
|
|
9
|
+
* status → updatedAt desc tiebreak.
|
|
10
|
+
*/
|
|
11
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
12
|
+
exports.rankTasks = rankTasks;
|
|
13
|
+
const PRIORITY_WEIGHT = {
|
|
14
|
+
URGENT: 0,
|
|
15
|
+
HIGH: 1,
|
|
16
|
+
MEDIUM: 2,
|
|
17
|
+
LOW: 3,
|
|
18
|
+
};
|
|
19
|
+
function priorityWeight(priority) {
|
|
20
|
+
return PRIORITY_WEIGHT[priority] ?? 99;
|
|
21
|
+
}
|
|
22
|
+
/** Earlier due first; missing/invalid dueDate sorts last. */
|
|
23
|
+
function dueValue(dueDate) {
|
|
24
|
+
if (!dueDate)
|
|
25
|
+
return Number.POSITIVE_INFINITY;
|
|
26
|
+
const t = new Date(dueDate).getTime();
|
|
27
|
+
return Number.isNaN(t) ? Number.POSITIVE_INFINITY : t;
|
|
28
|
+
}
|
|
29
|
+
const IN_FLIGHT = new Set(['IN_PROGRESS', 'IN_REVIEW']);
|
|
30
|
+
/** in-flight (0) before TODO (1) before anything else (2). */
|
|
31
|
+
function statusRank(status) {
|
|
32
|
+
if (IN_FLIGHT.has(status))
|
|
33
|
+
return 0;
|
|
34
|
+
if (status === 'TODO')
|
|
35
|
+
return 1;
|
|
36
|
+
return 2;
|
|
37
|
+
}
|
|
38
|
+
function inActiveSprint(task, activeSprintIds) {
|
|
39
|
+
return task.sprintId !== null && activeSprintIds.has(task.sprintId);
|
|
40
|
+
}
|
|
41
|
+
function deriveReasons(task, activeSprintIds, now) {
|
|
42
|
+
const reasons = [task.priority];
|
|
43
|
+
if (inActiveSprint(task, activeSprintIds)) {
|
|
44
|
+
reasons.push('active sprint');
|
|
45
|
+
}
|
|
46
|
+
if (task.dueDate) {
|
|
47
|
+
const day = task.dueDate.slice(0, 10);
|
|
48
|
+
const due = new Date(task.dueDate).getTime();
|
|
49
|
+
const overdue = !Number.isNaN(due) && due < now.getTime();
|
|
50
|
+
reasons.push(overdue ? `due ${day} (overdue)` : `due ${day}`);
|
|
51
|
+
}
|
|
52
|
+
if (task.status === 'IN_PROGRESS')
|
|
53
|
+
reasons.push('in progress');
|
|
54
|
+
else if (task.status === 'IN_REVIEW')
|
|
55
|
+
reasons.push('in review');
|
|
56
|
+
return reasons;
|
|
57
|
+
}
|
|
58
|
+
function rankTasks(tasks, activeSprintIds, now) {
|
|
59
|
+
const sorted = [...tasks].sort((a, b) => {
|
|
60
|
+
const pw = priorityWeight(a.priority) - priorityWeight(b.priority);
|
|
61
|
+
if (pw !== 0)
|
|
62
|
+
return pw;
|
|
63
|
+
const sa = inActiveSprint(a, activeSprintIds) ? 0 : 1;
|
|
64
|
+
const sb = inActiveSprint(b, activeSprintIds) ? 0 : 1;
|
|
65
|
+
if (sa !== sb)
|
|
66
|
+
return sa - sb;
|
|
67
|
+
const dv = dueValue(a.dueDate) - dueValue(b.dueDate);
|
|
68
|
+
if (dv !== 0 && !Number.isNaN(dv))
|
|
69
|
+
return dv;
|
|
70
|
+
const st = statusRank(a.status) - statusRank(b.status);
|
|
71
|
+
if (st !== 0)
|
|
72
|
+
return st;
|
|
73
|
+
return new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime();
|
|
74
|
+
});
|
|
75
|
+
return sorted.map(task => ({
|
|
76
|
+
task,
|
|
77
|
+
identifier: `${task.teamIdentifier}-${task.number}`,
|
|
78
|
+
reasons: deriveReasons(task, activeSprintIds, now),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
@@ -86,8 +86,7 @@ async function resolveTeamId(base, token, ref) {
|
|
|
86
86
|
throw new Error('workspace has multiple teams; pass --team <slug>.');
|
|
87
87
|
}
|
|
88
88
|
const needle = ref.trim().toLowerCase();
|
|
89
|
-
const match = teams.find(t => t.name.toLowerCase() === needle ||
|
|
90
|
-
t.identifier.toLowerCase() === needle);
|
|
89
|
+
const match = teams.find(t => t.name.toLowerCase() === needle || t.identifier.toLowerCase() === needle);
|
|
91
90
|
if (!match) {
|
|
92
91
|
throw new Error(`team "${ref}" not found. Try \`lumo team list\`.`);
|
|
93
92
|
}
|
|
@@ -160,11 +159,23 @@ async function resolveMilestoneId(base, token, identifier, projectRef) {
|
|
|
160
159
|
return { id: identifier, name: '', projectId: '' };
|
|
161
160
|
}
|
|
162
161
|
const projectId = await resolveProjectId(base, token, projectRef);
|
|
163
|
-
const { milestones } = await fetchJson(base, token, `/api/projects/${projectId}/milestones`);
|
|
162
|
+
const { milestones } = await fetchJson(base, token, `/api/projects/${projectId}/milestones?filter=all`);
|
|
163
|
+
// Exact id match first (milestone ids are cuids, unique).
|
|
164
|
+
const byId = milestones.find(m => m.id === identifier);
|
|
165
|
+
if (byId) {
|
|
166
|
+
return { id: byId.id, name: byId.name, projectId };
|
|
167
|
+
}
|
|
168
|
+
// Then by name (case-insensitive). Names are NOT unique within a project
|
|
169
|
+
// (only slug is), so reject an ambiguous name rather than silently picking
|
|
170
|
+
// the first match.
|
|
164
171
|
const needle = identifier.trim().toLowerCase();
|
|
165
|
-
const
|
|
166
|
-
if (
|
|
172
|
+
const hits = milestones.filter(m => m.name.toLowerCase() === needle);
|
|
173
|
+
if (hits.length === 0) {
|
|
167
174
|
throw new Error(`no milestone matches "${identifier}" in this project. Try \`lumo milestone list\`.`);
|
|
168
175
|
}
|
|
169
|
-
|
|
176
|
+
if (hits.length > 1) {
|
|
177
|
+
const ids = hits.map(h => h.id).join(', ');
|
|
178
|
+
throw new Error(`ambiguous milestone name "${identifier}" matches ${hits.length} milestones; re-run with the id: ${ids}`);
|
|
179
|
+
}
|
|
180
|
+
return { id: hits[0].id, name: hits[0].name, projectId };
|
|
170
181
|
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.fetchMemoryDraft = fetchMemoryDraft;
|
|
4
|
+
exports.applyMemoryReview = applyMemoryReview;
|
|
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 memory-review draft for the session. Throws on transport / non-200. */
|
|
10
|
+
async function fetchMemoryDraft(creds, sessionId) {
|
|
11
|
+
const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/session-memories`;
|
|
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(`memory draft fetch failed (HTTP ${res.status})`);
|
|
19
|
+
return (await res.json());
|
|
20
|
+
}
|
|
21
|
+
/** POST the review (deletes + promotes + watermark). Throws server message on non-2xx. */
|
|
22
|
+
async function applyMemoryReview(creds, sessionId, payload) {
|
|
23
|
+
const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/memory-review`;
|
|
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.ok) {
|
|
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 ?? `memory review failed (HTTP ${res.status})`);
|
|
45
|
+
}
|
|
46
|
+
return (await res.json());
|
|
47
|
+
}
|