@lumoai/cli 1.25.1 → 1.27.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 +9 -7
- package/assets/skill/references/artifacts-figma.md +1 -1
- package/assets/skill/references/criteria.md +10 -9
- package/assets/skill/references/docs.md +43 -10
- package/assets/skill/references/memory.md +18 -6
- package/assets/skill/references/milestones.md +6 -6
- package/assets/skill/references/sessions.md +30 -30
- package/assets/skill/references/sprints.md +6 -6
- package/assets/skill/references/task-context.md +5 -5
- package/assets/skill/references/tasks.md +15 -15
- package/assets/skill/references/verify.md +3 -3
- package/dist/cli/src/commands/doc-diff.js +82 -0
- package/dist/cli/src/commands/doc-show.js +19 -2
- package/dist/cli/src/commands/milestone-show.js +1 -1
- package/dist/cli/src/commands/next.js +1 -1
- package/dist/cli/src/commands/session-attach.js +5 -4
- package/dist/cli/src/commands/task-criteria-set.js +1 -1
- package/dist/cli/src/commands/task-deps.js +12 -12
- package/dist/cli/src/commands/verify.js +1 -1
- package/dist/cli/src/commands/wrap/blocked-prompt-section.js +9 -9
- package/dist/cli/src/commands/wrap/fragment-usage-section.js +6 -6
- package/dist/cli/src/commands/wrap/memory-review-section.js +6 -6
- package/dist/cli/src/commands/wrap/progress-comment-section.js +11 -11
- package/dist/cli/src/index.js +10 -3
- package/dist/cli/src/lib/hook-runner.js +9 -9
- package/dist/cli/src/lib/unified-diff.js +154 -0
- package/dist/cli/src/lib/wrap-panel.js +1 -1
- package/package.json +1 -1
|
@@ -199,7 +199,7 @@ Top 3 recommended tasks (of 12 open):
|
|
|
199
199
|
Next: lumo session attach LUM-42 && lumo task context LUM-42
|
|
200
200
|
```
|
|
201
201
|
|
|
202
|
-
When to suggest: the user asks "what should I work on", "what's next", "
|
|
202
|
+
When to suggest: the user asks "what should I work on", "what's next", "recommend my next task" (in any language), "pick my next task", or starts a session without a task in mind. After they choose, run `session attach` + `task context` for the picked task.
|
|
203
203
|
|
|
204
204
|
### `lumo task show <identifier>` — print one task's detail
|
|
205
205
|
|
|
@@ -227,7 +227,7 @@ The CLI does not support @-mention chip syntax. If the user wants to ping someon
|
|
|
227
227
|
|
|
228
228
|
### `lumo task deps list <LUM-N>` — show all dependency edges
|
|
229
229
|
|
|
230
|
-
Prints the task's dependency edges grouped into three sections: **CONFIRMED**, **SUGGESTED(
|
|
230
|
+
Prints the task's dependency edges grouped into three sections: **CONFIRMED**, **SUGGESTED (pending confirmation)**, and **DISMISSED**. Each row includes a short 8-character edge id in square brackets, the direction (`blocked by` / `blocks`), the other task's identifier and title, the other task's current status, the source (`MANUAL` or `DETECTED`), and inline evidence for detected edges.
|
|
231
231
|
|
|
232
232
|
```bash
|
|
233
233
|
lumo task deps list LUM-42
|
|
@@ -239,29 +239,29 @@ Example output:
|
|
|
239
239
|
Dependencies for LUM-42 (3)
|
|
240
240
|
|
|
241
241
|
CONFIRMED
|
|
242
|
-
[a1b2c3d4] blocked by LUM-9
|
|
242
|
+
[a1b2c3d4] blocked by LUM-9 "Fix auth token expiry" IN_PROGRESS · MANUAL
|
|
243
243
|
|
|
244
|
-
SUGGESTED(
|
|
245
|
-
[e5f6a7b8] blocks LUM-55
|
|
246
|
-
|
|
247
|
-
[c9d0e1f2] blocked by LUM-38
|
|
248
|
-
|
|
244
|
+
SUGGESTED (pending confirmation)
|
|
245
|
+
[e5f6a7b8] blocks LUM-55 "Migrate DB schema" TODO · shared_files(4 shared files: src/db/schema.ts, src/db/migrate.ts, ...)
|
|
246
|
+
confirm: lumo task deps confirm LUM-42 e5f6a7b8 (add --reverse if direction is flipped; false positive: dismiss)
|
|
247
|
+
[c9d0e1f2] blocked by LUM-38 "Add OAuth scopes" IN_REVIEW · task_mention(description)
|
|
248
|
+
confirm: lumo task deps confirm LUM-42 c9d0e1f2 (add --reverse if direction is flipped; false positive: dismiss)
|
|
249
249
|
|
|
250
250
|
DISMISSED
|
|
251
|
-
[b3c4d5e6] blocks LUM-12 ·
|
|
251
|
+
[b3c4d5e6] blocks LUM-12 · dismissed
|
|
252
252
|
```
|
|
253
253
|
|
|
254
|
-
CONFIRMED and SUGGESTED rows show the other task's identifier, title, current status, and source/evidence. DISMISSED rows render as `[shortId] <direction> <identifier> ·
|
|
254
|
+
CONFIRMED and SUGGESTED rows show the other task's identifier, title, current status, and source/evidence. DISMISSED rows render as `[shortId] <direction> <identifier> · dismissed` only — no title, status, or source.
|
|
255
255
|
|
|
256
256
|
When there are no edges at all the output is:
|
|
257
257
|
|
|
258
258
|
```
|
|
259
|
-
Dependencies for LUM-42:
|
|
259
|
+
Dependencies for LUM-42: no dependency edges.
|
|
260
260
|
```
|
|
261
261
|
|
|
262
262
|
**Evidence fields by detection signal:**
|
|
263
263
|
|
|
264
|
-
- `shared_files` — `shared_files(N
|
|
264
|
+
- `shared_files` — `shared_files(N shared files: path1, path2, …)` — number of shared write-touched files in the 14-day window, plus up to 5 sample paths.
|
|
265
265
|
- `task_mention` — `task_mention(description)` or `task_mention(comment)` — the surface where the mention appeared.
|
|
266
266
|
|
|
267
267
|
CONFIRMED rows also show `source`: `MANUAL` (user-declared via `deps add`) or `DETECTED` (auto-found then confirmed via `deps confirm`).
|
|
@@ -314,7 +314,7 @@ lumo task deps dismiss LUM-42 e5f6a7b8
|
|
|
314
314
|
lumo task deps dismiss LUM-42 LUM-38
|
|
315
315
|
```
|
|
316
316
|
|
|
317
|
-
Output: `Dismissed: [e5f6a7b8] LUM-38
|
|
317
|
+
Output: `Dismissed: [e5f6a7b8] LUM-38 "Add OAuth scopes" (won't be suggested again)`
|
|
318
318
|
|
|
319
319
|
Use `dismiss` for false positives. Use `rm` only when you want the pair to be eligible for re-detection in the future (the detection service can re-suggest pairs with no existing row).
|
|
320
320
|
|
|
@@ -352,6 +352,6 @@ Output: `Removed [a1b2c3d4] from LUM-42`
|
|
|
352
352
|
|
|
353
353
|
- **After `session attach` output shows a blocker warning or candidate-count hint** → run `lumo task deps list <LUM-N>` to review the full edge list, then `confirm` or `dismiss` each SUGGESTED candidate.
|
|
354
354
|
- **User says "X needs to wait for Y" or "LUM-42 is blocked by LUM-9"** → run `lumo task deps add LUM-42 --blocked-by LUM-9`.
|
|
355
|
-
- **Agent sees a `## ⚠
|
|
356
|
-
- **Agent sees only a standalone hint line
|
|
355
|
+
- **Agent sees a `## ⚠ Dependency alerts` block (form A — live blockers) at session-start** → evaluate whether to wait for the blocker to merge before starting work; if the edge is stale or wrong, clean it with `deps rm` or `deps dismiss`.
|
|
356
|
+
- **Agent sees only a standalone hint line `Detected N candidate dependencies awaiting confirmation…` (form B — no live blockers)** → no immediate blocker, but run `lumo task deps list <LUM-N>` to review and confirm/dismiss SUGGESTED candidates. See [sessions.md](sessions.md) for the full alert format.
|
|
357
357
|
- **User reports a false positive dependency suggestion** → `lumo task deps dismiss <LUM-N> <edge>` to permanently suppress it for this pair.
|
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
# lumo verify — machine verification loop
|
|
1
|
+
# lumo verify — machine verification loop
|
|
2
2
|
|
|
3
3
|
`lumo verify` is the machine half of the acceptance system (Acceptance v1,
|
|
4
4
|
LUM-343). It executes every **MACHINE** criterion's checkpointer in the local
|
|
5
5
|
repo, reports one structured PASS/FAIL verdict per criterion to the server,
|
|
6
6
|
and prints what to do next. The judge lives server-side: round numbering, the
|
|
7
7
|
3-round cap, escalation, and the IN_REVIEW transition all happen there
|
|
8
|
-
(
|
|
8
|
+
(execution on the client, adjudication on the server).
|
|
9
9
|
|
|
10
10
|
## The claim-done rule
|
|
11
11
|
|
|
@@ -71,7 +71,7 @@ the failures — re-running without changes burns a round and (at round 3)
|
|
|
71
71
|
pages a human. A FAIL round never changes task status; only an all-pass round
|
|
72
72
|
moves it (to IN_REVIEW, never further).
|
|
73
73
|
|
|
74
|
-
## lumo task status — the read half
|
|
74
|
+
## lumo task status — the read half (self-check entry point)
|
|
75
75
|
|
|
76
76
|
`lumo task status [task] [--json]` is the read-only counterpart of the loop
|
|
77
77
|
(LUM-344): pure read, milliseconds, no LLM, never writes — running it costs
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.docDiff = docDiff;
|
|
4
|
+
const config_1 = require("../lib/config");
|
|
5
|
+
const api_1 = require("../lib/api");
|
|
6
|
+
const resolve_doc_id_1 = require("../lib/resolve-doc-id");
|
|
7
|
+
const sanitize_1 = require("../lib/sanitize");
|
|
8
|
+
const doc_input_1 = require("../lib/doc-input");
|
|
9
|
+
const path_guard_1 = require("../lib/path-guard");
|
|
10
|
+
const unified_diff_1 = require("../lib/unified-diff");
|
|
11
|
+
/**
|
|
12
|
+
* `lumo doc diff <doc> --file <local.md>` (LUM-408).
|
|
13
|
+
*
|
|
14
|
+
* Compares the server-side markdown source (Document.sourceMarkdown — the
|
|
15
|
+
* byte-identical last markdown upload) against a local file, so a
|
|
16
|
+
* remote/local split-brain is visible on demand instead of discovered from
|
|
17
|
+
* memory after a stale upload clobbers someone's edit.
|
|
18
|
+
*
|
|
19
|
+
* Exit codes: 0 = sources byte-identical, 1 = divergent (or error).
|
|
20
|
+
*/
|
|
21
|
+
async function docDiff(reference, opts) {
|
|
22
|
+
if (!reference) {
|
|
23
|
+
console.error('Error: missing <doc>. Usage: lumo doc diff <doc> --file <local.md>');
|
|
24
|
+
return 1;
|
|
25
|
+
}
|
|
26
|
+
if (!opts.file) {
|
|
27
|
+
console.error('Error: --file <local.md> is required for doc diff');
|
|
28
|
+
return 1;
|
|
29
|
+
}
|
|
30
|
+
const creds = (0, config_1.readCredentials)();
|
|
31
|
+
if (!creds) {
|
|
32
|
+
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
33
|
+
return 1;
|
|
34
|
+
}
|
|
35
|
+
const apiUrl = (0, api_1.resolveAuthedApiUrl)(creds.apiUrl);
|
|
36
|
+
// Same sandbox as doc create/update --file: project-local, non-sensitive.
|
|
37
|
+
const check = (0, path_guard_1.checkArtifactFilePath)(opts.file);
|
|
38
|
+
if (!check.ok) {
|
|
39
|
+
console.error(check.reason === 'unreadable'
|
|
40
|
+
? `Error: ${(0, doc_input_1.unreadableFileMessage)(opts.file)}`
|
|
41
|
+
: `Error: refusing to read ${opts.file} — ${check.detail}. ` +
|
|
42
|
+
`--file must be a non-sensitive path inside the project directory.`);
|
|
43
|
+
return 1;
|
|
44
|
+
}
|
|
45
|
+
const localText = await (0, doc_input_1.readFileUtf8)(check.resolved);
|
|
46
|
+
const id = await (0, resolve_doc_id_1.lookupDocId)(apiUrl, creds.token, reference);
|
|
47
|
+
if (!id) {
|
|
48
|
+
console.error(`Error: Document not found: ${reference}`);
|
|
49
|
+
return 1;
|
|
50
|
+
}
|
|
51
|
+
const res = await fetch(`${(0, api_1.trimTrailingSlash)(apiUrl)}/api/documents/${id}`, {
|
|
52
|
+
headers: { Authorization: `Bearer ${creds.token}` },
|
|
53
|
+
});
|
|
54
|
+
if (!res.ok) {
|
|
55
|
+
const text = await res.text();
|
|
56
|
+
console.error(`Error: ${res.status} ${res.statusText}: ${(0, sanitize_1.sanitizeField)(text)}`);
|
|
57
|
+
return 1;
|
|
58
|
+
}
|
|
59
|
+
const data = (await res.json());
|
|
60
|
+
const d = data.document;
|
|
61
|
+
if (!d) {
|
|
62
|
+
console.error('Error: server returned an empty document response');
|
|
63
|
+
return 1;
|
|
64
|
+
}
|
|
65
|
+
if (typeof d.sourceMarkdown !== 'string') {
|
|
66
|
+
console.error(`Error: ${d.id} has no stored markdown source to diff against (last edit ` +
|
|
67
|
+
`was HTML-direct or predates markdown source storage). Upload a markdown ` +
|
|
68
|
+
`base once (\`lumo doc update ${d.id} --file <base.md>\`) and diff from then on.`);
|
|
69
|
+
return 1;
|
|
70
|
+
}
|
|
71
|
+
if (d.sourceMarkdown === localText) {
|
|
72
|
+
console.log(`Clean: remote markdown source of ${d.id} matches ${opts.file} (${Buffer.byteLength(localText, 'utf8')} bytes)`);
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
const diff = (0, unified_diff_1.formatUnifiedDiff)(d.sourceMarkdown, localText, `remote/${d.id} (sourceMarkdown)`, `local/${opts.file}`);
|
|
76
|
+
// Byte-level divergence with identical line sequences (e.g. trailing
|
|
77
|
+
// newline only) still counts as divergent — say so explicitly.
|
|
78
|
+
console.log(diff !== ''
|
|
79
|
+
? (0, sanitize_1.sanitizeField)(diff)
|
|
80
|
+
: `Divergent: byte-level difference only (e.g. trailing newline) between remote ${d.id} and ${opts.file}`);
|
|
81
|
+
return 1;
|
|
82
|
+
}
|
|
@@ -27,9 +27,9 @@ function formatShowOutput(vm) {
|
|
|
27
27
|
const header = lines.join('\n');
|
|
28
28
|
return vm.bodyMarkdown ? `${header}\n\n${(0, sanitize_1.sanitizeField)(vm.bodyMarkdown)}` : header;
|
|
29
29
|
}
|
|
30
|
-
async function docShow(reference) {
|
|
30
|
+
async function docShow(reference, opts = {}) {
|
|
31
31
|
if (!reference) {
|
|
32
|
-
console.error('Error: missing <doc>. Usage: lumo doc show <doc>');
|
|
32
|
+
console.error('Error: missing <doc>. Usage: lumo doc show <doc> [--raw]');
|
|
33
33
|
return 1;
|
|
34
34
|
}
|
|
35
35
|
const creds = (0, config_1.readCredentials)();
|
|
@@ -58,6 +58,23 @@ async function docShow(reference) {
|
|
|
58
58
|
console.error('Error: server returned an empty document response');
|
|
59
59
|
return 1;
|
|
60
60
|
}
|
|
61
|
+
if (opts.raw) {
|
|
62
|
+
// Byte-identical markdown source of the last markdown upload (LUM-408).
|
|
63
|
+
// Written verbatim (no header, no sanitization, no added newline) so the
|
|
64
|
+
// output is a legal edit base: `doc show --raw > base.md` round-trips.
|
|
65
|
+
// No silent fallback to the lossy HTML→markdown reverse render — that
|
|
66
|
+
// fallback is exactly what flattened tables in LUM-349.
|
|
67
|
+
if (typeof d.sourceMarkdown !== 'string') {
|
|
68
|
+
console.error(`Error: ${d.id} has no stored markdown source (last edit was HTML-direct ` +
|
|
69
|
+
`or predates markdown source storage). --raw refuses to fall back to the ` +
|
|
70
|
+
`lossy HTML→markdown render. Rebuild a base instead: run \`lumo doc show ${d.id}\`, ` +
|
|
71
|
+
`reconstruct the markdown faithfully, then \`lumo doc update ${d.id} --file <rebuilt.md>\` ` +
|
|
72
|
+
`— from then on --raw works.`);
|
|
73
|
+
return 1;
|
|
74
|
+
}
|
|
75
|
+
process.stdout.write(d.sourceMarkdown);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
61
78
|
// Server returns `contentMarkdown` derived from the HTML body (LUM-83+).
|
|
62
79
|
// Fall back to parsing the raw content as legacy Tiptap JSON for docs
|
|
63
80
|
// written before the storage shape changed.
|
|
@@ -34,7 +34,7 @@ function sprintCoverageLines(coverage) {
|
|
|
34
34
|
return ` ${num} ${status} ${name} ${s.doneCount}/${s.taskCount} done`;
|
|
35
35
|
});
|
|
36
36
|
if (coverage.unsprinted > 0) {
|
|
37
|
-
const label = '
|
|
37
|
+
const label = 'Unscheduled'.padEnd(numW + 2 + statusW + 2 + nameW);
|
|
38
38
|
rows.push(` ${label} ${coverage.unsprinted} task${coverage.unsprinted === 1 ? '' : 's'}`);
|
|
39
39
|
}
|
|
40
40
|
return ['', 'Sprint coverage:', ...rows];
|
|
@@ -97,7 +97,7 @@ function formatNextOutput(ranked, totalOpen) {
|
|
|
97
97
|
lines.push('');
|
|
98
98
|
lines.push(`Next: lumo session attach ${first.identifier} && lumo task context ${first.identifier}`);
|
|
99
99
|
if (ranked.length > 1) {
|
|
100
|
-
lines.push('
|
|
100
|
+
lines.push('(or pick any other LUM-N from the list)');
|
|
101
101
|
}
|
|
102
102
|
return lines.join('\n');
|
|
103
103
|
}
|
|
@@ -20,7 +20,7 @@ const line_prompt_1 = require("../lib/line-prompt");
|
|
|
20
20
|
* longer silently clobbers `Session.taskId` (LUM-266): the server returns
|
|
21
21
|
* the current binding and we confirm before overwriting —
|
|
22
22
|
* - `--force` skips the prompt and overwrites directly;
|
|
23
|
-
* - on a TTY we ask
|
|
23
|
+
* - on a TTY we ask `Already bound to LUM-X. Rebind to LUM-Y? [y/N]`;
|
|
24
24
|
* - off a TTY (the usual agent case) we refuse and point at `--force`.
|
|
25
25
|
*/
|
|
26
26
|
async function sessionAttach(identifier, options = {}) {
|
|
@@ -95,9 +95,9 @@ async function sessionAttach(identifier, options = {}) {
|
|
|
95
95
|
'(or run `lumo session detach` first).');
|
|
96
96
|
return 0;
|
|
97
97
|
}
|
|
98
|
-
const answer = await (0, line_prompt_1.promptLine)(
|
|
98
|
+
const answer = await (0, line_prompt_1.promptLine)(`Already bound to ${body.currentTaskIdentifier}. Rebind to ${identifier}? [y/N] `);
|
|
99
99
|
if (!/^y(es)?$/i.test(answer)) {
|
|
100
|
-
console.log(
|
|
100
|
+
console.log(`Cancelled — still bound to ${body.currentTaskIdentifier}.`);
|
|
101
101
|
return 0;
|
|
102
102
|
}
|
|
103
103
|
const second = await bind(true);
|
|
@@ -112,7 +112,8 @@ async function sessionAttach(identifier, options = {}) {
|
|
|
112
112
|
}
|
|
113
113
|
console.log(`Attached session ${sessionId} to ${body.taskIdentifier} "${(0, sanitize_1.sanitizeField)(body.taskTitle)}"`);
|
|
114
114
|
console.log(`Re-tagged ${body.retaggedEventCount} previously-untagged event${body.retaggedEventCount === 1 ? '' : 's'} in this session.`);
|
|
115
|
-
//
|
|
115
|
+
// Warnings come before contract/memory: matches the hook injection order —
|
|
116
|
+
// short, actionable information first.
|
|
116
117
|
if (body.blockerWarningSection) {
|
|
117
118
|
console.log('');
|
|
118
119
|
console.log((0, sanitize_1.sanitizeField)(body.blockerWarningSection));
|
|
@@ -99,7 +99,7 @@ async function taskCriteriaSet(identifier, options) {
|
|
|
99
99
|
Authorization: `Bearer ${creds.token}`,
|
|
100
100
|
'Content-Type': 'application/json',
|
|
101
101
|
};
|
|
102
|
-
// Session
|
|
102
|
+
// Session provenance for HUMAN_EDIT transcriptions — the server records the
|
|
103
103
|
// revision (with this session id) as a task comment.
|
|
104
104
|
const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
105
105
|
if (sessionId)
|
|
@@ -10,7 +10,7 @@ exports.taskDepsRm = taskDepsRm;
|
|
|
10
10
|
const config_1 = require("../lib/config");
|
|
11
11
|
const api_1 = require("../lib/api");
|
|
12
12
|
const sanitize_1 = require("../lib/sanitize");
|
|
13
|
-
/**
|
|
13
|
+
/** Short id = first 8 chars; used for both display and selectors. */
|
|
14
14
|
const shortId = (id) => id.slice(0, 8);
|
|
15
15
|
/**
|
|
16
16
|
* Resolve a user-supplied edge selector against the task's edge list. Accepts
|
|
@@ -35,7 +35,7 @@ function resolveEdgeSelector(edges, selector) {
|
|
|
35
35
|
*/
|
|
36
36
|
function formatDepsList(identifier, edges) {
|
|
37
37
|
if (edges.length === 0)
|
|
38
|
-
return `Dependencies for ${identifier}:
|
|
38
|
+
return `Dependencies for ${identifier}: no dependency edges.`;
|
|
39
39
|
const lines = [
|
|
40
40
|
`Dependencies for ${identifier} (${edges.length})`,
|
|
41
41
|
'',
|
|
@@ -43,7 +43,7 @@ function formatDepsList(identifier, edges) {
|
|
|
43
43
|
const dirLabel = (e) => e.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks';
|
|
44
44
|
const evidence = (e) => {
|
|
45
45
|
if (e.reason === 'shared_files')
|
|
46
|
-
return ` · shared_files(${e.detail?.count ?? '?'}
|
|
46
|
+
return ` · shared_files(${e.detail?.count ?? '?'} shared files${e.detail?.sample?.length ? ': ' + e.detail.sample.join(', ') : ''})`;
|
|
47
47
|
if (e.reason === 'task_mention')
|
|
48
48
|
return ` · task_mention(${e.detail?.surface ?? ''})`;
|
|
49
49
|
return '';
|
|
@@ -60,14 +60,14 @@ function formatDepsList(identifier, edges) {
|
|
|
60
60
|
const suggested = edges.filter(e => e.status === 'SUGGESTED');
|
|
61
61
|
const dismissed = edges.filter(e => e.status === 'DISMISSED');
|
|
62
62
|
section('CONFIRMED', confirmed, e => [
|
|
63
|
-
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier}
|
|
63
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier} "${e.other.title}" ${e.other.status} · ${e.source}${evidence(e)}`,
|
|
64
64
|
]);
|
|
65
|
-
section('SUGGESTED(
|
|
66
|
-
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier}
|
|
67
|
-
`
|
|
65
|
+
section('SUGGESTED (pending confirmation)', suggested, e => [
|
|
66
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier} "${e.other.title}" ${e.other.status}${evidence(e)}`,
|
|
67
|
+
` confirm: lumo task deps confirm ${identifier} ${shortId(e.id)} (add --reverse if direction is flipped; false positive: dismiss)`,
|
|
68
68
|
]);
|
|
69
69
|
section('DISMISSED', dismissed, e => [
|
|
70
|
-
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier} ·
|
|
70
|
+
` [${shortId(e.id)}] ${dirLabel(e)} ${e.other.identifier} · dismissed`,
|
|
71
71
|
]);
|
|
72
72
|
return lines.join('\n').trimEnd();
|
|
73
73
|
}
|
|
@@ -160,11 +160,11 @@ async function fetchDeps(ctx, taskDbId) {
|
|
|
160
160
|
function printSelectorCandidates(edges, selector) {
|
|
161
161
|
console.error(`Error: no unique dependency edge matches "${selector}". Candidates:`);
|
|
162
162
|
if (edges.length === 0) {
|
|
163
|
-
console.error(' (
|
|
163
|
+
console.error(' (no dependency edges)');
|
|
164
164
|
return;
|
|
165
165
|
}
|
|
166
166
|
for (const e of edges) {
|
|
167
|
-
console.error((0, sanitize_1.sanitizeField)(` [${shortId(e.id)}] ${e.status} ${e.other.identifier}
|
|
167
|
+
console.error((0, sanitize_1.sanitizeField)(` [${shortId(e.id)}] ${e.status} ${e.other.identifier} "${e.other.title}"`));
|
|
168
168
|
}
|
|
169
169
|
}
|
|
170
170
|
/**
|
|
@@ -247,7 +247,7 @@ async function taskDepsConfirm(identifier, selector, opts) {
|
|
|
247
247
|
}, 'dependency confirm');
|
|
248
248
|
if (typeof res === 'number')
|
|
249
249
|
return res;
|
|
250
|
-
console.log((0, sanitize_1.sanitizeField)(`Confirmed: [${shortId(edge.id)}] ${task.identifier} ${edge.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks'} ${edge.other.identifier}
|
|
250
|
+
console.log((0, sanitize_1.sanitizeField)(`Confirmed: [${shortId(edge.id)}] ${task.identifier} ${edge.direction === 'BLOCKED_BY' ? 'blocked by' : 'blocks'} ${edge.other.identifier} "${edge.other.title}"${opts.reverse ? ' (reversed)' : ''}`));
|
|
251
251
|
}
|
|
252
252
|
/** `lumo task deps dismiss <LUM-N> <edge>` */
|
|
253
253
|
async function taskDepsDismiss(identifier, selector) {
|
|
@@ -262,7 +262,7 @@ async function taskDepsDismiss(identifier, selector) {
|
|
|
262
262
|
const res = await depsFetch(ctx, `/api/tasks/${encodeURIComponent(task.id)}/dependencies/${encodeURIComponent(edge.id)}`, { method: 'PATCH', body: JSON.stringify({ action: 'dismiss' }) }, 'dependency dismiss');
|
|
263
263
|
if (typeof res === 'number')
|
|
264
264
|
return res;
|
|
265
|
-
console.log((0, sanitize_1.sanitizeField)(`Dismissed: [${shortId(edge.id)}] ${edge.other.identifier}
|
|
265
|
+
console.log((0, sanitize_1.sanitizeField)(`Dismissed: [${shortId(edge.id)}] ${edge.other.identifier} "${edge.other.title}" (won't be suggested again)`));
|
|
266
266
|
}
|
|
267
267
|
/** `lumo task deps rm <LUM-N> <edge> --yes` */
|
|
268
268
|
async function taskDepsRm(identifier, selector, opts) {
|
|
@@ -14,7 +14,7 @@ function tail(s, max) {
|
|
|
14
14
|
return s.length > max ? `…${s.slice(-max)}` : s;
|
|
15
15
|
}
|
|
16
16
|
/**
|
|
17
|
-
* Execute one MACHINE checkpointer in the local repo (
|
|
17
|
+
* Execute one MACHINE checkpointer in the local repo (runs client-side — the
|
|
18
18
|
* server can't run repo tests) and fold the result into a structured verdict.
|
|
19
19
|
* Exit 0 = PASS with a `cmd:` evidence pointer; non-zero = CRITERION_UNMET;
|
|
20
20
|
* spawn failure or timeout = CHECK_EXECUTION_ERROR.
|
|
@@ -14,7 +14,7 @@ const failure_summary_api_1 = require("../../lib/failure-summary-api");
|
|
|
14
14
|
*/
|
|
15
15
|
class BlockedPromptSection {
|
|
16
16
|
deps;
|
|
17
|
-
title = '
|
|
17
|
+
title = 'Blocked check';
|
|
18
18
|
draft = null;
|
|
19
19
|
constructor(deps) {
|
|
20
20
|
this.deps = deps;
|
|
@@ -28,14 +28,14 @@ class BlockedPromptSection {
|
|
|
28
28
|
if (!draft || !draft.shouldPrompt || !draft.taskIdentifier)
|
|
29
29
|
return;
|
|
30
30
|
const top = draft.topFailure;
|
|
31
|
-
const where = top ? (0, sanitize_1.sanitizeField)(top.label) : '
|
|
31
|
+
const where = top ? (0, sanitize_1.sanitizeField)(top.label) : 'an operation';
|
|
32
32
|
const count = top ? top.count : 0;
|
|
33
|
-
process.stdout.write(
|
|
33
|
+
process.stdout.write(`This session looks repeatedly stuck on ${where} (${count} failures).\n`);
|
|
34
34
|
if (top?.lastErrorSummary) {
|
|
35
|
-
process.stdout.write(
|
|
35
|
+
process.stdout.write(`Last error: ${(0, sanitize_1.sanitizeField)(top.lastErrorSummary)}\n`);
|
|
36
36
|
}
|
|
37
37
|
if (opts.dryRun) {
|
|
38
|
-
process.stdout.write(`(dry-run
|
|
38
|
+
process.stdout.write(`(dry-run, no changes; confirming would tag ${draft.taskIdentifier} blocked)\n`);
|
|
39
39
|
return;
|
|
40
40
|
}
|
|
41
41
|
// Tagging the shared board is opt-in: it requires an explicit interactive
|
|
@@ -43,10 +43,10 @@ class BlockedPromptSection {
|
|
|
43
43
|
// does NOT auto-tag — silently flipping shared board state is exactly what
|
|
44
44
|
// LUM-153 set out to avoid. We surface the suggestion and move on.
|
|
45
45
|
if (opts.yes) {
|
|
46
|
-
process.stdout.write(`(--yes
|
|
46
|
+
process.stdout.write(`(--yes does not auto-tag; answer y interactively, or run \`lumo task update ${draft.taskIdentifier} --add-tag blocked\` manually)\n`);
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
const choice = (await (0, line_prompt_1.promptLine)(
|
|
49
|
+
const choice = (await (0, line_prompt_1.promptLine)(`Tag ${draft.taskIdentifier} as blocked? [y] tag [s] skip > `))
|
|
50
50
|
.trim()
|
|
51
51
|
.toLowerCase();
|
|
52
52
|
if (choice === 'y') {
|
|
@@ -54,11 +54,11 @@ class BlockedPromptSection {
|
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
// Empty / 's' / anything else → do nothing. Tagging is opt-in.
|
|
57
|
-
process.stdout.write('
|
|
57
|
+
process.stdout.write('Skipped — not tagged.\n');
|
|
58
58
|
}
|
|
59
59
|
async mark() {
|
|
60
60
|
const { taskIdentifier, tag } = await (0, failure_summary_api_1.markTaskBlocked)(this.deps.creds, this.deps.sessionId);
|
|
61
|
-
process.stdout.write(
|
|
61
|
+
process.stdout.write(`Tagged ${taskIdentifier} with ${tag}.\n`);
|
|
62
62
|
}
|
|
63
63
|
}
|
|
64
64
|
exports.BlockedPromptSection = BlockedPromptSection;
|
|
@@ -24,7 +24,7 @@ function parseUsedHandles(spec) {
|
|
|
24
24
|
*/
|
|
25
25
|
class FragmentUsageSection {
|
|
26
26
|
deps;
|
|
27
|
-
title = '
|
|
27
|
+
title = 'Fragment-usage vote';
|
|
28
28
|
draft = null;
|
|
29
29
|
constructor(deps) {
|
|
30
30
|
this.deps = deps;
|
|
@@ -38,19 +38,19 @@ class FragmentUsageSection {
|
|
|
38
38
|
if (!draft || draft.candidates.length === 0)
|
|
39
39
|
return;
|
|
40
40
|
if (draft.alreadyVoted) {
|
|
41
|
-
process.stdout.write('
|
|
41
|
+
process.stdout.write('This session already voted — skipping.\n');
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
process.stdout.write('
|
|
44
|
+
process.stdout.write('Context fragments injected in this session:\n');
|
|
45
45
|
draft.candidates.forEach((c, i) => {
|
|
46
46
|
process.stdout.write(` [${i + 1}] ${(0, sanitize_1.sanitizeField)(c.label)}\n`);
|
|
47
47
|
});
|
|
48
48
|
if (opts.dryRun) {
|
|
49
|
-
process.stdout.write('(dry-run
|
|
49
|
+
process.stdout.write('(dry-run, no changes)\n');
|
|
50
50
|
return;
|
|
51
51
|
}
|
|
52
52
|
if (this.deps.used === undefined) {
|
|
53
|
-
process.stdout.write('
|
|
53
|
+
process.stdout.write('Use `lumo session wrap --used <indices>` (or `--used none`) to record which fragments you actually used.\n');
|
|
54
54
|
return;
|
|
55
55
|
}
|
|
56
56
|
const idx = parseUsedHandles(this.deps.used);
|
|
@@ -60,7 +60,7 @@ class FragmentUsageSection {
|
|
|
60
60
|
fragmentId: draft.candidates[i].fragmentId,
|
|
61
61
|
}));
|
|
62
62
|
const { used, unused } = await (0, fragment_usage_api_1.applyFragmentUsage)(this.deps.creds, this.deps.sessionId, { usedRefs });
|
|
63
|
-
process.stdout.write(
|
|
63
|
+
process.stdout.write(`Recorded: ${used} used, ${unused} unused.\n`);
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
66
|
exports.FragmentUsageSection = FragmentUsageSection;
|
|
@@ -32,7 +32,7 @@ function parseReviewInstruction(line) {
|
|
|
32
32
|
*/
|
|
33
33
|
class MemoryReviewSection {
|
|
34
34
|
deps;
|
|
35
|
-
title = '
|
|
35
|
+
title = 'Memory review';
|
|
36
36
|
draft = null;
|
|
37
37
|
constructor(deps) {
|
|
38
38
|
this.deps = deps;
|
|
@@ -45,19 +45,19 @@ class MemoryReviewSection {
|
|
|
45
45
|
const draft = this.draft;
|
|
46
46
|
if (!draft || !draft.watermark || draft.memories.length === 0)
|
|
47
47
|
return;
|
|
48
|
-
process.stdout.write(
|
|
48
|
+
process.stdout.write(`This session recorded ${draft.memories.length} new memories:\n`);
|
|
49
49
|
process.stdout.write(`${(0, sanitize_1.sanitizeField)((0, memory_content_1.formatMemoryReviewList)(draft.memories))}\n`);
|
|
50
50
|
if (opts.dryRun) {
|
|
51
|
-
process.stdout.write('(dry-run
|
|
51
|
+
process.stdout.write('(dry-run, no changes)\n');
|
|
52
52
|
return;
|
|
53
53
|
}
|
|
54
54
|
if (opts.yes) {
|
|
55
55
|
await this.apply(draft.watermark, [], []);
|
|
56
56
|
return;
|
|
57
57
|
}
|
|
58
|
-
const line = (await (0, line_prompt_1.promptLine)('[
|
|
58
|
+
const line = (await (0, line_prompt_1.promptLine)('[Enter] keep all [d 1,3] delete [p 2] promote to project [s] skip > ')).trim();
|
|
59
59
|
if (line.toLowerCase() === 's') {
|
|
60
|
-
process.stdout.write('
|
|
60
|
+
process.stdout.write('Section skipped.\n');
|
|
61
61
|
return;
|
|
62
62
|
}
|
|
63
63
|
if (line === '') {
|
|
@@ -75,7 +75,7 @@ class MemoryReviewSection {
|
|
|
75
75
|
}
|
|
76
76
|
async apply(watermark, deleteIds, promoteIds) {
|
|
77
77
|
const { deleted, promoted } = await (0, session_memory_api_1.applyMemoryReview)(this.deps.creds, this.deps.sessionId, { watermark, deleteIds, promoteIds });
|
|
78
|
-
process.stdout.write(
|
|
78
|
+
process.stdout.write(`Deleted ${deleted}, promoted ${promoted} to project scope.\n`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
exports.MemoryReviewSection = MemoryReviewSection;
|
|
@@ -6,7 +6,7 @@ const sanitize_1 = require("../../lib/sanitize");
|
|
|
6
6
|
const line_prompt_1 = require("../../lib/line-prompt");
|
|
7
7
|
const editor_1 = require("../../lib/editor");
|
|
8
8
|
const progress_comment_api_1 = require("../../lib/progress-comment-api");
|
|
9
|
-
const HEADER = '
|
|
9
|
+
const HEADER = 'Session progress';
|
|
10
10
|
/** Join turn summaries into a bulleted progress comment body under a header. */
|
|
11
11
|
function formatProgressBody(summaries) {
|
|
12
12
|
return [HEADER, ...summaries.map(s => `- ${s}`)].join('\n');
|
|
@@ -18,7 +18,7 @@ function formatProgressBody(summaries) {
|
|
|
18
18
|
*/
|
|
19
19
|
class ProgressCommentSection {
|
|
20
20
|
deps;
|
|
21
|
-
title = '
|
|
21
|
+
title = 'Progress comment';
|
|
22
22
|
draft = null;
|
|
23
23
|
body = '';
|
|
24
24
|
constructor(deps) {
|
|
@@ -37,31 +37,31 @@ class ProgressCommentSection {
|
|
|
37
37
|
if (!draft || !draft.watermark)
|
|
38
38
|
return;
|
|
39
39
|
// Preview: sanitize the server free-text before it hits the terminal.
|
|
40
|
-
process.stdout.write(
|
|
40
|
+
process.stdout.write(`Will post to ${draft.taskIdentifier} "${(0, sanitize_1.sanitizeField)(draft.taskTitle ?? '')}":\n`);
|
|
41
41
|
process.stdout.write(`${(0, sanitize_1.sanitizeField)(this.body)}\n`);
|
|
42
42
|
if (opts.dryRun) {
|
|
43
|
-
process.stdout.write('(dry-run
|
|
43
|
+
process.stdout.write('(dry-run, not posted)\n');
|
|
44
44
|
return;
|
|
45
45
|
}
|
|
46
46
|
if (opts.yes) {
|
|
47
47
|
await this.post(draft.watermark, this.body);
|
|
48
48
|
return;
|
|
49
49
|
}
|
|
50
|
-
const choice = (await (0, line_prompt_1.promptLine)('[y]
|
|
50
|
+
const choice = (await (0, line_prompt_1.promptLine)('[y] post [e] edit [s] skip > ')).toLowerCase();
|
|
51
51
|
if (choice === 's' || choice === '') {
|
|
52
|
-
process.stdout.write('
|
|
52
|
+
process.stdout.write('Skipped.\n');
|
|
53
53
|
return;
|
|
54
54
|
}
|
|
55
55
|
if (choice === 'e') {
|
|
56
56
|
const edited = (await (0, editor_1.editInEditor)(this.body)).trim();
|
|
57
57
|
if (edited.length === 0) {
|
|
58
|
-
process.stdout.write('
|
|
58
|
+
process.stdout.write('Empty body — skipped.\n');
|
|
59
59
|
return;
|
|
60
60
|
}
|
|
61
61
|
process.stdout.write(`${(0, sanitize_1.sanitizeField)(edited)}\n`);
|
|
62
|
-
const confirm = (await (0, line_prompt_1.promptLine)('[y]
|
|
62
|
+
const confirm = (await (0, line_prompt_1.promptLine)('[y] post [s] skip > ')).toLowerCase();
|
|
63
63
|
if (confirm !== 'y') {
|
|
64
|
-
process.stdout.write('
|
|
64
|
+
process.stdout.write('Skipped.\n');
|
|
65
65
|
return;
|
|
66
66
|
}
|
|
67
67
|
await this.post(draft.watermark, edited);
|
|
@@ -71,11 +71,11 @@ class ProgressCommentSection {
|
|
|
71
71
|
await this.post(draft.watermark, this.body);
|
|
72
72
|
return;
|
|
73
73
|
}
|
|
74
|
-
process.stdout.write('
|
|
74
|
+
process.stdout.write('Unrecognized choice — skipped.\n');
|
|
75
75
|
}
|
|
76
76
|
async post(watermark, body) {
|
|
77
77
|
const { commentId } = await (0, progress_comment_api_1.postProgressComment)(this.deps.creds, this.deps.sessionId, { body, watermark });
|
|
78
|
-
process.stdout.write(
|
|
78
|
+
process.stdout.write(`Posted progress comment (comment ${commentId})\n`);
|
|
79
79
|
}
|
|
80
80
|
}
|
|
81
81
|
exports.ProgressCommentSection = ProgressCommentSection;
|
package/dist/cli/src/index.js
CHANGED
|
@@ -107,6 +107,7 @@ const doc_import_gdoc_1 = require("./commands/doc-import-gdoc");
|
|
|
107
107
|
const doc_sync_1 = require("./commands/doc-sync");
|
|
108
108
|
const doc_update_1 = require("./commands/doc-update");
|
|
109
109
|
const doc_show_1 = require("./commands/doc-show");
|
|
110
|
+
const doc_diff_1 = require("./commands/doc-diff");
|
|
110
111
|
const doc_list_1 = require("./commands/doc-list");
|
|
111
112
|
const doc_delete_1 = require("./commands/doc-delete");
|
|
112
113
|
const doc_bind_1 = require("./commands/doc-bind");
|
|
@@ -396,7 +397,7 @@ taskCriteria
|
|
|
396
397
|
.command('set <task>')
|
|
397
398
|
.description('Submit the whole acceptance contract from a JSON file. Default = initial agent draft (locked once submitted); --human records a HUMAN_EDIT revision (desired final list; items with "id" keep/update, missing ones are deleted).')
|
|
398
399
|
.requiredOption('--file <path>', 'JSON array of criteria: [{"statement","verifierType":"MACHINE"|"HUMAN","checkpointer?","evidenceRequired?","id?"}]')
|
|
399
|
-
.option('--human', 'Record a human contract revision (HUMAN_EDIT) transcribed from the conversation, with session
|
|
400
|
+
.option('--human', 'Record a human contract revision (HUMAN_EDIT) transcribed from the conversation, with session provenance')
|
|
400
401
|
.option('--cause <tag>', 'Why the contract drifted (with --human): NEW_INFO | SCOPE_CHANGE | DRAFT_BLIND_SPOT | GRANULARITY | OTHER')
|
|
401
402
|
.action(wrap((taskId, options) => (0, task_criteria_set_1.taskCriteriaSet)(taskId, options)));
|
|
402
403
|
taskCriteria
|
|
@@ -665,8 +666,14 @@ doc
|
|
|
665
666
|
.action(wrap((reference, opts) => (0, doc_update_1.docUpdate)(reference, opts)));
|
|
666
667
|
doc
|
|
667
668
|
.command('show <doc>')
|
|
668
|
-
.description('Show document header and body (doc = title or cuid)')
|
|
669
|
-
.
|
|
669
|
+
.description('Show document header and body (doc = title or cuid). --raw prints the byte-identical markdown source of the last markdown upload (a legal edit base); it errors when no markdown source is stored instead of falling back to the lossy HTML→markdown render.')
|
|
670
|
+
.option('--raw', 'Print the stored markdown source verbatim (no header); errors if absent')
|
|
671
|
+
.action(wrap((reference, opts) => (0, doc_show_1.docShow)(reference, opts)));
|
|
672
|
+
doc
|
|
673
|
+
.command('diff <doc>')
|
|
674
|
+
.description('Compare the server-side markdown source against a local file. Exit 0 when byte-identical, 1 with a unified diff when divergent. Requires the doc to have a stored markdown source.')
|
|
675
|
+
.requiredOption('--file <path>', 'Local markdown file to compare against')
|
|
676
|
+
.action(wrap((reference, opts) => (0, doc_diff_1.docDiff)(reference, opts)));
|
|
670
677
|
doc
|
|
671
678
|
.command('list')
|
|
672
679
|
.description('List documents visible to the current user')
|