@lemoncode/lemony 0.4.0 → 0.5.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/catalog/VERSION CHANGED
@@ -1 +1 @@
1
- 0.4.0
1
+ 0.5.0
@@ -54,6 +54,16 @@ what you run, only how many calls carry it.
54
54
  behavior (vertical slices, never all-tests-then-all-code). When your invocation
55
55
  says **auto-commit is OFF**, the whole run obeys §Staging protocol (below) —
56
56
  zero commits, staged save-points.
57
+ **The checkbox is yours** (L1 — a task with a `tasks.md`; L2 has none). A task's
58
+ `- [ ]` is its done-marker: flip it to `- [x]` the moment the task reaches green
59
+ (suite passing), and let the tick ride the task's own commit (auto-commit OFF:
60
+ the same `git add` — at the human's OK the Orchestrator's pathspec split lands it
61
+ in the step's state commit beside the code commit; the index is where the two
62
+ move together) — so the file always says where the work is. Nobody
63
+ else ticks for you: `lemony review-ledger validate` reports a task still `[ ]` in
64
+ a group the loop has passed (`unticked-completed-task`) and the human reads it as
65
+ a forgotten mark **or as work never done** — the honest reason to keep the two in
66
+ step.
57
67
  **Scope: exactly what the invocation hands you.** By default that is the whole
58
68
  `tasks.md` list (all-at-once). In **step-by-step mode** the Orchestrator
59
69
  invokes you per step — scoped to **one group**, handed **by reference** (the
@@ -124,9 +134,10 @@ iteration only knows what this contract and the worktree tell it:
124
134
 
125
135
  - **Zero commits, zero pushes.** Never `git commit`, never `git push` — not for
126
136
  code, not for state. The Orchestrator commits once the human OKs.
127
- - **Stage after every green task** — `git add -A` when a checkbox/task reaches
128
- green (suite passing). The index is your save-point ladder: everything staged
129
- is a proven-green floor.
137
+ - **Stage after every green task** — tick the task's checkbox in `tasks.md`
138
+ (`- [ ]` → `- [x]`, step 2 of the procedure), then `git add -A`, when a task
139
+ reaches green (suite passing). The index is your save-point ladder: everything
140
+ staged is a proven-green floor, and the tick is part of the floor.
130
141
  - **Never stage mid-experiment.** A `git add` while red or mid-refactor silently
131
142
  **overwrites the save-point** — `git status` looks identical afterwards, and
132
143
  recovery yields nameless blobs (practically unrecoverable). Stage only at
@@ -365,7 +365,9 @@ numbers still never shift. For each group, in order:
365
365
  (fresh context, as always) scoped to **this one group**: give it the branch, the
366
366
  task-state paths, and the group's id + header line in `tasks.md` — by reference
367
367
  (§Sub-agent invocation), it reads the group's tasks from the file itself (`tdd`
368
- skill — checkbox by checkbox with small commits, as always). It commits to the
368
+ skill — checkbox by checkbox with small commits, as always, and it **ticks each
369
+ checkbox at green** — the tick rides the task's commit, or its `git add` under
370
+ auto-commit OFF; the file is the group's progress record). It commits to the
369
371
  branch (auto-commit OFF: it **stages** instead of committing — point the
370
372
  invocation at the staging protocol in `implementer.md`), logs to `progress.md`,
371
373
  and signals done. **No PR yet** — the PR opens after the last group,
@@ -422,13 +424,15 @@ numbers still never shift. For each group, in order:
422
424
  gates or the bare real-run floor (`undeclared`).
423
425
  Red → **an APPROVE with a red ledger is never relayed**, and a red ledger is not a
424
426
  REJECT of the code: the Implementer is not at fault and is **never** re-invoked for
425
- it. Route on the `[kind]` lines the verb prints — it closes with
427
+ the red itself (a human's `changes` at the checkpoint it feeds is the human's call, as
428
+ at every checkpoint). Route on the `[kind]` lines the verb prints — it closes with
426
429
  `N spec-side problem(s) … do not retry the Reviewer while any of these stands.`
427
430
  whenever any problem lives outside the sidecar:
428
431
 
429
432
  - **any spec-side problem** — the verb counts them out loud (`unknown-risk-class`, a
430
433
  tag or ref list that did not parse, a duplicated group number, an orphan task, an
431
- empty group, a dangling requirement ref, a step with no group) → no retry. They
434
+ empty group, a dangling requirement ref, a step with no group, a task still
435
+ `[ ]` in a group the loop has passed) → no retry. They
432
436
  live in `tasks.md` / `requirements.md`, which the Reviewer cannot fix: stop and
