patch-cc 0.2.1__tar.gz → 0.2.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.2.1 → patch_cc-0.2.3}/PKG-INFO +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/docs/PLAYBOOK.md +69 -14
- {patch_cc-0.2.1 → patch_cc-0.2.3}/pyproject.toml +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/__init__.py +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/__init__.py +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/container.py +2 -2
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/elf.py +4 -4
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/macho.py +2 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/cli.py +3 -3
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/codex/gateway.py +4 -2
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/codex/oauth.py +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/locate.py +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/menu.py +9 -8
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patcher.py +1 -1
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/agents.py +3 -2
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/base.py +18 -5
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/codex.py +2 -6
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/streaming.py +147 -296
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/thinking.py +7 -6
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/ui.py +1 -1
- patch_cc-0.2.3/uv.lock +422 -0
- patch_cc-0.2.1/uv.lock +0 -358
- {patch_cc-0.2.1 → patch_cc-0.2.3}/.github/workflows/ci.yml +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/.github/workflows/release.yml +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/.gitignore +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/AGENTS.md +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/CLAUDE.md +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/LICENSE +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/README.md +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/docs/CONDUCT.md +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/docs/INTERNALS.md +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/docs/demo.gif +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/blob.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/bun/errors.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/cache.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/codex/__init__.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/codex/models.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/codex/translate.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/doctor.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/__init__.py +3 -3
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/chrome.py +0 -0
- {patch_cc-0.2.1 → patch_cc-0.2.3}/src/patch_cc/patches/output.py +0 -0
|
@@ -17,10 +17,56 @@ constantly. These rules are what keep matchers alive:
|
|
|
17
17
|
literals, `case` labels, prop names, or control-flow shape.
|
|
18
18
|
- Match the *semantic shape* of a function body, not its symbol names.
|
|
19
19
|
- When upstream ships more than one shape for the same feature, add a second
|
|
20
|
-
narrow branch — don't widen one regex until it over-matches.
|
|
20
|
+
narrow branch — don't widen one regex until it over-matches. A different
|
|
21
|
+
*spelling* of one shape is not a second shape, and does not earn a branch; see
|
|
22
|
+
"applied to, not reached through" below for where that line falls.
|
|
21
23
|
- Accept statement variants a minifier flips between. The thinking gate broke
|
|
22
24
|
on 2.1.216 solely because `if(x)return null;` became `if(x){return null}` —
|
|
23
25
|
matchers should allow both from day one.
|
|
26
|
+
- **Applied to, not reached through.** Match what a helper is applied to; say
|
|
27
|
+
nothing about how it is reached. The callee of a zod schema is pure minifier
|
|
28
|
+
noise — the persisted `effortLevel` reads `E.enum(` on 2.1.216, `A.enum(`,
|
|
29
|
+
`b.enum(`, `v.enum(`, `w.enum(` through 2.1.223, then `Ir(` on 2.1.224 — and
|
|
30
|
+
every matcher that named any of it dated itself to one build. 2.1.224 is the
|
|
31
|
+
proof: `.enum([` dropped from 241 sites to 23 in a single release, because the
|
|
32
|
+
spelling tracks which *module* a schema lives in (bundled namespace alias vs
|
|
33
|
+
bare standalone factory), never what the schema is. So the callee is
|
|
34
|
+
`base.ARRAY_CALL` — whatever sits between the prop name and the array literal,
|
|
35
|
+
bounded only by statement and block edges — and no spelling is enumerated.
|
|
36
|
+
Enumerating the spellings seen so far only defers the same break to the next
|
|
37
|
+
one; that is the boundary of the narrow-branch rule above.
|
|
38
|
+
- **Dissolving the callee raises what the surviving anchors must carry**, and
|
|
39
|
+
that debt is per-site, never general. `effortLevel:` plus its level array is
|
|
40
|
+
unique on every build. `model:` plus an array is *not* — 6 sites on
|
|
41
|
+
2.1.216–220, 12 on 2.1.221–224 — so `MODEL_ENUM`'s
|
|
42
|
+
``.optional().describe(`Optional model override`` tail is its sole
|
|
43
|
+
discriminator, not belt-and-braces. Trimmed, the matcher finds three sites, two
|
|
44
|
+
of them the settings-scope enum `["user","project","local"]`, and
|
|
45
|
+
`codex-models` splices Codex ids into it. Measure an anchor's match count on
|
|
46
|
+
every backup before removing any part of it: under the older, tighter callee
|
|
47
|
+
that same probe read 1 on all 8 builds, so a measurement taken before the
|
|
48
|
+
callee was dissolved does not survive it.
|
|
49
|
+
- **Rewrite at the dispatch point; never model the arm.** When an update is
|
|
50
|
+
valid at arm entry, splice it in where the code dispatches on the semantic
|
|
51
|
+
string — after a `case` label chain, or by wrapping the dispatch test
|
|
52
|
+
itself, `(test&&((update),!0))`, which preserves the test's value exactly
|
|
53
|
+
and so survives any surrounding composition (wrapping the *enclosing*
|
|
54
|
+
condition instead would run the update for the other side of a bare `||`) —
|
|
55
|
+
and say nothing about what the arm already does. Arm bodies are upstream's
|
|
56
|
+
busiest surface: 2.1.226 threaded one flag through five arms of the stream
|
|
57
|
+
reducer (`stmt1,stmt2;` became `if(stmt1,stmt2,d)call;`) and appended a
|
|
58
|
+
declarator to the head's `let`, and every literal that had modelled those
|
|
59
|
+
bodies — a cross-product of `mode(` vs `mode?.(`, braced vs unbraced arms,
|
|
60
|
+
four `displayTransform` spellings — died or was already dead weight. The
|
|
61
|
+
dispatch strings themselves (`"thinking_delta"`, `"message_stop"`) are the
|
|
62
|
+
API's vocabulary and do not churn. Same doctrine as the callee rule above:
|
|
63
|
+
the arm is *reached through*, the dispatch point is what the update is
|
|
64
|
+
*applied to*. Entry insertion does reorder against mid-arm work (the
|
|
65
|
+
message-stop reset now runs before upstream's `finalize()`), so it asks
|
|
66
|
+
that the update be independent of the arm's own channels — decide that,
|
|
67
|
+
don't assume it. Bound heads at grammar edges for the same reason — in a
|
|
68
|
+
minified bundle only `;` or `,` can follow `let{…}=t`, and which one is
|
|
69
|
+
noise (`(?=[;,])`, not `;`).
|
|
24
70
|
- Match an optional brace **pair** conditionally (`(\{)?…(?(1)\})`), never as
|
|
25
71
|
two independent `\{?` / `\}?`. A lone optional closing brace eats the
|
|
26
72
|
*enclosing* block's `}` on the unbraced shape, and a rewrite that emits its
|
|
@@ -41,8 +87,8 @@ Anything the binary can enumerate for us, it does:
|
|
|
41
87
|
`agentType:"<name>",whenToUse:` carrying `source:"built-in"` in the same
|
|
42
88
|
object (`agents.discover_agents`). Definitions whose `whenToUse` begins with
|
|
43
89
|
`"Internal` are plumbing and are not offered.
|
|
44
|
-
- **Model aliases** come from the Task tool's own input schema: the
|
|
45
|
-
|
|
90
|
+
- **Model aliases** come from the Task tool's own input schema: the `model`
|
|
91
|
+
enum whose describe-string starts
|
|
46
92
|
`Optional model override` — `agents.MODEL_ENUM`, one compiled anchor because
|
|
47
93
|
`codex-models` splices the chosen Codex ids into the very group
|
|
48
94
|
`discover_models` reads back out.
|
|
@@ -149,7 +195,7 @@ first is how a manifest starts lying. The manifest is held to the same rule:
|
|
|
149
195
|
|
|
150
196
|
## Sub-steps, and why `live-thinking` has them
|
|
151
197
|
|
|
152
|
-
Most patches are one rewrite. `live-thinking` is ~
|
|
198
|
+
Most patches are one rewrite. `live-thinking` is ~20 named sub-steps, because
|
|
153
199
|
upstream has reshaped the stream reducer at least three times and a single hit
|
|
154
200
|
count cannot tell "all landed" from "half silently drifted".
|
|
155
201
|
|
|
@@ -158,9 +204,16 @@ kinds:
|
|
|
158
204
|
|
|
159
205
|
- **Independent fixes** (`memo-cache`, `linger`, `display-mode`, `bottom-row`,
|
|
160
206
|
…) — each may or may not be present on a given build.
|
|
161
|
-
- **Mutually-exclusive reducer variants** — `reducer-
|
|
162
|
-
|
|
163
|
-
|
|
207
|
+
- **Mutually-exclusive reducer variants** — `reducer-options` (2.1.138+, the
|
|
208
|
+
options-bag head; whether the bag still destructures `onStreamingThinking`
|
|
209
|
+
is the same shape with the prop present or absent, not a second variant) and
|
|
210
|
+
`reducer-legacy` (pre-2.1.138, positional parameters). On any one build
|
|
211
|
+
exactly one should land; its count is *recognition*, one per reducer. The
|
|
212
|
+
state updates themselves are per-dispatch-point steps (`request-start`,
|
|
213
|
+
`message-stop`, `text-clear`, `message-delta-clear`, `thinking-start`,
|
|
214
|
+
`thinking-append`), so a build that folds one arm away reads as that point's
|
|
215
|
+
absence by name, never as a bare count drop inside an aggregate. On
|
|
216
|
+
2.1.216–2.1.226 the variant is `reducer-options` with all six points.
|
|
164
217
|
|
|
165
218
|
An *optional* sub-step that finds nothing is reported as absent, not broken —
|
|
166
219
|
it is just a shape this build doesn't have. A sub-step that finds a shape but
|
|
@@ -176,11 +229,11 @@ The rest carry expectations, so their absence is checked rather than noted:
|
|
|
176
229
|
but *proof*: they are credited only when the rewritten reducer body actually
|
|
177
230
|
contains the state updates, which is what recognising a reducer does not by
|
|
178
231
|
itself establish.
|
|
179
|
-
- **The `reducer` group** — `reducer-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
232
|
+
- **The `reducer` group** — `reducer-options`, `reducer-legacy`. At least one
|
|
233
|
+
must land; none landing is the signal that upstream shipped a new reducer
|
|
234
|
+
head that needs a new variant. Two landing is allowed on purpose — a
|
|
235
|
+
transitional build carrying two reducers would have both correctly patched,
|
|
236
|
+
and that is no reason to cry wolf.
|
|
184
237
|
- **Optional** — `memo-cache`, `memo-removal`, `linger`, `bottom-row`,
|
|
185
238
|
`final-summary`. The first four match nothing on 2.1.216+ and are kept for
|
|
186
239
|
older builds; `discover` is a notes-only channel that records how far back the
|
|
@@ -329,11 +382,13 @@ for you. Each entry: what it changes, the stable anchor, and where it lives.
|
|
|
329
382
|
|
|
330
383
|
### Live thinking — `streaming.py`
|
|
331
384
|
|
|
332
|
-
- **`live-thinking`** — the ~
|
|
385
|
+
- **`live-thinking`** — the ~20-step patch above. Discovery anchor is
|
|
333
386
|
`onStreamingThinking:` → `useState(null)` (the older `hidePastThinking`
|
|
334
387
|
anchor is gone as of 2.1.216 — the fallback back-scan is load-bearing).
|
|
335
388
|
Reducer anchors: `type==="stream_request_start"`, `case"thinking_delta"`,
|
|
336
|
-
`content_block_start
|
|
389
|
+
`content_block_start` — the reducer is the function carrying all three, its
|
|
390
|
+
head bounded at the declarator edge, and every state update is an insertion
|
|
391
|
+
at a dispatch point (the rule above); the arm bodies are never matched.
|
|
337
392
|
The `display-mode` sub-step defaults the request's thinking display to
|
|
338
393
|
`"summarized"`; without it the API only streams summary text when the
|
|
339
394
|
`showThinkingSummaries` setting is on. Two shapes: the legacy inline env
|
|
@@ -85,7 +85,7 @@ def write(
|
|
|
85
85
|
compared against ``source`` -- *before* it is moved into place. A rebuild
|
|
86
86
|
bug therefore fails without ever touching the live binary.
|
|
87
87
|
"""
|
|
88
|
-
import shutil
|
|
88
|
+
import shutil
|
|
89
89
|
|
|
90
90
|
new_blob = blobmod.rebuild(
|
|
91
91
|
bundle.blob, source.encode("utf8"), drop_bytecode=drop_bytecode
|
|
@@ -120,7 +120,7 @@ def verify(path: str, expected: str) -> None:
|
|
|
120
120
|
"""Re-extract from a written binary and assert it round-trips exactly."""
|
|
121
121
|
try:
|
|
122
122
|
written = read(path)
|
|
123
|
-
except Exception as exc:
|
|
123
|
+
except Exception as exc:
|
|
124
124
|
raise ContainerError(f"patched binary could not be re-read: {exc}") from exc
|
|
125
125
|
if written.source != expected:
|
|
126
126
|
raise ContainerError(
|
|
@@ -224,11 +224,11 @@ def write_section(buf: bytes, payload: bytes, name: str = ".bun") -> bytes:
|
|
|
224
224
|
and seg.filesz
|
|
225
225
|
and seg.offset >= end
|
|
226
226
|
and seg.align
|
|
227
|
+
and (seg.offset + delta) % seg.align != seg.vaddr % seg.align
|
|
227
228
|
):
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
)
|
|
229
|
+
raise ElfError(
|
|
230
|
+
f"shifting LOAD segment {seg.index} would break its alignment"
|
|
231
|
+
)
|
|
232
232
|
|
|
233
233
|
new_size = bun.size + delta
|
|
234
234
|
body = bytearray(payload)
|
|
@@ -27,7 +27,7 @@ class MachOError(BunError):
|
|
|
27
27
|
|
|
28
28
|
def _lief():
|
|
29
29
|
try:
|
|
30
|
-
import lief
|
|
30
|
+
import lief
|
|
31
31
|
except ImportError as exc: # pragma: no cover - platform dependent
|
|
32
32
|
raise MachOError(
|
|
33
33
|
"LIEF is required to patch macOS binaries. Install it with "
|
|
@@ -107,6 +107,7 @@ def codesign(path: str) -> None:
|
|
|
107
107
|
["codesign", "--sign", "-", "--force", path],
|
|
108
108
|
capture_output=True,
|
|
109
109
|
text=True,
|
|
110
|
+
check=False,
|
|
110
111
|
)
|
|
111
112
|
if result.returncode != 0: # pragma: no cover - macOS only
|
|
112
113
|
raise MachOError(f"codesign failed: {result.stderr.strip()}")
|
|
@@ -36,7 +36,7 @@ from pathlib import Path
|
|
|
36
36
|
from rich.markup import escape
|
|
37
37
|
|
|
38
38
|
from . import cache, locate, patcher
|
|
39
|
-
from .bun import
|
|
39
|
+
from .bun import Bundle, BunError
|
|
40
40
|
from .codex import DEFAULT_PORT, is_valid_port
|
|
41
41
|
from .codex.models import CodexModel, catalogue, is_reserved_id, is_valid_id, reconcile
|
|
42
42
|
from .patches import (
|
|
@@ -549,7 +549,7 @@ def _doctor_target(path: str | None) -> tuple[Bundle, str] | None:
|
|
|
549
549
|
given -- that is how any kept backup becomes a regression corpus -- while the
|
|
550
550
|
installed binary falls back to its pristine copy when it is already patched.
|
|
551
551
|
"""
|
|
552
|
-
from .bun import container
|
|
552
|
+
from .bun import container
|
|
553
553
|
|
|
554
554
|
if path is not None:
|
|
555
555
|
# A name the user typed is data, not markup: an unescaped `claude[old]`
|
|
@@ -695,7 +695,7 @@ def _baked_port() -> int | None:
|
|
|
695
695
|
why ``serve`` asks once at startup rather than per request -- and why a re-bake
|
|
696
696
|
onto a different port needs a ``serve`` restart, as the apply report says.
|
|
697
697
|
"""
|
|
698
|
-
from .bun import container
|
|
698
|
+
from .bun import container
|
|
699
699
|
|
|
700
700
|
install = locate.find()
|
|
701
701
|
if install is None:
|
|
@@ -155,7 +155,7 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
155
155
|
def log_message(self, *_args) -> None: # keep the terminal clean
|
|
156
156
|
pass
|
|
157
157
|
|
|
158
|
-
def do_POST(self) -> None:
|
|
158
|
+
def do_POST(self) -> None:
|
|
159
159
|
# One request per connection: an SSE reply is open-ended (no
|
|
160
160
|
# Content-Length), so the stream ends by connection close.
|
|
161
161
|
self.close_connection = True
|
|
@@ -177,7 +177,9 @@ class _Handler(BaseHTTPRequestHandler):
|
|
|
177
177
|
# this API just as unparseable bytes are, and is answered the same
|
|
178
178
|
# way. Reaching `_messages` with one used to raise straight out of
|
|
179
179
|
# the handler thread: a traceback, a reset connection, no reply.
|
|
180
|
-
raise ValueError(
|
|
180
|
+
raise ValueError( # noqa: TRY004 - lands in json's own handler below
|
|
181
|
+
"not a JSON object"
|
|
182
|
+
)
|
|
181
183
|
except ValueError:
|
|
182
184
|
return self._error(
|
|
183
185
|
400, "invalid_request_error", "request body must be a JSON object"
|
|
@@ -74,7 +74,7 @@ class Credentials:
|
|
|
74
74
|
}
|
|
75
75
|
|
|
76
76
|
@classmethod
|
|
77
|
-
def from_json(cls, data: object) ->
|
|
77
|
+
def from_json(cls, data: object) -> Credentials | None:
|
|
78
78
|
if not isinstance(data, dict):
|
|
79
79
|
return None
|
|
80
80
|
access, refresh = data.get("access"), data.get("refresh")
|
|
@@ -78,7 +78,7 @@ def _resolve(path: Path) -> Installation | None:
|
|
|
78
78
|
|
|
79
79
|
def _is_native(binary: Path) -> bool:
|
|
80
80
|
"""Is this file one of the containers we can actually open?"""
|
|
81
|
-
from .bun import BunError, container
|
|
81
|
+
from .bun import BunError, container
|
|
82
82
|
|
|
83
83
|
try:
|
|
84
84
|
container.detect(str(binary))
|
|
@@ -27,8 +27,9 @@ from __future__ import annotations
|
|
|
27
27
|
import sys
|
|
28
28
|
import textwrap
|
|
29
29
|
import threading
|
|
30
|
+
from collections.abc import Callable
|
|
30
31
|
from dataclasses import dataclass, field
|
|
31
|
-
from typing import TYPE_CHECKING, Any
|
|
32
|
+
from typing import TYPE_CHECKING, Any
|
|
32
33
|
|
|
33
34
|
from blessed import Terminal
|
|
34
35
|
from rich import box
|
|
@@ -140,7 +141,7 @@ class MenuModel:
|
|
|
140
141
|
"""Everything the menu operates on, independent of the rendering engine."""
|
|
141
142
|
|
|
142
143
|
install: locate.Installation
|
|
143
|
-
status:
|
|
144
|
+
status: Status
|
|
144
145
|
pristine: Bundle
|
|
145
146
|
agents: list[BuiltinAgent]
|
|
146
147
|
models: list[str]
|
|
@@ -154,8 +155,8 @@ class MenuModel:
|
|
|
154
155
|
|
|
155
156
|
@classmethod
|
|
156
157
|
def build(
|
|
157
|
-
cls, install: locate.Installation, status:
|
|
158
|
-
) ->
|
|
158
|
+
cls, install: locate.Installation, status: Status, pristine: Bundle
|
|
159
|
+
) -> MenuModel:
|
|
159
160
|
agents = discover_agents(pristine.source)
|
|
160
161
|
models = [INHERIT, *discover_models(pristine.source)]
|
|
161
162
|
model = cls(
|
|
@@ -577,7 +578,7 @@ class MenuApp:
|
|
|
577
578
|
self.view = "select" # select | busy | report | doctor
|
|
578
579
|
self.stack: list[tuple[Modal, Callable[[object], None] | None]] = []
|
|
579
580
|
self.report: patcher.PatchReport | None = None
|
|
580
|
-
self.doctor_result:
|
|
581
|
+
self.doctor_result: DryRun | None = None
|
|
581
582
|
self.busy_message = ""
|
|
582
583
|
self.flash = ""
|
|
583
584
|
self.exit_code = 0
|
|
@@ -642,7 +643,7 @@ class MenuApp:
|
|
|
642
643
|
attrs = termios.tcgetattr(fd)
|
|
643
644
|
attrs[0] &= ~(termios.IXON | termios.IXOFF)
|
|
644
645
|
termios.tcsetattr(fd, termios.TCSANOW, attrs)
|
|
645
|
-
except Exception:
|
|
646
|
+
except Exception: # noqa: BLE001, S110 - best effort; no termios, no freeze risk
|
|
646
647
|
pass
|
|
647
648
|
|
|
648
649
|
def _key_name(self, keystroke) -> str:
|
|
@@ -1080,7 +1081,7 @@ class MenuApp:
|
|
|
1080
1081
|
|
|
1081
1082
|
def _apply(
|
|
1082
1083
|
self, selection: cache.Selection
|
|
1083
|
-
) -> tuple[patcher.PatchReport,
|
|
1084
|
+
) -> tuple[patcher.PatchReport, Status | None]:
|
|
1084
1085
|
options = selection.options
|
|
1085
1086
|
if options.codex_models:
|
|
1086
1087
|
# Fill in each model's name and window right before baking. A pick
|
|
@@ -1154,7 +1155,7 @@ class MenuApp:
|
|
|
1154
1155
|
def run() -> None:
|
|
1155
1156
|
try:
|
|
1156
1157
|
self._worker_result = (kind, work(), None)
|
|
1157
|
-
except Exception as exc:
|
|
1158
|
+
except Exception as exc: # noqa: BLE001 - the worker's failure IS the result
|
|
1158
1159
|
self._worker_result = (kind, None, str(exc) or exc.__class__.__name__)
|
|
1159
1160
|
|
|
1160
1161
|
threading.Thread(target=run, daemon=True).start()
|
|
@@ -326,7 +326,7 @@ def restore(install: locate.Installation) -> Path:
|
|
|
326
326
|
"for this version was never saved -- reinstall to get a clean binary."
|
|
327
327
|
)
|
|
328
328
|
# A full-file copy-back, so it works for both ELF and Mach-O.
|
|
329
|
-
from .bun.elf import atomic_write
|
|
329
|
+
from .bun.elf import atomic_write
|
|
330
330
|
|
|
331
331
|
atomic_write(str(install.binary), backup.read_bytes(), mode_from=str(backup))
|
|
332
332
|
return install.binary
|
|
@@ -17,6 +17,7 @@ import re
|
|
|
17
17
|
from dataclasses import dataclass
|
|
18
18
|
|
|
19
19
|
from .base import (
|
|
20
|
+
ARRAY_CALL,
|
|
20
21
|
GROUP_MODELS,
|
|
21
22
|
GROUP_OUTPUT,
|
|
22
23
|
IDENT,
|
|
@@ -78,7 +79,7 @@ def _subagent_prompt(content: str, _options: Options, outcome: Outcome) -> str:
|
|
|
78
79
|
transcript_var = transcript.group(1)
|
|
79
80
|
gate_pattern = compile_js(rf"{re.escape(transcript_var)}&&({IDENT})&&")
|
|
80
81
|
|
|
81
|
-
def drop_gate(match: re.Match[str]) -> str:
|
|
82
|
+
def drop_gate(match: re.Match[str], segment: str = segment) -> str:
|
|
82
83
|
prompt_var = match.group(1)
|
|
83
84
|
nearby = segment[match.end() : match.end() + 260]
|
|
84
85
|
if f"{{prompt:{prompt_var},theme:" not in nearby:
|
|
@@ -156,7 +157,7 @@ _DEF_WINDOW = 3000
|
|
|
156
157
|
#: why that ordering is load-bearing). Matched separately, the two would drift
|
|
157
158
|
#: apart on the next upstream reshape, and only one of them would be repaired.
|
|
158
159
|
MODEL_ENUM = compile_js(
|
|
159
|
-
rf'model:{
|
|
160
|
+
rf'model:{ARRAY_CALL}((?:"{_MODEL_CHARS}+",?)+)\]\)\.optional\(\)'
|
|
160
161
|
rf'\.describe\([`"]Optional model override'
|
|
161
162
|
)
|
|
162
163
|
#: Used only if the Task-tool schema anchor ever disappears.
|
|
@@ -69,7 +69,7 @@ def derived_brand() -> str:
|
|
|
69
69
|
|
|
70
70
|
try:
|
|
71
71
|
user = getpass.getuser().strip()
|
|
72
|
-
except Exception:
|
|
72
|
+
except Exception: # noqa: BLE001 - however it fails, the answer is the same
|
|
73
73
|
user = ""
|
|
74
74
|
return f"{user}'s Code" if user else DEFAULT_BRAND
|
|
75
75
|
|
|
@@ -84,7 +84,7 @@ class Options:
|
|
|
84
84
|
#: Codex models to register and route -- ordinary patch configuration, like
|
|
85
85
|
#: every field here: chosen in the menu or with ``--codex``, and recorded in
|
|
86
86
|
#: the manifest by the patch that bakes it.
|
|
87
|
-
codex_models: list[
|
|
87
|
+
codex_models: list[CodexModel] = field(default_factory=list)
|
|
88
88
|
#: Localhost port shared by the redirect patch and the gateway. Defaulted
|
|
89
89
|
#: from the one home (:data:`patch_cc.codex.DEFAULT_PORT`) so the number
|
|
90
90
|
#: never lives in two places.
|
|
@@ -116,7 +116,7 @@ class Outcome:
|
|
|
116
116
|
applied: int = 0
|
|
117
117
|
notes: list[str] = field(default_factory=list)
|
|
118
118
|
#: Named sub-steps, for patches built from several independent rewrites.
|
|
119
|
-
steps: dict[str,
|
|
119
|
+
steps: dict[str, Outcome] = field(default_factory=dict)
|
|
120
120
|
#: What this sub-step's absence means (set via :meth:`step`). ``False`` --
|
|
121
121
|
#: a shape some builds simply lack; ``True`` -- the patch is broken without
|
|
122
122
|
#: it; a string -- name of a group of which at least one member must land.
|
|
@@ -153,7 +153,7 @@ class Outcome:
|
|
|
153
153
|
def note(self, message: str) -> None:
|
|
154
154
|
self.notes.append(message)
|
|
155
155
|
|
|
156
|
-
def step(self, name: str, expect: bool | str = False) ->
|
|
156
|
+
def step(self, name: str, expect: bool | str = False) -> Outcome:
|
|
157
157
|
"""Get (or create) a named sub-outcome.
|
|
158
158
|
|
|
159
159
|
A single scalar count cannot distinguish "all twelve rewrites landed"
|
|
@@ -167,7 +167,7 @@ class Outcome:
|
|
|
167
167
|
sub.expect = expect
|
|
168
168
|
return sub
|
|
169
169
|
|
|
170
|
-
def finalize(self) ->
|
|
170
|
+
def finalize(self) -> Outcome:
|
|
171
171
|
"""Roll sub-step totals up into this outcome."""
|
|
172
172
|
if self.steps:
|
|
173
173
|
self.candidates += sum(s.candidates for s in self.steps.values())
|
|
@@ -277,6 +277,19 @@ def compile_js(pattern: str, flags: int = 0) -> re.Pattern[str]:
|
|
|
277
277
|
# Matches a minified identifier, the JS `[A-Za-z_$][\w$]*` idiom.
|
|
278
278
|
IDENT = r"[A-Za-z_$][\w$]*"
|
|
279
279
|
|
|
280
|
+
#: From a schema's property name to the *opening* of the array literal it is
|
|
281
|
+
#: built from -- the ``([`` included, so a site composes this as
|
|
282
|
+
#: ``prop:{ARRAY_CALL}`` and never spells the call open itself.
|
|
283
|
+
#:
|
|
284
|
+
#: Whatever expression the build reaches the factory through is skipped
|
|
285
|
+
#: unmodelled, because the callee is minifier noise that churns every few
|
|
286
|
+
#: releases (``E.enum(`` ... ``w.enum(`` ... ``Ir(`` for one unchanged schema).
|
|
287
|
+
#: Lazy but not unbounded: ``[^;{}]`` cannot cross a statement or block edge,
|
|
288
|
+
#: and the spans it actually spends are 47-51 and 88-92 characters on every
|
|
289
|
+
#: build we hold. What the surviving anchors must carry instead -- which is a
|
|
290
|
+
#: debt this raises, per site: docs/PLAYBOOK.md.
|
|
291
|
+
ARRAY_CALL = r"[^;{}]*?\(\["
|
|
292
|
+
|
|
280
293
|
|
|
281
294
|
def switch_case_end(content: str, start: int) -> int:
|
|
282
295
|
"""End offset of a ``switch`` arm beginning at ``start``.
|
|
@@ -304,12 +304,8 @@ def _register_picker(
|
|
|
304
304
|
# because this is the only place in the picker where a model leaves Anthropic,
|
|
305
305
|
# and nothing else in the list would say so.
|
|
306
306
|
entries = ",".join(
|
|
307
|
-
"{value
|
|
308
|
-
|
|
309
|
-
js_string(value),
|
|
310
|
-
js_string(_display_name(value)),
|
|
311
|
-
js_string(_describe(model)),
|
|
312
|
-
)
|
|
307
|
+
f"{{value:{js_string(value)},label:{js_string(_display_name(value))},"
|
|
308
|
+
f"description:{js_string(_describe(model))}}}"
|
|
313
309
|
for value, model in picks
|
|
314
310
|
)
|
|
315
311
|
# Every row is emitted with no build-time "already present?" filter, and dedup
|