@lumoai/cli 1.11.0 → 1.17.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 (30) hide show
  1. package/README.md +13 -13
  2. package/assets/skill/SKILL.md +111 -0
  3. package/assets/skill/references/artifacts-figma.md +124 -0
  4. package/assets/skill/references/docs.md +306 -0
  5. package/assets/skill/references/memory.md +69 -0
  6. package/assets/skill/references/milestones.md +244 -0
  7. package/assets/skill/references/onboarding.md +102 -0
  8. package/assets/skill/references/sessions.md +142 -0
  9. package/assets/skill/references/sprints.md +157 -0
  10. package/assets/skill/references/task-context.md +109 -0
  11. package/assets/skill/references/tasks.md +205 -0
  12. package/dist/cli/src/commands/milestone-archive.js +60 -0
  13. package/dist/cli/src/commands/milestone-list.js +24 -5
  14. package/dist/cli/src/commands/milestone-move.js +84 -0
  15. package/dist/cli/src/commands/milestone-reorder.js +72 -0
  16. package/dist/cli/src/commands/milestone-show.js +35 -0
  17. package/dist/cli/src/commands/milestone-unarchive.js +60 -0
  18. package/dist/cli/src/commands/session-wrap.js +5 -2
  19. package/dist/cli/src/commands/setup.js +50 -22
  20. package/dist/cli/src/commands/sprint-show.js +32 -3
  21. package/dist/cli/src/commands/task-context.js +4 -0
  22. package/dist/cli/src/commands/task-update.js +12 -4
  23. package/dist/cli/src/commands/wrap/blocked-prompt-section.js +64 -0
  24. package/dist/cli/src/index.js +31 -2
  25. package/dist/cli/src/lib/failure-summary-api.js +43 -0
  26. package/dist/cli/src/lib/hook-runner.js +1 -0
  27. package/dist/cli/src/lib/milestone-reorder.js +92 -0
  28. package/dist/cli/src/lib/resolve.js +17 -6
  29. package/package.json +1 -1
  30. package/assets/skill.md +0 -1333