433
437
  bring them to the human as an **anticipated checkpoint** (below) with the verb's
434
438
  lines as the content — the spec needs a decision, and the fix routes as a discovery
@@ -436,6 +440,37 @@ numbers still never shift. For each group, in order:
436
440
  the sidecar on disk: green → the checkpoint; a sidecar-side red then follows the
437
441
  one-retry rule below. The human's `ok` on an anticipated checkpoint is their call,
438
442
  as at every gate — what never happens is you relaying a red as an APPROVE.
443
+ **`unticked-completed-task` is the one spec-side kind that needs no discovery**:
444
+ the Implementer ticks each task at green, so an unticked task in a passed group
445
+ is a forgotten mark or work that was never done, and only the human can tell
446
+ which. Name the task ids in the checkpoint and ask. `ok` = "done, the mark was
447
+ forgotten" → tick it yourself (`- [ ]` → `- [x]`) and **stage it the moment you
448
+ do** (`git add .claude/state/tasks/<id>/spec/tasks.md`) — a mediated edit, staged
449
+ on arrival like a Spec Author update, so the checkpoint contract's spec check
450
+ (item 4) never reads it as an unconfirmed human spec edit — then re-run the verb.
451
+ Wherever an OK's composite commits state, the staged tick rides its
452
+ `.claude/state` half and needs nothing more: every step OK (both knob states),
453
+ and auto-commit OFF's deferral-ending checkpoint OK, which its pre-gate full pass
454
+ feeds. **At the merge-gate presentation nothing else commits state before the
455
+ merge** — the step-8 full pass in either mode, all-at-once under auto-commit ON
456
+ having had no checkpoint at all — so there, commit it yourself as you stage it,
457
+ in the same composite turn as the re-run (§Turn economy):
458
+
459
+ ```bash
460
+ git commit -m "task(<id>): tick <T-ids> — human ok on unticked-completed-task" \
461
+ -- .claude/state/tasks/<id>/spec/tasks.md; \
462
+ git push # best-effort — a failure warns, never blocks
463
+ ```
464
+
465
+ A merged `tasks.md` still reading `[ ]` is exactly the harm the kind exists to
466
+ stop. That post-APPROVE state write moves the stale-approve fingerprint the way
467
+ the ledger's own write does, and routes as the merge gate's exit-40 rule says
468
+ (§Merge gate) — a fresh APPROVE re-records the hashes; `--force` stays the
469
+ human's separate, informed call. `changes` = the
470
+ task is not done → the normal fix iteration, feedback naming the task. Never tick
471
+ on your own judgment: the mark is the Implementer's claim, and the verb exists so
472
+ a missing one is read by a person, not smoothed over by you.
473
+
439
474
  - **anything else** (a problem in the sidecar itself) → re-invoke the **Reviewer**
440
475
  (fresh, as always) **once**, with the
441
476
  verb's `[kind] message` lines **verbatim** in the spawn prompt — the delta is the
@@ -461,7 +496,12 @@ numbers still never shift. For each group, in order:
461
496
  `step_completed` emit (here `review_iterations` is 3) and the same transient
462
497
  `awaiting human checkpoint (step N/M)` line in `progress.md` — except you present
463
498
  the unresolved disagreement (both positions, the spec slice) instead of a clean
464
- step.
499
+ step. When its content is the verb's `unticked-completed-task` lines, `ok` carries
500
+ one act before the state commit — tick and stage the named tasks (step 2's routing
501
+ bullet) — and the transient line names them,
502
+ `awaiting human checkpoint (step N/M) [unticked: T3, T4]` (step 5), so a cold
503
+ `/resume` re-presents the same content and owes the same act (the clean-step line
504
+ carries no suffix).
465
505
 
466
506
  In auto-commit OFF, `OK` is also the moment the group's single code commit
467
507
  lands, and `changes` sends the fresh Implementer to iterate **over the worktree**
@@ -571,8 +611,10 @@ numbers still never shift. For each group, in order:
571
611
  step's line is transient — update it in place as the loop progresses
