Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
61 commits
Select commit Hold shift + click to select a range
626b0f9
chore(release): open dev at 2.56.0 before releasing 2.55.0 (#4618)
github-actions[bot] Sep 14, 2026
4f788f9
docs(devlog): record the 2.55.0 release evidence and the pending regi…
lidge-jun Sep 14, 2026
68951a1
feat(routing): separate auth identity, quota domain and cache domain …
lidge-jun Sep 14, 2026
00f1762
feat(lib): reserve tokens and output before dispatch, and keep the le…
lidge-jun Sep 14, 2026
627274b
feat(routing): bound recovery with a half-open probe lease and honour…
lidge-jun Sep 14, 2026
7b7648e
docs(devlog): plan godfile round2 decomposition and file-size ratchet
lidge-jun Sep 14, 2026
45fca0a
test(ci-workflows): add file-size ratchet gate
lidge-jun Sep 14, 2026
913e0d0
refactor(responses,codex): split state and shim behind facades
lidge-jun Sep 14, 2026
c63e9ea
refactor(codex): split inject and catalog sync behind facades
lidge-jun Sep 14, 2026
3596985
refactor(codex,providers): split routing and quota behind facades
lidge-jun Sep 14, 2026
ce51b3e
fix(responses): restore imports dropped by the state split
lidge-jun Sep 14, 2026
a6eb03b
Merge branch 'codex/m2k-l3-state-shim' into codex/m2k-l4-inject-sync
lidge-jun Sep 14, 2026
e874436
fix(codex): point split leaves at the real definition modules
lidge-jun Sep 14, 2026
a6c6e29
Merge branch 'codex/m2k-l4-inject-sync' into codex/m2k-l5-routing-quota
lidge-jun Sep 14, 2026
48abcfb
fix(codex,providers): restore bindings dropped by the routing and quo…
lidge-jun Sep 14, 2026
e443f58
fix(codex): correct the config import depth in routing/active-account
lidge-jun Sep 14, 2026
cf10995
Merge pull request #4645 from lidge-jun/codex/m2k-l5-routing-quota
lidge-jun Sep 14, 2026
60d935f
Merge pull request #4643 from lidge-jun/codex/m2k-l4-inject-sync
lidge-jun Sep 14, 2026
ccb7454
Merge pull request #4642 from lidge-jun/codex/m2k-l3-state-shim
lidge-jun Sep 14, 2026
f9e2ee0
Merge pull request #4636 from lidge-jun/codex/m2k-l2-ratchet
lidge-jun Sep 14, 2026
d5585a0
fix(lib): make the dispatch permit the charge, and close the uncounte…
lidge-jun Sep 14, 2026
8caf0a5
feat(responses): put combo hops and adapter inner retries on the shar…
lidge-jun Sep 14, 2026
a223a25
feat(usage): report sends, spend and cache provenance per logical req…
lidge-jun Sep 14, 2026
c3106e3
fix(codex): stop retrying a doomed pool credential refresh, and say i…
lidge-jun Sep 14, 2026
38a2d9f
feat(codex): give V2 threads real lineage and place a child on its pa…
lidge-jun Sep 14, 2026
2b43c14
fix(responses): drop account-bound continuation when the serving acco…
lidge-jun Sep 14, 2026
571cbe2
Merge dev into the godfile round2 lane
lidge-jun Sep 14, 2026
55cd467
Merge pull request #4635 from lidge-jun/codex/m2k-l1-roadmap
lidge-jun Sep 14, 2026
ce0ac61
fix(responses): reserve the last two generic-OAuth hops from the shar…
lidge-jun Sep 14, 2026
f2dd9dd
docs(devlog): record why the root workflow budget expires a long sess…
lidge-jun Sep 14, 2026
db6b9f2
docs(devlog): plan godfile round3 for the remaining five src modules
lidge-jun Sep 14, 2026
836511b
fix(lib): bound the root workflow ceilings by a window instead of a l…
lidge-jun Sep 14, 2026
d2d35e0
refactor(config): split config.ts behind a facade
lidge-jun Sep 14, 2026
ee9f4df
refactor(providers): split the provider registry declaration table
lidge-jun Sep 14, 2026
0c745bd
refactor(codex): split the codex auth management API
lidge-jun Sep 14, 2026
47b1879
refactor(catalog): split provider-fetch into six leaves
lidge-jun Sep 14, 2026
90aeffa
refactor(adapters): split the openai-chat adapter and update owner docs
lidge-jun Sep 14, 2026
90e7c23
Merge branch 'dev' of https://github.com/lidge-jun/opencodex into cod…
lidge-jun Sep 14, 2026
1686980
Merge dev and lower the size baseline onto the split facades
lidge-jun Sep 14, 2026
a90a99a
Merge pull request #4658 from lidge-jun/codex/m3-impl
lidge-jun Sep 14, 2026
49dcdbf
fix(server): name the ceiling that refused, and let an operator see a…
lidge-jun Sep 14, 2026
9eb6290
Merge dev into the godfile round3 lane
lidge-jun Sep 14, 2026
09067c5
Merge pull request #4655 from lidge-jun/codex/m3-l1-roadmap
lidge-jun Sep 14, 2026
d97f740
docs(devlog): record what round3 actually delivered
lidge-jun Sep 14, 2026
8301dcb
docs(devlog): point the round3 phase docs at the outcome record
lidge-jun Sep 14, 2026
f5a8a44
test(ci-workflows): assert every relative import under src resolves
lidge-jun Sep 14, 2026
0eab385
test(ci-workflows): extend the import-resolution guard to the dashboard
lidge-jun Sep 14, 2026
ca00b7e
docs(devlog): record the guard the round3 audit produced
lidge-jun Sep 14, 2026
aa91958
Merge pull request #4661 from lidge-jun/codex/m3-l7-record-correction
lidge-jun Sep 14, 2026
9b71107
refactor(adapters): split openai-responses.ts behind a facade (#4671)
lidge-jun Sep 15, 2026
369be81
fix(responses): give every reasoning input item the summary the upstr…
lidge-jun Sep 15, 2026
11f1119
refactor(bridge): split bridge.ts behind a facade (#4672)
lidge-jun Sep 15, 2026
3ea88f3
test(lab): check that the sync activation window's callees are synchr…
lidge-jun Sep 15, 2026
a63a473
refactor(server): split server/index.ts behind a facade (#4675)
lidge-jun Sep 15, 2026
89bc673
fix(tests): stop a quota test from deleting the real OpenCodex home (…
lidge-jun Sep 15, 2026
485a525
refactor(responses): split core.ts behind a facade (#4677)
lidge-jun Sep 15, 2026
4bef58b
docs(devlog): record the round5 outcome, the oracle lesson, and a loc…
lidge-jun Sep 15, 2026
2046e68
docs(devlog): open the 2.56.0 release-train plan unit (#4685)
lidge-jun Sep 15, 2026
2702911
fix(responses): keep the whole conversation when a continuation repla…
lidge-jun Sep 15, 2026
386303a
fix(responses,codex): stop charging a send that never happened, and l…
lidge-jun Sep 15, 2026
08ff7c9
release: bring preview onto the verified 2.56.0 product tree
lidge-jun Sep 15, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 11 additions & 4 deletions bunfig.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,15 @@
# The npm script already uses `bun test ./tests/`; this makes a bare `bun test` behave the same.
[test]
root = "tests"
# Sandboxes HOME/OPENCODEX_HOME/CODEX_HOME and arms the real-home write guard for EVERY
# invocation, including a bare `bun test <file>` that skips `scripts/test.ts`. A test
# once overwrote a real user config through that unwrapped path; see
# devlog/_fin/260730_codex_rs_upstream_v2_live_handoff/070.
# Sandboxes HOME/OPENCODEX_HOME/CODEX_HOME and arms the real-home write guard for every
# invocation THAT READS THIS FILE, including a bare `bun test <file>` that skips
# `scripts/test.ts`. A test once overwrote a real user config through that unwrapped path;
# see devlog/_fin/260730_codex_rs_upstream_v2_live_handoff/070.
#
# That qualifier is load-bearing. Bun resolves bunfig.toml from the CURRENT WORKING
# DIRECTORY, so `cd /tmp && bun test <repo>/tests/x.test.ts` loads no preload at all: nothing
# is sandboxed, the guard stays disarmed, and every `getConfigDir()` in that process returns
# the developer's real `~/.opencodex`. On 2026-09-15 such a run deleted one. A test that needs
# a config directory pins its own OPENCODEX_HOME instead of trusting this line, and
# `tests/ci-workflows/test-home-guard.test.ts` enforces that for the destructive case.
preload = ["./tests/preload.ts"]
6 changes: 4 additions & 2 deletions devlog/_plan/260914_cost_guard_stabilization/000_unit.md
Original file line number Diff line number Diff line change
Expand Up @@ -83,8 +83,10 @@ what protects the operator who explicitly opts back out.
## Write scope

Permitted: `src/codex/routing.ts`, `src/types/config.ts`, `src/config.ts`, the
account-pool and session-affinity code, their tests under
`tests/codex-integration/`, `docs-site/` configuration reference and its locales,
account-pool and session-affinity code, `src/routing/` for the identity, quota and
cache-domain layers `090_remaining_stack.md` plans (wpc's classifier, wpe's reservation
ledger, wpf's probe lease), their tests under `tests/codex-integration/` and
`tests/routing/`, `docs-site/` configuration reference and its locales,
`structure/` docs that own the affected invariants, and this unit.

Excluded, owned by concurrent lanes: `src/providers/devin*`,
Expand Down
33 changes: 33 additions & 0 deletions devlog/_plan/260914_cost_guard_stabilization/070_delivery.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,36 @@ The configuration reference and every locale translation change in the same pull
request as the behaviour, because a default documented in eight languages is wrong
in eight languages the moment the code lands. `structure/` ownership docs for the
affected invariants change with them.

## 2.55.0 release record

| field | value |
| --- | --- |
| product snapshot | `62f02223a0` on `dev` |
| preview SHA | `7bdd1b29b5` (`2.55.0-preview.20260914`) |
| stable SHA | `1cc89cf88c` (`2.55.0`) |
| dev next | `2.56.0` (#4618) |
| preview push CI | run 34833399886, success |
| preview service lifecycle | run 34833399853, success |
| preview dry-run / publish | 34834321705 / 34834502951, both success |
| main push CI | run 34835022788, success |
| main service lifecycle | run 34835022762, success |
| main dry-run / publish | 34836327017 / 34836498588, both success |
| registry: preview | verified, `registry.npmjs.org/@bitkyc08%2Fopencodex/2.55.0-preview.20260914` returns 200 |
| registry: stable | **pending** -- the version endpoint still returns 404 |

The preview and stable trees are byte-identical apart from `package.json.version`;
`git diff origin/preview origin/main -- . ':!package.json'` is empty.

**The stable registry line is the honest part.** The publish job reported success, its
post-publish registry smoke passed on the runner, and the `v2.55.0` tag and GitHub Release point
at `1cc89cf88c`. Fifteen minutes later the registry version endpoint still answers 404 and
`dist-tags.latest` still reads `2.54.0`, while the preview published minutes earlier answers 200.
So the receipt exists and availability is unconfirmed, which is `registryVerification: pending` --
not a missing package. Do not re-run the publish: a second dispatch against the same version is
the failure mode the bounded-read path exists to prevent. Recover the observation, then announce.

What this release does not claim: the PRD's RG2 set is not complete. The durable cross-restart
reservation ledger, V2 child first placement, the minimum quota/cache domain contract, the
transient half-open probe lease, combo hops on the shared budget, Cursor's inner retries and the
sends-per-logical-request surfacing all remain open, so #4546 stays open too.
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
# 090 — what is left after 2.55.0, as a seven-layer stack

## Why this doc exists

`070_delivery.md` closed the 2.55.0 release record with a list of things the
release deliberately does not claim: the durable cross-restart reservation
ledger, V2 child first placement, the minimum quota/cache domain contract, the
transient half-open probe lease, combo hops on the shared budget, Cursor's inner
retries, and sends-per-logical-request surfacing. That list is accurate and it is
also unordered, which is the problem. Each item touches a different layer of the
same request path, and three of them change the same two files.

This doc fixes the order and the write scopes so the remaining work can ship as a
stack of independently revertible pull requests rather than one unreviewable diff.

## What is already true

Stating this once, because repeating the original problem statement as if nothing
landed is the failure mode this unit keeps hitting. On `dev@4f788f91`: a request
carries a guarded four-send profile with a three-send base allowance and one shared
final-recovery reserve (#4609); a zero budget no longer floors to one (#4613); the
workflow guard caps physical sends, distinct children and concurrency and reserves
an interactive slot (#4614); a healthy detour is promoted rather than discarded when
a hold expires, and `Retry-After` is honoured on the transient path (#4616).

So the remaining work is not "add a budget". It is: make the budget reach the
paths it still cannot see, make it correct under concurrency, and stop it from
being reset by a restart or side-stepped by a fresh identity.

## The stack

Listed in the order the branches are stacked, each one based on the branch above it.

| # | Layer | Branch | What it closes |
| --- | --- | --- | --- |
| 1 | wpc | `codex/4546-wpc-quota-cache-domains` | Auth identity, quota domain and cache domain as three separate values, plus conversational-state portability as its own check |
| 2 | wpe | `codex/4546-wpe-durable-reservation` | Token-and-output reservation at three scopes, unresolved spend, and a ledger that survives restart |
| 3 | wpf | `codex/4546-wpf-probe-lease-backpressure` | The half-open probe lease, `Retry-After` preserved past the local maximum, and pool-wide retry backpressure |
| 4 | wpd | `codex/4546-wpd-v2-lineage-placement` | V2 root/parent/thread lineage and child first placement onto the parent's current serving account |
| 5 | wpa | `codex/4546-wpa-dispatch-coverage` | The reset-retry counting seam, compact's routed fallback, the generic OAuth and Anthropic hops, the gated-400 ladder's relation to the shared cap, and permit atomicity |
| 6 | wpb | `codex/4546-wpb-combo-adapter-retries` | Combo's real hop and target transition under a per-target policy, and Cursor's and Kiro's inner retries |
| 7 | wpg | `codex/4546-wpg-spend-instrumentation` | Sends per logical request, reserved/settled/unresolved spend, cache provenance, and a no-account failure that explains itself |

Only two of those adjacencies are real dependencies. wpb needs wpa's permit
contract to be atomic before a second dispatcher may be wired to it, and wpg
reports what every earlier layer produces, so it is last by construction rather
than by importance. The rest are contract layers that introduce a module and its
tests without rewiring a call site, which is what makes them stackable in
readiness order and revertible one at a time.

That independence is deliberate and it is also the honest limitation of the first
three layers: wpc's classifier, wpe's ledger and wpf's lease are each landed
tested and, for now, partly unreferenced. Each one names in its own pull request
which later layer is obliged to call it. A module that nobody calls does not
protect anything, so the stack is not finished until the wiring layers land on
top of it.

## The three corrections this stack is built on

**"Passes the holder" and "limits every send" are different completion
conditions.** #4608 gave a combo child the budget object; #4609 gave the request a
policy. Neither makes a second combo target draw the remainder, because the
adapter's initial send still reads its own policy allowance. A layer that receives
the counter and does not consult it as a limit reintroduces the multiplier
silently.

**The permit is not yet atomic.** `reserveDispatch()` evaluates the remainder and
`permit.use()` charges it. Two legs that reserve concurrently against one
remaining send both receive a permit. The fix is to make the reservation the
charge and add an explicit release for an abandoned reservation, which is why wpa
has to land before anything else wires a new caller.

**A memory Map is not a budget.** The ledger lives in process memory, and cleanup
only protects roots with active requests, so an exhausted-but-idle root can be
deleted and recreated fresh under the same id. Until reservations are durable and
cleanup is exhaustion-aware, "this root is out of budget" means "out of budget
until something restarts".

## Verification posture

Unchanged from `070_delivery.md` and restated because it governs every layer here:
the local suite, typecheck, install and build are **not run**, by explicit
instruction. Pushes use `--no-verify`. The only proof is hosted CI at the exact
final head SHA of each branch, and a green run against an earlier commit is not
evidence for the head that merges. Each pull request states that posture in its
Verification section rather than implying a local green.

## What would make this fail

Landing wpe's refusal path with a default limit low enough to refuse an
unconfigured install. The count caps from #4614 are already live and permissive;
token accounting must start observational and only enforce behind explicit
operator configuration, or the first upgrade turns a cost guard into an outage.
86 changes: 86 additions & 0 deletions devlog/_plan/260914_godfile_round2/000_plan.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# 260914 godfile round2 — src 갓파일 분해와 파일 크기 래칫

`dev`에는 사람이 유지하는 2,000줄 이상 텍스트 파일이 52개 있고(래칫이 실제로 스캔하는 범위, 즉 `devlog/` 제외 기준으로는 51개) 그중 15개가 `src/`에 있다. 이 단위는 그중 7개를 facade 보존 순수 이동으로 분해하고, 같은 일이 다시 쌓이지 않도록 파일 크기 래칫 게이트를 CI에 넣는다. 기여자에게 바뀌는 것은 두 가지다. 새 파일은 처음부터 2,000줄 미만이어야 하고, 기존 초과 파일은 더 길어질 수 없다. 분해 대상 파일을 import하던 코드는 facade가 남으므로 바뀌지 않는다.

숫자의 출처는 `git ls-files`에 대한 줄 수 측정이며, 생성물·벤더 스냅샷·로케일 미러 12경로를 제외한 값이다. 그 12경로는 `010_phase1_file_size_ratchet.md`의 `generated` 목록이 권위를 갖는다.

## 로프스펙

| 항목 | 내용 |
|---|---|
| Loop archetype | satisfy-spec. 완료 조건이 파일별로 고정돼 있고 사이클마다 종료한다 |
| Trigger | 사용자 요청: 스택 PR로 쌓고 tip-only CI로 추적하며 5~6 PABCD 사이클로 dev 머지까지 완료 |
| Goal | 7개 `src/` 갓파일을 1,999줄 이하로 분해하고 래칫 게이트와 함께 `origin/dev`에 머지 |
| Non-goals | 기능 정책 변경, 버그 수정 동반, `tests/` 33개 분해, i18n·생성물·devlog 분해, `core.ts`·`server/index.ts`·`auth-api.ts`·`providers/registry.ts` 본체(별도 단위) |
| Verifier | hosted CI. 수동 브랜치 체인의 tip PR 실행을 레인 게이트로 쓴다(DEV-STACK-08, owner 승인) |
| Stop condition | 6개 사이클의 D가 모두 닫히고 레인이 `dev`에 머지될 때 |
| Memory artifact | 이 단위(`devlog/_plan/260914_godfile_round2/`)와 각 PR 본문 |
| Expected outcomes | 성공 = 7파일 facade화 + 래칫 녹색 / 차단 = tip CI 적색이 반복되고 원인이 분해 외부일 때 |
| Escalation | tip CI가 분해와 무관한 이유로 적색이거나, 래칫 기준선이 다른 작업과 충돌할 때 |

## 제약

로컬에 `node_modules`가 없고 사용자 환경에서 install·build·typecheck·full suite를 돌리지 않는다. 따라서 이 단위의 모든 검증은 hosted CI이며, 문서와 PR 본문에서 로컬 실행 결과를 주장하지 않는다. 각 PR의 CI를 개별로 기다리지 않고 후행 추적한다. 최종 판정은 레인 tip의 exact-head 실행이다.

열린 PR과의 충돌은 순서 제약에서 제외한다(사용자 지시). 순서는 기술 의존성만으로 정한다. 그 대가로 `src/config.ts`에 걸린 21건을 포함해 47건이 리베이스 대상이 되며, 이 단위는 그 비용을 감수한 것으로 기록한다.

## 작업 단계 지도

| 사이클 | 문서 | 대상 | 브랜치 | PR base |
|---|---|---|---|---|
| 0 | `000_plan.md` + 010~050 | 로드맵(코드 변경 없음) | `codex/m2k-l1-roadmap` | `dev` |
| 1 | `010_phase1_file_size_ratchet.md` | 래칫 게이트 | `codex/m2k-l2-ratchet` | L1 |
| 2 | `020_phase2_state_and_shim.md` | `src/responses/state.ts`, `src/codex/shim.ts` | `codex/m2k-l3-state-shim` | L2 |
| 3 | `030_phase3_inject_and_catalog_sync.md` | `src/codex/inject.ts`, `src/codex/catalog/sync.ts` | `codex/m2k-l4-inject-sync` | L3 |
| 4 | `040_phase4_routing_and_quota.md` | `src/codex/routing.ts`, `src/providers/quota.ts` | `codex/m2k-l5-routing-quota` | L4 |
| 5 | `050_phase5_config.md` | `src/config.ts` | `codex/m2k-l6-config` | L5 |

의존은 단순하다. 사이클 1의 래칫이 먼저 있어야 이후 사이클이 만드는 새 파일이 게이트를 통과했다는 증거를 남길 수 있고, 사이클 2~5는 서로 파일이 겹치지 않으므로 체인 순서는 리뷰 편의를 위한 것이다. 사이클 5를 마지막에 두는 이유는 `src/config.ts`가 가장 많은 문서(10곳)와 오라클(9건)을 끌고 있어 앞 단계에서 얻은 패턴을 그대로 쓰기 위해서다.

사이클 내부의 PR 순서는 각 decade 문서가 소유한다. 특히 `050_phase5_config.md`는 초안의 묶음에 순환 의존이 있음을 실측으로 확인하고 순서를 재배치했다(salvage가 `configSchema`를, diagnostics가 salvage와 load-degrade를, live-reconcile이 `persistConfigUnlocked`를 쓴다). 이 문서의 표는 사이클 경계만 정의하며, 사이클 안의 순서는 decade 문서를 따른다.

## 스택 형태

수동 브랜치 체인이다. GitHub 네이티브 스택은 사용하지 않는다(DEV-STACK-OPT-IN-01: 명시적 opt-in 없음). 각 링크의 PR base는 바로 아래 링크의 head 브랜치이고, 최하단 L1만 `dev`를 base로 한다.

```
codex/m2k-l6-config → PR (base: l5) ← tip
codex/m2k-l5-routing-quota → PR (base: l4)
codex/m2k-l4-inject-sync → PR (base: l3)
codex/m2k-l3-state-shim → PR (base: l2)
codex/m2k-l2-ratchet → PR (base: l1)
codex/m2k-l1-roadmap → PR (base: dev) ← bottom
─────────────────────────── dev
```

## CI 정책 (DEV-STACK-08, owner 승인)

비-tip 링크의 head 커밋 제목에 `[skip ci]`를 붙여 tip만 비싼 스위트를 돌린다. 이 전략은 저장소 소유자가 이 배치에 한해 승인한 예외이며 기본값이 아니다.

지켜야 할 것은 셋이다. 누락·스킵·취소된 체크는 통과가 아니다. `[skip ci]`가 trunk에 착지하는 커밋 제목에 도달하면 안 된다(머지 커밋 제목에는 붙이지 않는다). 레인이 착지한 뒤 `dev`를 관찰하고 적색이면 다음 레인을 멈춘다.

## 머지 순서

체인 자식은 top-down으로 머지한다. 스택 자식을 머지하면 trunk가 아니라 부모 브랜치에 착지하기 때문이다. L6 → L5 → L4 → L3 → L2 순으로 각각 부모에 착지시키고, 마지막에 L1(base `dev`)을 머지하면 전체가 `dev`에 올라간다. L1을 머지하기 직전의 exact-head CI가 이 단위의 최종 게이트다.

각 머지 전에 조상 불변식을 확인한다.

```sh
git merge-base --is-ancestor origin/<link-branch> <tip-commit>
```

## 완료 조건

| 검사 | 조건 |
|---|---|
| 파일 크기 | 대상 7개가 전부 1,999줄 이하, 새 모듈 전부 1,999줄 이하 |
| 래칫 | 기준선 대비 증가 0. 각 사이클 D에서 `ratchet:update`로 기준선 회수 |
| 상태 소유권 | 각 decade 문서가 지정한 소유 모듈 배치대로, 인자로 새는 상태 0 |
| 금지 분할 | 각 decade 문서의 함정 항목 미발생 |
| 오라클·INV | 본문을 텍스트로 읽는 오라클의 읽기 경로 갱신 완료, INV 승계 모듈 지정 |
| 문서 | `structure/` 백틱 참조와 소유권 갱신, `bun run structure:check` 녹색 |
| 머지 | 6개 PR 전부 MERGED, `dev` 최종 CI 녹색 |

## 사이클 D에서 기록할 것

각 사이클은 D에서 다음을 이 단위에 남긴다. 남은 초과 파일 수, PR 번호와 head SHA, 관찰한 CI 실행 ID와 결론, 개선되지 않은 것과 죽은 가설(LOOP-PESSIMIST-01).
Loading
Loading