Retiring a facade can split one cfg(test)-guarded import into several
statements. Both flattening and upstream regrouping previously left the
extra statements unconditional. Reuse the existing mover attribute helper
and repeat the guard on each resulting statement.
Refuse carved output whose existing parent symlinks resolve outside the
canonical repository, matching the source-path containment boundary.
Local evidence: both import shapes lost their second cfg guard before this
change and preserve it after; four extra attribute cases and the existing
mover shape passed. Four Python modules parse; mover help exits 0. The
symlink-output probe accepted before and refuses after, without writing any
outside file. A formatted carve CLI dry-run on a real function exits 0.
No Rust build, module move, CI, provider call, or release claim for this
Python-only change. The active W2a Cargo source remains frozen separately.
Co-Authored-By: OpenAI Codex <[email protected]>
Follow-up to 3b35f098f7 after a note from the issue-drafts session: stdin-mode
`rustfmt --check` exits 0 even over a diff, so no exit status is used anywhere
in scripts/split. format_many now writes the formatted bytes of
`rustfmt --edition 2024 --emit stdout`, treats any rustfmt error as fatal,
requires the formatted text to be a fixed point of a second pass, and returns
the files whose base was not rustfmt-stable (left exactly as rewritten, so no
foreign line is reflowed). move-modules.py reports them as
`rustfmt_unstable_base` (null in a dry run, which does not format) and
`--strict-format` turns them into an error before anything is written.
Proof on a throwaway git repo (artifacts/crate-split-w0-20261025/mini):
`printf 'fn main( ){}' | rustfmt --check` exits 0 (the pitfall);
--strict-format on a tree with an unformatted file exits 2 and writes nothing;
a rewritten file rustfmt cannot parse ("unclosed delimiter") exits 2 and
writes nothing; a normal run formats the formatted-base file and leaves the
unformatted-base file untouched except for the rewritten path, listing both.
The 334/351 stable result of the W0 proof is unchanged in meaning: the 17
others are the include!d test_cases_* fragments whose base is unstable.
Python only; nothing compiled.
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Scripts only under scripts/split/. No Rust, no manifest, no budget touched.
Base 00be3d2e4f. tui strict production [M] 634,761 -> 634,761, file level [F]
943,686 -> 943,686, largest SCC 79 -> 79 (partition_report --non-test: [79, 8,
1, 1, 1]). Nothing was compiled: this is python; the rewrites it produces were
checked with rustfmt parse/format, module_graph.py, the path-keyed budget
scripts and counts, on a scratch `git init` export of this base (never the
shared tree). A compile of a rewritten tree is the first Lane A window.
What changed and why (reasoning lives here, not in comments):
move-modules.py (rewritten). R2.5: the old tool exited 0 in a tree without
.git after "moved 6 module(s)" (git failures were check=False), and its
dry-run headline (354 files / 3,487 lines) counted 12 files / 212 lines
inside the moved set that a real run never rewrites.
- --dest-crate/--src-crate replace every hard-coded runtime literal; --json;
--require-clean (dirty set from `git status --porcelain -z` plus files
younger than --quiet-minutes, default 15); --apply-cfg (flips only
`#[cfg(test)]` items that code outside the moved set uses; never touches
cfg(not(test)) forks, which are an FD-04 decision); --restore <journal>.
- No git mv (the shared index is not touched): plain file operations. Every
write is compare-and-swap (stat+sha against the bytes the plan was computed
from, temp file, os.replace) with a pre-image journal under CW/backups/;
a change between plan and write refuses before writing (exit 3); a change or
SIGTERM/SIGHUP/SIGINT during the write phase restores every pre-image and
removes created files and directories (exit 4). git/git grep failures and a
missing or foreign .git are fatal (exit 2). --require-clean refusals exit 5.
- Rewritten Rust goes through `rustfmt --edition 2024 --emit stdout` on stdin
per file; a file whose base text is not rustfmt-stable (the 17 include!d
test_cases_*.rs fragments) is left exactly as rewritten, so no foreign line
is reflowed.
- Coverage: plain and grouped `use crate::{..}` (3 sites), alias, unqualified
`<m>::` in the source lib (5 sites; cfg(test)-only use gets a cfg(test)
import), `<dest>::` -> `crate::` inside moved files (code only), `codewhale_tui::<m>`
in every other Rust file (cli config_keys 15 lines), sweep of path strings
(plain, `tui/src/..`, segmented `"tui" / "src" / "<m>"`: this adds
scripts/check_orcarouter_contract.py, which the old sweep missed) with
vendor/, crates/config/, scripts/split/, CHANGELOG* and web/data/*.generated.json
skipped; --derive-facts regenerates web/data (journalled pre-images).
- Reports (and plans, never applies): residue edges from moved files to
staying modules, super-root residue, manifest needs (deps, features,
consumer crates), cfg sites by class.
reexports.py (--resolve-reexports): resolves `pub use` facade names (globs
through the upstream sources and the build.rs-generated constants via
catalog_corrections.json / provider_descriptors.json) and rewrites
`crate::config::NAME` and grouped lists to the upstream path.
preflight.py (G11): whole-repo include_str!/include_bytes!/include!/#[path]
resolution against the old-to-new map, features.toml owners, path strings the
sweep cannot fix, `../src/<m>` references, CI test identities; exit 1 on residue.
widen.py: --dest-crate/--src-crate/--packages, signal-safe lints restore
(SIGTERM/SIGHUP/SIGINT and atexit; a manifest left capped by a killed run is
restored first), atomic writes, --prepass static widening, unused_imports
removed with the compiler's machine-applicable suggestions.
count_lines.py promoted from the plan's scratch (identical [M]) with the [F]
counter beside it. testsplit.py: function-level move/stay classifier with call
closure. partition_report/closure_report --non-test; module_graph --tui-src/
--runtime-src and a regex-driven lexer (11.0 s -> 2.5 s over 1,585 files,
0 mismatches against the old lex_mask on every tracked .rs).
Acceptance (scratch export of 00be3d2e4f, config settings config_persistence
config_keys oauth codex_model_cache):
- dry run: staying callers 342 files / 3,275 lines + inside the moved set 12 /
212 = the old tool's 354 / 3,487; grouped 3; self paths 105 + 5 in comments
= 110; other crates 2 files / 16 lines (15 cli + 1 runtime doc comment);
sweep 31 files (+15 skipped); 81 cfg sites (53 test items, 11 test
mods, 17 not(test) forks = the brief's 17); 9 s.
- real rewrite: 403 paths, 18.6 s, counts identical to the dry run line for
line; 334 of 351 changed .rs files rustfmt-stable (the 17 others are the
base-unstable fragments); no `crate::<moved>` left in tui, no
`codewhale_tui::<moved>` in cli; module_graph.py --check refuses this
premature move with 43 residue edges (it is the closedness gate working).
- closed module (remote_setup) real move: module_graph --check PASS with the
unchanged baseline, check-blocking-calls-budget PASS (543 sites, 165 files;
the sweep re-keyed the path-keyed JSON), dead-code budget PASS 209,
check-provider-registry PASS, G11 unresolved 0.
- guards on the scratch repo: change between plan and write -> exit 3 nothing
written; change at write 201 of 403 -> exit 4 and `git status` back to only
the concurrent edit; SIGTERM at 201 of 403 -> exit 4, status empty; dirty
file and a file touched < 15 min -> exit 5; no .git -> exit 2.
- widen.py: SIGTERM and SIGHUP while a compile round runs -> exit 130 and
crates/runtime/Cargo.toml back to `[lints] workspace = true`; a capped
manifest from an earlier run is restored at start; with a fake cargo JSON
stream the E0603 widening, --prepass and the unused-import removal applied.
- reexports: 226 config names (213 via models) -> 1,109 direct lines in 142
files and 68 grouped lists in 60 files outside the cluster, 166 files; the
ProviderKind figure matches the plan exactly (339 lines / 95 files); the
plan's 1,189 / 190 and 89 / 75 are not reproduced and likely include the
cluster's own files; module_graph --check unchanged afterwards.
- preflight on the cluster: residue 8 = lib.rs:20230 include_str, tests/
integration/main.rs:16 (path + relative), 5 skip-listed mentions in
crates/config (catalog_corrections.json:4584 and models_dev.bundled.json:11769
key a source receipt `crates/tui/src/config/models.rs`), vendor picker.rs.
- testsplit: config/tests.rs 407 tests (378 move, 29 stay), scope_tests.rs 8
(0 move: needs provider_readiness/fleet), whole cluster 687 tests (649 move,
38 stay); count_lines [M]/[F]: tui 634,761/943,686, cli 20,204/33,761,
runtime 9,526/15,121, config 29,415/35,830, app-server 7,572/13,095.
Not proven: no cargo build of any rewritten tree; widen.py's compile loop ran
only against a fake cargo; --apply-cfg flipping is planned, not applied or
compiled; Windows/FreeBSD cfg code; whether any test reads the source-receipt
keys in crates/config assets as paths; the re-export resolver does not
rewrite relative `config::NAME` uses (none found at this base); SCC/edge
counts were not re-run after a rewrite (the cargo-free G2 stays Lane A's).
Evidence: /Volumes/VIXinSSD/CW/artifacts/crate-split-w0-20261025/proof-w0.log
and guard-demos.log. Dirty work of other writers in the checkout was left
untouched (about 20 tui/core/models files, window W1).
Co-Authored-By: Claude Sonnet 5.5 <[email protected]>