@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.
- package/assets/skill/SKILL.md +15 -17
- package/assets/skill/references/artifacts-figma.md +4 -3
- package/assets/skill/references/confirmation.md +133 -0
- package/assets/skill/references/criteria.md +12 -21
- package/assets/skill/references/doc-editing.md +11 -9
- package/assets/skill/references/docs.md +4 -3
- 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 +5 -0
- package/assets/skill/references/sessions.md +4 -4
- package/assets/skill/references/sprints.md +18 -17
- package/assets/skill/references/task-deps.md +4 -3
- package/assets/skill/references/tasks.md +34 -2
- package/assets/skill/references/verify.md +71 -71
- package/assets/skill/references/worktree.md +13 -7
- package/dist/cli/src/commands/crossing-disposition.js +342 -0
- package/dist/cli/src/commands/crossing-explain.js +10 -21
- 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 +196 -111
- 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 +60 -48
- package/dist/cli/src/lib/blocked-error.js +183 -0
- package/dist/cli/src/lib/bound-task.js +32 -0
- package/dist/cli/src/lib/confirmation.js +119 -0
- package/dist/cli/src/lib/hook-runner.js +23 -11
- package/dist/cli/src/lib/open-crossings.js +6 -6
- package/dist/shared/src/referent-kind.js +31 -1
- package/dist/shared/src/security-scan.js +125 -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
|
@@ -68,8 +68,11 @@ function readStdin() {
|
|
|
68
68
|
*
|
|
69
69
|
* For 'pre-tool-use' the array contains:
|
|
70
70
|
* [0] (optional) a PreToolUse hookSpecificOutput JSON carrying the parallel-
|
|
71
|
-
* edit collision warning
|
|
72
|
-
*
|
|
71
|
+
* edit collision warning (LUM-150 step ③), the one-shot boundary-
|
|
72
|
+
* crossing reminder (LUM-542), and the one-shot PR security-scan
|
|
73
|
+
* reminder (LUM-737) as additionalContext, when the server returned any
|
|
74
|
+
* of `collisionWarning`, `crossingReminder`, `securityReminder`. Empty
|
|
75
|
+
* otherwise.
|
|
73
76
|
*
|
|
74
77
|
* The JSON lines conform to Claude Code's hookSpecificOutput envelope so the
|
|
75
78
|
* runtime injects additionalContext into the conversation automatically.
|
|
@@ -83,8 +86,9 @@ _now = new Date()) {
|
|
|
83
86
|
if (responseBody == null || typeof responseBody !== 'object')
|
|
84
87
|
return [];
|
|
85
88
|
const body = responseBody;
|
|
86
|
-
//
|
|
87
|
-
// reminder) share one
|
|
89
|
+
// Three independent PreToolUse signals (LUM-150 collision, LUM-542
|
|
90
|
+
// crossing reminder, LUM-737 security reminder) share one
|
|
91
|
+
// additionalContext block when more than one is present.
|
|
88
92
|
const parts = [];
|
|
89
93
|
if (typeof body.collisionWarning === 'string' &&
|
|
90
94
|
body.collisionWarning !== '')
|
|
@@ -92,6 +96,9 @@ _now = new Date()) {
|
|
|
92
96
|
if (typeof body.crossingReminder === 'string' &&
|
|
93
97
|
body.crossingReminder !== '')
|
|
94
98
|
parts.push(body.crossingReminder);
|
|
99
|
+
if (typeof body.securityReminder === 'string' &&
|
|
100
|
+
body.securityReminder !== '')
|
|
101
|
+
parts.push(body.securityReminder);
|
|
95
102
|
if (parts.length === 0)
|
|
96
103
|
return [];
|
|
97
104
|
return [
|
|
@@ -121,13 +128,14 @@ _now = new Date()) {
|
|
|
121
128
|
else if (tb && tb.bound === false) {
|
|
122
129
|
lines.push(unboundPromptLine(sessionId));
|
|
123
130
|
}
|
|
124
|
-
// Blocker warning + criteria + progress + memory +
|
|
125
|
-
// PR-review todos share one additionalContext block so
|
|
126
|
-
// single coherent context payload at session start.
|
|
127
|
-
// warning (LUM-172) slots in first so it stays
|
|
128
|
-
// session's work entirely.
|
|
129
|
-
//
|
|
130
|
-
//
|
|
131
|
+
// Blocker warning + security findings + criteria + progress + memory +
|
|
132
|
+
// linked resources + PR-review todos share one additionalContext block so
|
|
133
|
+
// Claude Code injects a single coherent context payload at session start.
|
|
134
|
+
// The dependency blocker warning (LUM-172) slots in first so it stays
|
|
135
|
+
// prominent — it can preempt the session's work entirely. The undispositioned
|
|
136
|
+
// security findings warning (LUM-737) follows next. Then the
|
|
137
|
+
// progressive-disclosure tiers (LUM-500): Tier-0 acceptance contract →
|
|
138
|
+
// Tier-1 prior-session progress → Tier-2 memory index → linked resources.
|
|
131
139
|
//
|
|
132
140
|
// LUM-500: the prior-session recovery is now the server-rendered Tier-1
|
|
133
141
|
// `progressSection`, which REPLACES the CLI-rendered recovery card so progress
|
|
@@ -137,6 +145,10 @@ _now = new Date()) {
|
|
|
137
145
|
// Blocker warning first: it can preempt the session's work entirely (wait
|
|
138
146
|
// for the blocker instead of starting) — LUM-172.
|
|
139
147
|
body.blockerWarningSection,
|
|
148
|
+
// LUM-737: undispositioned security findings — second, before the
|
|
149
|
+
// contract: it changes what "done" needs (fix + push, or a human
|
|
150
|
+
// disposition) but unlike a blocker does not preempt the session.
|
|
151
|
+
body.securityFindingsSection,
|
|
140
152
|
// Tier-0 acceptance contract: what the session's work is judged against
|
|
141
153
|
// (LUM-342).
|
|
142
154
|
body.criteriaSection,
|
|
@@ -28,9 +28,9 @@ function normalizeSeverity(s) {
|
|
|
28
28
|
* first, via the EXISTING LUM-435 read endpoint — `GET …/boundary-crossings`
|
|
29
29
|
* returns every crossing (open and dispositioned); we keep only the
|
|
30
30
|
* undispositioned ones (`disposition == null`). This is the **read/awareness**
|
|
31
|
-
* half of the acceptance loop: there is no new query and
|
|
32
|
-
*
|
|
33
|
-
*
|
|
31
|
+
* half of the acceptance loop: there is no new query and this helper cannot
|
|
32
|
+
* clear anything — a ruling goes through `lumo crossing disposition` (exit-4
|
|
33
|
+
* user approval, LUM-769) or the web panel.
|
|
34
34
|
*
|
|
35
35
|
* Fails *closed*, not open (LUM-480): any transport / non-ok HTTP / parse
|
|
36
36
|
* failure returns `{ status: 'error', reason }` so the caller can say "check
|
|
@@ -75,9 +75,9 @@ async function fetchOpenCrossings(apiUrl, token, taskIdentifier) {
|
|
|
75
75
|
return { status: 'ok', crossings };
|
|
76
76
|
}
|
|
77
77
|
/**
|
|
78
|
-
* The web deep link where a
|
|
79
|
-
*
|
|
80
|
-
*
|
|
78
|
+
* The web deep link where a human dispositions crossings in the panel — the
|
|
79
|
+
* counterpart of `lumo crossing disposition` (LUM-769); the awareness surfaces
|
|
80
|
+
* (task status, DONE_BLOCKED) point here. Built from the workspace slug +
|
|
81
81
|
* identifier alone (the `/my-tasks/<id>` route needs no project slug), so no
|
|
82
82
|
* extra fetch is required.
|
|
83
83
|
*/
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.REFERENT_KIND_DECLARABLE = void 0;
|
|
3
|
+
exports.TASK_REFERENT_KIND_DECLARABLE = exports.REFERENT_KIND_DECLARABLE = void 0;
|
|
4
4
|
exports.classifyCheckpointerGrounding = classifyCheckpointerGrounding;
|
|
5
5
|
exports.effectiveReferentKind = effectiveReferentKind;
|
|
6
|
+
exports.deriveReferentKind = deriveReferentKind;
|
|
6
7
|
/**
|
|
7
8
|
* referentKind (LUM-602): what a criterion anchors — the only dimension that
|
|
8
9
|
* determines independence. Dependency-light (no @prisma/client) so both the
|
|
@@ -14,6 +15,15 @@ exports.REFERENT_KIND_DECLARABLE = [
|
|
|
14
15
|
'AGENT_CONSTRUCTED_STATE',
|
|
15
16
|
'PENDING_OUTCOME',
|
|
16
17
|
];
|
|
18
|
+
/**
|
|
19
|
+
* LUM-733: the kinds a TASK criterion may still carry. PENDING_OUTCOME is no
|
|
20
|
+
* longer accepted on task criteria (it had no check and no reader); it remains
|
|
21
|
+
* declarable on milestone exit criteria only.
|
|
22
|
+
*/
|
|
23
|
+
exports.TASK_REFERENT_KIND_DECLARABLE = [
|
|
24
|
+
'EXTERNAL_FACT',
|
|
25
|
+
'AGENT_CONSTRUCTED_STATE',
|
|
26
|
+
];
|
|
17
27
|
/** Tools whose output the agent cannot author — external facts. */
|
|
18
28
|
const EXTERNAL_TOOLS = [
|
|
19
29
|
'git',
|
|
@@ -319,3 +329,23 @@ function effectiveReferentKind(args) {
|
|
|
319
329
|
}
|
|
320
330
|
return declared;
|
|
321
331
|
}
|
|
332
|
+
/**
|
|
333
|
+
* LUM-733: the referent kind is DERIVED by the system, not declared by the
|
|
334
|
+
* agent. A MACHINE criterion's kind follows its checkpointer — EXTERNAL_FACT
|
|
335
|
+
* only when the check actually consumes an external tool (git/gh/curl/psql/…),
|
|
336
|
+
* AGENT_CONSTRUCTED_STATE otherwise — so any declaration on a MACHINE criterion
|
|
337
|
+
* is ignored (there is nothing to mis-declare and nothing to catch). A HUMAN
|
|
338
|
+
* criterion has no checkpointer to read, so an optional declaration is kept
|
|
339
|
+
* as-is; absent, it stays null (unclassified).
|
|
340
|
+
*/
|
|
341
|
+
function deriveReferentKind(args) {
|
|
342
|
+
const { verifierType, checkpointer, declared } = args;
|
|
343
|
+
if (verifierType === 'MACHINE') {
|
|
344
|
+
return classifyCheckpointerGrounding(checkpointer) === 'EXTERNAL'
|
|
345
|
+
? 'EXTERNAL_FACT'
|
|
346
|
+
: 'AGENT_CONSTRUCTED_STATE';
|
|
347
|
+
}
|
|
348
|
+
return declared === 'EXTERNAL_FACT' || declared === 'AGENT_CONSTRUCTED_STATE'
|
|
349
|
+
? declared
|
|
350
|
+
: null;
|
|
351
|
+
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SECRET_DOWNGRADE_LABELS = exports.SCAN_STAGE_STATE_LABELS = exports.SCAN_STAGE_LABELS = exports.SCAN_STAGE_STATES = exports.SCAN_STAGE_KEYS = exports.EXTERNAL_ERROR_PREFIX = void 0;
|
|
4
|
+
exports.externalFailureReason = externalFailureReason;
|
|
5
|
+
exports.scanStageLabel = scanStageLabel;
|
|
6
|
+
exports.scanStageStateLabel = scanStageStateLabel;
|
|
7
|
+
exports.secretDowngradeNote = secretDowngradeNote;
|
|
8
|
+
/**
|
|
9
|
+
* PR security scan — the one `PrSecurityScan.error` shape that may be shown
|
|
10
|
+
* as an external-scanner reason (spec P8). Shared by the web panel, the PR
|
|
11
|
+
* summary (both via `lib/security-scan/stages.ts`, which re-exports this)
|
|
12
|
+
* and the CLI (`cli/src/commands/task-status.ts`), so the three renderers
|
|
13
|
+
* cannot drift apart (LUM-756).
|
|
14
|
+
*
|
|
15
|
+
* The column has two writers: the ingest / managed-scan workflow, which
|
|
16
|
+
* scrubs its reason and stamps this prefix on it (the prefix is the proof of
|
|
17
|
+
* provenance), and stage A's own catch-all, which writes a RAW, unscrubbed
|
|
18
|
+
* `error.message` with no prefix. Renderers therefore check the prefix first
|
|
19
|
+
* and print only what follows it; unprefixed text is never published.
|
|
20
|
+
*/
|
|
21
|
+
exports.EXTERNAL_ERROR_PREFIX = 'external: ';
|
|
22
|
+
/**
|
|
23
|
+
* The scanner's reason, or null when there is none to show: a missing /
|
|
24
|
+
* empty error, an unprefixed (stage A raw) error, or a prefix with nothing
|
|
25
|
+
* after it.
|
|
26
|
+
*/
|
|
27
|
+
function externalFailureReason(error) {
|
|
28
|
+
if (!error || !error.startsWith(exports.EXTERNAL_ERROR_PREFIX))
|
|
29
|
+
return null;
|
|
30
|
+
const reason = error.slice(exports.EXTERNAL_ERROR_PREFIX.length);
|
|
31
|
+
return reason.length > 0 ? reason : null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* LUM-763 — the scan's five layers, as stored.
|
|
35
|
+
*
|
|
36
|
+
* These keys are a persisted contract, not a display choice: they sit in
|
|
37
|
+
* `PrSecurityScan.stages` on every historical row, travel over the
|
|
38
|
+
* task-status API, and are read by CLIs older than this change. They do not
|
|
39
|
+
* get renamed; only what a reader is shown does.
|
|
40
|
+
*/
|
|
41
|
+
exports.SCAN_STAGE_KEYS = [
|
|
42
|
+
'secrets',
|
|
43
|
+
'external',
|
|
44
|
+
'supplyChain',
|
|
45
|
+
'judge',
|
|
46
|
+
'hunt',
|
|
47
|
+
];
|
|
48
|
+
/** The states a layer can be in. `ScanStageState` in `lib/security-scan/types.ts` derives from this. */
|
|
49
|
+
exports.SCAN_STAGE_STATES = [
|
|
50
|
+
'RAN',
|
|
51
|
+
'FAILED',
|
|
52
|
+
'SKIPPED',
|
|
53
|
+
'NOT_CONFIGURED',
|
|
54
|
+
'PENDING',
|
|
55
|
+
'PARTIAL',
|
|
56
|
+
'SUPERSEDED',
|
|
57
|
+
];
|
|
58
|
+
/**
|
|
59
|
+
* What each layer is called where a person reads it — the PR comment and
|
|
60
|
+
* `lumo task status`, which share this map so the two cannot drift (the web
|
|
61
|
+
* panel renders the same wording through `securityScan.stage.*` in i18n).
|
|
62
|
+
*
|
|
63
|
+
* Named for what the layer checks, never for who runs it or which model it
|
|
64
|
+
* uses: `external` was accurate only while the scanners were the customer's,
|
|
65
|
+
* and stopped being true the moment Lumo started running them itself.
|
|
66
|
+
*/
|
|
67
|
+
exports.SCAN_STAGE_LABELS = {
|
|
68
|
+
secrets: 'Secrets',
|
|
69
|
+
external: 'Code scan',
|
|
70
|
+
supplyChain: 'Dependencies',
|
|
71
|
+
judge: 'AI review',
|
|
72
|
+
hunt: 'Exploit paths',
|
|
73
|
+
};
|
|
74
|
+
/**
|
|
75
|
+
* What each state is called. `RAN` deliberately reads as "checked", not as a
|
|
76
|
+
* tick or "clean": it means the layer completed, and a layer that completed
|
|
77
|
+
* may well have found something — the findings are counted on their own
|
|
78
|
+
* lines. Only the states that leave the scan incomplete carry a glyph, so
|
|
79
|
+
* attention is drawn to "do not trust this line" and never to "this is fine"
|
|
80
|
+
* (spec P9 — a security signal never claims safety it did not establish).
|
|
81
|
+
*/
|
|
82
|
+
exports.SCAN_STAGE_STATE_LABELS = {
|
|
83
|
+
RAN: 'checked',
|
|
84
|
+
FAILED: '✗ failed',
|
|
85
|
+
SKIPPED: 'skipped',
|
|
86
|
+
NOT_CONFIGURED: 'off',
|
|
87
|
+
PENDING: 'scanning',
|
|
88
|
+
PARTIAL: '⚠ incomplete',
|
|
89
|
+
SUPERSEDED: '⚠ superseded',
|
|
90
|
+
};
|
|
91
|
+
/** The layer's display name; an unrecognised key renders as itself rather than vanishing. */
|
|
92
|
+
function scanStageLabel(key) {
|
|
93
|
+
return exports.SCAN_STAGE_LABELS[key] ?? key;
|
|
94
|
+
}
|
|
95
|
+
/** The state's display name; an unrecognised state renders as itself. */
|
|
96
|
+
function scanStageStateLabel(state) {
|
|
97
|
+
return exports.SCAN_STAGE_STATE_LABELS[state] ?? state;
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* LUM-758 follow-up — why a deterministic secret hit was lowered to LOW.
|
|
101
|
+
*
|
|
102
|
+
* Downgrading is not suppression: the row is still reported, deliberately, so
|
|
103
|
+
* that a human decides. But a reported row with no stated cause reads as an
|
|
104
|
+
* unexplained credential, which is how a test fixture the scanner *already
|
|
105
|
+
* recognised* as a placeholder ends up looking like an open risk. These say
|
|
106
|
+
* which rule fired, in the same words on every surface.
|
|
107
|
+
*
|
|
108
|
+
* Phrased as a property of the value or the path, never as a verdict: the
|
|
109
|
+
* scanner knows the string looks like a placeholder, not that it is harmless.
|
|
110
|
+
*/
|
|
111
|
+
exports.SECRET_DOWNGRADE_LABELS = {
|
|
112
|
+
PLACEHOLDER_VALUE: 'placeholder value',
|
|
113
|
+
DOWNGRADED_PATH: 'test/fixture/doc path',
|
|
114
|
+
};
|
|
115
|
+
/**
|
|
116
|
+
* The parenthesised note appended to a rendered finding line, or `''` when the
|
|
117
|
+
* row carries no downgrade — an unrecognised reason renders as itself rather
|
|
118
|
+
* than vanishing, so a value added server-side is never silently dropped by an
|
|
119
|
+
* older CLI.
|
|
120
|
+
*/
|
|
121
|
+
function secretDowngradeNote(reason) {
|
|
122
|
+
if (!reason)
|
|
123
|
+
return '';
|
|
124
|
+
return ` — ${exports.SECRET_DOWNGRADE_LABELS[reason] ?? reason}, downgraded`;
|
|
125
|
+
}
|
package/package.json
CHANGED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
# Fidelity read-outs (mechanical change-pattern)
|
|
2
|
-
|
|
3
|
-
`lumo fidelity` is the fidelity-axis read surface — pure, read-only analytics over what the acceptance loop already records, in the same family as `lumo criteria audit` and `lumo outcome rate`. It adds **no storage and no write path**; it only reads the LUM-609 delivery snapshots and the `CRITERION_CHANGED` audit trail.
|
|
4
|
-
|
|
5
|
-
## `lumo fidelity show <task>`
|
|
6
|
-
|
|
7
|
-
```bash
|
|
8
|
-
lumo fidelity show LUM-42
|
|
9
|
-
lumo fidelity show LUM-42 --json
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
For **each delivery** of the task (one append-only `TaskCriteriaSnapshot` per fresh IN_REVIEW entry — a reopen→re-deliver appends another), it discloses two compositions and deliberately stops there. There is **no single fidelity score and no good/bad direction verdict** — the read-out is purely mechanical (timing + op + checkpointer grounding; it never reads statement or diff semantics).
|
|
13
|
-
|
|
14
|
-
### 1. Grounding composition
|
|
15
|
-
|
|
16
|
-
Runs the same `effectiveReferentKind` classifier the LUM-605 verify gate uses over the **frozen** contract (delivery-time truth, not the since-edited `latest`), rolled into three buckets plus the full per-kind tally:
|
|
17
|
-
|
|
18
|
-
- **grounded** — `EXTERNAL_FACT` (anchored on a fact the agent cannot author: a git SHA/diff, CI status, a DB row). This count is a **machine-checkable _upper bound_** — the checkpointer heuristic can over-count external grounding, never under-count it.
|
|
19
|
-
- **self-confirming** — `AGENT_CONSTRUCTED_STATE` + `UNVERIFIED_ASSERTION` (passes by checking the agent's own output, incl. a declared-EXTERNAL_FACT checkpointer that doesn't truly ground external).
|
|
20
|
-
- **inconclusive** — `PENDING_OUTCOME` + `UNCLASSIFIED` (not mechanically resolvable here). **Surfaced, never silently swallowed.**
|
|
21
|
-
|
|
22
|
-
### 2. Independence signal
|
|
23
|
-
|
|
24
|
-
A criterion is a **backward-inference suspect** when it is both (a) in this delivery's frozen contract and (b) was `ADDED`/`UPDATED` at **round>0** (after work started) at or before the freeze — i.e. the contract the delivery rode on was bent after the work began rather than fixed up front. round-0 (initial-draft) criteria are clean; `DELETED` ops and edits recorded after the freeze (a later cycle) don't count. Each suspect shows its `op`, `round`, and `causeTag`. The read-out **counts and discloses — it does not judge** whether a given edit was legitimate sharpening or tampering (that direction call is left to a human / a later axis).
|
|
25
|
-
|
|
26
|
-
### Caveat (printed on every report)
|
|
27
|
-
|
|
28
|
-
The time anchor is the **work-start boundary** (`Task.workStartedAt`), so the independence signal detects a contract edited _after work started_ — not specifically _after output was produced_. A task with **no delivery snapshot** (never entered IN_REVIEW, or predates the LUM-609 freeze) is reported explicitly — "nothing to read yet", which is **not** a pass.
|
|
29
|
-
|
|
30
|
-
`--json` emits the full report (`taskId`, `workStartedAt`, `hasSnapshots`, `deliveries[]` with `grounding`/`independence`, `caveat`) for scripting.
|
|
31
|
-
|
|
32
|
-
**When to suggest**: when the user wants to see how externally-grounded a task's delivered acceptance contract actually was, or whether criteria were added/changed after work began (independence). It's a disclosure tool, not a gate — it never blocks DONE and emits no verdict.
|
|
@@ -1,108 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.fidelityShow = fidelityShow;
|
|
4
|
-
const config_1 = require("../lib/config");
|
|
5
|
-
const api_1 = require("../lib/api");
|
|
6
|
-
const sanitize_1 = require("../lib/sanitize");
|
|
7
|
-
/**
|
|
8
|
-
* `lumo fidelity show <task>` — the LUM-610 mechanical change-pattern read-out
|
|
9
|
-
* (fidelity axis, second block, on the LUM-609 delivery-snapshot foundation).
|
|
10
|
-
*
|
|
11
|
-
* Per delivery snapshot it discloses two compositions and NOTHING else — no
|
|
12
|
-
* single score, no good/bad verdict (approach (a): purely mechanical):
|
|
13
|
-
* • grounding — how much of the FROZEN contract is externally grounded
|
|
14
|
-
* (EXTERNAL_FACT) vs self-confirming (agent-authored) vs inconclusive.
|
|
15
|
-
* `grounded` is a machine-checkable UPPER BOUND.
|
|
16
|
-
* • independence — criteria ADDED/UPDATED after work started (round>0) that
|
|
17
|
-
* the delivery rode on: backward-inference suspects. Direction left blank.
|
|
18
|
-
*/
|
|
19
|
-
const EFFECTIVE_KINDS = [
|
|
20
|
-
'EXTERNAL_FACT',
|
|
21
|
-
'AGENT_CONSTRUCTED_STATE',
|
|
22
|
-
'UNVERIFIED_ASSERTION',
|
|
23
|
-
'PENDING_OUTCOME',
|
|
24
|
-
'UNCLASSIFIED',
|
|
25
|
-
];
|
|
26
|
-
function authBase() {
|
|
27
|
-
const creds = (0, config_1.readCredentials)();
|
|
28
|
-
if (!creds)
|
|
29
|
-
return { error: 'not logged in. Run `lumo auth login` first.' };
|
|
30
|
-
const base = (0, api_1.trimTrailingSlash)((0, api_1.resolveAuthedApiUrl)(creds.apiUrl));
|
|
31
|
-
const headers = {
|
|
32
|
-
Authorization: `Bearer ${creds.token}`,
|
|
33
|
-
};
|
|
34
|
-
const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
35
|
-
if (sessionId)
|
|
36
|
-
headers['X-Lumo-Session-Id'] = sessionId;
|
|
37
|
-
return { base, headers };
|
|
38
|
-
}
|
|
39
|
-
function formatReport(report, taskId) {
|
|
40
|
-
const lines = [];
|
|
41
|
-
lines.push(`Fidelity change-pattern — ${(0, sanitize_1.sanitizeField)(taskId)}`);
|
|
42
|
-
if (!report.hasSnapshots) {
|
|
43
|
-
lines.push(' No delivery snapshot on record (task never entered IN_REVIEW, or ' +
|
|
44
|
-
'predates the LUM-609 freeze). Nothing to read yet — not a pass.');
|
|
45
|
-
lines.push('');
|
|
46
|
-
lines.push(` ${report.caveat}`);
|
|
47
|
-
return lines.join('\n') + '\n';
|
|
48
|
-
}
|
|
49
|
-
report.deliveries.forEach((d, i) => {
|
|
50
|
-
const when = d.frozenAt.slice(0, 19).replace('T', ' ');
|
|
51
|
-
lines.push('');
|
|
52
|
-
lines.push(` Delivery ${i + 1}/${report.deliveries.length} · frozen ${when}Z · ${(0, sanitize_1.sanitizeField)(d.trigger)} · ${d.total} criteria`);
|
|
53
|
-
const g = d.grounding;
|
|
54
|
-
lines.push(` grounding: grounded ${g.grounded} · self-confirming ${g.selfConfirming} · inconclusive ${g.inconclusive} (grounded = machine-checkable upper bound)`);
|
|
55
|
-
const kinds = EFFECTIVE_KINDS.filter(k => g.byEffectiveKind[k] > 0)
|
|
56
|
-
.map(k => `${k} ${g.byEffectiveKind[k]}`)
|
|
57
|
-
.join(', ');
|
|
58
|
-
lines.push(` effective kinds: ${kinds || '(none)'}`);
|
|
59
|
-
const ind = d.independence;
|
|
60
|
-
lines.push(` independence: ${ind.postStartChanges}/${ind.total} criteria ADDED/UPDATED after work started (backward-inference suspects; direction not judged)`);
|
|
61
|
-
for (const s of ind.suspects) {
|
|
62
|
-
const cause = s.causeTag ? ` · ${(0, sanitize_1.sanitizeField)(s.causeTag)}` : '';
|
|
63
|
-
lines.push(` ↳ ${(0, sanitize_1.sanitizeField)(s.criterionId)} ${s.op}@round${s.round}${cause} (${s.at.slice(0, 10)})`);
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
lines.push('');
|
|
67
|
-
lines.push(` ${report.caveat}`);
|
|
68
|
-
return lines.join('\n') + '\n';
|
|
69
|
-
}
|
|
70
|
-
async function fidelityShow(taskId, options = {}) {
|
|
71
|
-
if (!taskId || taskId.trim() === '') {
|
|
72
|
-
console.error('Error: a task is required: lumo fidelity show <task>');
|
|
73
|
-
return 1;
|
|
74
|
-
}
|
|
75
|
-
const auth = authBase();
|
|
76
|
-
if ('error' in auth) {
|
|
77
|
-
console.error(`Error: ${auth.error}`);
|
|
78
|
-
return 1;
|
|
79
|
-
}
|
|
80
|
-
let res;
|
|
81
|
-
try {
|
|
82
|
-
res = await fetch(`${auth.base}/api/tasks/${encodeURIComponent(taskId)}/fidelity`, { headers: auth.headers });
|
|
83
|
-
}
|
|
84
|
-
catch (err) {
|
|
85
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
86
|
-
console.error(`Error: could not reach Lumo API (${msg})`);
|
|
87
|
-
return 1;
|
|
88
|
-
}
|
|
89
|
-
if (res.status === 401) {
|
|
90
|
-
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
91
|
-
return 1;
|
|
92
|
-
}
|
|
93
|
-
if (res.status === 404) {
|
|
94
|
-
console.error(`Error: task ${(0, sanitize_1.sanitizeField)(taskId)} not found.`);
|
|
95
|
-
return 1;
|
|
96
|
-
}
|
|
97
|
-
if (!res.ok) {
|
|
98
|
-
console.error(`Error: could not read the fidelity change-pattern (HTTP ${res.status}).`);
|
|
99
|
-
return 1;
|
|
100
|
-
}
|
|
101
|
-
const report = (await res.json());
|
|
102
|
-
if (options.json) {
|
|
103
|
-
process.stdout.write(JSON.stringify(report, null, 2) + '\n');
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
process.stdout.write(formatReport(report, taskId));
|
|
107
|
-
return;
|
|
108
|
-
}
|
|
@@ -1,189 +0,0 @@
|
|
|
1
|
-
"use strict";
|
|
2
|
-
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.collectCriterion = collectCriterion;
|
|
4
|
-
exports.verdict = verdict;
|
|
5
|
-
const config_1 = require("../lib/config");
|
|
6
|
-
const api_1 = require("../lib/api");
|
|
7
|
-
const sanitize_1 = require("../lib/sanitize");
|
|
8
|
-
const browser_1 = require("../lib/browser");
|
|
9
|
-
/**
|
|
10
|
-
* Rejection-reason vocabulary (mirrors the server's VerificationRejectionReason
|
|
11
|
-
* enum). Required on the agent --fail path — the agent pays the structured tax
|
|
12
|
-
* a human is spared (LUM-422 ①).
|
|
13
|
-
*/
|
|
14
|
-
const FAIL_REASONS = [
|
|
15
|
-
'CRITERION_UNMET',
|
|
16
|
-
'EVIDENCE_INSUFFICIENT',
|
|
17
|
-
'CHECK_EXECUTION_ERROR',
|
|
18
|
-
'SCOPE_MISMATCH',
|
|
19
|
-
'OTHER',
|
|
20
|
-
];
|
|
21
|
-
/** Collect repeatable `--criterion <id>` flags into an array (commander idiom). */
|
|
22
|
-
function collectCriterion(value, prev = []) {
|
|
23
|
-
return [...prev, value];
|
|
24
|
-
}
|
|
25
|
-
/**
|
|
26
|
-
* `lumo verdict [task]` — human + agent acceptance verdicts (LUM-422).
|
|
27
|
-
*
|
|
28
|
-
* Two modes, exactly one required:
|
|
29
|
-
* --pass opens the browser to the task's verdict bar, focused on Pass (a deep
|
|
30
|
-
* link — writes NOTHING; a passing data row is only ever produced by a
|
|
31
|
-
* human's own click, red line).
|
|
32
|
-
* --fail --reason <enum> [--note] [--criterion …] records an AGENT send-back
|
|
33
|
-
* (verdict hard-coded FAIL server-side) and bounces the task to
|
|
34
|
-
* IN_PROGRESS. Bearer-authed.
|
|
35
|
-
*
|
|
36
|
-
* Defaults to the session-bound task; an explicit identifier overrides.
|
|
37
|
-
*/
|
|
38
|
-
async function verdict(identifier, options = {}) {
|
|
39
|
-
const modes = [options.pass && 'pass', options.fail && 'fail'].filter(Boolean);
|
|
40
|
-
if (modes.length === 0) {
|
|
41
|
-
console.error('Error: choose a verdict mode — --pass or --fail.');
|
|
42
|
-
return 1;
|
|
43
|
-
}
|
|
44
|
-
if (modes.length > 1) {
|
|
45
|
-
console.error(`Error: pick exactly one verdict mode (got ${modes.join(', ')}).`);
|
|
46
|
-
return 1;
|
|
47
|
-
}
|
|
48
|
-
const creds = (0, config_1.readCredentials)();
|
|
49
|
-
if (!creds) {
|
|
50
|
-
console.error('Error: not logged in. Run `lumo auth login` first.');
|
|
51
|
-
return 1;
|
|
52
|
-
}
|
|
53
|
-
const base = (0, api_1.trimTrailingSlash)((0, api_1.resolveAuthedApiUrl)(creds.apiUrl));
|
|
54
|
-
const headers = {
|
|
55
|
-
Authorization: `Bearer ${creds.token}`,
|
|
56
|
-
};
|
|
57
|
-
const sessionId = process.env.CLAUDE_CODE_SESSION_ID;
|
|
58
|
-
if (sessionId)
|
|
59
|
-
headers['X-Lumo-Session-Id'] = sessionId;
|
|
60
|
-
// ── Resolve the task: explicit identifier or the session binding ──────────
|
|
61
|
-
let taskId = identifier;
|
|
62
|
-
if (!taskId) {
|
|
63
|
-
if (!sessionId) {
|
|
64
|
-
console.error('Error: no task given and $CLAUDE_CODE_SESSION_ID is not set.\n' +
|
|
65
|
-
'Run `lumo verdict <LUM-N> …` or run inside a session bound via `lumo session attach`.');
|
|
66
|
-
return 1;
|
|
67
|
-
}
|
|
68
|
-
let res;
|
|
69
|
-
try {
|
|
70
|
-
res = await fetch(`${base}/api/sessions/${encodeURIComponent(sessionId)}`, { headers });
|
|
71
|
-
}
|
|
72
|
-
catch (err) {
|
|
73
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
74
|
-
console.error(`Error: could not reach Lumo API (${msg})`);
|
|
75
|
-
return 1;
|
|
76
|
-
}
|
|
77
|
-
const data = res.ok
|
|
78
|
-
? (await res.json())
|
|
79
|
-
: null;
|
|
80
|
-
if (!data?.taskIdentifier) {
|
|
81
|
-
console.error('Error: this session is not bound to a task. Run `lumo session attach <LUM-N>` first, or pass the task explicitly.');
|
|
82
|
-
return 1;
|
|
83
|
-
}
|
|
84
|
-
taskId = data.taskIdentifier;
|
|
85
|
-
}
|
|
86
|
-
if (options.fail) {
|
|
87
|
-
return failVerdict(base, headers, taskId, options, creds.workspaceSlug);
|
|
88
|
-
}
|
|
89
|
-
return passDeepLink(base, headers, taskId, creds.workspaceSlug);
|
|
90
|
-
}
|
|
91
|
-
/**
|
|
92
|
-
* --pass: open the human's verdict bar pre-focused on Pass. The CLI never writes
|
|
93
|
-
* the verdict — it only carries the human to the one click that does (red line:
|
|
94
|
-
* no agent-produced passing row).
|
|
95
|
-
*/
|
|
96
|
-
async function passDeepLink(base, headers, taskId, workspaceSlug) {
|
|
97
|
-
let res;
|
|
98
|
-
try {
|
|
99
|
-
res = await fetch(`${base}/api/tasks/by-identifier/${encodeURIComponent(taskId)}`, { headers });
|
|
100
|
-
}
|
|
101
|
-
catch (err) {
|
|
102
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
103
|
-
console.error(`Error: could not reach Lumo API (${msg})`);
|
|
104
|
-
return 1;
|
|
105
|
-
}
|
|
106
|
-
if (res.status === 401) {
|
|
107
|
-
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
108
|
-
return 1;
|
|
109
|
-
}
|
|
110
|
-
if (res.status === 404) {
|
|
111
|
-
console.error(`Error: task ${taskId} not found in workspace ${workspaceSlug}`);
|
|
112
|
-
return 1;
|
|
113
|
-
}
|
|
114
|
-
if (!res.ok) {
|
|
115
|
-
console.error(`Error: could not load task (HTTP ${res.status})`);
|
|
116
|
-
return 1;
|
|
117
|
-
}
|
|
118
|
-
const { task } = (await res.json());
|
|
119
|
-
if (!task?.url) {
|
|
120
|
-
console.error('Error: server did not return a task URL to open.');
|
|
121
|
-
return 1;
|
|
122
|
-
}
|
|
123
|
-
const sep = task.url.includes('?') ? '&' : '?';
|
|
124
|
-
const deepLink = `${task.url}${sep}verdict=pass`;
|
|
125
|
-
process.stdout.write(`Opening ${taskId} for a human "Pass" verdict (nothing is recorded until they click):\n` +
|
|
126
|
-
` ${(0, sanitize_1.sanitizeField)(deepLink)}\n`);
|
|
127
|
-
(0, browser_1.openBrowser)(deepLink);
|
|
128
|
-
return;
|
|
129
|
-
}
|
|
130
|
-
/**
|
|
131
|
-
* --fail: record an AGENT send-back. The server hard-codes verdict=FAIL and
|
|
132
|
-
* verifierType=AGENT — there is no passing verdict the CLI can express.
|
|
133
|
-
*/
|
|
134
|
-
async function failVerdict(base, headers, taskId, options, workspaceSlug) {
|
|
135
|
-
if (!options.reason) {
|
|
136
|
-
console.error(`Error: --fail requires --reason <${FAIL_REASONS.join('|')}>.`);
|
|
137
|
-
return 1;
|
|
138
|
-
}
|
|
139
|
-
// Case-insensitive like every other CLI enum flag (LUM-420): fold to the
|
|
140
|
-
// canonical upper-case enum before validating and sending.
|
|
141
|
-
const reason = options.reason.toUpperCase();
|
|
142
|
-
if (!FAIL_REASONS.includes(reason)) {
|
|
143
|
-
console.error(`Error: invalid --reason "${options.reason}". Allowed: ${FAIL_REASONS.join(', ')}.`);
|
|
144
|
-
return 1;
|
|
145
|
-
}
|
|
146
|
-
const body = { rejectionReasonEnum: reason };
|
|
147
|
-
if (options.note)
|
|
148
|
-
body.note = options.note;
|
|
149
|
-
if (options.criterion && options.criterion.length > 0) {
|
|
150
|
-
body.criterionIds = options.criterion;
|
|
151
|
-
}
|
|
152
|
-
let res;
|
|
153
|
-
try {
|
|
154
|
-
res = await fetch(`${base}/api/tasks/${encodeURIComponent(taskId)}/agent-verdict`, {
|
|
155
|
-
method: 'POST',
|
|
156
|
-
headers: { ...headers, 'Content-Type': 'application/json' },
|
|
157
|
-
body: JSON.stringify(body),
|
|
158
|
-
});
|
|
159
|
-
}
|
|
160
|
-
catch (err) {
|
|
161
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
162
|
-
console.error(`Error: could not reach Lumo API (${msg})`);
|
|
163
|
-
return 1;
|
|
164
|
-
}
|
|
165
|
-
if (res.status === 401) {
|
|
166
|
-
console.error('Error: API key invalid or revoked. Run `lumo auth login`.');
|
|
167
|
-
return 1;
|
|
168
|
-
}
|
|
169
|
-
if (res.status === 404) {
|
|
170
|
-
console.error(`Error: task ${taskId} not found in workspace ${workspaceSlug}`);
|
|
171
|
-
return 1;
|
|
172
|
-
}
|
|
173
|
-
if (!res.ok) {
|
|
174
|
-
const errBody = (await res.json().catch(() => null));
|
|
175
|
-
const detail = errBody && typeof errBody.error === 'string'
|
|
176
|
-
? (0, sanitize_1.sanitizeField)(errBody.error)
|
|
177
|
-
: '';
|
|
178
|
-
console.error(`Error: send-back rejected (HTTP ${res.status})${detail ? ` — ${detail}` : ''}`);
|
|
179
|
-
return 1;
|
|
180
|
-
}
|
|
181
|
-
const outcome = (await res.json());
|
|
182
|
-
process.stdout.write(`✗ Sent ${taskId} back (AGENT FAIL, reason ${reason}) — ` +
|
|
183
|
-
`${outcome.criterionIds.length} ${outcome.criterionIds.length === 1 ? 'criterion' : 'criteria'} at round ${outcome.round}; task is now ${outcome.taskStatus}.\n`);
|
|
184
|
-
if (outcome.commentId) {
|
|
185
|
-
process.stdout.write(' A send-back note was posted as a task comment.\n');
|
|
186
|
-
}
|
|
187
|
-
process.stdout.write('Address the unmet criteria (see `lumo task status`), then re-verify.\n');
|
|
188
|
-
return;
|
|
189
|
-
}
|