@lumoai/cli 1.46.0 → 1.48.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 +35 -5
- package/assets/skill/references/criteria.md +9 -9
- package/assets/skill/references/doc-editing.md +10 -10
- package/assets/skill/references/docs.md +22 -22
- package/assets/skill/references/memory.md +9 -10
- package/assets/skill/references/outcome.md +65 -0
- package/assets/skill/references/sessions.md +10 -10
- package/assets/skill/references/task-context.md +7 -7
- package/assets/skill/references/tasks.md +8 -8
- package/assets/skill/references/verify.md +43 -33
- package/assets/skill/references/worktree.md +1 -1
- package/dist/cli/src/commands/criteria-audit.js +52 -0
- package/dist/cli/src/commands/outcome.js +221 -0
- package/dist/cli/src/commands/task-criteria-list.js +8 -1
- package/dist/cli/src/commands/task-criteria-set.js +35 -1
- package/dist/cli/src/commands/task-status.js +68 -4
- package/dist/cli/src/commands/verify.js +14 -1
- package/dist/cli/src/index.js +30 -0
- package/dist/shared/src/referent-kind.js +168 -0
- package/package.json +1 -1
package/assets/skill/SKILL.md
CHANGED
|
@@ -31,6 +31,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
31
31
|
| `task criteria set/list`, drafting the acceptance contract | [references/criteria.md](references/criteria.md) |
|
|
32
32
|
| `verify`, `task status` — machine verification loop, claim-done flow, self-check/resume | [references/verify.md](references/verify.md) |
|
|
33
33
|
| `cost` — per-operation (per-tool) token cost read-out; `task lineage` Top-5 | [references/task-context.md](references/task-context.md) |
|
|
34
|
+
| `outcome show/record/rate` — post-hoc outcome well (falsifier oracle, rejection-only) | [references/outcome.md](references/outcome.md) |
|
|
34
35
|
| `project list`, `milestone*` | [references/milestones.md](references/milestones.md) |
|
|
35
36
|
| `doc create/update/list/move/bind/share/import` (CRUD) | [references/docs.md](references/docs.md) |
|
|
36
37
|
| `doc show --raw/--section`, `doc patch/append/diff/rebuild-source` (editing live docs) | [references/doc-editing.md](references/doc-editing.md) |
|
|
@@ -83,7 +84,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
83
84
|
|
|
84
85
|
**Verification (machine acceptance loop)** — see [verify.md](references/verify.md)
|
|
85
86
|
|
|
86
|
-
- `lumo verify [task] [--timeout <seconds>]` — run every MACHINE criterion's checkpointer locally and report a structured PASS/FAIL verdict per criterion. Round cap 3: all-pass → task to IN_REVIEW (agent stops); round-3 fail → escalate to human. **Run this before claiming a task is done.**
|
|
87
|
+
- `lumo verify [task] --note "<self-report>" [--timeout <seconds>]` — run every MACHINE criterion's checkpointer locally and report a structured PASS/FAIL verdict per criterion. **`--note` is required** when a round will be posted: a one-line self-report ("我改了 X,因为 Y 可验收") frozen as your task's claim (source AGENT) the moment the round passes into IN_REVIEW, then checked against the diff for faithfulness. No note → no round posted (none burned). Round cap 3: all-pass → task to IN_REVIEW (agent stops); round-3 fail → escalate to human. **Run this before claiming a task is done.**
|
|
87
88
|
- `lumo task status [task] [--json]` — read-only acceptance self-check (no LLM): the contract with each criterion's latest verdict, verification history/round, last failure reasons, `nextActions` (the unmet criteria), and any OPEN boundary crossings (fails closed — `null`/`⚠` means "could not confirm", not safe). **Run it first when resuming a task or after a round was rejected.**
|
|
88
89
|
- `lumo verdict [task] --pass | --fail` — acceptance verdicts. `--pass` deep-links to the human verdict bar (records nothing). `--fail --reason <enum> …` records an AGENT send-back → IN_PROGRESS. **An unresolved send-back blocks the DONE transition with 409.**
|
|
89
90
|
- `lumo crossing explain <id> --note "<text>"` — append an agent self-explanation ("申辩") to a boundary crossing; append-only, **never clears the crossing or unblocks Done** (disposition stays web + human-only). A review aid, not a self-clear.
|
|
@@ -92,6 +93,16 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
92
93
|
|
|
93
94
|
- `lumo cost [--task <id>|--session <id>|--since <date>] [--by tool|model|member|session] [--json]` — per-operation token cost read-out, attributing each model step's token delta to the tool(s) it ran. Scope is mutually exclusive (default = workspace last-30-days). For the per-task Top-5 inline, see `lumo task lineage`.
|
|
94
95
|
|
|
96
|
+
**Criteria audit** — workspace-level referent-kind analytics
|
|
97
|
+
|
|
98
|
+
- `lumo criteria audit` — workspace-level read-out: count of acceptance criteria by effective referent kind (EXTERNAL_FACT / AGENT_CONSTRUCTED_STATE / PENDING_OUTCOME / UNVERIFIED_ASSERTION / UNCLASSIFIED) + the self-confirming-green ratio
|
|
99
|
+
|
|
100
|
+
**Outcome well (post-hoc oracle)** — see [outcome.md](references/outcome.md)
|
|
101
|
+
|
|
102
|
+
- `lumo outcome show <task>` — read the post-hoc outcome well: the **falsifier** verdict (`REJECTED` | `INCONCLUSIVE` — never a "pass") plus its backing rejection signals. `INCONCLUSIVE` = no rejection on record, **not** "satisfied".
|
|
103
|
+
- `lumo outcome record <task> --note "<what reality did>" [--kind <kind>]` — record a human-observed post-hoc **rejection** of a delivery (revert / rollback / CI regression / downstream redirect / bypass). Append-only; **there is no "mark satisfied" counterpart** — the well only asserts rejection. `--note` is required (the observed referent).
|
|
104
|
+
- `lumo outcome rate [--min N] [--json]` — workspace **trust × post-hoc-fate** join (LUM-603): per delivery-time forecast-confidence bracket (high/medium/low), the post-hoc REJECTED rate from the well. Honest by construction — thin brackets read `insufficient` (no fabricated rate), the high-vs-low comparison stays `inconclusive` until CIs separate, and the rate is a **lower bound** (no signal = INCONCLUSIVE, never satisfied). Under-sampled today by design — the skeleton produces real strata once the well fills.
|
|
105
|
+
|
|
95
106
|
**Artifacts & Figma** — see [artifacts-figma.md](references/artifacts-figma.md)
|
|
96
107
|
|
|
97
108
|
- `lumo task artifact add/update/list/show/rm` — record spec/plan products on a task
|
|
@@ -137,7 +148,7 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
137
148
|
|
|
138
149
|
- `lumo session attach <id>` — bind this session to a task (then run `task context`). **Lifetime lock**: re-attaching to the same task is a no-op; attaching to a _different_ task is refused with 409 — start a new Claude Code session instead. No `--force`, no `session detach`.
|
|
139
150
|
- `lumo session status` — show current binding
|
|
140
|
-
- End-of-session housekeeping is fully automatic (no command
|
|
151
|
+
- End-of-session housekeeping is fully automatic (no command). On DONE the server runs three best-effort silent passes: Layer-1 memory curation, fragment-usage audit, and blocked-tag automation. See [sessions.md](references/sessions.md)
|
|
141
152
|
- Git-suggest at session start (suggests `session attach`, never auto-binds) + Layer-2 project-memory review — see the reference
|
|
142
153
|
|
|
143
154
|
**Worktrees (local dev tooling)** — see [worktree.md](references/worktree.md)
|
|
@@ -146,12 +157,31 @@ The command catalog below is a **map**: it lists every command grouped by domain
|
|
|
146
157
|
- `lumo worktree rm <LUM-N> --yes` — remove a worktree (keeps the branch unless `--delete-branch`)
|
|
147
158
|
- `lumo worktree list` — list `.worktrees/` worktrees (task id, branch, dirty, node_modules link)
|
|
148
159
|
|
|
160
|
+
## Criteria audit
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
lumo criteria audit
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
`lumo criteria audit` — workspace-level acceptance-criteria analytics. Calls `GET /api/criteria/audit` and prints two things:
|
|
167
|
+
|
|
168
|
+
1. **Declared-kind distribution** — count of criteria by effective referent kind:
|
|
169
|
+
- `EXTERNAL_FACT` — declared as externally observable (git SHA, CI status, DB row) and the checkpointer invokes real external tooling
|
|
170
|
+
- `AGENT_CONSTRUCTED_STATE` — passes by checking output the agent itself produced
|
|
171
|
+
- `PENDING_OUTCOME` — deferred to post-hoc falsifier
|
|
172
|
+
- `UNVERIFIED_ASSERTION` — declared `EXTERNAL_FACT` but the checkpointer does **not** actually invoke external tooling (git/CI/HTTP); downgraded by the audit
|
|
173
|
+
- `UNCLASSIFIED` — legacy rows predating the `referentKind` field (null in DB)
|
|
174
|
+
|
|
175
|
+
2. **Self-confirming-green headline ratio** — `(AGENT_CONSTRUCTED_STATE + UNVERIFIED_ASSERTION) / classified`. Criteria in these two buckets pass by confirming only the agent's own work, not an independent external state.
|
|
176
|
+
|
|
177
|
+
**When to suggest**: when the user wants to audit how much of the workspace's "green" criteria are self-confirming vs externally anchored; when diagnosing whether acceptance criteria have real external hooks or are circular.
|
|
178
|
+
|
|
149
179
|
## Commands & flags that do NOT exist (common mistakes)
|
|
150
180
|
|
|
151
|
-
|
|
181
|
+
Don't guess these:
|
|
152
182
|
|
|
153
183
|
- No `lumo session start` — binding is `lumo session attach <LUM-N>`
|
|
154
|
-
- No `lumo session detach` — the session↔task binding is a lifetime lock
|
|
184
|
+
- No `lumo session detach` — the session↔task binding is a lifetime lock; to work on a different task, start a new Claude Code session
|
|
155
185
|
- No `lumo task delete` — tasks can't be deleted from the CLI (web UI only)
|
|
156
186
|
- No `lumo task artifact edit` — it's `lumo task artifact update`
|
|
157
187
|
- No `lumo auth status` — identity check is `lumo whoami`
|
|
@@ -175,7 +205,7 @@ Measured from real agent sessions (LUM-392) — don't guess these:
|
|
|
175
205
|
|
|
176
206
|
**Git-suggest at start:** when unbound, session-start may infer the task from the git branch / recent commits (any team prefix, e.g. `SPEC-12`) and print `Detected LUM-N … Run lumo session attach LUM-N to bind.` — **without** binding. Confirm it's right, then attach yourself. See [sessions.md](references/sessions.md)
|
|
177
207
|
|
|
178
|
-
### Golden rules (most-violated
|
|
208
|
+
### Golden rules (most-violated)
|
|
179
209
|
|
|
180
210
|
- **Attach + draft criteria before coding.** Skipping attach triggers `SESSION_BINDING_MISSING`; skipping criteria means verification has nothing to check.
|
|
181
211
|
- **Evidence before "done".** Never claim done from reading code — run `lumo verify`. While a PR is open, the task is IN_REVIEW, not DONE.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Acceptance criteria (contract)
|
|
2
2
|
|
|
3
|
-
The acceptance contract is a small set of structured criteria the task's work is verified against
|
|
3
|
+
The acceptance contract is a small set of structured criteria the task's work is verified against. The agent drafts it; the server validates and stores it; verification rounds (`lumo verify`) judge against it. Criteria are injected at session start and in `lumo task context` as the `## Acceptance criteria (contract)` section.
|
|
4
4
|
|
|
5
5
|
## When to draft — the golden rule
|
|
6
6
|
|
|
@@ -45,7 +45,7 @@ The 3–7 range is calibrated for typical multi-file tasks. Criterion count must
|
|
|
45
45
|
- **Outcome-level definition of done, not micro-steps** — a verifiable result (`"lumo task criteria set rejects a second agent draft with 409"`), not a task step ("add a check in the service").
|
|
46
46
|
- **Repo-wide baselines don't take slots** — tests pass / `tsc --noEmit` clean / i18n locale parity / lint are already required by the repo's PR checklist; never spend one of your 3–7 criteria on them.
|
|
47
47
|
- `evidenceRequired: true` — marks criteria whose verdict must point at proof (a MACHINE PASS always requires evidence regardless of this flag).
|
|
48
|
-
- **Edited a MACHINE checkpointer mid-task? Re-run `lumo verify`.** A prior PASS was recorded against the old command — swapping the checkpointer (or rewording the criterion) makes that pass stale: `lumo task status` / the acceptance tab flag it `⚠ pre-edit version
|
|
48
|
+
- **Edited a MACHINE checkpointer mid-task? Re-run `lumo verify`.** A prior PASS was recorded against the old command — swapping the checkpointer (or rewording the criterion) makes that pass stale: `lumo task status` / the acceptance tab flag it `⚠ pre-edit version`. The stale pass still counts as met (render-only, doesn't block DONE), but re-verify so the green reflects the current check.
|
|
49
49
|
|
|
50
50
|
**MACHINE vs HUMAN:**
|
|
51
51
|
|
|
@@ -55,10 +55,10 @@ The 3–7 range is calibrated for typical multi-file tasks. Criterion count must
|
|
|
55
55
|
|
|
56
56
|
**Two checkpointer traps:**
|
|
57
57
|
|
|
58
|
-
- `npx tsx scripts/jest-t.ts '<exact test name>' [test file path]` — jest-by-name checkpointers must go through this zero-match guard. A bare `npx jest -t '<name>'` exits 0 even when the pattern matches **no** test — rename or delete the test and the checkpointer silently fake-PASSes
|
|
59
|
-
- `instance-independent, never self-comparing` — a checkpointer is stored once and re-run for the life of the task, so it must not depend on a baseline that shifts under it. The trap is a `vs origin/main` self-comparison ("current file is strictly smaller than origin/main"): passes pre-merge, but the moment the change merges every branch cut from main has `current == base` and the check fails forever
|
|
58
|
+
- `npx tsx scripts/jest-t.ts '<exact test name>' [test file path]` — jest-by-name checkpointers must go through this zero-match guard. A bare `npx jest -t '<name>'` exits 0 even when the pattern matches **no** test — rename or delete the test and the checkpointer silently fake-PASSes. `jest-t.ts` fails unless ≥1 matching test ran and passed; the optional path scopes the run to one file (much faster than name-filtering the whole suite).
|
|
59
|
+
- `instance-independent, never self-comparing` — a checkpointer is stored once and re-run for the life of the task, so it must not depend on a baseline that shifts under it. The trap is a `vs origin/main` self-comparison ("current file is strictly smaller than origin/main"): passes pre-merge, but the moment the change merges every branch cut from main has `current == base` and the check fails forever. Anchor to a **fixed, absolute target** — a literal budget (`byteCount ≤ 9500`), a committed fixture, or a stable named test through the zero-match guard. A jest-by-name checkpointer likewise rots when its test is renamed/deleted — often by _another_ task's work, so the stored PASS goes stale invisibly. Keep test names stable; `npx tsx scripts/checkpointer-drift.ts <LUM-N> | --mine` statically sweeps stored contracts for these zero-match checkpointers (detection-only).
|
|
60
60
|
|
|
61
|
-
### judgeSteps — agent-drafted judging steps for HUMAN criteria
|
|
61
|
+
### judgeSteps — agent-drafted judging steps for HUMAN criteria
|
|
62
62
|
|
|
63
63
|
A HUMAN criterion is judged by a person, not a checkpointer — so don't hand them a bare assertion ("the copy reads naturally") and make them reverse-engineer what to do. Attach **`judgeSteps`**: short, human-readable instructions the adjudication card renders verbatim (light markdown, URLs made clickable). Structured labour is yours; the human just follows the steps.
|
|
64
64
|
|
|
@@ -68,7 +68,7 @@ A HUMAN criterion is judged by a person, not a checkpointer — so don't hand th
|
|
|
68
68
|
2. `What to do` — the concrete action ("open the acceptance tab", "read the `judgeSteps` block aloud", "resize to mobile width").
|
|
69
69
|
3. `✓ pass / ✗ send-back` — the decision rule, both directions, so the verdict isn't a coin-flip ("pass if every URL opens; send back if any step is vague").
|
|
70
70
|
|
|
71
|
-
**One criterion = one judgment point.** If a HUMAN criterion bundles two checks, split it — each half gets its own steps. The canonical miss is
|
|
71
|
+
**One criterion = one judgment point.** If a HUMAN criterion bundles two checks, split it — each half gets its own steps. The canonical miss is an "additive schema **and** self-credit not implemented" criterion: two assertions; the first is even machine-able (below), the second is a separate human read.
|
|
72
72
|
|
|
73
73
|
**Machine-able → make it MACHINE, don't burn human attention.** Before writing `judgeSteps`, ask whether a checkpointer could decide it. "The migration is purely additive" → `grep` the `migration.sql` for `DROP`/destructive DDL → MACHINE. "No file under `prisma/migrations/` was deleted" → a `git diff` probe → MACHINE. Reserve HUMAN + `judgeSteps` for genuine taste/feel/fidelity judgments a check can't make.
|
|
74
74
|
|
|
@@ -90,7 +90,7 @@ Most criteria assert that **something that should happen, happened** ("the endpo
|
|
|
90
90
|
- `How to phrase it` — state the invariant as _still holding after the change_, not as a step you took. The **c-CRAB boundary** is the hard rule: encode **"the problem does not (re)occur"**, never **"a specific fix exists"**. Write "`prisma/migrations/` has no deleted files vs origin/main" (the bad state is absent) — not "the migration-delete guard function is present" (a named fix). The first survives a refactor of _how_ the invariant is enforced; the second re-fails the moment someone renames the guard, and passes even if the protection was gutted some other way.
|
|
91
91
|
- `Pairing with a checkpointer` — an invariant almost always has a runnable check (its advantage), so prefer MACHINE. The existing `checkpointer` syntax carries it as-is: a `git diff` path/filter probe, a full-suite `jest` run, a structure-verify script, a parity check.
|
|
92
92
|
|
|
93
|
-
Two real-repo invariant examples — `prisma/migrations/` files never deleted (the CLAUDE.md red line), and a live-doc's table structure not flattened (
|
|
93
|
+
Two real-repo invariant examples — `prisma/migrations/` files never deleted (the CLAUDE.md red line), and a live-doc's table structure not flattened (an HTML→md round-trip that silently collapsed tables to plain text):
|
|
94
94
|
|
|
95
95
|
```json
|
|
96
96
|
[
|
|
@@ -107,7 +107,7 @@ Two real-repo invariant examples — `prisma/migrations/` files never deleted (t
|
|
|
107
107
|
]
|
|
108
108
|
```
|
|
109
109
|
|
|
110
|
-
A third
|
|
110
|
+
A third example invariant is "SKILL.md frontmatter description stays ≤ 1000 chars" — the standing context-budget invariant (the description is resident in every session), checkpointed by the `description cap` case in `scripts/analysis/lum392-cli-friction/__tests__/doc-examples.test.ts`.
|
|
111
111
|
|
|
112
112
|
One invariant criterion is usually enough — it's the guardrail, not the whole contract. Pair it with the positive criteria that say what the change should achieve; together they assert _did the right thing_ **and** _touched nothing it shouldn't_.
|
|
113
113
|
|
|
@@ -178,7 +178,7 @@ Optionally annotate **why** the contract drifted with `--cause <NEW_INFO|SCOPE_C
|
|
|
178
178
|
- `GRANULARITY` — wrong granularity.
|
|
179
179
|
- `OTHER` — anything else.
|
|
180
180
|
|
|
181
|
-
The tag lands in the drift record (TaskActivity payload)
|
|
181
|
+
The tag lands in the drift record (TaskActivity payload). Every criteria add/update/delete is mirrored as a structured `CRITERION_CHANGED` activity automatically; `--cause` just enriches it.
|
|
182
182
|
|
|
183
183
|
### `lumo task criteria list <task>`
|
|
184
184
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Commands for **reading a faithful edit base and writing back surgically** — the safe way to edit live docs (registers, ledgers, reports) without flattening tables or clobbering concurrent edits. For document CRUD, sharing, and import, see [docs.md](docs.md) (it also documents the shared **content channels** — `--content` / `--file` / stdin — and the **compact-tables authoring convention**, which apply equally to `doc patch` / `doc append`).
|
|
4
4
|
|
|
5
|
-
**The red line
|
|
5
|
+
**The red line:** never treat rendered `doc show` output as a re-uploadable source — HTML→markdown is lossy and flattens tables. The only legal edit base is `doc show --raw` / `--section`, which print the byte-identical stored `sourceMarkdown`.
|
|
6
6
|
|
|
7
7
|
## `lumo doc show <doc> [--raw | --section <heading>]` — print one document's detail
|
|
8
8
|
|
|
@@ -14,19 +14,19 @@ lumo doc show cmd_xxx --raw > base.md # byte-identical edit base (rev
|
|
|
14
14
|
lumo doc show cmd_xxx --section "D 状态表" > sec.md # one section only (revision on stderr)
|
|
15
15
|
```
|
|
16
16
|
|
|
17
|
-
**`--raw
|
|
17
|
+
**`--raw`** prints the byte-identical markdown source of the last markdown upload — no header, no trailing newline added. The server stores the raw markdown (`sourceMarkdown`) alongside the rendered HTML on every markdown write (`doc create/update --content/--file/stdin`, gdoc import/sync), so `--raw` output IS a legal edit base: `doc show --raw > base.md`, edit, `doc update --file base.md` round-trips losslessly.
|
|
18
18
|
|
|
19
19
|
- A web-editor (HTML-direct) edit or revision restore **invalidates** the stored source — the doc's markdown source is gone until the next markdown upload.
|
|
20
|
-
- When no source is stored (legacy doc or after an HTML edit), `--raw` **fails with exit 1 and a rebuild hint** — it never silently falls back to the lossy HTML→markdown reverse render
|
|
20
|
+
- When no source is stored (legacy doc or after an HTML edit), `--raw` **fails with exit 1 and a rebuild hint** — it never silently falls back to the lossy HTML→markdown reverse render. Rebuild flow: run **`lumo doc rebuild-source <doc>`** — it regenerates the source from the stored HTML with a lossless serializer (tables round-trip) and a structure guard, so `--raw` works from then on.
|
|
21
21
|
- Raw output is verbatim (unsanitized) by design — redirect it to a file rather than reading it in a terminal when the doc's provenance is uncertain.
|
|
22
22
|
|
|
23
23
|
Note: the markdown rendered by **default-mode** `doc show` is still best-effort (tables flatten). Round-trip via `doc show > tmp.md && doc update --file tmp.md` is NOT a no-op — use `--raw` as the edit base instead.
|
|
24
24
|
|
|
25
|
-
Output budget
|
|
25
|
+
Output budget: **default-mode** `doc show` caps the rendered body to the output-token budget (25,000 tokens) and, when truncated, ends in a pointer to `--section "<heading>"` / `--raw`. `--raw` and `--section` are **never** capped — they are byte-faithful edit bases.
|
|
26
26
|
|
|
27
|
-
**`--section <heading
|
|
27
|
+
**`--section <heading>`** prints just one heading-addressed section of the markdown source — a byte-faithful slice from the heading line through (not including) the next same-or-higher-level heading, subsections included. No header on stdout (the slice is a legal `doc patch` base); the current revision is printed to **stderr** as `Revision: N`. Mutually exclusive with `--raw`.
|
|
28
28
|
|
|
29
|
-
- Section addressing: pass the heading text (`--section "D 状态表"`), matched in three tiers — exact, then case-insensitive, then
|
|
29
|
+
- Section addressing: pass the heading text (`--section "D 状态表"`), matched in three tiers — exact, then case-insensitive, then full-width↔half-width punctuation + whitespace normalization, so a half-width query (`问题(P4)`) lands on a full-width stored heading (`问题(P4)`) and vice-versa. Prefix with `#…` to pin the level when the same text exists at several depths (`--section "## Status"`). Normalization never relaxes the ambiguity guard — multiple matches still give the candidate list + exit 1.
|
|
30
30
|
- Missing heading → exit 1 listing the available headings; ambiguous heading → exit 1 with a depth-disambiguation hint.
|
|
31
31
|
- Requires a stored markdown source — same no-fallback rule and rebuild flow as `--raw`.
|
|
32
32
|
- Heading detection is markdown-aware: `#` lines inside fenced code blocks or blockquotes are never section boundaries.
|
|
@@ -55,7 +55,7 @@ Replaces the **whole addressed section** (heading line included, subsections inc
|
|
|
55
55
|
|
|
56
56
|
Concurrency: the splice always commits **conditionally** on the revision the server read the source at — even without `--if-revision`, a concurrent body edit between read and write returns 409 instead of clobbering. On 409 the CLI prints the server reason plus a re-read-and-retry hint and exits 1.
|
|
57
57
|
|
|
58
|
-
Structure guard
|
|
58
|
+
Structure guard, **scoped to the addressed section**: a replacement whose render has fewer `table`/`tr`/heading elements than the old section's render is rejected with 422 naming each shrunk category (old→new counts); structure elsewhere in the document never factors in. Dropping the heading line itself trips the guard too. Pass `--allow-shrink` when the deletion is intentional. `doc append` is pure insertion and is never guarded.
|
|
59
59
|
|
|
60
60
|
Requires a stored markdown source (same rule as `--raw`); errors with the rebuild hint otherwise.
|
|
61
61
|
|
|
@@ -84,7 +84,7 @@ Inserts the new content at the **end of the addressed section** (just before the
|
|
|
84
84
|
| `--file <path>` | string | Content from file (project-local sandbox). |
|
|
85
85
|
| `--if-revision <n>` | int | Only apply if the body is still at revision `n`; 409 + retry hint otherwise. |
|
|
86
86
|
|
|
87
|
-
Same concurrency contract as `doc patch` (always a conditional commit; 409 on conflict). **End-of-document append (no `--section`) does NOT require a stored markdown source
|
|
87
|
+
Same concurrency contract as `doc patch` (always a conditional commit; 409 on conflict). **End-of-document append (no `--section`) does NOT require a stored markdown source**: when the source is missing (web HTML edit / revision restore / legacy doc) it renders the new block to HTML and concatenates it onto the stored HTML body — so one web operation can no longer lock the whole doc against the agent write path. The source stays null (the doc is still HTML-only afterward; `--raw`/`--section`/`doc patch` keep erroring with the rebuild hint). **Section-addressed append (`--section`) still requires a stored source** — it needs the markdown to locate the heading boundary.
|
|
88
88
|
|
|
89
89
|
```bash
|
|
90
90
|
lumo doc append cmd_xxx --section "F 待办队列" --content "- [ ] 评估 XYZ 论文"
|
|
@@ -119,7 +119,7 @@ lumo doc diff cmd_xxx --file docs/live-docs/research-intake-ledger.md
|
|
|
119
119
|
|
|
120
120
|
## `lumo doc rebuild-source <doc>` — regenerate the markdown source from the HTML body
|
|
121
121
|
|
|
122
|
-
The **recovery path for a source-less doc
|
|
122
|
+
The **recovery path for a source-less doc**. When a doc has no stored `sourceMarkdown` — a web HTML-direct edit or revision restore nulled it, or the doc predates source storage — every markdown write path (`--raw`, `--section`, `doc patch`, `doc append --section`, `doc diff`) is locked. This regenerates a valid source by serializing the **stored HTML structure model** back to markdown with a **lossless serializer that round-trips tables/rows/headings** (the default `doc show` render flattens tables, so it was never a safe rebuild base). Only the `sourceMarkdown` column is backfilled; the rendered body is untouched, so the doc reads identically and you just regain the edit base.
|
|
123
123
|
|
|
124
124
|
| Flag | Type | Notes |
|
|
125
125
|
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
@@ -127,7 +127,7 @@ The **recovery path for a source-less doc** (LUM-446). When a doc has no stored
|
|
|
127
127
|
| `--force` | boolean | Re-derive even when a source already exists (default: 409 — protects a byte-faithful human source from a downgrade). |
|
|
128
128
|
| `--if-revision <n>` | int | Only apply if the body is still at this revision (from `doc show`). |
|
|
129
129
|
|
|
130
|
-
The rebuild is **structure-guarded** (the same
|
|
130
|
+
The rebuild is **structure-guarded** (the same guard as `doc update`/`doc patch`): if the serializer would drop any table/row/heading, it is rejected with **422** rather than silently committing a flattened source — `--allow-shrink` is the explicit escape hatch. A doc that already has a source is refused **409** unless `--force`.
|
|
131
131
|
|
|
132
132
|
```bash
|
|
133
133
|
lumo doc rebuild-source cmd_xxx # restore a source-less doc; --raw works after
|
|
@@ -28,7 +28,7 @@ not the aligned form (`| col | meaning |` padded so columns line up). Two reas
|
|
|
28
28
|
- **prettier re-pads tables.** A compact table gets re-aligned and a padded one churns the diff on every prettier run — so repo-tracked live-doc sources under `docs/live-docs/` are in `.prettierignore` to stay byte-stable (see the live-docs README).
|
|
29
29
|
- **the Edit tool's exact-match fails on padding.** Incremental edits match on exact cell text; alignment whitespace makes that match fragile (measured to fail). Compact cells edit reliably.
|
|
30
30
|
|
|
31
|
-
This is a pure authoring convention — the server stores your markdown **byte-for-byte** (`sourceMarkdown`), so `doc show --raw` and `doc diff` stay byte-exact
|
|
31
|
+
This is a pure authoring convention — the server stores your markdown **byte-for-byte** (`sourceMarkdown`), so `doc show --raw` and `doc diff` stay byte-exact; there is **no** server-side table normalization to lean on. The structure guard compares _rendered_ structure and is whitespace-insensitive, so compactness buys local editability, not a verify pass.
|
|
32
32
|
|
|
33
33
|
## `lumo doc create [title] [flags]` — create a new document
|
|
34
34
|
|
|
@@ -75,27 +75,27 @@ The cuid (`cmd_xxx`) is still printed as a stable identifier you can pass back i
|
|
|
75
75
|
|
|
76
76
|
`<doc>` accepts a cuid or a case-insensitive title. Ambiguous titles fail with a candidate list — re-run with the cuid.
|
|
77
77
|
|
|
78
|
-
| Flag | Type | Notes
|
|
79
|
-
| ------------------------ | ------------------- |
|
|
80
|
-
| `--title <text>` | string | New title (cannot be empty).
|
|
81
|
-
| `--content <text>` | string | Replace content (inline).
|
|
82
|
-
| `--file <path>` | string | Replace content from file.
|
|
83
|
-
| (stdin) | — | Pipe to replace content. Empty / whitespace-only stdin (a non-TTY shell with nothing piped — the common agent case) is treated as **no content channel**, not a body clear
|
|
84
|
-
| `--scope <scope>` | enum | `personal` / `workspace`.
|
|
85
|
-
| `--project <ref>` | string | Project name/slug. `--project ""` clears the filing.
|
|
86
|
-
| `--tag <name>` | string (repeatable) | **Bulk replace** the tag set by name. Creates tag if missing. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`.
|
|
87
|
-
| `--tag-id <cuid>` | string (repeatable) | **Bulk replace** the tag set by id. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`.
|
|
88
|
-
| `--add-tag <name>` | string (repeatable) | Attach tag by name (find-or-create). Max 20.
|
|
89
|
-
| `--add-tag-id <cuid>` | string (repeatable) | Attach tag by id. Max 20.
|
|
90
|
-
| `--remove-tag <name>` | string (repeatable) | Detach tag by name (find-or-create; an unknown name creates an orphan Tag row before the no-op detach — use `--remove-tag-id` to avoid orphans). Max 20.
|
|
91
|
-
| `--remove-tag-id <cuid>` | string (repeatable) | Detach tag by id. Unknown ids are a no-op. Max 20.
|
|
92
|
-
| `--allow-shrink` | boolean | Let a body update through even when it drops tables/rows/headings versus the stored body (see structure guard below).
|
|
93
|
-
|
|
94
|
-
A **metadata-only update leaves the body untouched
|
|
95
|
-
|
|
96
|
-
Optimistic concurrency
|
|
97
|
-
|
|
98
|
-
**Structure guard
|
|
78
|
+
| Flag | Type | Notes |
|
|
79
|
+
| ------------------------ | ------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
80
|
+
| `--title <text>` | string | New title (cannot be empty). |
|
|
81
|
+
| `--content <text>` | string | Replace content (inline). |
|
|
82
|
+
| `--file <path>` | string | Replace content from file. |
|
|
83
|
+
| (stdin) | — | Pipe to replace content. Empty / whitespace-only stdin (a non-TTY shell with nothing piped — the common agent case) is treated as **no content channel**, not a body clear. |
|
|
84
|
+
| `--scope <scope>` | enum | `personal` / `workspace`. |
|
|
85
|
+
| `--project <ref>` | string | Project name/slug. `--project ""` clears the filing. |
|
|
86
|
+
| `--tag <name>` | string (repeatable) | **Bulk replace** the tag set by name. Creates tag if missing. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
87
|
+
| `--tag-id <cuid>` | string (repeatable) | **Bulk replace** the tag set by id. Max 20. Mutually exclusive with `--add-tag*` / `--remove-tag*`. |
|
|
88
|
+
| `--add-tag <name>` | string (repeatable) | Attach tag by name (find-or-create). Max 20. |
|
|
89
|
+
| `--add-tag-id <cuid>` | string (repeatable) | Attach tag by id. Max 20. |
|
|
90
|
+
| `--remove-tag <name>` | string (repeatable) | Detach tag by name (find-or-create; an unknown name creates an orphan Tag row before the no-op detach — use `--remove-tag-id` to avoid orphans). Max 20. |
|
|
91
|
+
| `--remove-tag-id <cuid>` | string (repeatable) | Detach tag by id. Unknown ids are a no-op. Max 20. |
|
|
92
|
+
| `--allow-shrink` | boolean | Let a body update through even when it drops tables/rows/headings versus the stored body (see structure guard below). |
|
|
93
|
+
|
|
94
|
+
A **metadata-only update leaves the body untouched**: when no content channel is supplied (`--title`/`--scope`/`--project`/tag flags only), the body is omitted from the PATCH — it does not get blanked and the structure guard cannot fire. To deliberately clear or replace the body you must supply a content channel explicitly (`--content ""` to clear, which then hits the structure guard as a shrink → pair with `--allow-shrink`).
|
|
95
|
+
|
|
96
|
+
Optimistic concurrency: `--if-revision <n>` only applies the update if the doc body is still at revision `n` (from `doc show`). Mismatch → 409 conflict, nothing written — re-read, rebase, retry. `--if-revision` alone is not an update (still errors "no fields to update"); same for `--allow-shrink`.
|
|
97
|
+
|
|
98
|
+
**Structure guard, built into the server:** a body update whose new render has **fewer `table` / `tr` / heading elements than the stored body** is rejected with **422** before anything is written — the error names each shrunk category with old→new counts (e.g. `table 1→0, tr 4→0`). This is the `verify-live-doc.ts` reconciliation moved into the write path, so table flattening and stale-base section loss fail loudly. When the deletion is intentional, re-run with `--allow-shrink`. On a 422: don't reach for `--allow-shrink` reflexively — first check whether your edit base is stale (`doc show <doc> --raw`) and rebase. Only markdown-path writes are guarded; web-editor edits and `doc sync` (Google authority) are not.
|
|
99
99
|
|
|
100
100
|
`--tag` / `--tag-id` (bulk replace) are mutually exclusive with `--add-tag` / `--add-tag-id` / `--remove-tag` / `--remove-tag-id`. The CLI errors before any network call if both families are mixed. Like `doc create`, `--file` is sandboxed.
|
|
101
101
|
|
|
@@ -88,10 +88,9 @@ detail on demand instead of carrying every memory's content in context.
|
|
|
88
88
|
### `lumo memory sync` (downsync to local Claude Code memory)
|
|
89
89
|
|
|
90
90
|
Writes the team's project memory into the dev's local Claude Code memory store so
|
|
91
|
-
Claude Code's **native recall** surfaces it — the direction
|
|
92
|
-
per-session `<untrusted-team-memory>` injection. This
|
|
93
|
-
|
|
94
|
-
during the transition both paths are active and overlap is expected, not a bug.
|
|
91
|
+
Claude Code's **native recall** surfaces it — the direction chosen over
|
|
92
|
+
per-session `<untrusted-team-memory>` injection. This runs **alongside** the
|
|
93
|
+
existing injection, so during the transition both paths are active and overlap is expected, not a bug.
|
|
95
94
|
|
|
96
95
|
- **Where it writes**: `~/.claude/projects/<encoded-cwd>/memory/` — team files as
|
|
97
96
|
`team/<memoryId>.md` (YAML frontmatter + a `metadata.lumo` ownership marker),
|
|
@@ -100,12 +99,12 @@ during the transition both paths are active and overlap is expected, not a bug.
|
|
|
100
99
|
- **Only touches what it owns**: a file is owned only if its frontmatter carries
|
|
101
100
|
`metadata.lumo.source: team`. Your own hand-written memory files and any
|
|
102
101
|
`MEMORY.md` lines outside the managed block are **never** read or written.
|
|
103
|
-
- **Mirrors the whole project
|
|
102
|
+
- **Mirrors the whole project**: the bundle is the project's **full
|
|
104
103
|
ACTIVE memory set** — every dev in the project gets the same corpus, regardless
|
|
105
104
|
of how many active tasks they hold. There is **no** task-level routing/relevance
|
|
106
105
|
filter and **no** token-budget cap on the corpus at sync time: sync only writes
|
|
107
106
|
local files (no tokens, no conversation), so "which memory is relevant" is left
|
|
108
|
-
to Claude Code's native recall, not decided at sync.
|
|
107
|
+
to Claude Code's native recall, not decided at sync. Judge-used 履历
|
|
109
108
|
still orders the resident index (so the most-proven memories list first) but
|
|
110
109
|
never drops anything. Relevance/budget gating remains only on the **injection**
|
|
111
110
|
path (`lumo task context` / session-start), which does spend tokens.
|
|
@@ -118,7 +117,7 @@ during the transition both paths are active and overlap is expected, not a bug.
|
|
|
118
117
|
upsync candidate (the reverse direction is a later phase).
|
|
119
118
|
- **Reversible**: `--dry-run` prints the add/update/remove/drift-skip plan without
|
|
120
119
|
writing; `--clean` removes every owned file + the managed block (full rollback).
|
|
121
|
-
- **Code-anchor staleness check
|
|
120
|
+
- **Code-anchor staleness check**: after downsync, each synced memory's
|
|
122
121
|
code anchors (file paths, and backtick-wrapped symbols/flags) are checked against
|
|
123
122
|
this repo — resolved against **git-tracked** files (`git ls-files`) + exact
|
|
124
123
|
word-boundary grep, never the dirty working tree. A memory whose **every** anchor
|
|
@@ -142,7 +141,7 @@ store. Each candidate is a JSON file in `<memory-dir>/outbox/` shaped
|
|
|
142
141
|
`{ "category": "convention", "content": { ... } }` (the same per-category content
|
|
143
142
|
shape as `lumo project memory add`). `push` POSTs each to the **existing**
|
|
144
143
|
create-project-memory endpoint, so it runs through the same canonicalize → dedup →
|
|
145
|
-
**reconcile-on-write** pipeline
|
|
144
|
+
**reconcile-on-write** pipeline — no parallel upsync path — and a
|
|
146
145
|
successful push removes the file from the outbox.
|
|
147
146
|
|
|
148
147
|
- **Scope**: structured local memories (the lossless path). A team file you edited
|
|
@@ -157,7 +156,7 @@ successful push removes the file from the outbox.
|
|
|
157
156
|
shared with the team — drop a `{category, content}` JSON in the memory `outbox/`
|
|
158
157
|
and run `lumo memory push` (or just use `lumo project memory add` for a one-off).
|
|
159
158
|
|
|
160
|
-
### Automatic sync/push triggers
|
|
159
|
+
### Automatic sync/push triggers
|
|
161
160
|
|
|
162
161
|
Both directions also fire automatically, best-effort — a failure never blocks the
|
|
163
162
|
session or command:
|
|
@@ -168,7 +167,7 @@ session or command:
|
|
|
168
167
|
a non-empty `<memory-dir>/outbox/*.json` is drained via the same
|
|
169
168
|
create-project-memory pipeline as `lumo memory push`. An **empty outbox does zero
|
|
170
169
|
network** (the fast path checked first), so the high-frequency `stop` hook stays
|
|
171
|
-
cheap.
|
|
170
|
+
cheap.
|
|
172
171
|
- **Env vars**: `LUMO_SYNC_THROTTLE_HOURS` (downsync throttle window, default 12),
|
|
173
172
|
`LUMO_DISABLE_MEMORY_AUTO=1` (disable **both** auto-paths). The `--no-anchor-check`
|
|
174
173
|
flag on `lumo memory sync` is unchanged.
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# Outcome well (post-hoc oracle) — `lumo outcome`
|
|
2
|
+
|
|
3
|
+
The **post-hoc outcome well** (LUM-598) is the single external oracle for correctness + fidelity. It records the real-world fate a delivery met **after** it shipped — facts the agent cannot construct and that occur post-delivery (a revert, a rollback, CI turning red, a reopen, a downstream redirect of the design).
|
|
4
|
+
|
|
5
|
+
## The one invariant: falsifier, not verifier
|
|
6
|
+
|
|
7
|
+
The well **only** ever produces a rejection. Its verdict space is exactly:
|
|
8
|
+
|
|
9
|
+
- `REJECTED` — the well holds ≥1 rejection signal (reality revoked / redid / bypassed the delivery).
|
|
10
|
+
- `INCONCLUSIVE` — no rejection on record.
|
|
11
|
+
|
|
12
|
+
There is **no `SATISFIED` / `PASS`**. "N sprints and nobody touched it" is the _absence_ of a rejection — satisfied, merely-tolerated, and never-noticed are externally indistinguishable — so it reads `INCONCLUSIVE`, never a pass. Treat `INCONCLUSIVE` as "could not confirm a rejection", not as approval.
|
|
13
|
+
|
|
14
|
+
## Collection (where signals come from)
|
|
15
|
+
|
|
16
|
+
- **Automatic** — a DONE task reopened (the lineage `REWORKED` transition) is folded into the well as a `REOPENED` signal. No action needed; it happens server-side.
|
|
17
|
+
- **Manual** — a human who observed reality reject a delivery records it with `lumo outcome record`. The `--note` is mandatory: it is the observed referent, frozen as evidence.
|
|
18
|
+
|
|
19
|
+
## `lumo outcome show <task>`
|
|
20
|
+
|
|
21
|
+
Read the well for a task — the verdict plus its backing signals.
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
lumo outcome show LUM-42
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
An empty well prints `INCONCLUSIVE` with the reminder that silence is not a pass. A non-empty well prints `REJECTED` and lists each signal (date, kind, source, note).
|
|
28
|
+
|
|
29
|
+
## `lumo outcome record <task> --note "<what reality did>" [--kind <kind>]`
|
|
30
|
+
|
|
31
|
+
Record one human-observed post-hoc rejection. Append-only — every call inserts a new signal; nothing is ever mutated or cleared.
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
lumo outcome record LUM-42 --note "reverted in #812 after a prod incident" --kind reverted
|
|
35
|
+
lumo outcome record LUM-42 --note "downstream rewrote the API shape this task introduced" --kind downstream_redirect
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
- `--kind` (case-insensitive, default `manual`): `reverted | rolled_back | ci_regression | downstream_redirect | bypassed | manual`. `reopened` is **not** a manual kind — it is owned by the automatic lineage fold.
|
|
39
|
+
- `--occurred-at <iso>` — when the event actually happened (defaults to now).
|
|
40
|
+
|
|
41
|
+
## `lumo outcome rate [--min N] [--json]`
|
|
42
|
+
|
|
43
|
+
The workspace-level **trust × post-hoc-fate** join (LUM-603). For every delivery that has a frozen forecast snapshot, it buckets the delivery by its **delivery-time forecast-confidence** (high / medium / low — the only trust axis frozen at delivery, recomputed from the snapshot's `cohortSize` / `tokenCostBasis`) and reports each bracket's post-hoc **REJECTED** rate from the well. The question it answers once data is sufficient: _do high-trust deliveries get rejected by reality less often than low-trust ones?_
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
lumo outcome rate
|
|
47
|
+
lumo outcome rate --min 20 --json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
This is the falsifier discipline applied at the **query layer** — it refuses to manufacture a finding off a thin sample:
|
|
51
|
+
|
|
52
|
+
- A bracket with fewer than `--min` (default 10) deliveries reads **`insufficient`** with the rate withheld — the raw `rejected/delivered` counts are still shown, but never divided into a believable proportion.
|
|
53
|
+
- The per-bracket rate is an explicit **lower bound** on true failure: the well only records rejections, so a zero-signal delivery is `INCONCLUSIVE` (counted in the denominator), never "satisfied".
|
|
54
|
+
- The headline **high-vs-low** comparison asserts a direction **only** when both brackets are measured _and_ their 95% confidence intervals are disjoint; otherwise it stays **`inconclusive`**.
|
|
55
|
+
|
|
56
|
+
**Expect `inconclusive` today.** The well is freshly wired and under-sampled — that is the designed state, not a bug. The same command produces real strata once enough post-hoc signals accumulate (the deferred outcome the skeleton exists to read).
|
|
57
|
+
|
|
58
|
+
> Scope note (debt-a): the trust axis is **task-level** forecast confidence only. Fragment-level trust is not frozen at delivery and drifts, so it is deliberately **not** joined here — doing so would read a post-hoc value against a past delivery. Freezing fragment trust is a separate follow-up.
|
|
59
|
+
|
|
60
|
+
## When to suggest
|
|
61
|
+
|
|
62
|
+
- A teammate says a shipped task **was reverted / rolled back / broke prod / got redone / was bypassed** — record it with `lumo outcome record` so the well carries reality's verdict.
|
|
63
|
+
- Auditing whether a "done" task actually held up — `lumo outcome show <task>` reads the well.
|
|
64
|
+
- Asking whether **higher-confidence deliveries actually fail less** (does delivery-time trust predict post-hoc fate?) — `lumo outcome rate`. Expect `inconclusive` until the well fills; it will never print a correlation the sample can't support.
|
|
65
|
+
- **Never** use it to mark something good: the well has no positive verdict by design. To assert a delivery met its contract, that is the acceptance loop (`lumo verify` / `lumo task status`), not the well.
|
|
@@ -4,12 +4,12 @@
|
|
|
4
4
|
|
|
5
5
|
### Suggest-on-start from local git (no auto-bind)
|
|
6
6
|
|
|
7
|
-
When a session starts **without** a bound task, the `session-start` hook infers a task from local git and **suggests** it — it never binds for you
|
|
7
|
+
When a session starts **without** a bound task, the `session-start` hook infers a task from local git and **suggests** it — it never binds for you.
|
|
8
8
|
|
|
9
9
|
Detection order and rules:
|
|
10
10
|
|
|
11
11
|
- `current branch name` first (e.g. `lumo/LUM-145-...`), then the `most recent commit subjects` (e.g. `... [LUM-145]`), extracting the first task identifier.
|
|
12
|
-
- `prefix-agnostic`
|
|
12
|
+
- `prefix-agnostic` — any team prefix matches (`SPEC-12` as much as `LUM-145`), using the same pattern the server uses to link PR branches to tasks.
|
|
13
13
|
- `well-known acronym-number tokens` (`UTF-8`, `SHA-256`, `ISO-8601`, …) are skipped, never suggested.
|
|
14
14
|
|
|
15
15
|
On a hit it prints one suggestion line and stops — the session stays **unbound**, no context injected yet:
|
|
@@ -28,7 +28,7 @@ You see a suggestion line. Confirm the inferred task is the one the user wants,
|
|
|
28
28
|
|
|
29
29
|
### Layer 2 project-memory review at session start
|
|
30
30
|
|
|
31
|
-
When the session is bound, session-start may inject a **"🆕 Review needed: project memories auto-consolidated by the previous session"** section alongside the memory / PR-review blocks
|
|
31
|
+
When the session is bound, session-start may inject a **"🆕 Review needed: project memories auto-consolidated by the previous session"** section alongside the memory / PR-review blocks. It lists the **PROJECT-scope** memories that the member's **immediately-preceding session** auto-consolidated (Layer 2 runs asynchronously when a task is marked `done`). Each item shows its `id`.
|
|
32
32
|
|
|
33
33
|
- **Why async / next-session:** Layer 2 promotions land after the task hits DONE, so they surface at the _next_ session-start, when they've definitely landed.
|
|
34
34
|
- **Show-once:** the section appears only at the session immediately following the one that produced the memories. It does **not** re-nag later, so act now or it scrolls off.
|
|
@@ -91,11 +91,11 @@ What it does:
|
|
|
91
91
|
- Reads `CLAUDE_CODE_SESSION_ID` from the environment (Claude Code sets it automatically); errors out if unset — must run from inside a Claude Code session.
|
|
92
92
|
- Calls `POST /api/sessions/<session_id>/bind-task`, which sets the Session row's `taskId` and re-tags previously-untagged HookEvent rows in this session.
|
|
93
93
|
- The binding lives entirely on the server (`Session.taskId`); subsequent hooks read it back via the session row. The CLI keeps no local sentinel.
|
|
94
|
-
- Prints the task's **acceptance contract** (`## Acceptance criteria (contract)
|
|
94
|
+
- Prints the task's **acceptance contract** (`## Acceptance criteria (contract)`) right after the bind confirmation — or, when a still-open task has none, the draft reminder to draft 3–7 criteria before the first line of code (see [criteria.md](criteria.md)). The same section is auto-injected at session start when already bound (highest priority in the injection budget, ahead of memory).
|
|
95
95
|
|
|
96
96
|
After attaching, always run `lumo task context <identifier>` to load the task background.
|
|
97
97
|
|
|
98
|
-
#### Auto-downsync on attach
|
|
98
|
+
#### Auto-downsync on attach
|
|
99
99
|
|
|
100
100
|
A successful `session attach` also runs a **best-effort team-memory downsync** for the bound task's project — the same work as `lumo memory sync` (including the P4b code-anchor staleness check), landing the team's memory in your local Claude Code memory store without a separate command.
|
|
101
101
|
|
|
@@ -105,7 +105,7 @@ A successful `session attach` also runs a **best-effort team-memory downsync** f
|
|
|
105
105
|
- Manual `lumo memory sync` stays unthrottled.
|
|
106
106
|
- `LUMO_DISABLE_MEMORY_AUTO=1` turns off the auto-downsync (and the hook auto-upsync — see [memory.md](memory.md)) entirely; `--no-anchor-check` on `lumo memory sync` is unchanged.
|
|
107
107
|
|
|
108
|
-
#### Lifetime lock
|
|
108
|
+
#### Lifetime lock
|
|
109
109
|
|
|
110
110
|
`Session.taskId` is **write-once**. Re-attaching to the **same** task is always a no-op re-bind (idempotent, re-emits context). Attaching to a **different** task is refused with HTTP 409 — the server returns `{ error, currentTaskIdentifier, currentTaskTitle }` and the CLI prints:
|
|
111
111
|
|
|
@@ -135,13 +135,13 @@ Prints which task the current Claude Code session is bound to, or "(no task)" if
|
|
|
135
135
|
|
|
136
136
|
The user asks "which task am I on", "what's this session bound to", or you need to decide whether to suggest `session attach` for a mentioned task ID.
|
|
137
137
|
|
|
138
|
-
### Automatic end-of-session housekeeping (no command
|
|
138
|
+
### Automatic end-of-session housekeeping (no command)
|
|
139
139
|
|
|
140
|
-
The old end-of-session command was **removed
|
|
140
|
+
The old end-of-session command was **removed**. The three passes it ran interactively now happen **automatically server-side** — all evidence-gated, best-effort, and silent. There is nothing for the agent to run or confirm. Two fire when the bound task reaches **DONE** (`lumo task update <id> --status done`, which threads `CLAUDE_CODE_SESSION_ID` so attribution lands); one runs continuously off the failure/progress hooks.
|
|
141
141
|
|
|
142
142
|
**1. Layer-1 memory curation (on DONE).** An LLM judge reviews the Layer-1 memories each of the task's sessions recorded, against that session's event log, and **soft-invalidates only the clearly-wrong / self-contradictory ones**: the row flips to `INVALIDATED` and is excluded from injection but **kept for audit — never hard-deleted**. **Uncertain memories are left untouched** (the judge defaults to keeping). Promotion to project scope is **not** done here — that stays with the Layer-2 flow (surfaced at the next session-start, see above).
|
|
143
143
|
|
|
144
|
-
**2. Fragment-usage audit (
|
|
144
|
+
**2. Fragment-usage audit (on DONE).** An LLM judge sees the fragments this session consumed (its lineage edges) plus the session's event log and votes which were **actually used**:
|
|
145
145
|
|
|
146
146
|
- confidently-used edges → `used=true`
|
|
147
147
|
- confidently-unused → `used=false`
|
|
@@ -149,7 +149,7 @@ The old end-of-session command was **removed in LUM-544**. The three passes it r
|
|
|
149
149
|
|
|
150
150
|
Already-voted sessions are skipped; a cron backstop drains any backlog. **Why:** upgrades the flywheel signal from "co-loaded" (constant) to "actually used" (discriminative); `task context` then prefers each fragment's usage-based merge rate, falling back to the presence rate when usage samples are thin.
|
|
151
151
|
|
|
152
|
-
**3. Blocked-tag automation (
|
|
152
|
+
**3. Blocked-tag automation (server-side).** When a session crosses the same-tool failure threshold (**≥ 3** same-type failures, aggregated from `POST_TOOL_USE_FAILURE` grouped by tool name + `STOP_FAILURE` turn-level failures), the server **auto-applies the shared `blocked` tag** to the bound task. This inverts the old manual gate — no prompt, no human in the loop — and is safe via three safeguards:
|
|
153
153
|
|
|
154
154
|
- **idempotent:** at most one active auto-block per task, so re-crossing is a no-op.
|
|
155
155
|
- **auto-untag on progress:** the next observable progress (a successful tool call or a non-failure turn end) removes the tag.
|
|
@@ -13,7 +13,7 @@ lumo task context LUM-42
|
|
|
13
13
|
The command prints a markdown document to stdout with these sections, in order:
|
|
14
14
|
|
|
15
15
|
1. **Task header** — identifier, title, status, description.
|
|
16
|
-
2. **`## Acceptance criteria (contract)`**
|
|
16
|
+
2. **`## Acceptance criteria (contract)`** — shown right after the header. Each line `[MACHINE|HUMAN] statement`, with a `↳ check:` line for MACHINE checkpointers; HUMAN_EDIT / REVIEW_ADDED provenance tagged inline. A still-open task with none shows a draft reminder instead — draft 3–7 criteria **before writing code** (see [criteria.md](criteria.md)).
|
|
17
17
|
3. **Memory section** — cross-session learnings; trusted background context that persists, so you avoid re-learning decisions/constraints.
|
|
18
18
|
4. **Inline source cards** — Slack / web / Figma / artifacts / documents / comments / Pull Requests (see "Context Retrieval" below).
|
|
19
19
|
5. **`## PR review todos`** — mirrored PR review comments as a checkbox todo list. Each line-level comment shows `` `file:line` `` + reviewer's ask + GitHub comment link; each `changes_requested` review summary shows "🛑 Changes requested (whole PR)". Present only when the task's PR(s) have review comments. Each unchecked box is a TODO: resolve it, then reply on the PR (a Lumo comment mirrors back to GitHub).
|
|
@@ -33,7 +33,7 @@ The command prints a markdown document to stdout with these sections, in order:
|
|
|
33
33
|
|
|
34
34
|
## Context Retrieval (full text on demand)
|
|
35
35
|
|
|
36
|
-
|
|
36
|
+
Context injection is split into tiers: `lumo task context <LUM-N>` emits a **cheap inline card** per source instead of dumping full bodies. Slack/docs/artifacts/comments get an **LLM summary**; web/Figma/PR get **metadata only**. Each card ends with the **retrieval command** for the heavy content.
|
|
37
37
|
|
|
38
38
|
Run the matching command below when the card isn't enough. Pass the same `LUM-N` plus the id the card shows for that source:
|
|
39
39
|
|
|
@@ -47,7 +47,7 @@ Run the matching command below when the card isn't enough. Pass the same `LUM-N`
|
|
|
47
47
|
|
|
48
48
|
All five are **read-only** (no live Slack/GitHub/Figma calls except the web body fetch). Web/Figma/PR are v1 metadata-degraded: they print a `note:` saying live content needs an external integration.
|
|
49
49
|
|
|
50
|
-
**Output budget
|
|
50
|
+
**Output budget:** the whole `task context` handoff is capped to the output-token budget (25,000 tokens). On overflow, output is truncated and ends in a pointer to the precise sub-commands (`lumo task status` / `task comments list --full` / `task lineage` / `doc show`) to pull any dropped section just-in-time.
|
|
51
51
|
|
|
52
52
|
### `lumo task slack show <identifier> <contextId>` — full Slack thread snapshot
|
|
53
53
|
|
|
@@ -77,7 +77,7 @@ lumo task figma context LUM-42 cfl_abc123
|
|
|
77
77
|
|
|
78
78
|
Prints the thread: each comment as `author · createdAt` then its plain-text body (bodies stored as HTML, stripped to text). Replies indent two spaces under their parent. Author falls back to `unknown`. No comments prints `(no comments)`.
|
|
79
79
|
|
|
80
|
-
- `lumo task comments list LUM-42` — capped to the output budget (
|
|
80
|
+
- `lumo task comments list LUM-42` — capped to the output budget (25,000 tokens; every printed line spends from your context). On overflow, truncates and ends in a fetch-more pointer: `… +N more comments not shown (output capped at 25,000 tokens) — read the whole thread with: lumo task comments list <id> --full`.
|
|
81
81
|
- `lumo task comments list LUM-42 --full` — every comment, uncapped. Use only when you actually need the whole thread.
|
|
82
82
|
|
|
83
83
|
```bash
|
|
@@ -104,13 +104,13 @@ lumo task lineage LUM-42 # per-session causal trail + cost
|
|
|
104
104
|
lumo task lineage LUM-42 --signal # append workspace-level usage signal-health
|
|
105
105
|
```
|
|
106
106
|
|
|
107
|
-
- `lumo task lineage <id> --signal` — appends the workspace-level usage signal-health block. Used-vs-base merge rate uses iteration-taint fold (send-back / reopen / PR-close = negative class even if later merged); shows negative-class size per side; prints "metric cannot discriminate" when no failure outcomes exist yet. The block ends with a workspace-wide disclosure funnel (same format as the totals funnel below, aggregated over **all** workspace edges, not just this task) including the same `· ~T tokens saved` suffix when token data exists
|
|
107
|
+
- `lumo task lineage <id> --signal` — appends the workspace-level usage signal-health block. Used-vs-base merge rate uses iteration-taint fold (send-back / reopen / PR-close = negative class even if later merged); shows negative-class size per side; prints "metric cannot discriminate" when no failure outcomes exist yet. The block ends with a workspace-wide disclosure funnel (same format as the totals funnel below, aggregated over **all** workspace edges, not just this task) including the same `· ~T tokens saved` suffix when token data exists.
|
|
108
108
|
|
|
109
109
|
Output sections:
|
|
110
110
|
|
|
111
|
-
- **Totals banner** — distinct sessions, fragment count, edge count, total tokens (input/output/cache split), loops, and outcome distribution. After the outcome summary, one funnel line: `- Disclosure funnel: N impressions · M INDEX (X%) · K pulled (Y% of INDEX) · J used (Z%)`. Impressions = edge count; INDEX% and used% are over impressions; pull% is over INDEX only (FULL fragments have no pull opportunity). Divide-by-zero guarded (zero impressions or zero INDEX renders `0%`). With per-fragment token weights collected
|
|
111
|
+
- **Totals banner** — distinct sessions, fragment count, edge count, total tokens (input/output/cache split), loops, and outcome distribution. After the outcome summary, one funnel line: `- Disclosure funnel: N impressions · M INDEX (X%) · K pulled (Y% of INDEX) · J used (Z%)`. Impressions = edge count; INDEX% and used% are over impressions; pull% is over INDEX only (FULL fragments have no pull opportunity). Divide-by-zero guarded (zero impressions or zero INDEX renders `0%`). With per-fragment token weights collected, appends `· ~T tokens saved` = Σ(fullTokens − indexTokens) over un-pulled INDEX edges (the token cost index-only injection avoided); omitted cleanly when no edge carries token data (older edges predate the columns).
|
|
112
112
|
- **One block per session** — the group's cost shown **once** (token/loop), the date it consumed context, then each fragment as `[OUTCOME] TYPE — <source label>` plus a disclosure suffix: `· INDEX pulled` (INDEX, `pulledAt` set) / `· INDEX not-pulled` (INDEX, never pulled) / `· FULL` (injected in full at session-start). Per-group outcome summary follows.
|
|
113
|
-
- **Top operations by token cost
|
|
113
|
+
- **Top operations by token cost** — the totals also append a per-task Top-5 of the most expensive tools by attributed token cost (`<tool> — N tokens`), ending with `(full breakdown: lumo cost --task <id>)`. Omitted when no per-operation cost has been attributed yet.
|
|
114
114
|
|
|
115
115
|
Cost is attributed once per session (a session that injected many fragments is not double-counted). Fragment ids are canonical — MEMORY fragments survive consolidation drift. **Cold start:** a task with no edges prints a friendly note (lineage is captured when a session-bound run consumes the task's context), not an error.
|
|
116
116
|
|