patch-cc 0.4.0__tar.gz → 0.5.1__tar.gz

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.
Files changed (44) hide show
  1. {patch_cc-0.4.0 → patch_cc-0.5.1}/AGENTS.md +12 -6
  2. {patch_cc-0.4.0 → patch_cc-0.5.1}/PKG-INFO +4 -4
  3. {patch_cc-0.4.0 → patch_cc-0.5.1}/README.md +3 -3
  4. {patch_cc-0.4.0 → patch_cc-0.5.1}/docs/INTERNALS.md +75 -13
  5. {patch_cc-0.4.0 → patch_cc-0.5.1}/docs/PLAYBOOK.md +150 -29
  6. {patch_cc-0.4.0 → patch_cc-0.5.1}/docs/corpus.md +19 -8
  7. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/__init__.py +1 -1
  8. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/blob.py +228 -42
  9. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/container.py +73 -5
  10. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/cli.py +33 -7
  11. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/doctor.py +109 -2
  12. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/js.py +43 -0
  13. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/menu.py +38 -4
  14. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patcher.py +13 -7
  15. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/base.py +14 -0
  16. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/chrome.py +33 -2
  17. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/streaming.py +502 -132
  18. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/ui.py +31 -5
  19. {patch_cc-0.4.0 → patch_cc-0.5.1}/.github/workflows/ci.yml +0 -0
  20. {patch_cc-0.4.0 → patch_cc-0.5.1}/.github/workflows/release-watch.yml +0 -0
  21. {patch_cc-0.4.0 → patch_cc-0.5.1}/.github/workflows/release.yml +0 -0
  22. {patch_cc-0.4.0 → patch_cc-0.5.1}/.gitignore +0 -0
  23. {patch_cc-0.4.0 → patch_cc-0.5.1}/CLAUDE.md +0 -0
  24. {patch_cc-0.4.0 → patch_cc-0.5.1}/LICENSE +0 -0
  25. {patch_cc-0.4.0 → patch_cc-0.5.1}/docs/CONDUCT.md +0 -0
  26. {patch_cc-0.4.0 → patch_cc-0.5.1}/docs/demo.gif +0 -0
  27. {patch_cc-0.4.0 → patch_cc-0.5.1}/pyproject.toml +0 -0
  28. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/__init__.py +0 -0
  29. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/elf.py +0 -0
  30. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/errors.py +0 -0
  31. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/bun/macho.py +0 -0
  32. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/cache.py +0 -0
  33. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/codex/__init__.py +0 -0
  34. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/codex/gateway.py +0 -0
  35. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/codex/models.py +0 -0
  36. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/codex/oauth.py +0 -0
  37. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/codex/translate.py +0 -0
  38. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/locate.py +0 -0
  39. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/__init__.py +0 -0
  40. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/agents.py +0 -0
  41. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/codex.py +0 -0
  42. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/output.py +0 -0
  43. {patch_cc-0.4.0 → patch_cc-0.5.1}/src/patch_cc/patches/thinking.py +0 -0
  44. {patch_cc-0.4.0 → patch_cc-0.5.1}/uv.lock +0 -0
@@ -17,9 +17,15 @@ point it at the pristine copies in `~/.local/share/patch-cc/backups/` to sweep
17
17
  older builds). There is no test suite by design — doctor against real bundles
18
18
  is the check.
19
19
 
20
- Be exact about what that covers: doctor runs the **matchers** and parses the
21
- bundle they produce. It never runs the gateway, the translator, the OAuth flow
22
- or the menu, so a change under `src/patch_cc/codex/` (the bridge all of it
23
- runtime, none of it a patch; the Codex *patch* is `patches/codex.py`, which the
24
- sweep does cover) or in `menu.py` is checked by exercising it — a real `codex
25
- serve` against a real turn and a green sweep says nothing about it.
20
+ Be exact about what that covers: doctor runs the **matchers**, parses the
21
+ bundle they produce, then **bakes it into a temp binary and executes
22
+ `--version`** so the sweep also proves every build writes and boots (the
23
+ container layer included; 2.1.246 is why). It still never runs the gateway,
24
+ the translator, the OAuth flow or the menu, so a change under
25
+ `src/patch_cc/codex/` (the bridge all of it runtime, none of it a patch; the
26
+ Codex *patch* is `patches/codex.py`, which the sweep does cover) or in
27
+ `menu.py` is checked by exercising it — a real `codex serve` against a real
28
+ turn — and a green sweep says nothing about it. Nor does `--version` exercise
29
+ a patch's *feature* at runtime: it proves the patched code loads and runs, not
30
+ that a streamed turn renders — behaviour changes still want a real turn
31
+ watched.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: patch-cc
3
- Version: 0.4.0
3
+ Version: 0.5.1
4
4
  Summary: Interactive patcher for the Claude Code native binary
