@lemoncode/lemony 0.4.1 → 0.5.1
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 +1 -1
- package/catalog/agents/implementer.md +14 -3
- package/catalog/agents/orchestrator.md +56 -8
- package/catalog/agents/reviewer.md +8 -2
- package/catalog/commands/pause.md +8 -5
- package/catalog/commands/sync-design-tokens.md +3 -1
- package/catalog/harness.config.schema.json +4 -0
- package/catalog/hooks/init.sh +59 -9
- package/catalog/hooks/lib/merge-pr.sh +3 -2
- package/catalog/hooks/lib/playbook-scan.sh +4 -2
- package/catalog/hooks/session-close.sh +15 -7
- package/catalog/schemas/tier2-events-history.md +14 -0
- package/catalog/schemas/tier2-events.md +11 -9
- package/catalog/skills/a11y-audit/SKILL.md +3 -1
- package/catalog/skills/design-tool-sync/SKILL.md +40 -7
- package/catalog/skills/prd-to-spec/SKILL.md +5 -0
- package/catalog/skills/resolve-discovery/SKILL.md +6 -6
- package/catalog/skills/tdd/SKILL.md +3 -0
- package/catalog/templates/claude-code/harness.config.yml.tpl +13 -4
- package/dist/cli.mjs +1160 -356
- package/package.json +1 -1
package/catalog/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
0.
|
|
1
|
+
0.5.1
|
|
@@ -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** —
|
|
128
|
-
|
|
129
|
-
is
|
|
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
|
|
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
|
-
|
|
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
|
|
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
|
|
575
|
-
|
|
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
|
|
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**.
|
|
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
|
|
|
@@ -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
|
|
13
|
-
|
|
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`
|
|
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
|
|
@@ -18,7 +18,9 @@ Designer; follow the steps there.
|
|
|
18
18
|
deletes tool-only variables). Preview the plan, push on confirmation, then record the drift
|
|
19
19
|
baseline only after the push succeeds.
|
|
20
20
|
- _empty_ — check the drift state (`lemony status`) and, if an export is pending and the tool
|
|
21
|
-
is connected, offer `export`; otherwise report the current state.
|
|
21
|
+
is connected, offer `export`; otherwise report the current state. `status` shows no drift
|
|
22
|
+
line both when there is no token file and when the file cannot be read or fails
|
|
23
|
+
validation — tell the two apart with `lemony doctor`'s `design-tool-drift` check.
|
|
22
24
|
|
|
23
25
|
The design tool is a **projection** of the canonical JSON, never a peer source of truth.
|
|
24
26
|
Detect the tool at runtime: read the `com.lemony.design-tool` binding at the root of
|
package/catalog/hooks/init.sh
CHANGED
|
@@ -46,8 +46,17 @@ WARNINGS=()
|
|
|
46
46
|
# awk/grep/git (preinstalled) cover the rest.
|
|
47
47
|
CONFIG_VERSION=""
|
|
48
48
|
CONFIG_REPO=""
|
|
49
|
-
if [ ! -
|
|
49
|
+
if [ ! -e "$CONFIG" ]; then
|
|
50
50
|
ERRORS+=("harness.config.yml not found at $REPO_ROOT. Run \`lemony install\`.")
|
|
51
|
+
elif [ ! -f "$CONFIG" ]; then
|
|
52
|
+
# There, but not a file awk can read: a directory, or a FIFO it would block on
|
|
53
|
+
# until something wrote to it. `-e`/`-f` follow a symlink, so a dead link stays
|
|
54
|
+
# "not found" above and a link to one of these lands here.
|
|
55
|
+
ERRORS+=("harness.config.yml at $REPO_ROOT is not a regular file (a directory, a FIFO, a socket or a device), so it was not read. Replace it with the config file.")
|
|
56
|
+
elif [ ! -r "$CONFIG" ]; then
|
|
57
|
+
# A file awk cannot open failed the key check below, so the boot blamed missing keys
|
|
58
|
+
# under a raw awk error.
|
|
59
|
+
ERRORS+=("harness.config.yml at $REPO_ROOT cannot be read (check its permissions), so it was not read.")
|
|
51
60
|
elif ! awk '
|
|
52
61
|
{ sub(/\r$/, "") }
|
|
53
62
|
/^[^[:space:]#]/ {
|
|
@@ -138,7 +147,10 @@ fi
|
|
|
138
147
|
# ladder, and following `git pull --rebase` with `rebase.autostash` set flattens
|
|
139
148
|
# that index — the documented rollback then reverts to the last commit and the
|
|
140
149
|
# group's whole uncommitted work is gone. So on any other branch: silence.
|
|
141
|
-
|
|
150
|
+
# The full ref with `refs/heads/` stripped, not `--short`: the short form
|
|
151
|
+
# lengthens to `heads/<name>` when a tag shares the branch's name, which would
|
|
152
|
+
# fail the `harness/*` test below (same read as `session-close.sh` / `status`).
|
|
153
|
+
CURRENT_BRANCH="$(git symbolic-ref --quiet HEAD 2>/dev/null | sed -n 's#^refs/heads/##p')"
|
|
142
154
|
DEFAULT_BRANCH="$(git symbolic-ref --quiet refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@')"
|
|
143
155
|
if [ -n "$DEFAULT_BRANCH" ] && [ "$CURRENT_BRANCH" = "$DEFAULT_BRANCH" ]; then
|
|
144
156
|
BEHIND="$(git rev-list --count "HEAD..origin/$DEFAULT_BRANCH" 2>/dev/null || echo 0)"
|
|
@@ -187,12 +199,23 @@ if [ "${#ERRORS[@]}" -eq 0 ] && [ -n "$GIT_USER_EMAIL" ]; then
|
|
|
187
199
|
CURRENT_PATH="$REPO_ROOT/.claude/state/current-$USER_SLUG.md"
|
|
188
200
|
if [ -z "$USER_SLUG" ]; then
|
|
189
201
|
: # path-unsafe slug — pointer write skipped above
|
|
202
|
+
elif [ -L "$REPO_ROOT/.claude" ] || [ -L "$REPO_ROOT/.claude/state" ]; then
|
|
203
|
+
# A linked directory takes the pointer, and every refresh after it, out of the
|
|
204
|
+
# repo — the same write-through the dangling-link branch below refuses.
|
|
205
|
+
WARNINGS+=(".claude or .claude/state is a symlink, so the session start was not recorded (the pointer would be written wherever it points). Replace it with a directory.")
|
|
206
|
+
elif [ -e "$CURRENT_PATH" ] && [ ! -f "$CURRENT_PATH" ]; then
|
|
207
|
+
# There, but not a file: writing to a FIFO blocks until something reads it, and
|
|
208
|
+
# awk reading one blocks until something writes. The pointer is not critical, so
|
|
209
|
+
# the boot goes on without it — a warning, not a blocking error.
|
|
210
|
+
WARNINGS+=(".claude/state/current-$USER_SLUG.md is not a regular file (a directory, a FIFO, a socket or a device), so the session start was not recorded. Remove it; the next session recreates it.")
|
|
211
|
+
elif [ -L "$CURRENT_PATH" ] && [ ! -e "$CURRENT_PATH" ]; then
|
|
212
|
+
# A symlink whose target is gone: `-e`/`-f` follow it and see nothing, and the
|
|
213
|
+
# `cat >` below would create the target wherever the link points.
|
|
214
|
+
WARNINGS+=(".claude/state/current-$USER_SLUG.md is a symlink whose target is missing, so the session start was not recorded (writing through it would create the file it points at). Remove it; the next session recreates it.")
|
|
190
215
|
elif [ ! -f "$CURRENT_PATH" ]; then
|
|
191
216
|
mkdir -p "$REPO_ROOT/.claude/state"
|
|
192
217
|
cat > "$CURRENT_PATH" <<EOF
|
|
193
218
|
---
|
|
194
|
-
active_task: null
|
|
195
|
-
branch: $(git symbolic-ref --quiet --short HEAD 2>/dev/null || echo "(unknown)")
|
|
196
219
|
session_start_ts: $NOW_ISO
|
|
197
220
|
last_close_ts: ""
|
|
198
221
|
---
|
|
@@ -201,22 +224,45 @@ last_close_ts: ""
|
|
|
201
224
|
|
|
202
225
|
Per-dev pointer (gitignored). The lifecycle hooks read \`session_start_ts\`
|
|
203
226
|
to compute \`session_active_h\` and reset it on each SessionStart that orients.
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
_(One paragraph — what to pick up next. Updated by \`/pause\`.)_
|
|
227
|
+
The active task and its branch are not recorded here — \`session-close.sh\`
|
|
228
|
+
derives them from the live branch (\`harness/<id>-<slug>\`) at close time — and
|
|
229
|
+
the narrative resume lives under \`sessions/<user>/\` (written by \`/pause\`).
|
|
208
230
|
EOF
|
|
209
231
|
else
|
|
210
232
|
# Refresh session_start_ts on every orient so close-time math is accurate.
|
|
211
233
|
# awk in-place rewrite of the frontmatter scalar — without this,
|
|
212
234
|
# `session_start_ts` would stay frozen at the first orient and
|
|
213
235
|
# `session_active_h` would be cumulative across every clear/resume.
|
|
236
|
+
# The same pass drops the dead `active_task` / `branch` scalars a pointer
|
|
237
|
+
# created by an earlier version still carries — nothing ever updated them,
|
|
238
|
+
# so they lie (`null` / the init-day branch); the live branch is the source.
|
|
239
|
+
# Its "Resume hint" section goes too, but ONLY while it is still exactly
|
|
240
|
+
# the untouched placeholder — the heading, blank lines, the one placeholder
|
|
241
|
+
# line, and nothing else up to the next heading or EOF (its "Updated by
|
|
242
|
+
# `/pause`" promise was never kept). Anything a person wrote there — the
|
|
243
|
+
# placeholder replaced, a paragraph added below it, even a `---` rule —
|
|
244
|
+
# makes the section theirs and it stays, heading included; so does a
|
|
245
|
+
# section stripped to its bare heading. Decided by a read-only pre-pass so
|
|
246
|
+
# the rewrite below never looks ahead; `[[:space:]]*$` tolerates CRLF.
|
|
247
|
+
PRUNE_HINT="$(awk '
|
|
248
|
+
inhint && /^---/ { other = 1; next }
|
|
249
|
+
/^---/ { block++; next }
|
|
250
|
+
block < 2 { next }
|
|
251
|
+
/^## Resume hint[[:space:]]*$/ { inhint = 1; seen = 1; next }
|
|
252
|
+
inhint && /^## / { inhint = 0 }
|
|
253
|
+
!inhint { next }
|
|
254
|
+
/^[[:space:]]*$/ { next }
|
|
255
|
+
/Updated by `\/pause`/ { placeholder++; next }
|
|
256
|
+
{ other = 1 }
|
|
257
|
+
END { print (seen && placeholder == 1 && !other) ? 1 : 0 }
|
|
258
|
+
' "$CURRENT_PATH" 2>/dev/null)"
|
|
259
|
+
[ "$PRUNE_HINT" = "1" ] || PRUNE_HINT=0
|
|
214
260
|
# Write through a mktemp file, not a predictable `$CURRENT_PATH.tmp`: the
|
|
215
261
|
# latter is a known path an attacker could pre-plant as a symlink for the
|
|
216
262
|
# redirect to follow. `mktemp` refuses to reuse an existing path, and `mv`
|
|
217
263
|
# over CURRENT_PATH itself safely replaces a symlink with a regular file.
|
|
218
264
|
if tmp="$(mktemp "$REPO_ROOT/.claude/state/.ptr.XXXXXX")"; then
|
|
219
|
-
awk -v ts="$NOW_ISO" '
|
|
265
|
+
awk -v ts="$NOW_ISO" -v prune="$PRUNE_HINT" '
|
|
220
266
|
/^---/ {
|
|
221
267
|
block++
|
|
222
268
|
if (block == 2 && !seen) print "session_start_ts: " ts
|
|
@@ -224,6 +270,10 @@ EOF
|
|
|
224
270
|
next
|
|
225
271
|
}
|
|
226
272
|
block == 1 && /^session_start_ts:/ { print "session_start_ts: " ts; seen = 1; next }
|
|
273
|
+
block == 1 && /^(active_task|branch):/ { next }
|
|
274
|
+
prune && block >= 2 && /^## Resume hint[[:space:]]*$/ { inhint = 1; next }
|
|
275
|
+
inhint && /^## / { inhint = 0 }
|
|
276
|
+
inhint { next }
|
|
227
277
|
{ print }
|
|
228
278
|
' "$CURRENT_PATH" > "$tmp" && mv "$tmp" "$CURRENT_PATH" || rm -f "$tmp"
|
|
229
279
|
fi
|
|
@@ -309,12 +309,13 @@ fi
|
|
|
309
309
|
# same recipe as playbook-scan.sh): emit `<key>\t<value>` for the uncommented
|
|
310
310
|
# `checks_timeout_secs` / `allow_no_checks` entries — inline comments stripped
|
|
311
311
|
# (on the block header too) and surrounding quotes removed. Any column-0 line
|
|
312
|
-
# ends the block. Absent/unreadable config → baked defaults.
|
|
312
|
+
# ends the block. Absent/unreadable/non-regular config → baked defaults.
|
|
313
313
|
CONFIG_TIMEOUT=""
|
|
314
314
|
CONFIG_ALLOW_NO_CHECKS=""
|
|
315
315
|
read_merge_config() {
|
|
316
316
|
local config="$ROOT/harness.config.yml"
|
|
317
|
-
|
|
317
|
+
# `-f` as well as `-r`: a FIFO is readable, and awk on it would hang the merge.
|
|
318
|
+
{ [ -f "$config" ] && [ -r "$config" ]; } || return 0
|
|
318
319
|
|
|
319
320
|
local key value
|
|
320
321
|
while IFS=$'\t' read -r key value; do
|
|
@@ -39,7 +39,8 @@
|
|
|
39
39
|
# which would breach the per-fire budget) and with no materialized state file to
|
|
40
40
|
# go stale before an `update` command exists (P7). Falls back to the baked
|
|
41
41
|
# defaults when the config, its `paths` block, or a key is absent or commented
|
|
42
|
-
# out (fresh clone, the vendor repo dogfooding itself, an un-customized install)
|
|
42
|
+
# out (fresh clone, the vendor repo dogfooding itself, an un-customized install),
|
|
43
|
+
# and when the config is not a regular file.
|
|
43
44
|
_resolve_playbook_dirs() {
|
|
44
45
|
local repo_root="$1"
|
|
45
46
|
local home_dir="$2"
|
|
@@ -47,7 +48,8 @@ _resolve_playbook_dirs() {
|
|
|
47
48
|
PLAYBOOKS_GLOBAL_DIR="$home_dir/.claude/playbooks"
|
|
48
49
|
|
|
49
50
|
local config="$repo_root/harness.config.yml"
|
|
50
|
-
|
|
51
|
+
# `-f` as well as `-r`: a FIFO is readable, and awk on it would hang the hook.
|
|
52
|
+
{ [ -f "$config" ] && [ -r "$config" ]; } || return 0
|
|
51
53
|
|
|
52
54
|
# One awk pass: within the top-level `paths:` block, emit `<key>\t<value>` for
|
|
53
55
|
# the (uncommented) `playbooks` / `playbooks_global` entries — inline comments
|
|
@@ -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
|
-
#
|
|
147
|
-
#
|
|
148
|
-
if [ -n "$ACTIVE_TASK" ]
|
|
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
|
|
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.
|
|
@@ -70,34 +70,67 @@ variables and the DTCG JSON:
|
|
|
70
70
|
- `name` — the variable's dotted path. Map it to/from the tool's own grouping separator.
|
|
71
71
|
- `value` for a literal; `ref` for an alias (the name of the variable it points at).
|
|
72
72
|
- `modes` — per-theme overrides (the base theme is `value`/`ref`, not repeated).
|
|
73
|
+
- Every `value` and mode value is a **string** — `"1024"`, not `1024` — and every `ref` a
|
|
74
|
+
non-empty one; each variable carries a `value` or a `ref` (a `value` beside a `ref` is
|
|
75
|
+
ignored), and `modes` is an object whose mode names are not blank nor `__proto__`. A name
|
|
76
|
+
is dot-separated segments, none empty, none starting with `$`, none `__proto__`, not just
|
|
77
|
+
a tier (`primitive`, `semantic`, `component`), and no two variables share one. The CLI
|
|
78
|
+
refuses a file that breaks any of these, naming the variable (and the mode, for a mode
|
|
79
|
+
value).
|
|
73
80
|
|
|
74
81
|
The CLI maps this to the 3-tier DTCG model: `ref` → an alias `{path}`, `modes` →
|
|
75
|
-
`$extensions["com.lemony.modes"]`, and the tier follows the path
|
|
76
|
-
|
|
82
|
+
`$extensions["com.lemony.modes"]`, and the tier follows the path. A name without a tier
|
|
83
|
+
lands on the one tier `docs/design-tokens.json` already has it in; otherwise a literal
|
|
84
|
+
defaults to `primitive` and an alias to `semantic`. A bare `ref` — or a bare `{alias}` mode
|
|
85
|
+
value — resolves to the tiered path its target lands at in the same neutral file, or to the
|
|
86
|
+
one tier the token file has it in.
|
|
77
87
|
|
|
78
88
|
## import — tool → JSON
|
|
79
89
|
|
|
80
90
|
1. **Read** the tool's variables over MCP and write them to a neutral file (a temp path).
|
|
81
91
|
2. **Preview** the diff (writes nothing):
|
|
82
92
|
`lemony design-tokens import --from=<neutral-file>`. It prints, per token, the target tier
|
|
83
|
-
and `new` / `changed` (
|
|
93
|
+
and `new` / `changed` (each thing that changes, old → new: the value, the `$type`, each
|
|
94
|
+
mode) / `unchanged`. When
|
|
95
|
+
`docs/design-tokens.json` already fails validation it adds a `Warning:` saying so: the
|
|
96
|
+
apply will refuse any slice that leaves it failing, so tell the human before curating.
|
|
84
97
|
3. **Present and curate.** Walk the human through it: which new tokens to take, which changes
|
|
85
98
|
to accept, and — where a tool-origin name is ambiguous — whether a literal belongs in
|
|
86
99
|
`primitive` or `semantic`. The human owns the slice.
|
|
87
100
|
4. **Apply** the agreed slice:
|
|
88
101
|
`lemony design-tokens import --from=<neutral-file> --apply --only=<dotted,paths>`.
|
|
89
102
|
This does the additive merge into `docs/design-tokens.json` deterministically (it
|
|
90
|
-
bootstraps the file if it does not exist yet
|
|
91
|
-
|
|
103
|
+
bootstraps the file if it does not exist yet and the slice writes something). A changed
|
|
104
|
+
token keeps what the neutral file does not carry — `$description`, a contrast pairing,
|
|
105
|
+
other extensions — and an unchanged one is left as it is. A token the tool cannot hold
|
|
106
|
+
(see export) is never written over: the preview shows it as a change from
|
|
107
|
+
`(outside the sync: <why>)`, and the apply lists it as not applied. It writes nothing
|
|
108
|
+
when the merged file would fail validation — typically a `semantic` alias whose
|
|
109
|
+
`primitive` target is neither in the file nor in the slice: add the target to the same
|
|
110
|
+
`--only`, or fix the file first. When the neutral file does not carry the target at
|
|
111
|
+
all, the refusal says so: create it in the tool first, or leave out what references
|
|
112
|
+
it. An `--only` path no variable lands at is refused rather than skipped. A refusal
|
|
113
|
+
prints the preview again, so the slice can be re-curated from the paths it lists. Then run
|
|
114
|
+
`lemony design-tokens validate` to confirm the result is well-formed.
|
|
92
115
|
|
|
93
116
|
## export — JSON → tool
|
|
94
117
|
|
|
95
|
-
1. **Plan.**
|
|
118
|
+
1. **Plan.** `export` refuses (exit 1) a `docs/design-tokens.json` that fails
|
|
119
|
+
`lemony design-tokens validate` — fix the file first. Read the tool's current variables
|
|
120
|
+
over MCP into a neutral file, then:
|
|
96
121
|
`lemony design-tokens export --tool-state=<tool-state> --out=<projection-file>`. It prints
|
|
97
122
|
the additive upsert plan (how many to create, how many to update — **never any deletes**)
|
|
98
123
|
and writes the projection the tool should hold to `<projection-file>`. The `--tool-state`
|
|
99
124
|
is optional; without it every variable is planned as a create (the upsert is idempotent by
|
|
100
|
-
name either way).
|
|
125
|
+
name either way). A tool variable named without its tier (`color.brand`) is matched to the
|
|
126
|
+
path `import` lands it at (`primitive.color.brand`), so when you push, write into that
|
|
127
|
+
existing variable rather than creating a tiered twin. A token a tool variable cannot hold
|
|
128
|
+
is not projected: a composite (a shadow, a typography, a cubic Bézier — an object or
|
|
129
|
+
array `$value`), a token whose modes the neutral format cannot carry (a mode value that
|
|
130
|
+
is not text, a number or a boolean, a blank or `__proto__` mode name, a modes extension
|
|
131
|
+
that is not an object), and any alias to one of those. The plan names each as "not
|
|
132
|
+
projected", with why — say so to the human. `--out` takes a file path (not a pipe, nor
|
|
133
|
+
the token file itself).
|
|
101
134
|
2. **Confirm.** Show the plan; the human approves.
|
|
102
135
|
3. **Push.** Write the projection's variables into the tool over MCP — create new ones, update
|
|
103
136
|
changed ones, leave tool-only variables untouched.
|
|
@@ -215,6 +215,11 @@ The review evidence ledger's validator enumerates each group's review slice from
|
|
|
215
215
|
refs; a task it cannot read is reported as `malformed-task-refs` — a spec defect that
|
|
216
216
|
pauses the loop, never read as "declares none".
|
|
217
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
|
+
|
|
218
223
|
Rules: order so the first task is a tracer bullet; never "write all tests" then
|
|
219
224
|
"write all code"; keep each task small enough to verify on its own. Grouping never
|
|
220
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
|