Skip to content

Commit 11e5973

Browse files
aparkinclaude
andcommitted
feat(cycle4-phase1): chrome.py + decision.v1 foundation (DP6, no skill wiring)
CRAFT Cycle-4 (chrome/legibility, DP6) Phase 1 — the craft-side foundation + the SKILL.md contract text. NO skill repos are touched and NO conformance family fires live yet; Phase 2 vendors the copies and flips Family-F / Family-G on as a coordinated release. No tag this phase. Two facts (proven in the Phase-0 synthetic harness) shape the design: 1. Inside Claude the orchestrator's stdout is non-TTY (no color) and long output folds — so the legible signal is Claude's MESSAGE BODY re-rendering the structured contract as text, not the raw box. 2. decision.v1 EXTENDS .handoff.json: it retains the keys the continue CLI reads (notably `phase`, which authorizes --pick) and adds the presentation fields; `gate` must equal `phase`. New — src/craft/chrome.py (canonical output-signature renderer): - STAGE (single-rule box) / DECISION (double-rule, loudest) / RESULT (terse) + per-skill glyph signature (◆ presentation-maker · ✎ paper-writer · ⚔ adversarial) and `CRAFT · <skill>` label. - MUST-FIX honored: pads by DISPLAY WIDTH (stdlib unicodedata, NOT len(), NOT external wcwidth) so glyph-heavy / em-dash / CJK lines align to the border. Dependency-free so it vendors clean (Family-F). - Single _color() seam: TTY-gated (isatty), NO_COLOR-aware, CRAFT_FORCE_COLOR=1 test hook; no-op when piped (i.e. inside Claude). - render_decision never leaks the raw --pick flag or the draft path; shows every option's FULL detail; ★ marks default; clean "tell me TL1 / TL2" choice prompt. note_noise() appends orchestrator NOISE to audit/orchestrator.log off stdout. New — src/craft/decision.py (decision.v1 shape contract): - validate_decision(payload) -> list[str]; never raises; substring- stable messages (sibling of run_record.validate_run_record). - Checks BOTH contracts: retained handoff keys (phase, draft_dir) AND the decision.v1 presentation fields; cross-checks gate==phase, default∈option ids, confirm is a real bool, continue.cmd carries the {id} placeholder. New — tests: - tests/test_chrome.py (15): display-width basics + alignment on glyph/em-dash/CJK lines (the must-fix), color seam via subprocess (0 ANSI piped, ANSI under force, NO_COLOR overrides), signature content, no path/flag leak, note_noise append-and-never-raise. - tests/test_decision.py (51): golden validation + full negative-case matrix over the handoff-extension contract. - tests/fixtures/decision_v1/: two platform goldens, each a realistic handoff-extension — throughline (single_select, confirm:true) + image (approve_reject, confirm:false). New conformance (armed but dormant — graceful-skip until Phase 2): - Family F: each skill's vendored chrome.py is byte-identical to the canonical (same copy-not-share discipline as llm_config / user_intent; glyphs are multi-byte UTF-8 — copy verbatim). - Family G: each skill's decision.v1 goldens validate against the shared validator. Docs: - docs/reference/chrome-interaction-contract.md — canonical spec + the SKILL.md chrome contract text Phase 2 vendors into each skill (re-render decision.v1 completely inline as message text; honor `confirm` [echo-and-confirm on consequential gates, act directly on cheap/already-spent ones]; report stage/cost at boundaries from the run-record, no continuous tick; suppress orchestrator NOISE). - CRAFT-CONTRACT.md: §2.1 decision.v1 producer row + new §3.7 (the chrome / interaction layer) pointing at the canonical doc. - mkdocs.yml: Reference nav entry for the new doc. - prototypes/cycle4-chrome/: Phase-0 synthetic harness preserved as reference (the canonical doc + memory cite it); its generated scratch-draft/.handoff.json is gitignored. Verify: pytest 218 passed, 7 skipped (the dormant Family-F/G rows + the pre-existing adversarial run-record golden skip); ruff clean. Note for Adam: the stale .commit-message-craft-v0.4.1.txt (already-used Cycle-3 follow-up message) can be deleted — `git rm` or just remove it. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent 9d54874 commit 11e5973

15 files changed

Lines changed: 2280 additions & 0 deletions

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,3 +10,6 @@ dist/
1010
build/
1111
# MkDocs build output
1212
site/
13+
# Cycle-4 chrome prototype: generated scratch run artifacts (the harness
14+
# source IS committed; its synthetic .handoff.json output is not)
15+
prototypes/cycle4-chrome/scratch-draft/