5
5
  Project-URL: Homepage, https://github.com/anfreire/patch-cc
6
6
  Project-URL: Repository, https://github.com/anfreire/patch-cc
@@ -94,7 +94,7 @@ patch-cc # then just run it
94
94
  | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
95
95
  | | Mark `--version` | Appends `(patched)` — or any marker you choose |
96
96
  | | Custom startup name | Defaults to `<your username>'s Code` |
97
- | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line) |
97
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line). Upstream stopped drawing the segment in 2.1.246, so newer builds offer this dimmed as *not on this build* |
98
98
 
99
99
  ## Usage
100
100
 
@@ -113,8 +113,8 @@ uvx patch-cc apply --org-label "Ada's Lab" # ...or show this instead
113
113
  uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
114
114
  uvx patch-cc apply --from-cache # replay your last remembered selection
115
115
  uvx patch-cc status # exactly what is applied
116
- uvx patch-cc doctor # do all patches match this build?
117
- uvx patch-cc doctor path/to/claude # ...or match some other binary
116
+ uvx patch-cc doctor # do all patches match this build, and does it bake + boot?
117
+ uvx patch-cc doctor path/to/claude # ...same, for some other binary
118
118
  uvx patch-cc list # every patch, described
119
119
  uvx patch-cc restore # put the original back
120
120
  ```
@@ -63,7 +63,7 @@ patch-cc # then just run it
63
63
  | Chrome & branding | Disable spinner tips | No rotating tips on the spinner |
64
64
  | | Mark `--version` | Appends `(patched)` — or any marker you choose |
65
65
  | | Custom startup name | Defaults to `<your username>'s Code` |
66
- | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line) |
66
+ | | Startup org/email label | Replace the org/email on the welcome screen — or hide it (demo mode keeps the stock line). Upstream stopped drawing the segment in 2.1.246, so newer builds offer this dimmed as *not on this build* |
67
67
 
68
68
  ## Usage
69
69
 
@@ -82,8 +82,8 @@ uvx patch-cc apply --org-label "Ada's Lab" # ...or show this instead
82
82
  uvx patch-cc apply --codex gpt-5.6-sol # + a Codex model (see below)
83
83
  uvx patch-cc apply --from-cache # replay your last remembered selection
84
84
  uvx patch-cc status # exactly what is applied
85
- uvx patch-cc doctor # do all patches match this build?
86
- uvx patch-cc doctor path/to/claude # ...or match some other binary
85
+ uvx patch-cc doctor # do all patches match this build, and does it bake + boot?
86
+ uvx patch-cc doctor path/to/claude # ...same, for some other binary
87
87
  uvx patch-cc list # every patch, described
88
88
  uvx patch-cc restore # put the original back
89
89
  ```
@@ -23,17 +23,33 @@ treats every module the container declares to be JavaScript as one surface.
23
23
  .bun section
24
24
  └── [u64 size prefix] (u32 on Bun < 1.3.4)
25
25
  └── Bun blob
26
- ├── payload arena name / contents / sourcemap / bytecode / ... bytes
26
+ ├── payload arena name / contents / sourcemap / bytecode / ... bytes,
27
+ │ plus the record chain's own payloads (Bun >= 1.4.1)
27
28
  ├── module table N records × 52 bytes (36 on old Bun)
29
+ ├── record chain flag-gated records (Bun >= 1.4.1), see below
28
30
  ├── compileExecArgv
29
31
  ├── offsets struct 32 bytes: byteCount, modulesPtr, entryId, argvPtr, flags
30
32
  └── "\n---- Bun! ----\n" 15-byte trailer
31
33
  ```
32
34
 
