patch-cc 0.4.0__tar.gz → 0.5.0__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.0}/AGENTS.md +12 -6
  2. {patch_cc-0.4.0 → patch_cc-0.5.0}/PKG-INFO +4 -4
  3. {patch_cc-0.4.0 → patch_cc-0.5.0}/README.md +3 -3
  4. {patch_cc-0.4.0 → patch_cc-0.5.0}/docs/INTERNALS.md +75 -13
  5. {patch_cc-0.4.0 → patch_cc-0.5.0}/docs/PLAYBOOK.md +82 -23
  6. {patch_cc-0.4.0 → patch_cc-0.5.0}/docs/corpus.md +17 -8
  7. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/__init__.py +1 -1
  8. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/blob.py +228 -42
  9. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/container.py +73 -5
  10. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/cli.py +33 -7
  11. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/doctor.py +109 -2
  12. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/js.py +43 -0
  13. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/menu.py +38 -4
  14. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patcher.py +13 -7
  15. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/base.py +14 -0
  16. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/chrome.py +33 -2
  17. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/streaming.py +103 -92
  18. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/ui.py +31 -5
  19. {patch_cc-0.4.0 → patch_cc-0.5.0}/.github/workflows/ci.yml +0 -0
  20. {patch_cc-0.4.0 → patch_cc-0.5.0}/.github/workflows/release-watch.yml +0 -0
  21. {patch_cc-0.4.0 → patch_cc-0.5.0}/.github/workflows/release.yml +0 -0
  22. {patch_cc-0.4.0 → patch_cc-0.5.0}/.gitignore +0 -0
  23. {patch_cc-0.4.0 → patch_cc-0.5.0}/CLAUDE.md +0 -0
  24. {patch_cc-0.4.0 → patch_cc-0.5.0}/LICENSE +0 -0
  25. {patch_cc-0.4.0 → patch_cc-0.5.0}/docs/CONDUCT.md +0 -0
  26. {patch_cc-0.4.0 → patch_cc-0.5.0}/docs/demo.gif +0 -0
  27. {patch_cc-0.4.0 → patch_cc-0.5.0}/pyproject.toml +0 -0
  28. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/__init__.py +0 -0
  29. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/elf.py +0 -0
  30. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/errors.py +0 -0
  31. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/bun/macho.py +0 -0
  32. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/cache.py +0 -0
  33. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/codex/__init__.py +0 -0
  34. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/codex/gateway.py +0 -0
  35. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/codex/models.py +0 -0
  36. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/codex/oauth.py +0 -0
  37. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/codex/translate.py +0 -0
  38. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/locate.py +0 -0
  39. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/__init__.py +0 -0
  40. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/agents.py +0 -0
  41. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/codex.py +0 -0
  42. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/output.py +0 -0
  43. {patch_cc-0.4.0 → patch_cc-0.5.0}/src/patch_cc/patches/thinking.py +0 -0
  44. {patch_cc-0.4.0 → patch_cc-0.5.0}/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.0
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 &
@@ -848,28 +868,37 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
848
868
  *initialised* to is deliberately not asked: `useState(null)` is every
849
869
  build's spelling and `useState(void 0)` would be the same state, while the
850
870
  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.
871
+ - **The state has two homes, and each is proven by the only name it has.**
872
+ Through 2.1.235 the scope that hands `onStreamingThinking` declared the
873
+ state itself, and the state is the array pattern binding the handed setter
874
+ `useState`'s pair, which nothing else names. 2.1.236 moved it into an
875
+ external stream store (`subscribe`/`getSnapshot`/`_publish` — the
876
+ `useSyncExternalStore` shape) that a component reads back by destructuring
877
+ a hook call (`{streamingToolUses:…}=useX(<store>)`), so the state is that
878
+ pattern's own `streamingThinking` binding upstream's the day it takes
879
+ one, the goal achieved, and until then ours, inserted at the front of the
880
+ pattern. The pattern names *itself*: it binds the store's own
881
+ `streamingToolUses` field the name the store publishes and the
882
+ transcript signature already rests on and that membership is its whole
883
+ identity, provided its value is a call taking exactly one argument, one
884
+ answer or none (`js.only`). It was once proven by the handing instead
885
+ "the call's only argument is the very expression the setter was read off"
886
+ and 2.1.246 moved the handing into the engine
887
+ (`this.stream.setStreamingThinking`), out of every component, without
888
+ moving the state: four renders that plainly draw the conversation read as
889
+ having none in scope, with `onStreamingThinking:` still twice in the
890
+ bundle. The handed setter was `agentDefinitions` again a neighbour
891
+ standing in for the thing itself, one more fact upstream had to keep.
892
+ Sole argument stays deliberate a second is a selector whose result is
893
+ no longer the snapshot, and extending a pattern of unknowable provenance
894
+ binds `undefined` with every count green. The insertion also pays for a
895
+ witness the way `thinking-summaries` does: the field it binds must still
896
+ be named by the bundle's own objects (the store's snapshot initialiser,
897
+ its publish call), so a store that renames the field reads as the step
898
+ reporting the store rather than threading `undefined`. Two things come
899
+ free with the store: its setter takes functional updaters — React's own
900
+ contract, which the reducer splices already speak — and it hides a
901
+ finished block itself after 30 s, upstream's own linger.
873
902
 