572
612
  (`fix-loop iteration K — in progress` while implementing/reviewing,
573
613
  `awaiting ledger retry (step N/M, retry 1/1)` while the fresh Reviewer redoes a red
574
- ledger, `awaiting human checkpoint (step N/M)` while waiting on the human), then
575
- replace it with the resolved outcome:
614
+ ledger, `awaiting human checkpoint (step N/M)` while waiting on the human — with
615
+ the `[unticked: T3, T4]` suffix when the checkpoint is the anticipated one an
616
+ `unticked-completed-task` red raised, in all-at-once too, where the counter is
617
+ absent), then replace it with the resolved outcome:
576
618
 
577
619
  ```markdown
578
620
  Mode: step-by-step
@@ -611,7 +653,10 @@ spec-side problem to the human, and never an APPROVE relayed on a red ledger. Ou
611
653
  the step loop the transient line is `awaiting ledger retry (full pass, retry 1/1)` in
612
654
  `progress.md` (no step counter), and "the human" is the gate the pass feeds: the
613
655
  auto-commit-OFF checkpoint when there is one, otherwise the merge-gate presentation,
614
- with the verb's lines as the content.
656
+ with the verb's lines as the content. An `unticked-completed-task` there resolves as
657
+ in step 2's routing bullet — on `ok`, tick and stage the named tasks; at the
658
+ auto-commit-OFF checkpoint the OK's composite commits them, at the merge-gate
659
+ presentation you commit them yourself — then re-run the verb.
615
660
 
616
661
  ## Checkpoint contract (how a human gate presents work)
617
662
 
@@ -668,7 +713,10 @@ contract, not a vibe:
668
713
  agent-staged floor, so this check fires only on **unmediated** edits and
669
714
  the re-presented checkpoint is clean; an unstaged edit that matches the
670
715
  recorded `**Resolution**` in `discoveries.md` is confirmed content — stage
671
- it, don't re-raise.
716
+ it, don't re-raise. The one mediated edit that is **not** a discovery — your
717
+ own `- [ ]` → `- [x]` tick on the human's `ok` to an `unticked-completed-task`
718
+ checkpoint (step 2) — is staged the same way, as you make it; it has no
719
+ `discoveries.md` entry to match, and needs none: the `ok` is its record.
672
720
 
673
721
  The **spec check runs in both knob states** — in auto-commit ON, run it
674
722
  **before** step 3's `awaiting` state commit, which would otherwise silently
@@ -228,9 +228,15 @@ What the validator enforces in this version, so you never have to guess:
228
228
  - **Spec-side problems are reported, never dropped — and they are not yours to fix.** A
229
229
  `[risk: …]` tag outside the vocabulary (`unknown-risk-class`), a tag or a `(R<n>)` ref
230
230
  list that did not parse, a duplicated group number, a task above the first header, an
231
- empty group, a ref `requirements.md` never declares, a step with no group: each names
231
+ empty group, a ref `requirements.md` never declares, a step with no group, a task
232
+ still `[ ]` in a group the loop has passed (`unticked-completed-task` — the
233
+ Implementer's done-marker is missing, and whether the work is too is the human's
234
+ call, not yours): each names
232
235
  a defect in `tasks.md` / `requirements.md`, and the verb counts them out loud as
233
- **spec-side**. Finish the ledger (a dangling ref leaves the slice — never invent an
236
+ **spec-side**. The ticks themselves, wherever a diff shows them (the full pass's PR
237
+ diff does), are the Implementer's done-marks flipped at green — **not a spec
238
+ change**: never drift, never a finding on their own. Finish the ledger (a dangling
239
+ ref leaves the slice — never invent an
234
240
  entry for a requirement that does not exist), return your verdict as usual, and name
235
241
  them in it — the Orchestrator takes them to the human instead of sending you back.
236
242
 
@@ -44,7 +44,9 @@ the task branch before invoking you, so you are handed a real `<id>` from the st
44
44
  acceptance criteria. Always include the unwanted-behavior (`If … then …`) paths.
45
45
  - `design.md` — files, functions/interfaces, approach, edge cases, testing.
46
46
  - `tasks.md` — atomic, ordered checkboxes (vertical slices for TDD), each
47
- referencing the requirements it satisfies, grouped under **risk-sized step
47
+ referencing the requirements it satisfies as a comma-separated `(R<n>, R<m>)`
48
+ list that ends a line (rule stated in `prd-to-spec`; a validator reads it),
49
+ grouped under **risk-sized step
48
50
  headers** (grouping criterion in `prd-to-spec`) — in step-by-step mode the
49
51
  loop runs one implement→review→checkpoint cycle per group, and the human
50
52
  approves the grouping with the rest of the spec.
@@ -9,8 +9,9 @@ A two-step pause:
9
9
 
10
10
  1. **Write the resume narrative** so future-you (or whoever resumes) can pick
11
11
  up cold:
12
- - Read `.claude/state/current-<your-user>.md` and the active task's
13
- `progress.md` (if any).
12
+ - Read the active task's `progress.md` (if any) and the latest note under
13
+ `.claude/state/sessions/<your-user>/` (if any) — that is where the
14
+ narrative lives; `current-<your-user>.md` only holds session timestamps.
14
15
  - Generate a UTC timestamp slug — run `date -u +%Y-%m-%dT%H:%M:%SZ` and use
15
16
  it as `<ts>`. Pick a short topic slug from the work in progress.
16
17
  - Write `.claude/state/sessions/<your-user>/<ts>-<topic>.md` with this
@@ -19,8 +20,8 @@ A two-step pause:
19
20
  ```markdown
