@lumoai/cli 1.41.0 → 1.42.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 +47 -41
- package/assets/skill/references/criteria.md +97 -252
- package/assets/skill/references/doc-editing.md +146 -0
- package/assets/skill/references/docs.md +49 -278
- package/assets/skill/references/memory.md +29 -0
- package/assets/skill/references/onboarding.md +4 -4
- package/assets/skill/references/sessions.md +84 -97
- package/assets/skill/references/task-context.md +65 -149
- package/assets/skill/references/task-deps.md +113 -0
- package/assets/skill/references/tasks.md +2 -133
- package/assets/skill/references/verify.md +121 -201
- package/assets/skill/references/worktree.md +21 -36
- package/dist/cli/src/commands/memory-fold.js +76 -0
- package/dist/cli/src/index.js +6 -0
- package/package.json +1 -1
|
@@ -1,165 +1,78 @@
|
|
|
1
1
|
# Acceptance criteria (contract)
|
|
2
2
|
|
|
3
|
-
The acceptance contract is a small set of structured criteria the task's work
|
|
4
|
-
will be verified against (Acceptance v1, LUM-341/342). The agent drafts it;
|
|
5
|
-
the server validates and stores it; verification rounds (`lumo verify`,
|
|
6
|
-
Slice 1 task #3) judge against it. Criteria are injected at session start and
|
|
7
|
-
in `lumo task context` as the `## Acceptance criteria (contract)` section.
|
|
3
|
+
The acceptance contract is a small set of structured criteria the task's work is verified against (Acceptance v1, LUM-341/342). The agent drafts it; the server validates and stores it; verification rounds (`lumo verify`, Slice 1 task #3) judge against it. Criteria are injected at session start and in `lumo task context` as the `## Acceptance criteria (contract)` section.
|
|
8
4
|
|
|
9
5
|
## When to draft — the golden rule
|
|
10
6
|
|
|
11
7
|
**Attach → read context → draft criteria → THEN write the first line of code.**
|
|
12
8
|
|
|
13
|
-
If `lumo task context` / session-start shows the draft reminder ("This task
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
-
|
|
32
|
-
TODO phase): a `criteria set` resubmit replaces the whole contract freely —
|
|
33
|
-
full-group replace, recorded as `AGENT_DRAFT@round0`. Iterate as much as
|
|
34
|
-
needed during planning.
|
|
35
|
-
- **Work has started** (the task left TODO at least once): a `criteria set`
|
|
36
|
-
resubmit is **still allowed** and applies an **identity-preserving diff** — the
|
|
37
|
-
CLI matches submitted items to existing criteria by id (attaching ids
|
|
38
|
-
automatically by exact-statement match) and records each add, update, or delete
|
|
39
|
-
as a `CRITERION_CHANGED` drift event at the current round. **No 409.** The
|
|
40
|
-
intent: the contract can be sharpened as understanding grows, but every change
|
|
41
|
-
is traceable.
|
|
42
|
-
- **Task is DONE**: the contract locks — a `criteria set` resubmit is rejected
|
|
43
|
-
with 409. Reopen the task (move it back to in_progress) to change criteria, or
|
|
44
|
-
use `--human`.
|
|
45
|
-
|
|
46
|
-
`--human` revisions are allowed in every non-DONE stage. One deletion rule holds
|
|
47
|
-
across all stages: a criterion that already has verification runs is
|
|
48
|
-
**soft-deleted** (kept for audit, runs preserved) rather than hard-deleted; a
|
|
49
|
-
run-free criterion is hard-deleted. Either way, reword rather than delete when
|
|
50
|
-
possible.
|
|
9
|
+
If `lumo task context` / session-start shows the draft reminder ("This task has no acceptance criteria yet. …") instead of a contract, you MUST draft and submit criteria before starting implementation:
|
|
10
|
+
|
|
11
|
+
1. Read the task description, comments, linked resources, and memory first — the contract distills what "done" means, so understand the task before writing it.
|
|
12
|
+
2. Draft **3–7 criteria** for a typical multi-file task (soft range — the server warns outside it but never rejects; if you genuinely need more, merge related checks instead). Scale the count down for small tasks (see "Scale the contract to the task size").
|
|
13
|
+
3. `lumo task criteria set <task> --file <criteria.json>` — submit.
|
|
14
|
+
|
|
15
|
+
**The contract is editable until DONE — changes after work starts leave a recorded drift trail.** Behaviour changes at two thresholds tracked by `Task.workStartedAt` (set the first time the task leaves TODO; never resets even if the task bounces back to TODO):
|
|
16
|
+
|
|
17
|
+
| Stage | `criteria set` resubmit behaviour |
|
|
18
|
+
| ------------------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
19
|
+
| **Work never started** (`workStartedAt` unset, still initial TODO) | Replaces the whole contract freely — full-group replace, recorded `AGENT_DRAFT@round0`. Iterate as much as needed during planning. |
|
|
20
|
+
| **Work started** (task left TODO at least once) | Still allowed; applies an **identity-preserving diff** — CLI matches items to existing criteria by id (auto-attaching ids by exact-statement match), records each add/update/delete as a `CRITERION_CHANGED` drift event at the current round. **No 409.** Contract can be sharpened as understanding grows; every change is traceable. |
|
|
21
|
+
| **Task DONE** | Contract locks — resubmit rejected with **409**. Reopen the task (move back to in_progress) to change criteria, or use `--human`. |
|
|
22
|
+
|
|
23
|
+
`--human` revisions are allowed in every non-DONE stage. One deletion rule holds across all stages:
|
|
24
|
+
|
|
25
|
+
- A criterion that **already has verification runs** is **soft-deleted** (kept for audit, runs preserved).
|
|
26
|
+
- A **run-free** criterion is **hard-deleted**.
|
|
27
|
+
- Either way, reword rather than delete when possible.
|
|
51
28
|
|
|
52
29
|
### A send-back may mean the contract was wrong — amend it, don't spin off
|
|
53
30
|
|
|
54
|
-
If a send-back reveals the **contract itself** was wrong or incomplete, the
|
|
55
|
-
|
|
56
|
-
and
|
|
57
|
-
DRAFT_BLIND_SPOT`) and re-verify — **not** to open a new task. Sharpening the
|
|
58
|
-
contract as understanding grows is expected; that's what the drift trail is
|
|
59
|
-
_for_, not something to avoid. **The line:** amending to reflect reality is
|
|
60
|
-
legitimate; **weakening a criterion you were just FAILed on, purely to make it
|
|
61
|
-
pass, is tampering** — it's audited and counts as a failure either way. Deleting
|
|
62
|
-
a failed criterion doesn't help: the DONE gate still blocks on its standing FAIL
|
|
63
|
-
(the run is soft-deleted, the verdict survives).
|
|
31
|
+
If a send-back reveals the **contract itself** was wrong or incomplete, **amend the criteria on this task** (`lumo task criteria set <id>`, and with `--human` annotate the reason via `--cause NEW_INFO | SCOPE_CHANGE | DRAFT_BLIND_SPOT`) and re-verify — **not** open a new task. Sharpening the contract as understanding grows is expected; that's what the drift trail is _for_.
|
|
32
|
+
|
|
33
|
+
**The line:** amending to reflect reality is legitimate; **weakening a criterion you were just FAILed on, purely to make it pass, is tampering** — audited, and counts as a failure either way. Deleting a failed criterion doesn't help: the DONE gate still blocks on its standing FAIL (the run is soft-deleted, the verdict survives).
|
|
64
34
|
|
|
65
35
|
## Scale the contract to the task size
|
|
66
36
|
|
|
67
|
-
The 3–7 range is calibrated for typical multi-file tasks.
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
- **
|
|
71
|
-
|
|
72
|
-
criteria IS a complete contract** — one targeted MACHINE criterion plus at
|
|
73
|
-
most one HUMAN criterion.
|
|
74
|
-
- **Never pad toward the soft floor.** A filler criterion (a restated
|
|
75
|
-
baseline, a third angle on the same check) dilutes the contract and taxes
|
|
76
|
-
every verification round. For a micro task the below-minimum warning is
|
|
77
|
-
expected — ignore it.
|
|
78
|
-
- **Shrink the contract, never skip it.** Every task still drafts and locks a
|
|
79
|
-
contract before the first line of code; task size only changes how many
|
|
80
|
-
criteria that takes.
|
|
37
|
+
The 3–7 range is calibrated for typical multi-file tasks. Criterion count must track the size of the work — it is not a fixed ritual:
|
|
38
|
+
|
|
39
|
+
- **Micro task** (fits a single session, blast radius of one or two files — a docs tweak, copy change, small targeted fix): **1–2 criteria IS a complete contract** — one targeted MACHINE criterion plus at most one HUMAN criterion.
|
|
40
|
+
- **Never pad toward the soft floor.** A filler criterion (a restated baseline, a third angle on the same check) dilutes the contract and taxes every verification round. For a micro task the below-minimum warning is expected — ignore it.
|
|
41
|
+
- **Shrink the contract, never skip it.** Every task drafts and locks a contract before the first line of code; task size only changes how many criteria that takes.
|
|
81
42
|
|
|
82
43
|
## How to write good criteria
|
|
83
44
|
|
|
84
|
-
- **Outcome-level definition of done, not micro-steps
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
- **
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
MACHINE.
|
|
100
|
-
- **Jest-by-name checkpointers must go through the zero-match guard.** A
|
|
101
|
-
bare `npx jest -t '<name>'` exits 0 even when the pattern matches **no**
|
|
102
|
-
test — rename or delete the test and the checkpointer silently fake-PASSes
|
|
103
|
-
(same trap class as the BSD-grep `-P` incident, LUM-401). Write
|
|
104
|
-
`npx tsx scripts/jest-t.ts '<exact test name>' [test file path]` instead:
|
|
105
|
-
it fails unless ≥1 matching test ran and passed. The optional path scopes
|
|
106
|
-
the run to one file (much faster than name-filtering the whole suite).
|
|
107
|
-
- **A checkpointer must be instance-independent — never self-comparing.** It
|
|
108
|
-
is stored once and re-run for the life of the task, so it must not depend on
|
|
109
|
-
a baseline that shifts under it. The trap is a `vs origin/main`
|
|
110
|
-
self-comparison ("current file is strictly smaller than origin/main"): it
|
|
111
|
-
passes pre-merge, but the moment the change merges, every branch cut from
|
|
112
|
-
main has `current == base` and the check fails forever (LUM-433 c1, fixed in
|
|
113
|
-
#528). Anchor to a **fixed, absolute target** instead — a literal budget
|
|
114
|
-
(`byteCount ≤ 9500`), a committed fixture, or a stable named test run through
|
|
115
|
-
the zero-match guard. A jest-by-name checkpointer likewise rots when its test
|
|
116
|
-
is renamed or deleted — often by _another_ task's work, so the stored PASS
|
|
117
|
-
goes stale invisibly. Keep test names stable; `scripts/checkpointer-drift.ts`
|
|
118
|
-
statically sweeps stored contracts for these zero-match checkpointers
|
|
119
|
-
(`npx tsx scripts/checkpointer-drift.ts <LUM-N> | --mine`, detection-only).
|
|
120
|
-
- `evidenceRequired: true` marks criteria whose verdict must point at proof
|
|
121
|
-
(a MACHINE PASS always requires evidence regardless of this flag).
|
|
122
|
-
- **Edited a MACHINE checkpointer mid-task? Re-run `lumo verify`.** A prior PASS
|
|
123
|
-
was recorded against the old command — once you swap the checkpointer (or
|
|
124
|
-
reword the criterion) that pass goes stale: `lumo task status` / the acceptance
|
|
125
|
-
tab flag it `⚠ pre-edit version` (LUM-457). The stale pass still counts as met
|
|
126
|
-
(render-only, doesn't block DONE), but re-verify so the green reflects the
|
|
127
|
-
current check.
|
|
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
|
+
- **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
|
+
- `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` (LUM-457). The stale pass still counts as met (render-only, doesn't block DONE), but re-verify so the green reflects the current check.
|
|
49
|
+
|
|
50
|
+
**MACHINE vs HUMAN:**
|
|
51
|
+
|
|
52
|
+
- `MACHINE` — an executable check exists. **Must** carry a `checkpointer` (the runnable check: a test command, script, probe…). The checkpointer runs on the agent's machine; the structured verdict is reported back to the server (execution on the client, adjudication on the server).
|
|
53
|
+
- `HUMAN` — needs human judgment (UX feel, copy tone, design fidelity).
|
|
54
|
+
- `checkpointer`-less MACHINE → demote to HUMAN — the server rejects checkpointer-less MACHINE criteria rather than silently rewriting them. Don't invent a fake checkpointer to keep it MACHINE.
|
|
55
|
+
|
|
56
|
+
**Two checkpointer traps:**
|
|
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 (same trap class as the BSD-grep `-P` incident, LUM-401). `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 (LUM-433 c1, fixed in #528). 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).
|
|
128
60
|
|
|
129
61
|
### judgeSteps — agent-drafted judging steps for HUMAN criteria (LUM-465)
|
|
130
62
|
|
|
131
|
-
A HUMAN criterion is judged by a person, not a checkpointer — so don't hand them
|
|
132
|
-
a bare assertion ("the copy reads naturally") and make them reverse-engineer
|
|
133
|
-
what to do. Attach **`judgeSteps`**: short, human-readable instructions the
|
|
134
|
-
adjudication card renders verbatim (light markdown, URLs are made clickable).
|
|
135
|
-
Structured labour is yours; the human just follows the steps.
|
|
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.
|
|
136
64
|
|
|
137
65
|
**Shape — 1–3 steps, always in this order:**
|
|
138
66
|
|
|
139
|
-
1.
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
schema **and** self-credit not implemented": those are two assertions; the first
|
|
149
|
-
is even machine-able (below), the second is a separate human read.
|
|
150
|
-
|
|
151
|
-
**Machine-able → make it MACHINE, don't burn human attention.** Before writing
|
|
152
|
-
`judgeSteps`, ask whether a checkpointer could decide it. "The migration is purely
|
|
153
|
-
additive" → `grep` the `migration.sql` for `DROP`/destructive DDL → MACHINE. "No
|
|
154
|
-
file under `prisma/migrations/` was deleted" → a `git diff` probe → MACHINE.
|
|
155
|
-
Reserve HUMAN + `judgeSteps` for genuine taste/feel/fidelity judgments a check
|
|
156
|
-
can't make.
|
|
157
|
-
|
|
158
|
-
`judgeSteps` is **optional and non-blocking** — a HUMAN criterion without it still
|
|
159
|
-
submits, but the server returns a soft warning (same channel as the 3–7 count
|
|
160
|
-
warning) and the card shows a "no judging guidance" fallback. It is part of the
|
|
161
|
-
contract: editing it drifts like any other field (`CRITERION_CHANGED` before/after
|
|
162
|
-
carries it), and it survives a statement-only web edit.
|
|
67
|
+
1. `Where to look` — the exact link or path (a task URL, a tab, a file). Paste the real URL; the card / `lumo task status` render it clickable.
|
|
68
|
+
2. `What to do` — the concrete action ("open the acceptance tab", "read the `judgeSteps` block aloud", "resize to mobile width").
|
|
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
|
+
|
|
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 LUM-397's "additive schema **and** self-credit not implemented": two assertions; the first is even machine-able (below), the second is a separate human read.
|
|
72
|
+
|
|
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
|
+
|
|
75
|
+
`judgeSteps` is **optional and non-blocking** — a HUMAN criterion without it still submits, but the server returns a soft warning (same channel as the 3–7 count warning) and the card shows a "no judging guidance" fallback. It is part of the contract: editing it drifts like any other field (`CRITERION_CHANGED` before/after carries it), and it survives a statement-only web edit.
|
|
163
76
|
|
|
164
77
|
```json
|
|
165
78
|
{
|
|
@@ -171,71 +84,32 @@ carries it), and it survives a statement-only web edit.
|
|
|
171
84
|
|
|
172
85
|
### Invariant (negative-assertion) criteria
|
|
173
86
|
|
|
174
|
-
Most criteria assert that **something that should happen, happened** ("the
|
|
175
|
-
|
|
176
|
-
the
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
**When to write one.** Add a single invariant criterion when the task has a
|
|
185
|
-
concrete, nameable surface the change must not disturb — a data-destruction red
|
|
186
|
-
line, a structure guard, an always-green baseline set, a diff that must stay
|
|
187
|
-
in-bounds, a standing budget. It is decision support, not a ritual: reach for it
|
|
188
|
-
only when you can point at the surface. If the only invariant you can name is
|
|
189
|
-
"don't break the build", that's already the repo's PR baseline (tests / `tsc` /
|
|
190
|
-
lint / i18n parity) — don't spend a slot restating it.
|
|
191
|
-
|
|
192
|
-
**How to phrase it.** State the invariant as _still holding after the change_,
|
|
193
|
-
not as a step you took. The **c-CRAB boundary** is the hard rule: the check
|
|
194
|
-
encodes **"the problem does not (re)occur"**, never **"a specific fix exists"**.
|
|
195
|
-
Write "`prisma/migrations/` has no deleted files vs origin/main" (the bad state
|
|
196
|
-
is absent) — not "the migration-delete guard function is present" (a named fix).
|
|
197
|
-
The first survives a refactor of _how_ the invariant is enforced; the second
|
|
198
|
-
re-fails the moment someone renames the guard, and passes even if the protection
|
|
199
|
-
was gutted some other way.
|
|
200
|
-
|
|
201
|
-
**Pairing with a checkpointer.** An invariant almost always has a runnable check
|
|
202
|
-
— that's its advantage — so prefer MACHINE. The existing `checkpointer` syntax
|
|
203
|
-
carries it as-is: a `git diff` path/filter probe, a full-suite `jest` run, a
|
|
204
|
-
structure-verify script, a parity check. Two real-repo examples:
|
|
205
|
-
|
|
206
|
-
- **`prisma/migrations/` files are never deleted** (the CLAUDE.md red line). The
|
|
207
|
-
bad state is a deleted migration file in the diff:
|
|
208
|
-
|
|
209
|
-
```json
|
|
87
|
+
Most criteria assert that **something that should happen, happened** ("the endpoint returns 409", "the section renders"). An _invariant criterion_ asserts the dual: that **something that must not happen, didn't** — the change left an explicit don't-touch surface intact. This is the acceptance analogue of AppWorld's _collateral-damage_ checks (arXiv 2407.18901) and ToolSandbox's _minefield_ states (arXiv 2408.04682): a finished task is judged not only on the intended effect but on the forbidden effects it avoided. `lumo verify` already judges final state per round, so an invariant fits the existing machinery with no new mechanism — a drafting habit, not a feature.
|
|
88
|
+
|
|
89
|
+
- `When to write one` — add a single invariant criterion when the task has a concrete, nameable surface the change must not disturb: a data-destruction red line, a structure guard, an always-green baseline set, a diff that must stay in-bounds, a standing budget. Decision support, not a ritual: reach for it only when you can point at the surface. If the only invariant you can name is "don't break the build", that's already the repo's PR baseline (tests / `tsc` / lint / i18n parity) — don't spend a slot restating it.
|
|
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
|
+
- `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
|
+
|
|
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 (the LUM-349 incident: an HTML→md round-trip silently collapsed tables to plain text):
|
|
94
|
+
|
|
95
|
+
```json
|
|
96
|
+
[
|
|
210
97
|
{
|
|
211
98
|
"statement": "No file under prisma/migrations/ is deleted by this change (vs origin/main)",
|
|
212
99
|
"verifierType": "MACHINE",
|
|
213
100
|
"checkpointer": "bash -c \"test -z \\\"$(git diff --diff-filter=D --name-only origin/main -- prisma/migrations/)\\\"\""
|
|
214
|
-
}
|
|
215
|
-
```
|
|
216
|
-
|
|
217
|
-
- **A live-doc's table structure is not flattened** (the LUM-349 incident: an
|
|
218
|
-
HTML→md round-trip silently collapsed tables to plain text). The verify script
|
|
219
|
-
hard-fails if any table / row / heading count shrank:
|
|
220
|
-
|
|
221
|
-
```json
|
|
101
|
+
},
|
|
222
102
|
{
|
|
223
103
|
"statement": "Live-doc keeps its table structure after the edit (no rows/headings dropped)",
|
|
224
104
|
"verifierType": "MACHINE",
|
|
225
105
|
"checkpointer": "npx tsx scripts/verify-live-doc.ts <docId> docs/live-docs/<file>.md"
|
|
226
106
|
}
|
|
227
|
-
|
|
107
|
+
]
|
|
108
|
+
```
|
|
228
109
|
|
|
229
|
-
A third lives in a current contract you can copy: **LUM-415**'s "SKILL.md
|
|
230
|
-
frontmatter description stays ≤ 1000 chars" — the standing context-budget
|
|
231
|
-
invariant (the description is resident in every session), checkpointed by the
|
|
232
|
-
`description cap` case in
|
|
233
|
-
`scripts/analysis/lum392-cli-friction/__tests__/doc-examples.test.ts`.
|
|
110
|
+
A third lives in a current contract you can copy: **LUM-415**'s "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`.
|
|
234
111
|
|
|
235
|
-
One invariant criterion is usually enough — it's the guardrail, not the whole
|
|
236
|
-
contract. Pair it with the positive criteria that say what the change should
|
|
237
|
-
achieve; together they assert _did the right thing_ **and** _touched nothing
|
|
238
|
-
it shouldn't_.
|
|
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_.
|
|
239
113
|
|
|
240
114
|
## JSON file format
|
|
241
115
|
|
|
@@ -262,73 +136,53 @@ it shouldn't_.
|
|
|
262
136
|
]
|
|
263
137
|
```
|
|
264
138
|
|
|
265
|
-
Fields:
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
139
|
+
Fields:
|
|
140
|
+
|
|
141
|
+
- `statement` — required, ≤2000 chars.
|
|
142
|
+
- `verifierType` — `"MACHINE"` | `"HUMAN"`.
|
|
143
|
+
- `checkpointer` — required for MACHINE.
|
|
144
|
+
- `evidenceRequired` — optional, default false.
|
|
145
|
+
- `judgeSteps` — optional, ≤2000 chars; agent-drafted human-judging steps for a HUMAN criterion (see "judgeSteps" above).
|
|
146
|
+
- `id` — only in `--human` revisions (see below).
|
|
270
147
|
|
|
271
148
|
## Commands
|
|
272
149
|
|
|
273
150
|
### `lumo task criteria set <task> --file <criteria.json>`
|
|
274
151
|
|
|
275
|
-
Agent draft, recorded as `AGENT_DRAFT` at round 0. **Editable until DONE:**
|
|
276
|
-
while `workStartedAt` is unset (task never left TODO) a resubmit replaces the
|
|
277
|
-
whole contract (full-group baseline). Once work has started an agent resubmit
|
|
278
|
-
applies an **identity-preserving diff** — matched by id, with each add/update/
|
|
279
|
-
delete recorded as a `CRITERION_CHANGED` drift event; no 409. The contract locks
|
|
280
|
-
(409) only once the task is **DONE** — reopen it to change criteria, or use
|
|
281
|
-
`--human`. A criterion that already has verification runs is soft-deleted (audit
|
|
282
|
-
trail preserved) rather than hard-deleted; run-free criteria are hard-deleted.
|
|
283
|
-
Echoes the stored criteria (with ids) plus the 3–7 soft-cap warning if outside
|
|
284
|
-
range.
|
|
152
|
+
Agent draft, recorded as `AGENT_DRAFT` at round 0. **Editable until DONE:** while `workStartedAt` is unset (task never left TODO) a resubmit replaces the whole contract (full-group baseline). Once work has started an agent resubmit applies an **identity-preserving diff** — matched by id, each add/update/delete recorded as a `CRITERION_CHANGED` drift event; no 409. The contract locks (409) only once the task is **DONE** — reopen it to change criteria, or use `--human`. A criterion that already has verification runs is soft-deleted (audit trail preserved); run-free criteria are hard-deleted. Echoes the stored criteria (with ids) plus the 3–7 soft-cap warning if outside range. `--file` must point inside the current project directory — write the JSON to the repo root or a subdirectory, not `/tmp`, and delete it after submission.
|
|
285
153
|
|
|
286
154
|
```bash
|
|
287
155
|
lumo task criteria set LUM-42 --file criteria-lum42.json
|
|
288
156
|
```
|
|
289
157
|
|
|
290
|
-
(`--file` must point inside the current project directory — write the JSON to
|
|
291
|
-
the repo root or a subdirectory, not `/tmp`, and delete it after submission.)
|
|
292
|
-
|
|
293
158
|
### `lumo task criteria set <task> --file <criteria.json> --human`
|
|
294
159
|
|
|
295
|
-
Record a **human contract revision** (HUMAN_EDIT), e.g. when the user changes
|
|
296
|
-
the contract in conversation — you transcribe their decision. Allowed at any
|
|
297
|
-
time, even after the agent lock. The file is the **desired final list**:
|
|
160
|
+
Record a **human contract revision** (HUMAN_EDIT), e.g. when the user changes the contract in conversation — you transcribe their decision. Allowed at any time, even after the agent lock. The file is the **desired final list**:
|
|
298
161
|
|
|
299
|
-
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
- items without `id` are created as `HUMAN_EDIT`
|
|
303
|
-
- existing criteria missing from the file are **deleted** — refused with 409
|
|
304
|
-
if the criterion already has verification runs (reword it instead)
|
|
162
|
+
- `id` present (from `criteria list`) — keeps that criterion; changed fields update it and stamp it `HUMAN_EDIT`; identical fields leave it (and its provenance) untouched
|
|
163
|
+
- `id` absent — the item is created as `HUMAN_EDIT`
|
|
164
|
+
- `id` missing-from-file — existing criteria not in the file are **deleted**; refused with 409 if the criterion already has verification runs (reword it instead)
|
|
305
165
|
|
|
306
|
-
The revision is auto-recorded as a task comment with the session origin
|
|
307
|
-
(`X-Lumo-Session-Id`). **Transcribing the contract is allowed; transcribing a
|
|
308
|
-
verdict is never** — human verdicts only enter through human-initiated paths
|
|
309
|
-
(web UI / Slack action), and the agent has no write path to them.
|
|
310
|
-
|
|
311
|
-
Only use `--human` for decisions a human actually made (in conversation, in a
|
|
312
|
-
comment, in review). Never use it to work around your own lock.
|
|
166
|
+
The revision is auto-recorded as a task comment with the session origin (`X-Lumo-Session-Id`). **Transcribing the contract is allowed; transcribing a verdict is never** — human verdicts only enter through human-initiated paths (web UI / Slack action), and the agent has no write path to them. Only use `--human` for decisions a human actually made (in conversation, in a comment, in review). Never use it to work around your own lock.
|
|
313
167
|
|
|
314
168
|
```bash
|
|
315
169
|
lumo task criteria set LUM-42 --file criteria-revision.json --human
|
|
316
170
|
lumo task criteria set LUM-42 --file criteria-revision.json --human --cause SCOPE_CHANGE
|
|
317
171
|
```
|
|
318
172
|
|
|
319
|
-
Optionally annotate **why** the contract drifted with
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
173
|
+
Optionally annotate **why** the contract drifted with `--cause <NEW_INFO|SCOPE_CHANGE|DRAFT_BLIND_SPOT|GRANULARITY|OTHER>` — pick the tag from the human's stated reason:
|
|
174
|
+
|
|
175
|
+
- `NEW_INFO` — new information.
|
|
176
|
+
- `SCOPE_CHANGE` — scope moved.
|
|
177
|
+
- `DRAFT_BLIND_SPOT` — the draft missed it.
|
|
178
|
+
- `GRANULARITY` — wrong granularity.
|
|
179
|
+
- `OTHER` — anything else.
|
|
180
|
+
|
|
181
|
+
The tag lands in the drift record (TaskActivity payload), feeding the Slice-3 drift-cause distribution. Every criteria add/update/delete is mirrored as a structured `CRITERION_CHANGED` activity automatically; `--cause` just enriches it.
|
|
326
182
|
|
|
327
183
|
### `lumo task criteria list <task>`
|
|
328
184
|
|
|
329
|
-
Print the contract: `<id> [MACHINE|HUMAN] SOURCE@rN [evidence] statement`
|
|
330
|
-
plus an indented `↳ check:` line for checkpointers. Use it to fetch ids
|
|
331
|
-
before a `--human` revision. Empty contract prints a drafting pointer.
|
|
185
|
+
Print the contract: `<id> [MACHINE|HUMAN] SOURCE@rN [evidence] statement` plus an indented `↳ check:` line for checkpointers. Use it to fetch ids before a `--human` revision. Empty contract prints a drafting pointer.
|
|
332
186
|
|
|
333
187
|
```bash
|
|
334
188
|
lumo task criteria list LUM-42
|
|
@@ -336,20 +190,11 @@ lumo task criteria list LUM-42
|
|
|
336
190
|
|
|
337
191
|
## Injection behavior
|
|
338
192
|
|
|
339
|
-
- **Session start** (bound task): the contract is the highest-priority
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
-
|
|
343
|
-
binding.
|
|
344
|
-
- **`lumo task context`**: the `## Acceptance criteria (contract)` section appears after the
|
|
345
|
-
task description, before memory.
|
|
346
|
-
- Review-time gap findings are appended at the round they surface and show up
|
|
347
|
-
in the contract automatically — `REVIEW_ADDED` provenance via human review
|
|
348
|
-
paths; findings a human raises in conversation are transcribed with
|
|
349
|
-
`--human` + `--cause` (see verify.md "Review-time drift habits").
|
|
193
|
+
- **Session start** (bound task): the contract is the highest-priority section in the injection budget — ahead of memory/PR/Slack/Figma/web. If a still-open task has no criteria, the draft reminder is injected instead.
|
|
194
|
+
- **`lumo session attach`**: prints the contract (or the draft reminder) right after binding.
|
|
195
|
+
- **`lumo task context`**: the `## Acceptance criteria (contract)` section appears after the task description, before memory.
|
|
196
|
+
- **Review-time gap findings** are appended at the round they surface and show up in the contract automatically — `REVIEW_ADDED` provenance via human review paths; findings a human raises in conversation are transcribed with `--human` + `--cause` (see verify.md "Review-time drift habits").
|
|
350
197
|
|
|
351
198
|
## After the contract: the verification loop
|
|
352
199
|
|
|
353
|
-
The contract is judged by `lumo verify` — run it before claiming the task is
|
|
354
|
-
done. See [verify.md](verify.md) for the loop (round cap 3, IN_REVIEW on
|
|
355
|
-
all-pass, escalation on a round-3 fail).
|
|
200
|
+
The contract is judged by `lumo verify` — run it before claiming the task is done. See [verify.md](verify.md) for the loop (round cap 3, IN_REVIEW on all-pass, escalation on a round-3 fail).
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Editing live documents
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
**The red line (LUM-349):** 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
|
+
|
|
7
|
+
## `lumo doc show <doc> [--raw | --section <heading>]` — print one document's detail
|
|
8
|
+
|
|
9
|
+
Default mode prints a key:value header (id, title, scope, project, created/updated, **revision**, mentioned tasks) and the content rendered back to markdown. `Revision:` is the body's optimistic-concurrency counter — feed it back as `--if-revision` on `doc update` / `doc patch` / `doc append`.
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
lumo doc show "RFC: doc CLI"
|
|
13
|
+
lumo doc show cmd_xxx --raw > base.md # byte-identical edit base (revision on stderr)
|
|
14
|
+
lumo doc show cmd_xxx --section "D 状态表" > sec.md # one section only (revision on stderr)
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
**`--raw` (LUM-408)** 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
|
+
|
|
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 (that fallback flattened tables: LUM-349). Rebuild flow (LUM-446): 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
|
+
- 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
|
+
|
|
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
|
+
|
|
25
|
+
Output budget (LUM-428): **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
|
+
|
|
27
|
+
**`--section <heading>` (LUM-409)** 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
|
+
|
|
29
|
+
- Section addressing: pass the heading text (`--section "D 状态表"`), matched in three tiers — exact, then case-insensitive, then (LUM-447) 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
|
+
- Missing heading → exit 1 listing the available headings; ambiguous heading → exit 1 with a depth-disambiguation hint.
|
|
31
|
+
- Requires a stored markdown source — same no-fallback rule and rebuild flow as `--raw`.
|
|
32
|
+
- Heading detection is markdown-aware: `#` lines inside fenced code blocks or blockquotes are never section boundaries.
|
|
33
|
+
|
|
34
|
+
Use default mode to read a doc; `--raw` whenever the full output will be edited and uploaded back; `--section` when only one part matters (keeps the context window small and the patch radius smaller).
|
|
35
|
+
|
|
36
|
+
### When to suggest `doc show --raw` / `--section`
|
|
37
|
+
|
|
38
|
+
- Before any `doc update --file` that starts from existing remote content — fetch the base with `--raw`, never from rendered `doc show` output.
|
|
39
|
+
- Before a `doc patch` — read the section with `--section` first, note the `Revision:` line, edit, patch back with `--if-revision`.
|
|
40
|
+
- User asks "what's the exact source of this doc", or an agent needs a faithful local copy of a live doc.
|
|
41
|
+
- User asks "what's in section X" of a long doc — `--section` avoids pulling the whole body into context.
|
|
42
|
+
- If `--raw`/`--section` errors (no stored source), run `lumo doc rebuild-source <doc>` to regenerate it losslessly, then retry — don't edit the rendered output.
|
|
43
|
+
|
|
44
|
+
## `lumo doc patch <doc> --section <heading>` — replace one section
|
|
45
|
+
|
|
46
|
+
Replaces the **whole addressed section** (heading line included, subsections included) with the provided content, server-side, leaving every byte outside the section untouched. The new content comes from `--content`, `--file`, or piped stdin (one required; `--file` is sandboxed like `doc update`).
|
|
47
|
+
|
|
48
|
+
| Flag | Type | Notes |
|
|
49
|
+
| --------------------- | ------- | ----------------------------------------------------------------------------------------------------------- |
|
|
50
|
+
| `--section <heading>` | string | Required. Heading text addressing the section; prefix `#…` pins the depth. |
|
|
51
|
+
| `--content <text>` | string | New section content (include the heading line — the whole section is replaced verbatim). |
|
|
52
|
+
| `--file <path>` | string | New section content from file (project-local sandbox). |
|
|
53
|
+
| `--if-revision <n>` | int | Only apply if the body is still at revision `n`. Recommended whenever the edit base was read earlier. |
|
|
54
|
+
| `--allow-shrink` | boolean | Let the patch through even when it drops tables/rows/headings within the addressed section (422 otherwise). |
|
|
55
|
+
|
|
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
|
+
|
|
58
|
+
Structure guard (LUM-410), **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
|
+
|
|
60
|
+
Requires a stored markdown source (same rule as `--raw`); errors with the rebuild hint otherwise.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
lumo doc show cmd_xxx --section "D 状态表" > sec.md # stderr: Revision: 6
|
|
64
|
+
# … edit sec.md …
|
|
65
|
+
lumo doc patch cmd_xxx --section "D 状态表" --file sec.md --if-revision 6
|
|
66
|
+
# → Patched cmd_xxx "登记册" § "D 状态表" revision 7
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### When to suggest `doc patch`
|
|
70
|
+
|
|
71
|
+
- User wants to change one section of a live doc ("update the status table", "rewrite section D") — patch beats full `doc update` on clobber radius and context size.
|
|
72
|
+
- Live-doc status updates that used to be read-whole/edit/upload-whole round-trips.
|
|
73
|
+
- If the patch 409s: re-read the section, rebase the edit, retry — never fall back to a full-body upload from the stale base.
|
|
74
|
+
- If the patch 422s (structure guard): the replacement drops tables/rows/headings the section has. Re-check the edit base first; add `--allow-shrink` only when the deletion is what the user wants.
|
|
75
|
+
|
|
76
|
+
## `lumo doc append <doc> [--section <heading>]` — append to a section (or the doc)
|
|
77
|
+
|
|
78
|
+
Inserts the new content at the **end of the addressed section** (just before the next same-or-higher-level heading), or at the end of the document when `--section` is omitted. Pure insertion: no pre-existing byte is modified, which makes it the natural write for running logs, ledgers and queues. Content channels and sandbox are the same as `doc patch`; separator blank lines are added automatically.
|
|
79
|
+
|
|
80
|
+
| Flag | Type | Notes |
|
|
81
|
+
| --------------------- | ------ | ---------------------------------------------------------------------------- |
|
|
82
|
+
| `--section <heading>` | string | Optional. Omit to append at the document end. |
|
|
83
|
+
| `--content <text>` | string | Content to append. |
|
|
84
|
+
| `--file <path>` | string | Content from file (project-local sandbox). |
|
|
85
|
+
| `--if-revision <n>` | int | Only apply if the body is still at revision `n`; 409 + retry hint otherwise. |
|
|
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** (LUM-444): 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
|
+
|
|
89
|
+
```bash
|
|
90
|
+
lumo doc append cmd_xxx --section "F 待办队列" --content "- [ ] 评估 XYZ 论文"
|
|
91
|
+
# → Appended to cmd_xxx "登记册" § "F 待办队列" revision 8
|
|
92
|
+
echo "## 2026-06-10\n吸收了 3 篇" | lumo doc append cmd_xxx # document end
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
### When to suggest `doc append`
|
|
96
|
+
|
|
97
|
+
- User wants to add an entry/row/log line to a section ("把 X 加进待办队列", "append today's notes") — this is the killer op for ledger-style live docs: zero clobber risk by construction.
|
|
98
|
+
- Whenever the alternative would be re-uploading the whole doc just to add lines at the end of one section.
|
|
99
|
+
|
|
100
|
+
## `lumo doc diff <doc> --file <local.md>` — compare remote markdown source vs a local file
|
|
101
|
+
|
|
102
|
+
Compares the server-side stored markdown source (the byte-identical last markdown upload) against a local file, making remote/local split-brain visible on demand.
|
|
103
|
+
|
|
104
|
+
| Flag | Type | Notes |
|
|
105
|
+
| --------------- | ------ | --------------------------------------------------------------------------------- |
|
|
106
|
+
| `--file <path>` | string | Required. Local markdown file; same project-local sandbox as `doc update --file`. |
|
|
107
|
+
|
|
108
|
+
Exit codes: **0** = byte-identical (prints `Clean: …`), **1** = divergent (prints a unified diff, `--- remote/<id> (sourceMarkdown)` vs `+++ local/<file>`) or error. A doc without a stored markdown source errors explicitly (upload a markdown base once, then diff works).
|
|
109
|
+
|
|
110
|
+
```bash
|
|
111
|
+
lumo doc diff cmd_xxx --file docs/live-docs/research-intake-ledger.md
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### When to suggest `doc diff`
|
|
115
|
+
|
|
116
|
+
- Before uploading a locally edited file with `doc update --file` — check whether the remote source moved since the local copy was taken (prevents stale-upload clobbering, the #460 incident class).
|
|
117
|
+
- When a repo-tracked markdown source (e.g. `docs/live-docs/`) and the live doc may have drifted and the user asks which is current.
|
|
118
|
+
- After a suspected concurrent edit: a clean diff (exit 0) proves remote and local are in sync.
|
|
119
|
+
|
|
120
|
+
## `lumo doc rebuild-source <doc>` — regenerate the markdown source from the HTML body
|
|
121
|
+
|
|
122
|
+
The **recovery path for a source-less doc** (LUM-446). 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 — LUM-349 — 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
|
+
|
|
124
|
+
| Flag | Type | Notes |
|
|
125
|
+
| ------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------ |
|
|
126
|
+
| `--allow-shrink` | boolean | Commit even if the rebuilt source re-renders with fewer tables/rows/headings than the stored body (default: 422 reject). |
|
|
127
|
+
| `--force` | boolean | Re-derive even when a source already exists (default: 409 — protects a byte-faithful human source from a downgrade). |
|
|
128
|
+
| `--if-revision <n>` | int | Only apply if the body is still at this revision (from `doc show`). |
|
|
129
|
+
|
|
130
|
+
The rebuild is **structure-guarded** (the same LUM-410 口径 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
|
+
|
|
132
|
+
```bash
|
|
133
|
+
lumo doc rebuild-source cmd_xxx # restore a source-less doc; --raw works after
|
|
134
|
+
lumo doc rebuild-source cmd_xxx --force # re-derive even if a source already exists
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
### When to suggest `doc rebuild-source`
|
|
138
|
+
|
|
139
|
+
- A `--raw` / `--section` / `doc patch` / `doc diff` call errored with "no stored markdown source" — rebuild, then retry. This is the first thing to try, not a manual reconstruction.
|
|
140
|
+
- A table-heavy live doc (e.g. a `docs/live-docs/` registry) lost its source after a web operation and the markdown write path is locked.
|
|
141
|
+
- Do **not** run it on a doc that already has a good source unless the user explicitly wants to re-derive it (then pass `--force`) — it replaces the byte-faithful source with a serializer-derived one.
|
|
142
|
+
|
|
143
|
+
## Out of scope (CLI v1)
|
|
144
|
+
|
|
145
|
+
- Lossless markdown round-trip from **rendered** `doc show` output (use `doc show --raw` — lossless whenever a markdown source is stored).
|
|
146
|
+
- `--from-editor` (interactive $EDITOR).
|