874
903
  A render was once selected by *position* — the observation that the real sites
875
904
  fall after the state's `useState` declaration. That is worth recording as a
@@ -922,7 +951,19 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
922
951
  with no memo at all, and the rewrite reuses the memo's *own* callee rather than
923
952
  re-spelling `.useMemo`. What a claim about the *text* around the block
924
953
  (`.contentBlock]}`) only approximated is the computation itself: one sibling
925
- property beside `content` read as the whole thing being gone.
954
+ property beside `content` read as the whole thing being gone. The wrapped
955
+ block is asked by membership among the element's *possible values*
956
+ (`js.values` — the same value routing the codex resolvers read), never asked
957
+ to be the read: 2.1.246 minted stable ids for streamed blocks
958
+ (`ce?{...S.contentBlock,id:V}:S.contentBlock`), and the exact-node question
959
+ read the same wrap one choice deeper as no wrap at all — nine required steps
960
+ to zero, every anchor count standing. The rewrite reuses upstream's callback
961
+ and dependencies verbatim — the callback invoked per entry with the same
962
+ three arguments `flatMap` hands it, each dependency spread rather than
963
+ transcribed — because a rebuilt copy is a copy free to shed whatever
964
+ upstream adds next: the re-spell this replaced would have silently dropped
965
+ the 2.1.246 minting, and with it went the uuid-stamp identity that re-spell
966
+ had to prove and the reuse no longer asks.
926
967
 
927
968
  **`display-mode`** defaults the request's thinking display to `"summarized"`;
928
969
  without it the API only streams summary text when the `showThinkingSummaries`
@@ -1313,6 +1354,24 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
1313
1354
  touched: the `/status` Organization/Email rows, the login screen, and the
1314
1355
  org's startup message (`"Message from <org>:"`) still show the real account.
1315
1356
 
1357
+ **2.1.246 retired the surface.** Upstream deleted the welcome-banner variant
1358
+ that composed the segment; the surviving banner draws `model · billing` with
1359
+ no org anywhere — the hidden state an empty `--org-label` asks for, now
1360
+ upstream's own default. That is a semantic change, not a matcher to repair
1361
+ (the `help-title` precedent), and it is *absence*, reported apart from broken
1362
+ (CONDUCT): the patch declares its surface (`Patch.absence`, answered by the
1363
+ same `_org_segments` its candidates count off, so the two cannot disagree),
1364
+ and every surface derives its answer from the bundle in hand — the menu shows
1365
+ the row dimmed (`not on this build`, unselectable), an explicit `--org-label`
1366
+ is refused at the front door with the same sentence, a cached replay skips it
1367
+ with a note, and `doctor` prints a `-` row apart from ✓/✗ and stays green.
1368
+ Nothing is keyed on a version, so a build that composes the segment again
1369
+ un-dims the row with no code change. What absence cannot tell apart is a
1370
+ composition respelled out of the locator's sight — that build would read as
1371
+ absent, visibly dimmed on a screen that still draws the segment — and the
1372
+ sweep over the corpus, where thirty-four builds carry the surface, is what
1373
+ keeps the locator honest.
1374
+
1316
1375
  ## Removed patches
1317
1376
 
1318
1377
  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.246` — 2.1.230 and 2.1.244 were never
25
+ published — one pristine binary per version, 35 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,12 @@ 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` |
67
70
 
68
71
  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.
72
+ (`2.1.210` → `2.1.233`), the 2.1.242 split, and the 2.1.246 stream-store
73
+ migration, so each is re-checkable here rather than historical.
71
74
 
72
75
  ## Rebuild or extend it
73
76
 
@@ -93,5 +96,11 @@ done; }
93
96
  sweep
94
97
  ```
95
98
 
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.
99
+ `doctor` never writes to the backup it reads — it runs the matchers, parses
100
+ their output, then bakes the composed result into a *temp* binary and executes
101
+ `<binary> --version` (the smoke run, [PLAYBOOK.md](PLAYBOOK.md#how-resilience-is-detected)),
102
+ removing the temp afterwards — so the sweep is safe to run against every backup
103
+ at any time, and proves each build bakes and boots, not just that it matches.
104
+ Each bake transiently writes a binary-sized temp file (a few hundred MB) to the
105
+ system temp dir — RAM, where that is tmpfs — one at a time; `TMPDIR` steers it
106
+ 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.0"