20
21
  ---
21
22
  session_close_ts: <ts>
22
- active_task: <issue-id or null>
23
- branch: <current branch>
23
+ active_task: <issue-id or null — the current branch's id: `harness/<id>-<slug>` → `<id>`>
24
+ branch: <current branch, or (unknown) if HEAD is detached>
24
25
  topic: <topic>
25
26
  reason: manual
26
27
  auto_close: false
@@ -42,7 +43,9 @@ A two-step pause:
42
43
  ```
43
44
 
44
45
  2. **Trigger the session-close hook** so the `session_closed` event lands in
45
- `events.jsonl` and `current-<your-user>.md` pointers update:
46
+ `events.jsonl` (its `task_id` is derived from the current branch when it is
47
+ a `harness/<id>-<slug>` task branch) and `current-<your-user>.md` gets its
48
+ `last_close_ts` stamped:
46
49
 
47
50
  ```bash
48
51
  .claude/hooks/session-close.sh --manual
@@ -89,6 +89,10 @@
89
89
  "items": {
90
90
  "type": "string"
91
91
  }
92
+ },
93
+ "verify": {
94
+ "type": "string",
95
+ "minLength": 1
92
96
  }
93
97
  },
94
98
  "additionalProperties": false
@@ -138,7 +138,10 @@ fi
138
138
  # ladder, and following `git pull --rebase` with `rebase.autostash` set flattens
139
139
  # that index — the documented rollback then reverts to the last commit and the
140
140
  # group's whole uncommitted work is gone. So on any other branch: silence.
141
- CURRENT_BRANCH="$(git symbolic-ref --quiet --short HEAD 2>/dev/null || true)"
141
+ # The full ref with `refs/heads/` stripped, not `--short`: the short form
142
+ # lengthens to `heads/<name>` when a tag shares the branch's name, which would
143
+ # fail the `harness/*` test below (same read as `session-close.sh` / `status`).
144
+ CURRENT_BRANCH="$(git symbolic-ref --quiet HEAD 2>/dev/null | sed -n 's#^refs/heads/##p')"
142
145
  DEFAULT_BRANCH="$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')"
143
146
  if [ -n "$DEFAULT_BRANCH" ] && [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ]; then
144
147
  BEHIND="$(git rev-list --count "HEAD..origin/$DEFAULT_BRANCH" 2>/dev/null || echo 0)"
@@ -191,8 +194,6 @@ if [ "${#ERRORS[@]}" -eq 0 ] && [ -n "$GIT_USER_EMAIL" ]; then
191
194
  mkdir -p "$REPO_ROOT/.claude/state"
192
195
  cat > "$CURRENT_PATH" <<EOF
193
196
  ---
194
- active_task: null
195
- branch: $(git symbolic-ref --quiet --short HEAD 2>/dev/null || echo "(unknown)")
196
197
  session_start_ts: $NOW_ISO
197
198
  last_close_ts: ""
198
199
  ---
@@ -201,22 +202,45 @@ last_close_ts: ""
201
202
 