@@ -0,0 +1,43 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.fetchFailureSummary = fetchFailureSummary;
4
+ exports.markTaskBlocked = markTaskBlocked;
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 blocked-tag prompt draft for the session. Throws on transport / non-200. */
10
+ async function fetchFailureSummary(creds, sessionId) {
11
+ const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/failure-summary`;
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(`failure summary fetch failed (HTTP ${res.status})`);
19
+ return (await res.json());
20
+ }
21
+ /** POST to attach the `blocked` tag to the session's bound task. Throws the server message on non-201. */
22
+ async function markTaskBlocked(creds, sessionId) {
23
+ const url = `${base(creds)}/api/sessions/${encodeURIComponent(sessionId)}/mark-blocked`;
24
+ const res = await fetch(url, {
25
+ method: 'POST',
26
+ headers: { Authorization: `Bearer ${creds.token}` },
27
+ });
28
+ if (res.status === 401)
29
+ throw new Error('API key invalid or revoked. Run `lumo auth login`.');
30
+ if (res.status !== 201) {
31
+ let serverMsg = null;
32
+ try {
33
+ const errBody = (await res.json());
34
+ if (typeof errBody.error === 'string')
35
+ serverMsg = errBody.error;
36
+ }
37
+ catch {
38
+ // body wasn't JSON
39
+ }
40
+ throw new Error(serverMsg ?? `mark blocked failed (HTTP ${res.status})`);
41
+ }
42
+ return (await res.json());
43
+ }
@@ -115,6 +115,7 @@ function formatHookStdoutLines(path, responseBody, now = new Date()) {
115
115
  body.memorySection,
116
116
  body.linkedResourcesSection,
117
117
  body.reviewTodosSection,
118
+ body.layer2ReviewSection,
118
119
  ]);
119
120
  if (envelope)
120
121
  lines.push(envelope);
@@ -0,0 +1,92 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.resolveOrderedIds = resolveOrderedIds;
4
+ exports.computeMoveOrder = computeMoveOrder;
5
+ function findRef(ref, milestones) {
6
+ // Try by id first (exact match — milestone ids are cuids, unique).
7
+ const byId = milestones.find(m => m.id === ref);
8
+ if (byId)
9
+ return { kind: 'found', ref: byId };
10
+ // Then by name (case-insensitive). Names are NOT unique within a project
11
+ // (only slug is), so a name can match more than one milestone.
12
+ const needle = ref.trim().toLowerCase();
13
+ const hits = milestones.filter(m => m.name.toLowerCase() === needle);
14
+ if (hits.length === 0)
15
+ return { kind: 'not-found' };
16
+ if (hits.length === 1)
17
+ return { kind: 'found', ref: hits[0] };
18
+ return { kind: 'ambiguous', candidates: hits };
19
+ }
20
+ function ambiguousError(ref, candidates) {
21
+ const ids = candidates.map(c => c.id).join(', ');
22
+ return `ambiguous milestone name "${ref}" matches ${candidates.length} milestones; re-run with the id: ${ids}`;
23
+ }
24
+ /** Milestones in current display order (sortOrder asc, stable). */
25
+ function sorted(milestones) {
26
+ return [...milestones].sort((a, b) => a.sortOrder - b.sortOrder);
27
+ }
28
+ /**
29
+ * Resolve a full list of milestone refs (name or UUID) to ids in the given
30
+ * order. The list must name EVERY milestone in the project exactly once.
31
+ */
32
+ function resolveOrderedIds(refs, milestones) {
33
+ const ids = [];
34
+ const seen = new Set();
35
+ for (const ref of refs) {
36
+ const found = findRef(ref, milestones);
37
+ if (found.kind === 'not-found') {
38
+ return { ok: false, error: `unknown milestone: "${ref}"` };
39
+ }
40
+ if (found.kind === 'ambiguous') {
41
+ return { ok: false, error: ambiguousError(ref, found.candidates) };
42
+ }
43
+ const match = found.ref;
44
+ if (seen.has(match.id)) {
45
+ return { ok: false, error: `duplicate milestone: "${ref}"` };
46
+ }
47
+ seen.add(match.id);
48
+ ids.push(match.id);
49
+ }
50
+ if (ids.length !== milestones.length) {
51
+ const missing = milestones
52
+ .filter(m => !seen.has(m.id))
53
+ .map(m => m.name);
54
+ return {
55
+ ok: false,
56
+ error: `incomplete order: list every milestone. Missing: ${missing.join(', ')}`,
57
+ };
58
+ }
59
+ return { ok: true, orderedIds: ids };
60
+ }
61
+ /**
62
+ * Compute the full orderedIds after moving `moveRef` immediately before/after
63
+ * `targetRef` in the project's current order.
64
+ */
65
+ function computeMoveOrder(moveRef, targetRef, position, milestones) {
66
+ const moveFound = findRef(moveRef, milestones);
67
+ if (moveFound.kind === 'not-found') {
68
+ return { ok: false, error: `unknown milestone: "${moveRef}"` };
69
+ }
70
+ if (moveFound.kind === 'ambiguous') {
71
+ return { ok: false, error: ambiguousError(moveRef, moveFound.candidates) };
72
+ }
73
+ const targetFound = findRef(targetRef, milestones);
74
+ if (targetFound.kind === 'not-found') {
75
+ return { ok: false, error: `unknown milestone: "${targetRef}"` };
76
+ }
77
+ if (targetFound.kind === 'ambiguous') {
78
+ return { ok: false, error: ambiguousError(targetRef, targetFound.candidates) };
79
+ }
80
+ const move = moveFound.ref;
81
+ const target = targetFound.ref;
82
+ if (move.id === target.id) {
83
+ return { ok: false, error: 'cannot move a milestone relative to itself' };
84
+ }
85
+ const order = sorted(milestones)
86
+ .map(m => m.id)
87
+ .filter(id => id !== move.id);
88
+ const targetIndex = order.indexOf(target.id);
89
+ const insertAt = position === 'before' ? targetIndex : targetIndex + 1;
90
+ order.splice(insertAt, 0, move.id);
91
+ return { ok: true, orderedIds: order };
92
+ }
@@ -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 match = milestones.find(m => m.name.toLowerCase() === needle);
166
- if (!match) {
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
- return { id: match.id, name: match.name, projectId };
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
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@lumoai/cli",
3
- "version": "1.11.0",
3
+ "version": "1.17.0",
4
4
  "description": "Lumo CLI — manage tasks and sessions from the terminal",
5
5
  "license": "MIT",
6
6
  "author": "cli@uselumo.ai",