CRAFT-CONTRACT.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,7 @@ support window (typically 1-2 minor releases per skill).
6161
| `layout-overlaps.v1` | presentation-maker | v1 (v0.8.0 G.10-A) | (cascade Tier-1; external integrators) | Stable through CRAFT v1.x |
6262
| `content-overflow.v1` | presentation-maker | v1 (v0.8.0 G.10-C) | (cascade Tier-1 + revise_loop) | Stable through CRAFT v1.x |
6363
| `review-cascade.v1` | paper-writer + presentation-maker | v1 (M4b pattern) | (external integrators; future review systems) | Stable through CRAFT v1.x |
64+
| `decision.v1` | presentation-maker, paper-writer, adversarial | v1 (Cycle-4 DP6; foundation v0.x, wired Phase 2) | the invoking Claude session (chrome/interaction layer) | Stable through CRAFT v1.x |
6465

6566
### 2.2 Schema-bump policy
6667

@@ -446,6 +447,48 @@ audit JSON at `audit/<your-skill>.json`. The platform's existing
446447
cascade readers will (eventually) lift them at Tier 1 via
447448
the read-if-present pattern.
448449

450+
### 3.7 The chrome / interaction layer (Cycle-4, DP6)
451+
452+
Skills run as subprocesses of an invoking Claude session. Two facts
453+
shape how their output reaches the user (both proven in the Cycle-4
454+
Phase-0 harness):
455+
456+
1. **The message body is the legible signal — not stdout.** A skill's
457+
stdout is non-TTY (no color) and the transcript folds long output,
458+
so the user sees the skill's progress/decisions only if Claude
459+
**re-renders the structured contract as message text.**
460+
2. **`decision.v1` extends `.handoff.json`.** A halt-and-handoff gate
461+
writes a `decision.v1` payload that RETAINS the handoff keys the
462+
`continue` CLI reads (notably `phase`, which authorizes `--pick`)
463+
and ADDS presentation fields; `gate` must equal `phase`.
464+
465+
The platform provides three pieces, all dependency-free / stdlib-only
466+
so they vendor cleanly into every skill:
467+
468+
- **`craft.chrome`** — the output-signature renderer (`STAGE` /
469+
`DECISION` / `RESULT` boxes + the per-skill glyph signature
470+
``/``/``; a single `_color()` seam that no-ops off-TTY; padding by
471+
**display width**, not `len()`). Each skill vendors a **byte-identical
472+
copy** (Family-F conformance — same copy-not-share discipline as
473+
`llm_config` and `user_intent`).
474+
- **`craft.decision`**`validate_decision(payload) -> list[str]`, the
475+
`decision.v1` shape contract (checks both the retained handoff keys
476+
and the presentation fields).
477+
- **The SKILL.md chrome contract text** — the Claude-facing instruction
478+
block each skill carries, telling the session to re-render decisions
479+
completely inline, honor the `confirm` flag (echo-and-confirm on
480+
consequential gates; act directly on cheap/already-spent ones), report
481+
stage/cost at boundaries from the run-record, and suppress orchestrator
482+
NOISE to `audit/orchestrator.log`.
483+
484+
Full specification + the canonical SKILL.md text:
485+
`docs/reference/chrome-interaction-contract.md`.
486+
487+
**Phasing:** Cycle-4 Phase 1 ships the foundation (the two modules,
488+
their goldens, the contract text, and armed-but-dormant Family-F /
489+
Family-G conformance). Phase 2 vendors the copies into each skill and
490+
flips the conformance families live.
491+
449492
---
450493