202
203
  Per-dev pointer (gitignored). The lifecycle hooks read \`session_start_ts\`
203
204
  to compute \`session_active_h\` and reset it on each SessionStart that orients.
204
-
205
- ## Resume hint
206
-
207
- _(One paragraph — what to pick up next. Updated by \`/pause\`.)_
205
+ The active task and its branch are not recorded here — \`session-close.sh\`
206
+ derives them from the live branch (\`harness/<id>-<slug>\`) at close time — and
207
+ the narrative resume lives under \`sessions/<user>/\` (written by \`/pause\`).
208
208
  EOF
209
209
  else
210
210
  # Refresh session_start_ts on every orient so close-time math is accurate.
211
211
  # awk in-place rewrite of the frontmatter scalar — without this,
212
212
  # `session_start_ts` would stay frozen at the first orient and
213
213
  # `session_active_h` would be cumulative across every clear/resume.
214
+ # The same pass drops the dead `active_task` / `branch` scalars a pointer
215
+ # created by an earlier version still carries — nothing ever updated them,
216
+ # so they lie (`null` / the init-day branch); the live branch is the source.
217
+ # Its "Resume hint" section goes too, but ONLY while it is still exactly
218
+ # the untouched placeholder — the heading, blank lines, the one placeholder
219
+ # line, and nothing else up to the next heading or EOF (its "Updated by
220
+ # `/pause`" promise was never kept). Anything a person wrote there — the
221
+ # placeholder replaced, a paragraph added below it, even a `---` rule —
222
+ # makes the section theirs and it stays, heading included; so does a
223
+ # section stripped to its bare heading. Decided by a read-only pre-pass so
224
+ # the rewrite below never looks ahead; `[[:space:]]*$` tolerates CRLF.
225
+ PRUNE_HINT="$(awk '
226
+ inhint && /^---/ { other = 1; next }
227
+ /^---/ { block++; next }
228
+ block < 2 { next }
229
+ /^## Resume hint[[:space:]]*$/ { inhint = 1; seen = 1; next }
230
+ inhint && /^## / { inhint = 0 }
231
+ !inhint { next }
232
+ /^[[:space:]]*$/ { next }
233
+ /Updated by `\/pause`/ { placeholder++; next }
234
+ { other = 1 }
235
+ END { print (seen && placeholder == 1 && !other) ? 1 : 0 }
236
+ ' "$CURRENT_PATH" 2>/dev/null)"
237
+ [ "$PRUNE_HINT" = "1" ] || PRUNE_HINT=0
214
238
  # Write through a mktemp file, not a predictable `$CURRENT_PATH.tmp`: the
215
239
  # latter is a known path an attacker could pre-plant as a symlink for the
216
240
  # redirect to follow. `mktemp` refuses to reuse an existing path, and `mv`
217
241
  # over CURRENT_PATH itself safely replaces a symlink with a regular file.
218
242
  if tmp="$(mktemp "$REPO_ROOT/.claude/state/.ptr.XXXXXX")"; then
219
- awk -v ts="$NOW_ISO" '
243
+ awk -v ts="$NOW_ISO" -v prune="$PRUNE_HINT" '
220
244
  /^---/ {
221
245
  block++
222
246
  if (block == 2 && !seen) print "session_start_ts: " ts
@@ -224,6 +248,10 @@ EOF
224
248
  next
225
249
  }
226
250
  block == 1 && /^session_start_ts:/ { print "session_start_ts: " ts; seen = 1; next }
251
+ block == 1 && /^(active_task|branch):/ { next }
252
+ prune && block >= 2 && /^## Resume hint[[:space:]]*$/ { inhint = 1; next }
253
+ inhint && /^## / { inhint = 0 }
254
+ inhint { next }
227
255
  { print }
228
256
  ' "$CURRENT_PATH" > "$tmp" && mv "$tmp" "$CURRENT_PATH" || rm -f "$tmp"
229
257
  fi
@@ -65,8 +65,6 @@ NOW_ISO="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
65
65
  # (preinstalled) cover the rest.
66
66
  CURRENT_PATH="$REPO_ROOT/.claude/state/current-$USER_SLUG.md"
67
67
  SESSION_START_TS=""
68
- ACTIVE_TASK=""
69
- TASK_BRANCH=""
70
68
  read_frontmatter_field() {
71
69
  local field="$1"
72
70
  awk -v field="$field" '
@@ -89,10 +87,20 @@ read_frontmatter_field() {
89
87
  }
90
88
  if [ -f "$CURRENT_PATH" ]; then
91
89
  SESSION_START_TS="$(read_frontmatter_field session_start_ts)"
92
- ACTIVE_TASK="$(read_frontmatter_field active_task)"
93
- TASK_BRANCH="$(read_frontmatter_field branch)"
94
90
  fi
95
91
 
92
+ # ── Derive the task context from the live branch ───────────────────────────
93
+ # The pointer never carried a real `active_task` / `branch` — nothing in the
94
+ # contract wrote them after init, so every auto-close record copied `null` /
95
+ # the init-day branch. The branch is the source of truth: the Orchestrator
96
+ # works every task on `harness/<id>-<slug>`, so HEAD names the task. The full
97
+ # ref is read and `refs/heads/` stripped rather than `--short`: the short form
98
+ # lengthens to `heads/<name>` when a tag shares the branch's name, which would
99
+ # silently drop the id. Empty (→ `(unknown)`, no task) on a detached HEAD, a
100
+ # rebase in progress, or outside a repo — never a stale guess.
101
+ TASK_BRANCH="$(git -C "$REPO_ROOT" symbolic-ref --quiet HEAD 2>/dev/null | sed -n 's#^refs/heads/##p')"
102
+ ACTIVE_TASK="$(printf '%s' "$TASK_BRANCH" | sed -n 's#^harness/\([0-9][0-9]*\)-..*$#\1#p')"
103
+
96
104
  # Fallback: a session with no recorded start defaults to "started now" so the
97
105
  # math is well-defined; duration becomes 0 hours, which the schema accepts.
98
106
  if [ -z "$SESSION_START_TS" ]; then
@@ -143,9 +151,9 @@ EMIT_ARGS=(
143
151
  --reason="$REASON"
144
152
  --auto-close="$AUTO_CLOSE"
145
153
  )
146
- # Skip a literal "null" — a buggy frontmatter may emit the string instead of a
147
- # real YAML null. The schema would accept it but the line is forensic noise.
148
- if [ -n "$ACTIVE_TASK" ] && [ "$ACTIVE_TASK" != "null" ]; then
154
+ # Only a task branch yields an id; a session closed on `main` (or detached)
155
+ # emits the envelope without `task_id`, as the schema allows.
156
+ if [ -n "$ACTIVE_TASK" ]; then
149
157
  EMIT_ARGS+=(--task-id="$ACTIVE_TASK")
150
158
  fi
151
159
  # Resolve the CLI via the launcher (local devDependency → global → fail-fast)
@@ -31,6 +31,20 @@ Empty sections may be omitted.
31
31
 
32
32
  ---
33
33
 
34
+ ## 0.4.2 — 2026-09-10
35
+
36
+ ### Changed
37
+
38
+ - **`session_closed.task_id` — now derived from the live branch at close time**
39
+ (`harness/<id>-<slug>` → `<id>`); absent on the default branch or a detached
40
+ HEAD. Previously the field was **never populated** on this event: the emitter
41
+ read it from a per-dev pointer field nothing ever wrote. **No field renamed,
42
+ added, or removed** — the envelope's optional `task_id` is simply present from
43
+ this version on when a session closes on a task branch. Readers bucketing
44
+ `session_closed` by task should expect the value to appear at this version.
45
+
46
+ ---
47
+
34
48
  ## 0.2.0 — 2026-07-31
35
49
 
36
50
  ### Changed
@@ -36,14 +36,14 @@ Every event line starts with this envelope. Per-type fields are added at the
36
36
  same top level — there is no nested `payload`, so Zod discriminated unions key on
37
37
  `type`.
38
38
 
39
- | Field | Type | Required | Axis | Notes |
40
- | ----------------- | ------ | -------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
- | `type` | string | yes | `internal-enum` | One of the 9 event types listed below. Discriminator. |
42
- | `ts` | string | yes | `metric` | UTC ISO 8601 with `Z` suffix (e.g. `2026-05-28T14:30:00.000Z`). **No local offsets.** |
43
- | `user` | string | yes | `local-only` | `git config user.email` of the actor. Never exported in any tier. |
44
- | `project` | string | yes | `identity` | `task_storage.repo` slug (e.g. `acme/widgets`), from `harness.config.yml`. **Never `OWNER/REPO`** — the CLI refuses to emit while that placeholder is the value (see [Placeholder guard](#placeholder-guard)). |
45
- | `task_id` | string | no | `identity` | Task issue id (e.g. `42`) when the event has a task context. Absent on session/global events. A per-project correlator — only meaningful alongside `project`, so it shares the `identity` axis. |
46
- | `harness_version` | string | yes | `metric` | `version` of the **installed** `@lemoncode/lemony` package — _not_ `vendor_version` from config. |
39
+ | Field | Type | Required | Axis | Notes |
40
+ | ----------------- | ------ | -------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
41
+ | `type` | string | yes | `internal-enum` | One of the 9 event types listed below. Discriminator. |
42
+ | `ts` | string | yes | `metric` | UTC ISO 8601 with `Z` suffix (e.g. `2026-05-28T14:30:00.000Z`). **No local offsets.** |
43
+ | `user` | string | yes | `local-only` | `git config user.email` of the actor. Never exported in any tier. |
44
+ | `project` | string | yes | `identity` | `task_storage.repo` slug (e.g. `acme/widgets`), from `harness.config.yml`. **Never `OWNER/REPO`** — the CLI refuses to emit while that placeholder is the value (see [Placeholder guard](#placeholder-guard)). |
45
+ | `task_id` | string | no | `identity` | Task issue id (e.g. `42`) when the event has a task context. Absent on global events; `session_closed` carries it when HEAD is a `harness/<id>-<slug>` task branch. A per-project correlator — only meaningful alongside `project`, so it shares the `identity` axis. |
46
+ | `harness_version` | string | yes | `metric` | `version` of the **installed** `@lemoncode/lemony` package — _not_ `vendor_version` from config. |
47
47
 
48
48
  ### Placeholder guard
49
49
 
@@ -114,7 +114,9 @@ forward-compatible — readers dispatch on `type` and ignore unknowns.
114
114
  ### 1. `session_closed` _(P5)_
115
115
 
116
116
  Emitted by `session-close.sh` on `SessionEnd` or `/pause` (manual). One per
117
- session.
117
+ session. The envelope's `task_id` is derived from the live branch at close time
118
+ (`harness/<id>-<slug>` → `<id>`); a session closed on the default branch or a
119
+ detached HEAD carries none.
118
120
 
119
121
  | Field | Type | Required | Axis | Notes |
120
122
  | ------------------ | ------- | -------- | --------------- | --------------------------------------------------------------------------------------------------------------- |
@@ -29,7 +29,9 @@ Run the tiers in order; each adds evidence the next builds on.
29
29
  token pair — including any dark-mode override — and exits non-zero on a pair below its
30
30
  floor. This is the one a11y measurement doable offline, because colour comes from the
31
31
  token file. It is complementary to `lemony design-tokens validate` (validate proves colour
32
- is a token reference; contrast proves the pair meets its floor).
32
+ is a token reference; contrast proves the pair meets its floor). When the project declares
33
+ `design_tokens.verify` in `harness.config.yml`, the same command also runs the design
34
+ system's own verifier and forwards its report — read that output as T0 evidence too.
33
35
  - **T1 — source a11y lint (rides the project).** The project's own linter usually carries an
34
36
  accessibility plugin (`eslint-plugin-jsx-a11y`, Svelte's or Vue's a11y rules). Run the
35
37
  project's `lint`; don't reimplement it. Read what it flags.
@@ -193,8 +193,33 @@ of the spec. Grouping criterion:
193
193
  ## Group 2 — <name> _(<one-line boundary rationale>)_ [risk: data-loss]
194
194
 
195
195
  - [ ] T3 — <error path> (R2, R3)
196
+ - [ ] **T4 — <a task that needs detail>.**
197
+ <detail prose, as many indented lines as it needs, with the ref list
198
+ ending the last one> (R3, R4)
196
199
  ```