33
35
  Every pointer is a `(u32 offset, u32 length)` pair relative to the blob start,
34
- and pointers live in only two places: the module table and the offsets struct.
35
- That is what makes rewriting tractable — move a payload, fix the handful of
36
- pointers that describe it.
36
+ and pointers live in exactly three places: the module table, the offsets
37
+ struct, and the record chain. That is what makes rewriting tractable — move a
38
+ payload, fix the handful of pointers that describe it.
39
+
40
+ Two invariants ride on payload *positions* rather than pointers, and `rebuild`
41
+ preserves both by keeping every payload's inter-payload gap and its offset
42
+ phase modulo 128 — so a rebuild with no edits reproduces the blob byte for
43
+ byte, and one with edits moves payloads only in whole alignment steps:
44
+
45
+ - Bytecode payloads (module bytecode, and the record chain's bytecode blobs)
46
+ sit at blob `offset % 128 == 120`, which is 128-byte alignment once the
47
+ section's 8-byte size prefix is in front. Bun ≥ 1.4.1 deserializes bytecode
48
+ in place and calls misalignment "a runtime assertion error or segfault"
49
+ (`append_bytecode_aligned`); older Bun quietly tolerated the phase drift the
50
+ rewriter used to introduce.
51
+ - `count_z` payloads (names, contents) carry a NUL terminator in the gap
52
+ after them.
37
53
 
38
54
  A module record (new 52-byte format) is six such pairs — `name`, `contents`,
39
55
  `sourcemap`, `bytecode`, `moduleInfo`, `bytecodeOriginPath` — followed by four
@@ -41,6 +57,42 @@ A module record (new 52-byte format) is six such pairs — `name`, `contents`,
41
57
 
42
58
  Code: `src/patch_cc/bun/blob.py`.
43
59
 
60
+ ## The record chain (Bun ≥ 1.4.1)
61
+
62
+ 2.1.246 moved to Bun 1.4.1, whose `StandaloneModuleGraph.rs` chains optional
63
+ records directly after the module table, each announced by a new `flags` bit
64
+ and read back in flag order:
65
+
66
+ | bit | record |
67
+ |---|---|
68
+ | 5 | `[u32; modules]` — each module's WTF hash of its source text (0 = none) |
69
+ | 6 | `u32 count`, then `count` × `{u32 id, ptr}` — internal-module bytecode |
70
+ | 7 | one pointer: the **shared bytecode string table** |
71
+ | 8 | `u32` — how many leading modules load before the first `import()` |
72
+ | 9 | one pointer: the string table `moduleInfo` bodies index |
73
+
74
+ The pointers point back into the arena: the shared string table (~9.9 MB on
75
+ 2.1.246) is the string data **every chunk's bytecode references by ordinal**,
76
+ so it is load-bearing for every module we did *not* touch. The hash is JSC's
77
+ SourceCodeKey hash, how a launch that runs from bytecode avoids paging in
78
+ source text just to hash it.
79
+
80
+ patch-cc parses the chain record for record (`_parse_records` — a build with
81
+ none of the bits, which is every Bun before 1.4.1, walks zero records through
82
+ the same code), carries the pointed-at payloads through `rebuild` like any
83
+ module payload, copies the whole tail between table and offsets struct
84
+ verbatim, and re-points the pointers in place. Two details matter:
85
+
86
+ - An **edited** module's hash word is zeroed — upstream's own "none, compute
87
+ it" value — because the pristine text's hash must not key our bytes in JSC's
88
+ source cache.
89
+ - **Unknown** record bits are refused at parse: a record of unknown size
90
+ cannot be walked past nor re-pointed, and rewriting around it is exactly how
91
+ a graph gets corrupted. 2.1.246 against patch-cc ≤ 0.4.0 is the lesson: the
92
+ chain-blind rewriter dropped the records and zero-filled the string table
93
+ while every *module* round-tripped byte-perfect — matcher-green, dead at
94
+ launch, `SIGSEGV` from inside Bun's graph loader.
95
+
44
96
  ## The 2.1.242 split, and the patchable surface
45
97
 
46
98
  Through 2.1.241 the entrypoint module *was* the app: one ~28 MB `contents`
@@ -86,9 +138,11 @@ detects the mismatch and recompiles that module from source at launch. So keepin
86
138
  a stale copy buys nothing — the recompile is paid either way — and dropping it
87
139
  reclaims the space and guarantees our edits are what runs. patch-cc drops the
88
140
  bytecode of exactly the modules it edited (`rebuild` over `changed_modules`) and
89
- leaves every untouched module its bytecode and its fast start. On Linux, where
90
- the ELF section is rewritten in place, the binary is smaller by exactly the
91
- edited modules' bytecode.
141
+ leaves every untouched module its bytecode and its fast start which is why
142
+ the shared bytecode string table those modules' bytecode indexes
143
+ ([the record chain](#the-record-chain-bun--141)) is never droppable. On Linux,
144
+ where the ELF section is rewritten in place, the binary is smaller by exactly
145
+ the edited modules' bytecode.
92
146
 
93
147
  Measured on 2.1.243, the full patch set:
94
148
 
@@ -114,10 +168,12 @@ re-laying `__LINKEDIT`, which is not done yet.
114
168
 
115
169
  Every write asserts each **edited** module carries `bytecode == 0` in the binary
116
170
  it produced (`container.verify`, beside the round-trip check), and `patch-cc
117
- status` reports the total for an installed one. `doctor` cannot: a dry run is
118
- handed a *clean* bundle, which still has all its bytecode by definition. If a
119
- future Bun build makes bytecode authoritative over source, that assert is the
120
- tripwire every edit would silently no-op otherwise.
171
+ status` reports the total for an installed one. `doctor`'s dry run cannot — a
172
+ clean bundle still has all its bytecode by definition but its smoke bake
173
+ writes a temp binary through the same `container.write` and then *executes* it,
174
+ so the sweep exercises the assert, and the loader itself, on every corpus
175
+ build. If a future Bun build makes bytecode authoritative over source, that
176
+ assert is the tripwire — every edit would silently no-op otherwise.
121
177
 
122
178
  ## Writing it back without ballooning
123
179
 
@@ -196,8 +252,14 @@ rather than from a store of their own.
196
252
  and dropped rather than aborting the run. See
197
253
  [PLAYBOOK.md](PLAYBOOK.md#the-syntax-gate).
198
254
  - Every write is verified: patch-cc re-extracts the JS from the binary it just
199
- wrote and asserts every module equals what it meant to write, and that each
200
- module it edited carries no leftover bytecode to run instead of the edit.
255
+ wrote and asserts every module equals what it meant to write, that each
256
+ module it edited carries no leftover bytecode to run instead of the edit, and
257
+ that the graph around the modules survived — the record chain kept its
258
+ length and flags, its arena payloads (the shared bytecode string table above
259
+ all) round-trip byte-identical, and every source-hash word is the pristine
260
+ one, except an edited module's, which must be zero. The chain checks exist
261
+ because 2.1.246 failed *only* there: every module compared equal while the
262
+ written binary was dead.
201
263
  - Patching a binary that is already marked, when no pristine backup exists, is
202
264
  refused outright — there is nothing clean to start from, and our edits change
203
265
  lengths, so a second pass would corrupt rather than update. `restore` or a
@@ -309,6 +309,20 @@ isolation was cheaper and could not see the one ordering this project calls
309
309
  load-bearing — `codex-models` registering the ids `subagent-models` then pins —
310
310
  nor anything at all about the bundle the run produced, which it discarded.
311
311
 
312
+ The composed result is then **baked and executed**: doctor writes it into a
313
+ temp binary through the same `container.write` as a real apply — staging,
314
+ round-trip verification, the container checks — and runs `<binary> --version`,
315
+ expecting the version-marker suffix in the output (our own edit's print, so the
316
+ check proves patched code *executes*, not merely that the binary boots around
317
+ it). Matching and running are different truths with different failure owners:
318
+ matchers break in the patches, the run breaks in the container layer, and
319
+ 2.1.246 is the build where they split — every matcher green, every module
320
+ round-tripping byte-perfect, and the written binary `SIGSEGV`ing in Bun's graph
321
+ loader because what the rewrite had destroyed (the Bun 1.4.1 record chain and
322
+ the shared bytecode string table, docs/INTERNALS.md) lives in bytes no module
323
+ owns. The temp binary is always removed; the backup under test is never
324
+ written to.
325
+
312
326
  Three readings of the two numbers, kept distinct:
313
327
 
314
328
  | symptom | meaning |
@@ -608,6 +622,12 @@ that broke a patch along with every note.
608
622
  enumerated with its hashes in [corpus.md](corpus.md) — so both sides are
609
623
  recomputed from the binaries themselves.
610
624
 
625
+ Each build's report ends with its baked binary's own `--version` output (the
626
+ smoke run above), so the sweep also proves every corpus build still *bakes
627
+ and boots* — the half matcher counts cannot see, and the half 2.1.246 broke
628
+ while every count held. A moved runtime line is as much a finding as a moved
629
+ count.
630
+
611
631
  ## Patch reference
612
632
 
613
633
  Grouped by source module. The menu's three groups (Output & display, Models &
@@ -804,12 +824,15 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
804
824
 
805
825
  - **`live-thinking`** — the fourteen-step patch above.
806
826
 
807
- **`prop-threading`** inserts `streamingThinking:<state>,` before the
808
- `conversationId` property of every props bag *handed to a component* an
809
- argument that also carries `messages`. Being an argument is part of the
810
- identity: a module-level literal, a return-value payload, or a config object
811
- may legitimately carry the pair, and none of them is a render. Three things
812
- do the work, and each replaced something that had broken:
827
+ **`prop-threading`** hands the live-thinking state to every conversation
828
+ render a props bag *handed to a component* (an argument) that carries
829
+ `conversationId` and `messages`. Being an argument is part of the identity:
830
+ a module-level literal, a return-value payload, or a config object may
831
+ legitimately carry the pair, and none of them is a render. On a build whose
832
+ component owns the state it inserts `streamingThinking:<state>,` before the
833
+ bag's `conversationId`; on a store build it reroutes the render through a
834
+ wrapper component of ours that subscribes itself (below). Three things do
835
+ the work, and each replaced something that had broken:
813
836
 
814
837
  - **Identity is membership — of the essential props alone.** The props that
815
838
  make a render a conversation render, asked of one object rather than of a
@@ -848,28 +871,96 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
848
871
  *initialised* to is deliberately not asked: `useState(null)` is every
849
872
  build's spelling and `useState(void 0)` would be the same state, while the
850
873
  setter is the identity that matters.
851
- - **The state has two homes, and the handed setter names both.** Through
852
- 2.1.235 the scope that hands `onStreamingThinking` declared the state
853
- itself, and the state is the array pattern binding the handed setter —
854
- `useState`'s pair. 2.1.236 moved it into an external stream store
855
- (`subscribe`/`getSnapshot`/`_publish` the `useSyncExternalStore` shape)
856
- and hands `<store>.setStreamingThinking` instead; the same scope reads the
857
- store back by destructuring the hook call it hands the store to
858
- (`{streamingToolUses:…}=useX(<store>)`), so the state is that pattern's own
859
- `streamingThinking` binding upstream's the day it takes one, the goal
860
- achieved, and until then ours, inserted at the front of the pattern. The
861
- pattern is proven the snapshot read by the store expression itself: the
862
- call's *only* argument is the very expression the setter was read off, one
863
- answer or none (`js.only`). Sole argument is deliberate — a second is a
864
- selector whose result is no longer the snapshot, and extending a pattern of
865
- unknowable provenance binds `undefined` with every count green. The
866
- insertion also pays for a witness the way `thinking-summaries` does: the
867
- field it binds must still be named by the bundle's own objects (the store's
868
- snapshot initialiser, its publish call), so a store that renames the field
869
- reads as the step reporting the store rather than threading `undefined`.
870
- Two things come free with the store: its setter takes functional updaters —
871
- React's own contract, which the reducer splices already speak and it
872
- hides a finished block itself after 30 s, upstream's own linger.
874
+ - **The state has two semantic homes, and each is identified by the only
875
+ name it has.** Through 2.1.235 the scope that hands `onStreamingThinking`
876
+ declared the state itself, and the state is the array pattern binding the
877
+ handed setter — `useState`'s pair, which nothing else names; its value is
878
+ threaded into the bag as it stands. 2.1.236 moved the state into an
879
+ external stream store (`subscribe`/`getSnapshot`/`_publish` the
880
+ `useSyncExternalStore` shape), and there the question this step used to
881
+ ask *which idiom holds the state* is retired, because it broke on
882
+ every answer it ever gave. Each answer was a spelling of React's
883
+ data-access fashion, the busiest surface upstream owns: the handing ("the
884
+ call's only argument is the very expression the setter was read off")
885
+ died on 2.1.246, when the handing moved into the engine
886
+ (`this.stream.setStreamingThinking`) with the state unmoved and
887
+ `onStreamingThinking:` still twice in the bundle; the whole-snapshot
888
+ destructure that replaced it (`{streamingToolUses:…}=useX(<store>)`, the
889
+ pattern naming the store's own field) died on 2.1.247, which reads the
890
+ store through per-field selectors instead (`et(<store>,wv)` with
891
+ `wv=(s)=>s.streamingToolUses`) same store, same fields, a third
892
+ spelling, and a fourth break in this one question while the other
893
+ thirteen steps rode through both restructures untouched. A matcher
894
+ enumerating idioms is a whitelist against a fashion, each branch buying
895
+ exactly one build.
896
+
897
+ What survives every spelling is the **twin**. Live thinking is the
898
+ sibling of live tool-uses — same store, same snapshot, same renderer —
899
+ and the tool-use half is a feature upstream ships working, so every build
900
+ must carry, in the render's own reach, a read of `streamingToolUses` off
901
+ the store. That read is the *production*, found as whichever of the field
902
+ name's two grammar positions this build spells — a snapshot pattern's own
903
+ key (the hook call its sole argument, since a second would make the
904
+ pattern something other than the snapshot), or a selector beside the
905
+ store (a function answering the field off its own parameter, inline or
906
+ hoisted behind a module-local name) — one answer or none per scope
907
+ (`js.only`), behind whatever `??` fallback the read wears (`js.values`;
908
+ 2.1.247 arrived wearing one). The production is then *reused verbatim* —
909
+ the reuse-their-expression rule `org-label` and the extras memo already
910
+ follow — so the state is read the way this build reads its twin, with one
911
+ field name swapped, and upstream can only break the claim by breaking
912
+ their own feature. That is the anchor-selection rule this step's whole
913
+ history teaches: prefer claims upstream cannot drop without paying for
914
+ them — the dispatch strings, the store's field names, the twin's dataflow
915
+ — over claims only this patch needs; a neighbour prop, a handing site and
916
+ a storage idiom were all the second kind, and every break above was one
917
+ of them.
918
+
919
+ - **On a store build the rewrite is a subscription, not a value.**
920
+ Threading the state's value was sound while the resolved render was
921
+ live-computed, and 2.1.247 retired that render too: the surviving
922
+ conversation render is react-compiler cached behind a fixed slot-test
923
+ chain (`if(Yr[15]!==ph||…)Du=r(Ih,{…})`), and a prop the compiler never
924
+ saw is a prop no slot tests — the element is reused, the child bails on
925
+ identity, and a perfectly threaded value renders exactly once.
926
+ Parse-green and frozen, a failure class `doctor` cannot see. So the
927
+ render is rerouted through a module-scope wrapper component
928
+ (`__cc_LiveConversation`) spliced beside it: the bag gains
929
+ `__cc_stream:<store>,` (the production's own store argument, whose
930
+ identity is as fresh as the conversation the cache already tests), the
931
+ render's component argument becomes the wrapper, and the wrapper
932
+ subscribes itself through the production's own hook with a selector of
933
+ ours in upstream's per-field shape, handing the component
934
+ `streamingThinking:` beside the spread props. The hook's optional
935
+ selector parameter is upstream's since the store era began — 2.1.236's
936
+ hook and 2.1.247's are the same two-parameter function, measured — and
937
+ where the destructure spelling leaves it undemonstrated, an in-module
938
+ hook is held to it (resolved, and required to declare a second
939
+ parameter), because a hook that ignored the argument would thread the
940
+ whole snapshot with every count green; an *imported* hook is accepted on
941
+ the measurement, a residue accepted by name rather than a cross-module
942
+ resolver built for a build that has never shipped. The transcript
943
+ renderer's own memo comparator compares unknown props by
944
+ identity (since 2.1.210), so a fresh value re-renders it and a quiet one
945
+ does not, cached parent or not: a component re-rendering from its own
946
+ subscription is the one contract in this chain no minifier or compiler
947
+ rewrites. Every name the wrapper re-spells — the JSX callee, the
948
+ component, the hook — is read off the site and owed module-scope meaning
949
+ (`_unshadowed`): any binding of the spelling on the way — a parameter, a
950
+ declarator, a function's or class's declared name, a catch clause's
951
+ parameter — would make the wrapper name something else that parses,
952
+ verifies, and throws at first render, so it refuses the wrap loudly
953
+ instead, and the store expression's own spellings are walked the same
954
+ way, bounded at the scope upstream wrote them in. The witness is
955
+ unchanged, paid the way `thinking-summaries` pays
956
+ it: the field the wrapper selects must still be named by the bundle's own
957
+ objects (the store's snapshot initialiser, its publish call —
958
+ `streamingThinking:null`, upstream's own since 2.1.236), so a store that
959
+ renames the field reads as the step reporting the store rather than
960
+ subscribing to `undefined`. Two things still come free with the store:
961
+ its setter takes functional updaters — React's own contract, which the
962
+ reducer splices already speak — and it hides a finished block itself
963
+ after 30 s, upstream's own linger.
873
964
 
874
965
  A render was once selected by *position* — the observation that the real sites
875
966
  fall after the state's `useState` declaration. That is worth recording as a
@@ -922,7 +1013,19 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
922
1013
  with no memo at all, and the rewrite reuses the memo's *own* callee rather than
923
1014
  re-spelling `.useMemo`. What a claim about the *text* around the block
924
1015
  (`.contentBlock]}`) only approximated is the computation itself: one sibling
925
- property beside `content` read as the whole thing being gone.
1016
+ property beside `content` read as the whole thing being gone. The wrapped
1017
+ block is asked by membership among the element's *possible values*
1018
+ (`js.values` — the same value routing the codex resolvers read), never asked
1019
+ to be the read: 2.1.246 minted stable ids for streamed blocks
1020
+ (`ce?{...S.contentBlock,id:V}:S.contentBlock`), and the exact-node question
1021
+ read the same wrap one choice deeper as no wrap at all — nine required steps
1022
+ to zero, every anchor count standing. The rewrite reuses upstream's callback
1023
+ and dependencies verbatim — the callback invoked per entry with the same
1024
+ three arguments `flatMap` hands it, each dependency spread rather than
1025
+ transcribed — because a rebuilt copy is a copy free to shed whatever
1026
+ upstream adds next: the re-spell this replaced would have silently dropped
1027
+ the 2.1.246 minting, and with it went the uuid-stamp identity that re-spell
1028
+ had to prove and the reuse no longer asks.
926
1029
 
927
1030
  **`display-mode`** defaults the request's thinking display to `"summarized"`;
928
1031
  without it the API only streams summary text when the `showThinkingSummaries`
@@ -1313,6 +1416,24 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
1313
1416
  touched: the `/status` Organization/Email rows, the login screen, and the
1314
1417
  org's startup message (`"Message from <org>:"`) still show the real account.
1315
1418
 
1419
+ **2.1.246 retired the surface.** Upstream deleted the welcome-banner variant
1420
+ that composed the segment; the surviving banner draws `model · billing` with
1421
+ no org anywhere — the hidden state an empty `--org-label` asks for, now
1422
+ upstream's own default. That is a semantic change, not a matcher to repair
1423
+ (the `help-title` precedent), and it is *absence*, reported apart from broken
1424
+ (CONDUCT): the patch declares its surface (`Patch.absence`, answered by the
1425
+ same `_org_segments` its candidates count off, so the two cannot disagree),
1426
+ and every surface derives its answer from the bundle in hand — the menu shows
1427
+ the row dimmed (`not on this build`, unselectable), an explicit `--org-label`
1428
+ is refused at the front door with the same sentence, a cached replay skips it
1429
+ with a note, and `doctor` prints a `-` row apart from ✓/✗ and stays green.
1430
+ Nothing is keyed on a version, so a build that composes the segment again
1431
+ un-dims the row with no code change. What absence cannot tell apart is a
1432
+ composition respelled out of the locator's sight — that build would read as
1433
+ absent, visibly dimmed on a screen that still draws the segment — and the
1434
+ sweep over the corpus, where thirty-four builds carry the surface, is what
1435
+ keeps the locator honest.
1436
+
1316
1437
  ## Removed patches
1317
1438
 
1318
1439
  Kept here so nobody reintroduces them without knowing why they left:
@@ -21,10 +21,11 @@ matches means the file changed under you, not that a number moved.
21
21
 
22
22
  ## On disk now
23
23
 
24
- The whole published span `2.1.210` → `2.1.243` — 2.1.230 was never published
25
- one pristine binary per version, 33 in all. It straddles the **2.1.242 code
26
- split** (INTERNALS.md): `2.1.242`/`2.1.243` are the first many-module builds, and
27
- they jump ~35 MB over `2.1.241` for it. Several pairs share a byte count
24
+ The whole published span `2.1.210` → `2.1.247` — 2.1.230 and 2.1.244 were never
25
+ published — one pristine binary per version, 36 in all. It straddles the
26
+ **2.1.242 code split** (INTERNALS.md): `2.1.242`/`2.1.243` are the first
27
+ many-module builds, and they jump ~35 MB over `2.1.241` for it. Several pairs
28
+ share a byte count
28
29
  (`2.1.225`/`2.1.226`, `2.1.229`/`2.1.231`, `2.1.239`–`2.1.241`, `2.1.242`/`2.1.243`)
29
30
  and are still distinct binaries, which is why the identity column is the hash (of
30
31
  the whole file, `sha256sum <version>.orig`) and never the size:
@@ -64,10 +65,14 @@ the whole file, `sha256sum <version>.orig`) and never the size:
64
65
  | `2.1.241` | 343 MB | `0771bd866cff82b76581fc0499f6529e1a36845078f144f8c81dccb3bc7037b8` |
65
66
  | `2.1.242` | 378 MB | `528ef039aa7d64d7b3fbc06925132755a516b4dcaad784cf0b51fe03167360d4` |
66
67
  | `2.1.243` | 378 MB | `4b0dafeedd0b469c41988e200036fd773e7553ba960349c9f02a82c6d1f2ba27` |
68
+ | `2.1.245` | 392 MB | `16ad2b94deaf7b29abed966d981c9991a47af0420f5be8ed4a3f83bea9f678bc` |
69
+ | `2.1.246` | 248 MB | `1a0a662dc1bb938eaec38545abce9a4a69113d7d7f7c5e1a553ea276617b906a` |
70
+ | `2.1.247` | 239 MB | `5fb321bf417ffc5cd4e3f36e7c9c7e029bf47aaa36d5621db979fcc5e6eabe15` |
67
71
 
68
72
  This set covers the span the playbook's tree-move measurements were taken over
69
- (`2.1.210` → `2.1.233`) and the 2.1.242 split, so both are re-checkable here
70
- rather than historical.
73
+ (`2.1.210` → `2.1.233`), the 2.1.242 split, the 2.1.246 stream-store
74
+ migration, and the 2.1.247 selector reads it was read back through, so each is
75
+ re-checkable here rather than historical.
71
76
 
72
77
  ## Rebuild or extend it
73
78
 
@@ -93,5 +98,11 @@ done; }
93
98
  sweep
94
99
  ```
95
100
 
96
- `doctor` is read-only — it runs the matchers and parses their output, and never
97
- writes a binary so the sweep is safe to run against every backup at any time.
101
+ `doctor` never writes to the backup it reads — it runs the matchers, parses
102
+ their output, then bakes the composed result into a *temp* binary and executes
103
+ `<binary> --version` (the smoke run, [PLAYBOOK.md](PLAYBOOK.md#how-resilience-is-detected)),
104
+ removing the temp afterwards — so the sweep is safe to run against every backup
105
+ at any time, and proves each build bakes and boots, not just that it matches.
106
+ Each bake transiently writes a binary-sized temp file (a few hundred MB) to the
107
+ system temp dir — RAM, where that is tmpfs — one at a time; `TMPDIR` steers it
108
+ elsewhere.
@@ -1,3 +1,3 @@
1
1
  """patch-cc: an interactive patcher for the Claude Code native binary."""
2
2
 
3
- __version__ = "0.4.0"
3
+ __version__ = "0.5.1"