451494
## 4. Joining the platform (for future skills)
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# CRAFT chrome / interaction-layer contract (canonical)
2+
3+
**Schema:** `decision.v1` (the halt-and-handoff presentation contract) + the
4+
CRAFT output-signature blocks (`STAGE` / `DECISION` / `RESULT`).
5+
**Renderer:** `craft.chrome` (canonical at `craft-platform/src/craft/chrome.py`;
6+
each skill VENDORS a byte-identical copy — Family-F conformance).
7+
**Validator:** `craft.decision.validate_decision`.
8+
**Status:** Cycle-4 (DP6) Phase 1 — foundation. Wired into skills in Phase 2.
9+
10+
This is the **source of record** for the chrome section that each CRAFT skill
11+
carries in its `SKILL.md`. Phase 2 vendors the "Contract text for SKILL.md"
12+
block below into each skill verbatim. Keep this doc and the per-skill SKILL.md
13+
copies in sync (they are a copy-not-share pair, like the chrome.py vendoring).
14+
15+
---
16+
17+
## Why this contract exists (the two facts it is built on)
18+
19+
Both were proven empirically in the Cycle-4 Phase-0 synthetic harness
20+
(`prototypes/cycle4-chrome/`):
21+
22+
1. **Inside Claude, the message body is the legible signal — not the box.**
23+
A skill's orchestrator runs as a non-TTY subprocess, so (a) its stdout
24+
carries **zero color** (the `chrome._color()` seam correctly no-ops when
25+
piped) and (b) the Claude transcript **folds** long tool output. The boxed
26+
STAGE/DECISION/RESULT blocks are real and useful at a bare terminal, but
27+
**inside Claude the user sees them only if Claude re-renders them as message
28+
text.** So the contract is: *Claude reconstructs the structured contract
29+
(decision.v1 / run-record) as its own message, in a consistent presentation
30+
style.* "Pass the box through verbatim" is NOT enough — folded output hides
31+
it.
32+
33+
2. **`decision.v1` EXTENDS the existing `.handoff.json` — it does not replace
34+
it.** The `continue` CLI reads the handoff's own keys (notably **`phase`**,
35+
which authorizes `--pick`). A decision payload therefore RETAINS those keys
36+
and ADDS the presentation fields on top. `gate` MUST equal `phase`. This is
37+
why `validate_decision` checks both contracts at once.
38+
39+
---
40+
41+
## The three signature classes (what the orchestrator emits)
42+
43+
| Class | Box | When | Prominence |
44+
|---|---|---|---|
45+
| `STAGE` | single rule `┌─┐` | a pipeline stage starts/updates | quiet, present |
46+
| `DECISION` | double rule `╔═╗` | a halt-and-handoff gate | **loudest** |
47+
| `RESULT` | no box, terse glyph line | a stage/run produces a deliverable | terse |
48+
49+
Each carries the per-skill signature: a glyph + `CRAFT · <skill>` label.
50+
51+
| Skill | Glyph |
52+
|---|---|
53+
| presentation-maker | `` |
54+
| paper-writer | `` |
55+
| adversarial | `` |
56+
57+
Color is strictly additive (a bare-terminal bonus through the single
58+
`_color()` seam); the structural signature (box + glyph + label) carries the
59+
whole distinction with color stripped — which is the inside-Claude condition.
60+
61+
---
62+
63+
## `decision.v1` shape
64+
65+
`decision.v1` is written to `<draft_dir>/.handoff.json` at a halt. It is the
66+
union of the real handoff keys and the presentation fields:
67+
68+
```jsonc
69+
{
70+
// --- retained handoff keys (the continue CLI reads these) ---
71+
"phase": "throughline_pick", // the halt id; AUTHORIZES --pick (required)
72+
"draft_dir": "/abs/.../talks/draft_N",
73+
// ... any other handoff fields the skill already wrote (candidates,
74+
// candidates_md, next_command, …) are retained, not stripped.
75+
76+
// --- decision.v1 presentation fields ---
77+
"schema_version": "decision.v1",
78+
"skill": "presentation-maker", // presentation-maker | paper-writer | adversarial
79+
"gate": "throughline_pick", // MUST equal `phase`
80+
"prompt": "Pick the throughline for the deck:",
81+
"kind": "single_select", // single_select | approve_reject | free_text
82+
"options": [
83+
{"id": "TL1", "summary": "one-line", "detail": "FULL text — show completely"}
84+
// … empty list allowed only for free_text
85+
],
86+
"default": "TL1", // str | null; if set, must be an option id
87+
"confirm": true, // bool — gates the echo-and-confirm beat (below)
88+
"continue": {"cmd": "beril-presentation-maker continue {draft_dir} --pick {id}"}
89+
}
90+
```
91+
92+
`continue.cmd` MUST carry the `{id}` placeholder so Claude can splice the
93+
chosen option id. The user must NEVER see the raw `--pick` flag or the draft
94+
path.
95+
96+
---
97+
98+
## Contract text for SKILL.md (vendor this block into each skill, Phase 2)
99+
100+
> *(Substitute the skill's own name + glyph. The text below is for
101+
> presentation-maker; paper-writer uses `✎ paper-writer`, adversarial uses
102+
> `⚔ adversarial`.)*
103+
104+
### CRAFT interaction layer — how to surface this skill's output
105+
106+
This skill's orchestrator runs as a subprocess. Inside this session its stdout
107+
is non-TTY (no color) and long output is folded out of view, so **the
108+
user-facing signal is YOUR message, not the raw tool output.** Your job is to
109+
re-render the skill's structured signals — STAGE progress, DECISION halts,
110+
RESULT deliverables — as clear, consistent message text. Follow these rules:
111+
112+
1. **Re-render the structured contract as message text — do not rely on the
113+
raw box being visible.** When a tool result contains a CRAFT `STAGE` /
114+
`DECISION` / `RESULT` block (or the `decision.v1` / run-record JSON behind
115+
it), reconstruct it in your own message in a consistent presentation style.
116+
The boxed block in stdout is bare-terminal chrome and a data source; the
117+
legible copy the user reads is the one you write. Keep your style consistent
118+
run-to-run so the skill's "voice" is recognizable.
119+
120+
2. **On a DECISION halt, render the choice completely + inline, honor
121+
`confirm`, then act on the reply.** When the orchestrator halts at a gate it
122+
writes a `decision.v1` to `<draft_dir>/.handoff.json` and exits; the run is
123+
paused waiting for the user. You MUST:
124+
- Present **every** option **completely** — show each option's full
125+
`detail` text, not just the `summary`, and never a file path or a "see
126+
`.handoff.json`". The user decides from what you show; show all of it.
127+
- Make choosing trivial: list the option `id`s (e.g. **TL1 / TL2 / TL3**),
128+
note which is the `default`, and ask the user to pick one.
129+
- **Honor `confirm`:**
130+
- `confirm: true` (consequential, hard-to-reverse gates — e.g. a
131+
throughline pick that sets the whole deck): after the user picks, **echo
132+
the pick back and confirm once** ("Resuming with **TL1** — go?") BEFORE
133+
running `continue.cmd`. Do not silent-auto-run.
134+
- `confirm: false` (the choice is cheap or already-spent — e.g. an
135+
image-approval where the cost is already incurred): run `continue.cmd`
136+
directly on the stated choice; no extra confirmation beat needed.
137+
- When you act, invoke the command in `continue.cmd`, substituting the
138+
chosen `id` for `{id}`. The user must NEVER see or type the raw `--pick`
139+
flag or the draft path — you translate their plain choice into the
140+
command and run it.
141+
- Do not invent options or change the `id`s; use exactly what `decision.v1`
142+
carries. For `kind: free_text`, the user's typed reply fills the `{id}`
143+
slot.
144+
145+
3. **Report progress + cost at boundaries, from the run-record — not a
146+
continuous tick.** The STAGE banners mark stage boundaries; surface a brief
147+
"Stage N/M (<stage>) — <state>" line at those boundaries, and read
148+
stage/cost/tokens/model from the run-record (`audit/run_record.json`) when
149+
reporting them. Do **not** narrate every internal step or invent a live
150+
progress meter (continuous live ticking is deferred). A final cost/summary
151+
line when the run completes is welcome; per-step "now I'll run the next
152+
stage" narration is noise.
153+
154+
4. **Suppress orchestrator NOISE.** Lines the orchestrator marks as internal
155+
(e.g. `[orchestrator] …` diagnostics) are written to
156+
`<audit_dir>/orchestrator.log`, not to the user. Do not surface that log
157+
content unless the user is debugging or an error/blocker requires it.
158+
159+
5. **Speak up only when it carries signal.** A DECISION (rule 2), an
160+
error/blocker, a boundary report (rule 3), or a final summary the user asked
161+
for. A bare "Stage 5 is running." with nothing else is worse than silence —
162+
let the structured re-render do the work.
163+
164+
---
165+
166+
## Conformance
167+
168+
- **Family F** (`tests/test_conformance.py`) — each skill's vendored
169+
`chrome.py` is byte-identical to craft-platform's canonical. Graceful-skips
170+
until Phase 2 vendors the copies; fails loud after. The glyphs are multi-byte
171+
UTF-8 — copy the file verbatim (a locale/encoding slip diverges them
172+
invisibly).
173+
- **Family G** (`tests/test_conformance.py`) — each skill's shipped
174+
`decision.v1` goldens (`tests/fixtures/decision_v1/*.json`) validate against
175+
the shared `validate_decision`. Graceful-skips until Phase 2.
176+
- **Platform goldens** (`tests/test_decision.py`) — the canonical
177+
`decision.v1` goldens (`tests/fixtures/decision_v1/throughline_single_select.json`,
178+
`image_approve_reject.json`) validate clean and are the worked examples of
179+
the handoff-extension shape (single_select/confirm:true and
180+
approve_reject/confirm:false).
181+
- **Renderer** (`tests/test_chrome.py`) — display-width alignment (the
182+
must-fix: pad by visible glyph width, not `len()`), the color seam, the
183+
signature content, and the no-path/no-flag-leak guarantee.

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -129,5 +129,6 @@ nav:
129129
- Adding a skill: extending/adding-a-skill.md
130130
- External contributors: extending/external-contributors.md
131131
- Reference:
132+
- Chrome / interaction contract: reference/chrome-interaction-contract.md
132133
- Release notes: reference/release-notes.md
133134
- Platform proposal: reference/platform-proposal.md