197
200
 
201
+ **Ref list — one rule, read by a script.** A task's requirement refs are one
202
+ comma-separated `(R<n>, R<m>)` group that **ends a line**: the end of the task's first
203
+ line, or the end of one continuation line (a line of its own, or the end of a detail
204
+ line). Nothing after the closing paren on that line, not even a period. No ranges —
205
+ `(R1–R4)` is not a list, write `(R1, R2, R3, R4)` — and no prose inside the parens.
206
+
207
+ **Mentions.** A `(R<n>` on the task's first line, or right after the title's bold
208
+ close, is always read as the declaration: it is reported if it does not end the line,
209
+ even when a list ends a later line. Keep every other `(R<n>)` mention in detail prose
210
+ **mid-line**. When two lists end continuation lines the validator cannot tell which is
211
+ the declaration and reports the task; on a task that declares no list, the one
212
+ line-ending mention is read as its declaration, and a mid-line one is reported.
213
+
214
+ The review evidence ledger's validator enumerates each group's review slice from these
215
+ refs; a task it cannot read is reported as `malformed-task-refs` — a spec defect that
216
+ pauses the loop, never read as "declares none".
217
+
218
+ The checkbox itself is the **Implementer's done-marker**: it flips `- [ ]` → `- [x]`
219
+ when the task reaches green, with the task's commit. Write every task `- [ ]`; the same
220
+ validator reports a task still `[ ]` in a group the loop has passed
221
+ (`unticked-completed-task`), so the file tracks the progress its shape promises.
222
+
198
223
  Rules: order so the first task is a tracer bullet; never "write all tests" then
