patch-cc 0.3.1__tar.gz → 0.3.3__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.
- patch_cc-0.3.3/.github/workflows/release-watch.yml +164 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/PKG-INFO +1 -1
- {patch_cc-0.3.1 → patch_cc-0.3.3}/docs/CONDUCT.md +1 -1
- {patch_cc-0.3.1 → patch_cc-0.3.3}/docs/PLAYBOOK.md +96 -28
- {patch_cc-0.3.1 → patch_cc-0.3.3}/docs/corpus.md +29 -23
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/__init__.py +1 -1
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/agents.py +15 -9
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/base.py +31 -6
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/chrome.py +2 -1
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/codex.py +19 -9
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/streaming.py +213 -75
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/thinking.py +7 -5
- {patch_cc-0.3.1 → patch_cc-0.3.3}/.github/workflows/ci.yml +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/.github/workflows/release.yml +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/.gitignore +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/AGENTS.md +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/CLAUDE.md +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/LICENSE +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/README.md +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/docs/INTERNALS.md +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/docs/demo.gif +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/pyproject.toml +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/__init__.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/blob.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/container.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/elf.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/errors.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/bun/macho.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/cache.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/cli.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/codex/__init__.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/codex/gateway.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/codex/models.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/codex/oauth.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/codex/translate.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/doctor.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/js.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/locate.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/menu.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patcher.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/__init__.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/patches/output.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/src/patch_cc/ui.py +0 -0
- {patch_cc-0.3.1 → patch_cc-0.3.3}/uv.lock +0 -0
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
name: Release watch
|
|
2
|
+
|
|
3
|
+
# Fires the "patch-cc release watch" cloud routine the moment Anthropic ships a
|
|
4
|
+
# new Claude Code build, instead of leaving it to the routine's own daily cron.
|
|
5
|
+
#
|
|
6
|
+
# The routine already knows how to decide whether anything moved -- it compares
|
|
7
|
+
# the upstream version and the checkout HEAD against the ledger in issue #1 and
|
|
8
|
+
# exits silently when neither did. Its schedule was never the interesting part;
|
|
9
|
+
# it was just a poll, once a day, which meant a release that broke a patch could
|
|
10
|
+
# sit unnoticed for up to 24 hours. This workflow moves the poll here, where a
|
|
11
|
+
# tick is one curl instead of a cloud session, and only wakes the routine when
|
|
12
|
+
# the version actually changes.
|
|
13
|
+
#
|
|
14
|
+
# The routine keeps its daily schedule as a backstop: if this workflow breaks or
|
|
15
|
+
# GitHub skips the schedule, the watch degrades to its old once-a-day behaviour
|
|
16
|
+
# rather than going silent. That is why a failed poll below is a warning and not
|
|
17
|
+
# a red run -- a flaky fetch must not page anyone.
|
|
18
|
+
#
|
|
19
|
+
# SETUP (one time, by hand -- there is no API for token creation):
|
|
20
|
+
# claude.ai/code/routines -> "patch-cc release watch" -> pencil icon
|
|
21
|
+
# -> Add another trigger -> API -> Generate token (shown once)
|
|
22
|
+
# -> store it as the repo secret ROUTINE_FIRE_TOKEN.
|
|
23
|
+
|
|
24
|
+
on:
|
|
25
|
+
schedule:
|
|
26
|
+
# Every 10 minutes. GitHub delays scheduled runs under load, so read this as
|
|
27
|
+
# "within ~10-25 min of a release", not as a guarantee.
|
|
28
|
+
- cron: "*/10 * * * *"
|
|
29
|
+
workflow_dispatch:
|
|
30
|
+
|
|
31
|
+
concurrency:
|
|
32
|
+
group: release-watch
|
|
33
|
+
cancel-in-progress: false
|
|
34
|
+
|
|
35
|
+
permissions:
|
|
36
|
+
contents: read
|
|
37
|
+
issues: read
|
|
38
|
+
|
|
39
|
+
env:
|
|
40
|
+
# The channel file the Claude Code installer itself reads -- the same source
|
|
41
|
+
# the routine treats as authoritative, so the two never disagree about what
|
|
42
|
+
# "the current release" means.
|
|
43
|
+
CHANNEL_URL: https://storage.googleapis.com/claude-code-dist-86c565f3-f756-42ad-8dfa-d59b1c096819/claude-code-releases/latest
|
|
44
|
+
# Not a secret: firing needs the bearer token, and that is one.
|
|
45
|
+
ROUTINE_ID: trig_01WrgxdCZahJEHD3NNYcetqS
|
|
46
|
+
LEDGER_ISSUE: "1"
|
|
47
|
+
|
|
48
|
+
jobs:
|
|
49
|
+
watch:
|
|
50
|
+
runs-on: ubuntu-latest
|
|
51
|
+
steps:
|
|
52
|
+
- name: Read the release channel
|
|
53
|
+
id: upstream
|
|
54
|
+
run: |
|
|
55
|
+
set -uo pipefail
|
|
56
|
+
if ! v=$(curl -fsSL --max-time 30 --retry 3 --retry-delay 5 "$CHANNEL_URL"); then
|
|
57
|
+
echo "::warning::could not reach the release channel; skipping this tick"
|
|
58
|
+
echo "skip=1" >> "$GITHUB_OUTPUT"
|
|
59
|
+
exit 0
|
|
60
|
+
fi
|
|
61
|
+
v=$(printf '%s' "$v" | tr -d '[:space:]')
|
|
62
|
+
case "$v" in
|
|
63
|
+
[0-9]*.[0-9]*.[0-9]*) ;;
|
|
64
|
+
*)
|
|
65
|
+
echo "::warning::channel returned something that is not a version: '$v'"
|
|
66
|
+
echo "skip=1" >> "$GITHUB_OUTPUT"
|
|
67
|
+
exit 0
|
|
68
|
+
;;
|
|
69
|
+
esac
|
|
70
|
+
echo "version=$v" >> "$GITHUB_OUTPUT"
|
|
71
|
+
echo "upstream: $v"
|
|
72
|
+
|
|
73
|
+
# First guard: did an earlier tick already fire for this version? The
|
|
74
|
+
# routine takes several minutes to run (it downloads a ~275 MB bundle), so
|
|
75
|
+
# without this every tick during that window would fire it again.
|
|
76
|
+
- name: Already fired for this version?
|
|
77
|
+
id: marker
|
|
78
|
+
if: steps.upstream.outputs.skip != '1'
|
|
79
|
+
uses: actions/cache/restore@v4
|
|
80
|
+
with:
|
|
81
|
+
path: .fired
|
|
82
|
+
key: fired-${{ steps.upstream.outputs.version }}
|
|
83
|
+
lookup-only: true
|
|
84
|
+
|
|
85
|
+
# Second guard, and the authoritative one: the ledger records the version
|
|
86
|
+
# the routine has actually finished processing. This is what catches the
|
|
87
|
+
# case where the cache entry was evicted but the work is long done.
|
|
88
|
+
- name: Read the ledger's last-version
|
|
89
|
+
id: ledger
|
|
90
|
+
if: steps.upstream.outputs.skip != '1' && steps.marker.outputs.cache-hit != 'true'
|
|
91
|
+
env:
|
|
92
|
+
GH_TOKEN: ${{ github.token }}
|
|
93
|
+
run: |
|
|
94
|
+
set -uo pipefail
|
|
95
|
+
if ! body=$(gh issue view "$LEDGER_ISSUE" --repo "$GITHUB_REPOSITORY" --json body -q .body); then
|
|
96
|
+
echo "::warning::could not read the ledger; skipping this tick rather than guessing"
|
|
97
|
+
echo "skip=1" >> "$GITHUB_OUTPUT"
|
|
98
|
+
exit 0
|
|
99
|
+
fi
|
|
100
|
+
last=$(printf '%s\n' "$body" | sed -n 's/^last-version:[[:space:]]*//p' | head -1 | tr -d '[:space:]')
|
|
101
|
+
echo "last=$last" >> "$GITHUB_OUTPUT"
|
|
102
|
+
echo "ledger: $last"
|
|
103
|
+
|
|
104
|
+
- name: Fire the routine
|
|
105
|
+
id: fire
|
|
106
|
+
if: >-
|
|
107
|
+
steps.upstream.outputs.skip != '1' &&
|
|
108
|
+
steps.marker.outputs.cache-hit != 'true' &&
|
|
109
|
+
steps.ledger.outputs.skip != '1' &&
|
|
110
|
+
steps.ledger.outputs.last != steps.upstream.outputs.version
|
|
111
|
+
env:
|
|
112
|
+
TOKEN: ${{ secrets.ROUTINE_FIRE_TOKEN }}
|
|
113
|
+
VERSION: ${{ steps.upstream.outputs.version }}
|
|
114
|
+
LAST: ${{ steps.ledger.outputs.last }}
|
|
115
|
+
run: |
|
|
116
|
+
set -uo pipefail
|
|
117
|
+
if [ -z "${TOKEN:-}" ]; then
|
|
118
|
+
echo "::error::ROUTINE_FIRE_TOKEN is not set -- see the setup note at the top of this file"
|
|
119
|
+
exit 1
|
|
120
|
+
fi
|
|
121
|
+
body=$(printf 'Claude Code %s is on the release channel; the ledger last recorded %s. This line is a hint for the run log only -- re-derive the version yourself from the channel, per your step 2.' "$VERSION" "${LAST:-(none)}")
|
|
122
|
+
payload=$(jq -nc --arg t "$body" '{text: $t}')
|
|
123
|
+
out=$(mktemp)
|
|
124
|
+
code=$(curl -sS -o "$out" -w '%{http_code}' -X POST \
|
|
125
|
+
"https://api.anthropic.com/v1/claude_code/routines/$ROUTINE_ID/fire" \
|
|
126
|
+
-H "Authorization: Bearer $TOKEN" \
|
|
127
|
+
-H "anthropic-version: 2023-06-01" \
|
|
128
|
+
-H "anthropic-beta: experimental-cc-routine-2026-04-01" \
|
|
129
|
+
-H "Content-Type: application/json" \
|
|
130
|
+
-d "$payload") || code=000
|
|
131
|
+
echo "HTTP $code"
|
|
132
|
+
cat "$out"
|
|
133
|
+
echo
|
|
134
|
+
if [ "$code" != "200" ]; then
|
|
135
|
+
# Deliberately not marking the version as fired, so the next tick
|
|
136
|
+
# retries. A 429 here just means the daily run cap was reached.
|
|
137
|
+
echo "::error::fire failed with HTTP $code"
|
|
138
|
+
exit 1
|
|
139
|
+
fi
|
|
140
|
+
echo "url=$(jq -r '.claude_code_session_url // empty' "$out")" >> "$GITHUB_OUTPUT"
|
|
141
|
+
: > .fired
|
|
142
|
+
|
|
143
|
+
# Only reached on a 200, so the marker never claims a fire that failed.
|
|
144
|
+
- name: Mark this version as fired
|
|
145
|
+
if: steps.fire.outcome == 'success'
|
|
146
|
+
uses: actions/cache/save@v4
|
|
147
|
+
with:
|
|
148
|
+
path: .fired
|
|
149
|
+
key: fired-${{ steps.upstream.outputs.version }}
|
|
150
|
+
|
|
151
|
+
- name: Summary
|
|
152
|
+
if: always()
|
|
153
|
+
run: |
|
|
154
|
+
{
|
|
155
|
+
echo "### Release watch"
|
|
156
|
+
echo
|
|
157
|
+
echo "- upstream: \`${{ steps.upstream.outputs.version || 'unreadable' }}\`"
|
|
158
|
+
echo "- ledger last-version: \`${{ steps.ledger.outputs.last || 'not read' }}\`"
|
|
159
|
+
if [ -n "${{ steps.fire.outputs.url }}" ]; then
|
|
160
|
+
echo "- **fired the routine** -> ${{ steps.fire.outputs.url }}"
|
|
161
|
+
else
|
|
162
|
+
echo "- no fire (nothing moved, or already fired for this version)"
|
|
163
|
+
fi
|
|
164
|
+
} >> "$GITHUB_STEP_SUMMARY"
|
|
@@ -56,7 +56,7 @@ shape that already absorbs every case, found rather than bolted on.
|
|
|
56
56
|
shape this build simply lacks — most patches carry several — not a regression.
|
|
57
57
|
Keep "gone", "already applied", and "not on this build" as distinct signals;
|
|
58
58
|
never collapse them into one number. Which one a sub-step's silence means is
|
|
59
|
-
not guesswork: declare it (`
|
|
59
|
+
not guesswork: declare it (`Outcome.declare`) so a green tick cannot cover a dead
|
|
60
60
|
feature. A step nobody declared is a step that cannot report its own death:
|
|
61
61
|
`branding` carried none, so a name badge that stopped being a bold render was
|
|
62
62
|
a green run, an unchanged banner, and a manifest asserting the new name. See
|
|
@@ -368,13 +368,13 @@ and why `prop-threading` resolves scope itself.
|
|
|
368
368
|
|
|
369
369
|
Counting alone cannot tell "this build lacks that shape" from "the feature is
|
|
370
370
|
dead": a patch whose optional shapes rewrite happily while a load-bearing one
|
|
371
|
-
is gone still changes bytes, and would read as green. So each sub-step
|
|
372
|
-
what its absence *means* (`Outcome.
|
|
371
|
+
is gone still changes bytes, and would read as green. So each sub-step is
|
|
372
|
+
declared with what its absence *means* (`Outcome.declare`):
|
|
373
373
|
|
|
374
374
|
| mark | meaning | absence is |
|
|
375
375
|
|---|---|---|
|
|
376
|
-
|
|
|
377
|
-
| `
|
|
376
|
+
| `optional` | a shape only some builds carry | informational |
|
|
377
|
+
| `required` | the patch does nothing useful without it | a regression |
|
|
378
378
|
|
|
379
379
|
`Outcome.unmet()` turns a violation into a sentence (`required step
|
|
380
380
|
group-routing found nothing`); `Outcome.failures()` adds any exception the
|
|
@@ -404,9 +404,15 @@ Three rules keep the net from having holes:
|
|
|
404
404
|
feature are now checked by the markers their builders emit
|
|
405
405
|
(`streaming._CORE_UPDATES`), which no amount of neighbouring churn can fake.
|
|
406
406
|
- **Declare an expectation before the work, not inside it.** A step created only
|
|
407
|
-
by its own success cannot report its own absence.
|
|
408
|
-
|
|
409
|
-
|
|
407
|
+
by its own success cannot report its own absence. This is the API's shape,
|
|
408
|
+
not a discipline: `Outcome.declare` is the only way a step comes to exist
|
|
409
|
+
(up front, required and optional named apart), and `outcome.step(name)`
|
|
410
|
+
retrieves — an undeclared name raises, so a typo cannot mint a silently
|
|
411
|
+
optional step. Work that is owed conditionally declares under the same
|
|
412
|
+
condition it runs (`codex._register_context`), and a name resolved from the
|
|
413
|
+
bundle is declared the moment it resolves — `agents.bypassed_agents` has no
|
|
414
|
+
step to name until the helper's guard gives up the pinned agent, and a guard
|
|
415
|
+
that vanishes leaves the always-printed note, with no name left to declare.
|
|
410
416
|
|
|
411
417
|
`apply` acts on the verdict: a broken patch is re-run out of the final pass, so
|
|
412
418
|
its orphan edits never reach the binary, the manifest never claims it, and the
|
|
@@ -449,7 +455,11 @@ Each sub-step records its own `candidates`/`applied`:
|
|
|
449
455
|
reducer whose setter threading applied while every arm had drifted once
|
|
450
456
|
reported hits and streamed nothing.
|
|
451
457
|
- **`final-summary`**, optional: redacted thinking in the closing summary is a
|
|
452
|
-
refinement, not the feature.
|
|
458
|
+
refinement, not the feature. Its guard is whichever enclosing `if` *tests the
|
|
459
|
+
block* — 2.1.236 nested an experiment gate between the test and the summary
|
|
460
|
+
it guards, and "the nearest `if`" read a shape that had merely moved as one
|
|
461
|
+
that was gone. The climb is bounded by the function that declares the block,
|
|
462
|
+
because a receiver's name is only a spelling until its scope is said.
|
|
453
463
|
|
|
454
464
|
An *optional* sub-step that finds nothing is reported as absent, not broken —
|
|
455
465
|
it is just a shape this build doesn't have. A sub-step that finds a shape but
|
|
@@ -584,6 +594,20 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
584
594
|
arm's head was baked in as the file's length, `newLines:String(t)`, counted
|
|
585
595
|
and green.
|
|
586
596
|
|
|
597
|
+
**Alive by eye:** ask for a new file *via the Write tool* — the row reads
|
|
598
|
+
`Added N lines` over green `+` rows. Two things legitimately show no diff and
|
|
599
|
+
are not this patch failing: a file created through **Bash** (heredoc, `tee`,
|
|
600
|
+
a script) is Bash output and never enters this renderer; and upstream keeps
|
|
601
|
+
planting early returns *ahead of* the spliced render — plan-directory files
|
|
602
|
+
("/plan to preview"), condensed contexts (subagent progress rows, session
|
|
603
|
+
summaries: "Wrote N lines to path"), and scratchpad/`.workshop.*` files in
|
|
604
|
+
non-verbose re-renders ("Wrote N lines ⧉"). The live conversation renders
|
|
605
|
+
the current message verbose, so the diff shows there today; those guards are
|
|
606
|
+
the patch's effective coverage narrowing upstream-side, which `doctor` —
|
|
607
|
+
counting matchers, not paths — cannot see. Measured on 2.1.234 (live,
|
|
608
|
+
resume, plain/workshop/scratchpad files): every reachable path drew the
|
|
609
|
+
diff.
|
|
610
|
+
|
|
587
611
|
### Thinking — `thinking.py`
|
|
588
612
|
|
|
589
613
|
- **`thinking-summaries`** — stop echoing the account's server-side experiment
|
|
@@ -725,16 +749,28 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
725
749
|
- **`live-thinking`** — the fourteen-step patch above.
|
|
726
750
|
|
|
727
751
|
**`prop-threading`** inserts `streamingThinking:<state>,` before the
|
|
728
|
-
`
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
`
|
|
752
|
+
`conversationId` property of every props bag *handed to a component* — an
|
|
753
|
+
argument — that also carries `messages`. Being an argument is part of the
|
|
754
|
+
identity: a module-level literal, a return-value payload, or a config object
|
|
755
|
+
may legitimately carry the pair, and none of them is a render. Three things
|
|
756
|
+
do the work, and each replaced something that had broken:
|
|
757
|
+
|
|
758
|
+
- **Identity is membership — of the essential props alone.** The props that
|
|
759
|
+
make a render a conversation render, asked of one object rather than of a
|
|
760
|
+
span of text. Four matchers once modelled this — a brace-free
|
|
761
|
+
`createElement` call, an ordered prompt renderer, and two regexes differing
|
|
762
|
+
only in the order two call sites listed the same props. 2.1.229 killed the
|
|
763
|
+
ordered pair at once by inserting `onRateLimitAutoQueueContinue:` between
|
|
764
|
+
two of them. The membership then carried `agentDefinitions` as a third
|
|
765
|
+
conjunct — witness and insertion point in one — and 2.1.235 retired that
|
|
766
|
+
prop from the bag: an identity resting on a *neighbour* read a build that
|
|
767
|
+
plainly drew four conversation renders as drawing none, with every anchor
|
|
768
|
+
count standing (`agentDefinitions` still occurs 97 times — as other
|
|
769
|
+
components' prop). The conversation pair is what the render *is*; the
|
|
770
|
+
neighbour was one more thing upstream had to keep, and upstream owes the
|
|
771
|
+
matcher nothing. Same class as the resolver default on 2.1.234: the
|
|
772
|
+
identity must be the weakest claim that still proves it, and every conjunct
|
|
773
|
+
past that is a break waiting on a harmless refactor.
|
|
738
774
|
- **The insertion point is a prop boundary**, so nothing computes where a
|
|
739
775
|
literal ends. Being an `object` and not an `object_pattern` is what
|
|
740
776
|
separates a prop being *passed* from one being *received*; the regex spelled
|
|
@@ -756,6 +792,28 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
756
792
|
*initialised* to is deliberately not asked: `useState(null)` is every
|
|
757
793
|
build's spelling and `useState(void 0)` would be the same state, while the
|
|
758
794
|
setter is the identity that matters.
|
|
795
|
+
- **The state has two homes, and the handed setter names both.** Through
|
|
796
|
+
2.1.235 the scope that hands `onStreamingThinking` declared the state
|
|
797
|
+
itself, and the state is the array pattern binding the handed setter —
|
|
798
|
+
`useState`'s pair. 2.1.236 moved it into an external stream store
|
|
799
|
+
(`subscribe`/`getSnapshot`/`_publish` — the `useSyncExternalStore` shape)
|
|
800
|
+
and hands `<store>.setStreamingThinking` instead; the same scope reads the
|
|
801
|
+
store back by destructuring the hook call it hands the store to
|
|
802
|
+
(`{streamingToolUses:…}=useX(<store>)`), so the state is that pattern's own
|
|
803
|
+
`streamingThinking` binding — upstream's the day it takes one, the goal
|
|
804
|
+
achieved, and until then ours, inserted at the front of the pattern. The
|
|
805
|
+
pattern is proven the snapshot read by the store expression itself: the
|
|
806
|
+
call's *only* argument is the very expression the setter was read off, one
|
|
807
|
+
answer or none (`js.only`). Sole argument is deliberate — a second is a
|
|
808
|
+
selector whose result is no longer the snapshot, and extending a pattern of
|
|
809
|
+
unknowable provenance binds `undefined` with every count green. The
|
|
810
|
+
insertion also pays for a witness the way `thinking-summaries` does: the
|
|
811
|
+
field it binds must still be named by the bundle's own objects (the store's
|
|
812
|
+
snapshot initialiser, its publish call), so a store that renames the field
|
|
813
|
+
reads as the step reporting the store rather than threading `undefined`.
|
|
814
|
+
Two things come free with the store: its setter takes functional updaters —
|
|
815
|
+
React's own contract, which the reducer splices already speak — and it
|
|
816
|
+
hides a finished block itself after 30 s, upstream's own linger.
|
|
759
817
|
|
|
760
818
|
A render was once selected by *position* — the observation that the real sites
|
|
761
819
|
fall after the state's `useState` declaration. That is worth recording as a
|
|
@@ -782,7 +840,17 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
782
840
|
|
|
783
841
|
**`transcript-signature` and `inline-extras`** are one hand-off and two
|
|
784
842
|
steps: the first threads `streamingThinking` into every renderer whose
|
|
785
|
-
signature carries
|
|
843
|
+
signature carries `messages` and `streamingToolUses`, inserting before the
|
|
844
|
+
latter — an insertion point inside the identity, for `prop-threading`'s
|
|
845
|
+
reason. The signature was a trio until `showAllInTranscript` was measured to
|
|
846
|
+
discriminate nothing on any build in the corpus: a third conjunct that was
|
|
847
|
+
find-anchor, identity and insertion point in one, the exact triple role
|
|
848
|
+
`agentDefinitions` held when 2.1.235 retired it. Deriving the renderer from
|
|
849
|
+
its consumer instead — the scope that binds what the extras memo computes
|
|
850
|
+
over — was measured and rejected: the memo's receiver is a react-compiler
|
|
851
|
+
memoized *local* on every current build, with real dataflow between it and
|
|
852
|
+
the signature, and a dataflow pass is a tool this project deliberately does
|
|
853
|
+
not build. The second step rewrites that renderer's
|
|
786
854
|
extras memo to draw the live block in order. What the memo reads is resolved
|
|
787
855
|
in **its own scope** — the prop its enclosing function was handed — rather
|
|
788
856
|
than carried over from whichever renderer the first step happened to see
|
|
@@ -866,10 +934,10 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
866
934
|
`CLAUDE_CODE_DISABLE_EXPLORE_INHERIT_CAP` escape hatch in the middle, which
|
|
867
935
|
silently cost every Explore override until the body matcher learned to skip
|
|
868
936
|
intervening statements. The body is a node now, so there is nothing left to
|
|
869
|
-
skip. Resolving the agent from the guard alone is
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
937
|
+
skip. Resolving the agent from the guard alone is why a future body reshape
|
|
938
|
+
costs nothing at all: the body is replaced whole, however upstream grows it,
|
|
939
|
+
and `bypass:<agent>` stands in the report as the record that an override was
|
|
940
|
+
at stake.
|
|
873
941
|
|
|
874
942
|
If the **guard** goes too there is no step to fail — nothing left names the
|
|
875
943
|
agent — and no way to tell "upstream stopped pinning" from "the guard
|
|
@@ -902,7 +970,7 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
902
970
|
| `context` | the real context window | the function that *reads* `CLAUDE_CODE_MAX_CONTEXT_TOKENS` as a member and returns what it read, taking the model as a parameter; the table goes before its first statement |
|
|
903
971
|
| `registry` | the binary's own model table — the status-line name, effort capabilities, `/advisor` eligibility | the object carrying both `models` and `aliases`, confirmed by its entries carrying `id`/`family`/`display_name` |
|
|
904
972
|
|
|
905
|
-
\* required
|
|
973
|
+
\* required — without any one of them the feature is dead.
|
|
906
974
|
`picker`, `context` and `registry` are refinements: absent, you can still
|
|
907
975
|
type `/model <id>` and get the 200k default under the model's raw id.
|
|
908
976
|
`context` has no step at all when no chosen model reports a window — there
|
|
@@ -1203,10 +1271,10 @@ enumerated with their hashes in [corpus.md](corpus.md). Each removed matcher hit
|
|
|
1203
1271
|
can still be running; each was carried as "kept for older builds", which is a
|
|
1204
1272
|
claim the corpus disproves. The verbatim matchers were re-run from `HEAD` over
|
|
1205
1273
|
the whole corpus after the move, and they are still zero everywhere. The
|
|
1206
|
-
measurements this section quotes were taken during the move across
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1274
|
+
measurements this section quotes were taken during the move across
|
|
1275
|
+
2.1.210 → 2.1.233 (2.1.230 was never published) — a span the corpus has since
|
|
1276
|
+
grown to hold in full, so they are re-checkable rather than historical, and
|
|
1277
|
+
re-running `doctor` over it is one command (below).
|
|
1210
1278
|
|
|
1211
1279
|
- **`live-thinking` / `reducer-legacy`** — the pre-2.1.138 reducer with
|
|
1212
1280
|
positional parameters. Its removal collapsed the `reducer` variant *group*
|
|
@@ -11,7 +11,8 @@ Before the first patch of a version, patch-cc copies the pristine binary to
|
|
|
11
11
|
`~/.local/share/patch-cc/backups/<version>.orig` ([INTERNALS.md](INTERNALS.md#safety)).
|
|
12
12
|
The corpus is exactly those copies: not a fixture checked into the repo (each is
|
|
13
13
|
~300 MB), but a set that **accretes on its own** as Claude auto-updates and you
|
|
14
|
-
re-patch
|
|
14
|
+
re-patch — and that can be backfilled by saving any pristine native binary under
|
|
15
|
+
the same name. `doctor <path>` reads one; the sweep below reads all of them.
|
|
15
16
|
|
|
16
17
|
The binaries are the artifact, not this file — `doctor` recomputes every count
|
|
17
18
|
from them, so nothing here can drift from what a matcher actually does. This
|
|
@@ -20,38 +21,52 @@ matches means the file changed under you, not that a number moved.
|
|
|
20
21
|
|
|
21
22
|
## On disk now
|
|
22
23
|
|
|
23
|
-
The
|
|
24
|
-
|
|
24
|
+
The whole published span `2.1.210` → `2.1.236` — 2.1.230 was never published —
|
|
25
|
+
one pristine binary per version, 26 in all. Two pairs share a byte count
|
|
26
|
+
(`2.1.225`/`2.1.226`, `2.1.229`/`2.1.231`) and are still distinct binaries,
|
|
27
|
+
which is why the identity column is the hash (of the whole file,
|
|
28
|
+
`sha256sum <version>.orig`) and never the size:
|
|
25
29
|
|
|
26
30
|
| version | size | sha256 |
|
|
27
31
|
|---|---|---|
|
|
32
|
+
| `2.1.210` | 261 MB | `e7d2ceb53ed4c2ced1fe7fc1c6331c98dc5f7b4c9b2722d9c5fa3dd5dff6f719` |
|
|
33
|
+
| `2.1.211` | 262 MB | `8272c8a474ac9ea1bc35f19b9f7c7e7dc4dc4eb6d5ad3e484b19335ac72446b2` |
|
|
34
|
+
| `2.1.212` | 264 MB | `044a88cf3a5180776617fd3da1238dcbf9141ddec449a39cf7d2af1ac78e684e` |
|
|
35
|
+
| `2.1.213` | 265 MB | `7999631426e1b6e4444e4ecf9cd8a63a05a0411ccfe503927d4c9d57bc41bc64` |
|
|
36
|
+
| `2.1.214` | 265 MB | `3c029136f7c81f54ed4a38e9d52e655aad536433dbbde50519c8c31bb646ad14` |
|
|
37
|
+
| `2.1.215` | 265 MB | `c1efffaaf370aa187cb6a09dd93d4e511c646899b0078476f83791b664bde7fe` |
|
|
28
38
|
| `2.1.216` | 267 MB | `74deca45220b8080ec75ab099bd5a5980e41a2b5879846a008fb115d436de085` |
|
|
29
39
|
| `2.1.217` | 269 MB | `2630fc5dc6db61bc03f86b95daf47766e5ed5b61873f7bb7cfea764c5ac5a9ba` |
|
|
30
40
|
| `2.1.218` | 273 MB | `e12071751a9336b8af1012c103358ff04ac18f9aaff4a738cff7ba5cdfaf63f2` |
|
|
31
41
|
| `2.1.219` | 275 MB | `22cfd6f5b3061c0391ba84e9cf8c9deaa37783aac18b004d42ec061e98f00691` |
|
|
32
42
|
| `2.1.220` | 275 MB | `674f61f20ff306f3100cf9200e4c36c4b70278b5bef2884549819b942a89c863` |
|
|
33
43
|
| `2.1.221` | 289 MB | `60db8e88d42c24b5199c92cfd56ec88370c510c3789c6f364af748354f087ada` |
|
|
44
|
+
| `2.1.222` | 289 MB | `10caae8f22b915c26bfff0e013a4d45608c4f1ae287583626569156f447730e5` |
|
|
34
45
|
| `2.1.223` | 291 MB | `98226474f802e3094d6a86c5ade8883c16206d0fcb5c400b7401c800063e99d7` |
|
|
46
|
+
| `2.1.224` | 296 MB | `a2b5add7dc4bcd8eaa029f4e8bdac4df7769b4073698db7989d206baf9419c2d` |
|
|
47
|
+
| `2.1.225` | 298 MB | `0a3be8d18cb0f5357d38ce2d588601753a60b44cc9c622579ed8b8405dee231e` |
|
|
35
48
|
| `2.1.226` | 298 MB | `4e9bec1177ce9690e8bd988b710ac24105e70da428dd094c5adcbbe786a55555` |
|
|
36
49
|
| `2.1.227` | 304 MB | `6832dc3f1797b890b71116e5f2dbbf9a83fd3d0498c235b4b0f9cd0e6e499ad6` |
|
|
37
50
|
| `2.1.228` | 309 MB | `d535985e6941a3eb00179ccd7f52ceb0c6623a0305a518ebc4e6514f84a94c99` |
|
|
51
|
+
| `2.1.229` | 311 MB | `200338139a3df04a9ad22233837d1fb53fb6dffa21cd82e47559bfaa115acc1b` |
|
|
52
|
+
| `2.1.231` | 311 MB | `47a01daebf794f6c86c13d1875ad6e5be0627029ad8600731161f24018ecde5b` |
|
|
53
|
+
| `2.1.232` | 323 MB | `61d23f8749136907d586d5b11831ea8a5234d4c1dea40a5e55c33b52e204c6d1` |
|
|
38
54
|
| `2.1.233` | 325 MB | `55d281096f57d411ebbdd94dbf5e9ff3accb7c05713e37348c2c11d4b83bf9d9` |
|
|
39
55
|
| `2.1.234` | 328 MB | `3473601ea695d5bf769c5b202844d4cb4fbf723ae995450fcb6973204775c84a` |
|
|
56
|
+
| `2.1.235` | 331 MB | `bfcf0ae2dbf94b2b6a106074aabf3938b9a10889c3b678e4cb5a00c03274d5d5` |
|
|
57
|
+
| `2.1.236` | 335 MB | `6c8818fa22187aa555c242be4abbacc44d6b71a32ac9631ee7b2b5d12f51f752` |
|
|
40
58
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
saved as `claude.unknown-<hash>.orig`. Both of the extras present hash-match the
|
|
45
|
-
`2.1.216` row above — same bytes, a different filename — which is why the count
|
|
46
|
-
of *files* (14) and the count of *distinct binaries* (12) differ.
|
|
59
|
+
This set covers the span the playbook's tree-move measurements were taken over
|
|
60
|
+
(`2.1.210` → `2.1.233`), so those numbers are re-checkable here rather than
|
|
61
|
+
historical.
|
|
47
62
|
|
|
48
63
|
## Rebuild or extend it
|
|
49
64
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
patching anything:
|
|
65
|
+
The corpus grows every time patch-cc touches a new build — each first patch of
|
|
66
|
+
a version leaves its `.orig` — and a version this machine never patched joins
|
|
67
|
+
the same way: save its pristine native binary as
|
|
68
|
+
`~/.local/share/patch-cc/backups/<version>.orig`. To read the JS a given binary
|
|
69
|
+
carries without patching anything:
|
|
55
70
|
|
|
56
71
|
```bash
|
|
57
72
|
patch-cc extract ~/.local/share/patch-cc/backups/2.1.233.orig > 2.1.233.js
|
|
@@ -71,12 +86,3 @@ sweep
|
|
|
71
86
|
|
|
72
87
|
`doctor` is read-only — it runs the matchers and parses their output, and never
|
|
73
88
|
writes a binary — so the sweep is safe to run against every backup at any time.
|
|
74
|
-
|
|
75
|
-
## Provenance of the wider span
|
|
76
|
-
|
|
77
|
-
Some measurements in the playbook were taken during "the move to the tree"
|
|
78
|
-
across a wider set than is durably on disk now — the range `2.1.210` → `2.1.233`
|
|
79
|
-
(2.1.230 was never published), whose earliest builds predate this backup set.
|
|
80
|
-
Those numbers were real when taken; the set above is what reproduces today, and
|
|
81
|
-
a claim that names a build not listed here is a historical one this file does
|
|
82
|
-
not stand behind byte-for-byte.
|
|
@@ -100,7 +100,8 @@ def _subagent_prompt(source: Source, _options: Options, outcome: Outcome) -> Sou
|
|
|
100
100
|
``transcript && content && ...`` conjunction, which gates the agent's
|
|
101
101
|
*output* and is not this patch's business.
|
|
102
102
|
"""
|
|
103
|
-
|
|
103
|
+
outcome.declare(required=("gate",))
|
|
104
|
+
gate = outcome.step("gate")
|
|
104
105
|
edits: list[Edit] = []
|
|
105
106
|
seen: set[int] = set()
|
|
106
107
|
|
|
@@ -346,7 +347,7 @@ def discover_models(source: Source) -> list[str]:
|
|
|
346
347
|
by a guessed default. A hardcoded fallback (`haiku/sonnet/opus`) read a lost
|
|
347
348
|
enum as those three: `doctor` printed a plausible list, every pin to one of
|
|
348
349
|
them landed, and a pin to a name the real bundle accepts was refused -- the
|
|
349
|
-
same emptiness that `codex-models`' own `enum` step
|
|
350
|
+
same emptiness that `codex-models`' own required `enum` step reports
|
|
350
351
|
as broken in the same run. With nothing to offer, the offer is empty:
|
|
351
352
|
`discover_models` returns `[]`, `doctor` shows `models: inherit` alone, and a
|
|
352
353
|
requested pin fails its required step rather than landing on a name nothing
|
|
@@ -481,13 +482,14 @@ def _subagent_models(source: Source, options: Options, outcome: Outcome) -> Sour
|
|
|
481
482
|
found = {agent.name: agent for agent in agents}
|
|
482
483
|
edits: list[Edit] = []
|
|
483
484
|
|
|
485
|
+
# Required: every override reaching a patch has already been validated
|
|
486
|
+
# against this bundle by its surface (CLI, --from-cache, or the menu), so
|
|
487
|
+
# one that cannot be written is not a shape this build lacks -- it is the
|
|
488
|
+
# asked-for change failing. Left optional, the patch stayed green, the
|
|
489
|
+
# binary shipped without the override, and the manifest claimed it.
|
|
490
|
+
outcome.declare(required=tuple(sorted(options.subagent_models)))
|
|
484
491
|
for name, target in sorted(options.subagent_models.items()):
|
|
485
|
-
|
|
486
|
-
# against this bundle by its surface (CLI, --from-cache, or the menu),
|
|
487
|
-
# so one that cannot be written is not a shape this build lacks -- it is
|
|
488
|
-
# the asked-for change failing. Left optional, the patch stayed green,
|
|
489
|
-
# the binary shipped without the override, and the manifest claimed it.
|
|
490
|
-
step = outcome.step(name, expect=True)
|
|
492
|
+
step = outcome.step(name)
|
|
491
493
|
if target not in offered:
|
|
492
494
|
step.note(f"model {target!r} not offered by this bundle; skipped")
|
|
493
495
|
continue
|
|
@@ -522,7 +524,11 @@ def _subagent_models(source: Source, options: Options, outcome: Outcome) -> Sour
|
|
|
522
524
|
for bypass in bypasses:
|
|
523
525
|
if bypass.agent not in options.subagent_models:
|
|
524
526
|
continue
|
|
525
|
-
|
|
527
|
+
# Declared the moment the guard resolves the agent's name -- the
|
|
528
|
+
# earliest this step *can* exist -- while a vanished guard stays the
|
|
529
|
+
# note above, with no name left to declare.
|
|
530
|
+
outcome.declare(required=(f"bypass:{bypass.agent}",))
|
|
531
|
+
step = outcome.step(f"bypass:{bypass.agent}")
|
|
526
532
|
step.candidates += 1
|
|
527
533
|
step.applied += 1
|
|
528
534
|
edits.append(
|
|
@@ -109,7 +109,7 @@ class Outcome:
|
|
|
109
109
|
notes: list[str] = field(default_factory=list)
|
|
110
110
|
#: Named sub-steps, for patches built from several independent rewrites.
|
|
111
111
|
steps: dict[str, Outcome] = field(default_factory=dict)
|
|
112
|
-
#: What this sub-step's absence means (set via :meth:`
|
|
112
|
+
#: What this sub-step's absence means (set via :meth:`declare`). ``False`` --
|
|
113
113
|
#: a shape some builds simply lack; ``True`` -- the patch is broken without
|
|
114
114
|
#: it.
|
|
115
115
|
expect: bool = False
|
|
@@ -167,18 +167,43 @@ class Outcome:
|
|
|
167
167
|
def note(self, message: str) -> None:
|
|
168
168
|
self.notes.append(message)
|
|
169
169
|
|
|
170
|
-
def
|
|
171
|
-
|
|
170
|
+
def declare(
|
|
171
|
+
self, required: tuple[str, ...] = (), optional: tuple[str, ...] = ()
|
|
172
|
+
) -> None:
|
|
173
|
+
"""Create sub-steps, before any of them does its work.
|
|
174
|
+
|
|
175
|
+
Declaring is the only way a step comes to exist (:meth:`step` only
|
|
176
|
+
retrieves), which makes "declare an expectation before the work" the
|
|
177
|
+
API's shape instead of each patch's discipline: a code path that never
|
|
178
|
+
runs leaves a required step at 0/0 with a verdict to fail, where a step
|
|
179
|
+
created by its own success could never report its own absence --
|
|
180
|
+
`branding`'s badge went unrenamed under exactly that silence, and the
|
|
181
|
+
lazily-created step was the hole the discipline papered over.
|
|
182
|
+
Conditional work declares under the same condition it runs (`context`),
|
|
183
|
+
and a name resolved from the bundle is declared the moment it resolves
|
|
184
|
+
(`bypass:<agent>`).
|
|
185
|
+
"""
|
|
186
|
+
for name in required:
|
|
187
|
+
self.steps.setdefault(name, Outcome()).expect = True
|
|
188
|
+
for name in optional:
|
|
189
|
+
self.steps.setdefault(name, Outcome())
|
|
190
|
+
|
|
191
|
+
def step(self, name: str) -> Outcome:
|
|
192
|
+
"""A declared sub-step, to record work against.
|
|
172
193
|
|
|
173
194
|
A single scalar count cannot distinguish "all twelve rewrites landed"
|
|
174
195
|
from "six landed and six silently drifted" -- which is exactly how
|
|
175
196
|
upstream's live-thinking patch hides its own regressions. Recording each
|
|
176
197
|
rewrite separately turns that into an actionable "reducer.message_stop
|
|
177
198
|
missed".
|
|
199
|
+
|
|
200
|
+
Retrieval only: a name nobody declared is a programming error and
|
|
201
|
+
raises, which :meth:`Patch.run` reports as the patch broken -- loud,
|
|
202
|
+
never a silently-optional step minted by a typo.
|
|
178
203
|
"""
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
return
|
|
204
|
+
if name not in self.steps:
|
|
205
|
+
raise RuntimeError(f"step {name!r} was never declared")
|
|
206
|
+
return self.steps[name]
|
|
182
207
|
|
|
183
208
|
def finalize(self) -> Outcome:
|
|
184
209
|
"""Roll sub-step totals up into this outcome."""
|
|
@@ -268,7 +268,8 @@ def _branding(source: Source, options: Options, outcome: Outcome) -> Source:
|
|
|
268
268
|
# rather than a green tick over a binary that still says Claude Code. The
|
|
269
269
|
# other two are sentences upstream may reword or drop, and their absence is
|
|
270
270
|
# a build lacking a shape, not a dead feature.
|
|
271
|
-
|
|
271
|
+
outcome.declare(required=("badge",), optional=("styled", "welcome"))
|
|
272
|
+
badge = outcome.step("badge")
|
|
272
273
|
styled = outcome.step("styled")
|
|
273
274
|
welcome = outcome.step("welcome")
|
|
274
275
|
|