prototypes/cycle4-chrome/README.md

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
# CRAFT Cycle-4 chrome — Phase-0 synthetic harness (PROTOTYPE)
2+
3+
Scratch prototype to prove the two riskiest design bets of Cycle-4 (chrome/
4+
legibility, DP6) in a **real Claude session**, cheaply, **before** touching any
5+
skill repo. Brief: `handoffs/CRAFT-cycle4-chrome-legibility-brief.md` (Phase 0
6+
+ Pillars 1–3).
7+
8+
Nothing here is shipped. No skill repos are touched; no git. This is the
9+
prototype we later harden into the real `chrome.py` + `decision.v1` validator.
10+
11+
## Files
12+
- `chrome.py` — prototype renderer. `render_stage` / `render_decision` /
13+
`render_result`; per-skill glyph+label (◆ presentation-maker · ✎ paper-writer
14+
· ⚔ adversarial); a single `_color()` seam (TTY-gated, `NO_COLOR`-aware,
15+
no-op when piped). `python chrome.py --demo` prints one of each block.
16+
- `synthetic_halt.py` — writes a `decision.v1` to a scratch `.handoff.json`,
17+
prints the DECISION block, exits 0 like the real halt. `--gate
18+
throughline_pick` (default, single_select) or `--gate image_approval`
19+
(approve_reject — proves the contract generalizes).
20+
- `chrome-interaction.md` — draft of the SKILL.md chrome section (the
21+
Claude-interaction contract under test in Test 2).
22+
23+
## Design choices in this prototype (the parked open decisions)
24+
- **STAGE = single-rule box** (`┌─┐`), **DECISION = double-rule box** (`╔═╗`) —
25+
DECISION is the most prominent class by design.
26+
- **RESULT = no box**, a terse glyph-tagged one-liner.
27+
- **Color is strictly additive.** Every distinction (box + glyph + label) is
28+
structural and survives with color stripped. `_color()` is the only ANSI
29+
emitter; it no-ops unless stdout is a TTY and `NO_COLOR` is unset.
30+
31+
---
32+
33+
## Adam's test protocol (run in a REAL Claude session)
34+
35+
### Test 1 — signature + color reality
36+
Have Claude run:
37+
```
38+
python chrome.py --demo
39+
```
40+
Observe in the tool-result block:
41+
- Do the **box + glyph + skill-ID label** read as DISTINCT from Claude's
42+
surrounding prose? Is "which skill is talking" instant?
43+
- Does **color** render, come through as raw escape codes (`\033[36m…`), or get
44+
stripped? (Expectation from the brief: stripped/absent inside Claude, since
45+
the subprocess stdout is non-TTY. Confirm empirically.)
46+
- Compare single-rule STAGE vs double-rule DECISION — does the prominence
47+
ordering read right?
48+
49+
Optional bare-terminal contrast (shows the color seam working where a TTY
50+
exists): `CRAFT_FORCE_COLOR=1 python chrome.py --demo` vs piped
51+
`python chrome.py --demo | cat` (latter must be plain).
52+
53+
### Test 2 — decision.v1 inline rendering (the central bet)
54+
With `chrome-interaction.md` loaded into the session context, have Claude run:
55+
```
56+
python synthetic_halt.py
57+
```
58+
Observe:
59+
- Does Claude present the **full** throughline options (TL1/TL2/TL3) cleanly
60+
**inline**, including each option's complete `detail` text — NOT a summary,
61+
NOT "see .handoff.json"?
62+
- Does it make choosing trivial (lists the ids + the default) and collect a
63+
choice (e.g. "TL1")?
64+
- On the reply, does it show it would invoke
65+
`beril-presentation-maker continue <draft_dir> --pick TL1` — with the user
66+
never seeing the raw `--pick` flag or the draft path?
67+
68+
Also run `python synthetic_halt.py --gate image_approval` to confirm the
69+
approve/reject `kind` renders + collects cleanly too.
70+
71+
**Gate:** if the in-session decision rendering doesn't behave, we adjust
72+
`decision.v1` + `chrome-interaction.md` HERE (cheap) before wiring any
73+
pipeline.
74+
75+
---
76+
77+
## Phase-0 findings (CC, in-session, 2026-06-09)
78+
79+
**Both bets PROVED.** Test 1: the box + glyph + `CRAFT · <skill>` label read as
80+
clearly distinct inside a Claude tool-result block, amid prose — structural,
81+
zero color needed. Test 2: with `chrome-interaction.md` as the contract, the
82+
synthetic `decision.v1` halt rendered as a complete inline choice (all three
83+
candidates, full detail) with a trivial "tell me TL1/TL2/TL3" and no
84+
path/flag exposed; the `.handoff.json` artifact is well-formed; both
85+
`single_select` and `approve_reject` kinds render + collect cleanly.
86+
87+
**Color reality confirmed empirically:** captured (non-TTY, i.e. inside Claude)
88+
→ ZERO ANSI, plain box-drawing. `CRAFT_FORCE_COLOR=1` (bare TTY) → 53 ANSI
89+
lines. `NO_COLOR` overrides force. So color is absent exactly where legibility
90+
matters, and the structural signature carries the whole distinction — as the
91+
brief predicted.
92+
93+
**Two defects found + fixed in-phase (the gate's purpose):**
94+
1. *Box width blowout* — one long line (the raw `continue.cmd`, ~190 chars with
95+
an absolute draft path) stretched the DECISION box off-screen. Fixed: the
96+
box is now a FIXED width and every content line is wrapped to it (full text
97+
preserved, never truncated); padding uses visible length so color escapes
98+
don't push the border.
99+
2. *Path/flag leak* — the rendered DECISION showed the raw
100+
`continue …/talks/draft_3 --pick {id}` line, which the brief says the user
101+
must NEVER see. Fixed: removed from the block entirely; replaced with
102+
"To choose: tell me TL1 / TL2 / TL3." Claude translates the plain id →
103+
`continue.cmd` per the SKILL.md contract.
104+
(Residual: a 1-char right-border off-by-one on a few lines ending exactly at
105+
the wrap boundary — em-dash/Unicode cell-width; cosmetic, deferred to
106+
hardening.)
107+
108+
## Three open decisions — CC's read (hand-back)
109+
110+
1. **Glyph set + box styling — KEEP as prototyped.** ◆/✎/⚔ + single-rule STAGE
111+
vs double-rule DECISION gave a clear, correct prominence ordering in-session
112+
(DECISION unmistakably loudest, STAGE present-but-quiet, RESULT terse). The
113+
double rule is worth reserving exclusively for DECISION. One caution: the
114+
glyphs are multi-byte Unicode — fine in the Claude transcript, but the
115+
hardened renderer's Family-F byte-identity check must store them UTF-8 and
116+
the shell-side presmaker copy must emit the same bytes.
117+
118+
2. **Auto-invoke vs confirm — PRESENT + CONFIRM, do not auto-invoke.** The
119+
decision halts are exactly the high-stakes, hard-to-reverse moments
120+
(throughline sets the whole deck; image-approval spends/commits). Claude
121+
should collect the choice, then run `continue.cmd` — but a throughline pick
122+
is consequential enough that a one-beat "resuming with TL1 — go?" is the
123+
right default over silent auto-run. For `approve_reject` image the cost is
124+
already spent, so confirm-then-run is fine too. Net: the SKILL.md contract
125+
should say "collect the choice and resume" but NOT "auto-run without
126+
acknowledgement." (Adam's call — flagged because it's a UX/safety tradeoff,
127+
not a code one.)
128+
129+
3. **Color — KEEP the `_color()` seam, structural-first.** It costs one
130+
well-contained function, it's correct (off when captured, on at a bare TTY,
131+
`NO_COLOR` honored), and it's a free win for anyone running a skill directly
132+
in a terminal. It is never load-bearing (Phase 0 proved structural-only
133+
carries the signal). So: ship structural as the contract, keep color as the
134+
documented progressive enhancement. Not worth REMOVING; not worth investing
135+
more than the seam.

0 commit comments

Comments
 (0)