199
224
  "write all code"; keep each task small enough to verify on its own. Grouping never
200
225
  changes task granularity — checkboxes stay atomic and TDD runs per task; only review
@@ -68,12 +68,12 @@ decision **stated in full** — at this moment it exists nowhere on disk; the en
68
68
  `**Resolution**` block is only written at step 4 — plus the `discoveries.md` entry
69
69
  **by path** for the surrounding context (it reads the entry itself):
70
70
 
71
- | Artifact changed by the decision | Owner to invoke |
72
- | --------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------- |
73
- | `spec/requirements.md`, `spec/design.md`, `spec/tasks.md` | **Spec Author** |
74
- | Implementation code, `progress.md`, `notes.md` | **Implementer** (often just the resumed sub-agent) |
75
- | A child issue's trace lines, a triage issue's fix plan, the parent partition-plan issue | **Orchestrator** (you) — `gh issue edit --body-file`, read-modify-write (an oversize discovery answered "partition": `.claude/agents/partition.md`) |
76
- | `docs/adr/NNNN-<slug>.md`, `docs/architecture.md`, `docs/playbooks/` | **Architect** (on-demand) — `write-adr` to record the decision, `update-architecture` to keep the map true, `playbook-iterate` for a `T6 PLAYBOOK_CONFLICT` |
71
+ | Artifact changed by the decision | Owner to invoke |
72
+ | --------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
73
+ | `spec/requirements.md`, `spec/design.md`, `spec/tasks.md` | **Spec Author** — except a task's checkbox: `- [ ]` → `- [x]` is the Implementer's done-mark at green (the Orchestrator's on a checkpoint `ok`), never a spec edit |
74
+ | Implementation code, `progress.md`, `notes.md` | **Implementer** (often just the resumed sub-agent) |
75
+ | A child issue's trace lines, a triage issue's fix plan, the parent partition-plan issue | **Orchestrator** (you) — `gh issue edit --body-file`, read-modify-write (an oversize discovery answered "partition": `.claude/agents/partition.md`) |
76
+ | `docs/adr/NNNN-<slug>.md`, `docs/architecture.md`, `docs/playbooks/` | **Architect** (on-demand) — `write-adr` to record the decision, `update-architecture` to keep the map true, `playbook-iterate` for a `T6 PLAYBOOK_CONFLICT` |
77
77
 
78
78
  Not every resolution needs an artifact update first. If the decision is simply "do X"
79
79
  with no change to the contract, skip straight to recording it and resuming. If it
@@ -80,6 +80,9 @@ Rules:
80
80
  - Only enough code to pass current test
81
81
  - Don't anticipate future tests
82
82
  - Keep tests focused on observable behavior
83
+ - When the behaviors belong to a `tasks.md` task, its checkbox is the task's
84
+ done-marker: flip `- [ ]` → `- [x]` when the task's **last** behavior is GREEN
85
+ (the suite passing), and commit (or stage) it with the task's code
83
86
 
84
87
  ### 4. Refactor
85
88
 
@@ -83,10 +83,16 @@ rollback:
83
83
  # - test
84
84
  # - build
85
85
 
86
- # Design tokens (`design-tokens validate`). The anti-hardcode scan inspects a built-in
87
- # set of UI/style extensions (.css/.scss/.ts/.tsx/.vue/.svelte/.astro/.js/.mdx/.html/…).
88
- # Add extra suffixes here for a stack the built-ins don't cover — additive, never a
89
- # replacement. Default none.
86
+ # Design tokens (`design-tokens validate` / `design-tokens contrast`).
87
+ # `scan_extensions`: the anti-hardcode scan inspects a built-in set of UI/style
88
+ # extensions (.css/.scss/.ts/.tsx/.vue/.svelte/.astro/.js/.mdx/.html/…). Add extra
89
+ # suffixes here for a stack the built-ins don't cover — additive, never a replacement.
90
+ # Default none.
91
+ # `verify`: a command line `design-tokens contrast` runs after its own WCAG checks
92
+ # (through `sh -c`, in the repo root) — your design system's own verifier for the rules
93
+ # only it can know (palette under colour-vision-deficiency simulation, property grammar,
94
+ # CSS scans). Its output is forwarded; a non-zero exit fails the gate. Default none.
90
95
  # design_tokens:
91
96
  # scan_extensions:
92
97
  # - .foo
98
+ # verify: pnpm exec my-design-system verify