Repository navigation
Description
Step under #1020 ("ratchet one-function-per-file from max_lines = 100 toward the default of 1"), which prescribes one PR per step. This step: 24 -> 16. Predecessor: #1033 / #1035 took 100 -> 24 for free.
This step is not free. At 16 the gate reports 5 violations across two scan roots, each of which is a real file split: a new module, its colocated unit test, imports updated at every call site, and the coverage / mutation / co-change fallout that follows.
Enumeration
Against origin/main at 2d69909, with testing-conventions@0.0.111 pinned (per #1020's closing note -- a bare npx testing-conventions resolves engine-aware and can silently pick an older release, #1018). Method: clean throwaway clone, all six max_lines occurrences rewritten to 16, then one run per scan root:
npx -y testing-conventions@0.0.111 unit one-function-per-file \
--language <lang> --config <config> <source>| Scan root | Language | Config it reads | Violations at 16 |
|---|---|---|---|
internals/checks/src |
python | internals/checks/testing-conventions.toml |
4 |
packages/ts/src |
typescript | packages/ts/testing-conventions.toml |
1 |
packages/python/dirsql |
python | packages/python/testing-conventions.toml |
0 |
internals/distcheck/src |
python | testing-conventions.toml (root default) |
0 |
plugins/dirsql-plugin-embeddings/src |
python | plugins/dirsql-plugin-embeddings/testing-conventions.toml |
0 |
The five violations, verbatim:
internals/checks/src/checks/preflight/gate.py:137: `prepare` runs 20 lines, and `invocation` already holds this file
internals/checks/src/checks/preflight/gate.py:167: `run` runs 24 lines, and `invocation` already holds this file
internals/checks/src/checks/preflight/matrix.py:141: `discovered` runs 23 lines, and `parse_gate_matrix` already holds this file
internals/checks/src/checks/release_globs/decide.py:125: `unprechecked` runs 21 lines, and `glob_problems` already holds this file
packages/ts/src/cli/resolve-config-extensions.ts:56: `withResolvedExtensions` runs 21 lines, and `configPathsFromArgv` already holds this file
This confirms the prior sweep recorded in #1033 and extends it: the three clean roots (packages/python/dirsql floor 14, internals/distcheck/src floor 13, plugins/.../src floor 6) are still clean at 16 on current main. The plugin is clean, so this step does not touch plugins/dirsql-plugin-embeddings/ and does not collide with #1034.
The ladder, 24 down to 16
Swept every intermediate threshold in the same harness, so the cost of each split is known rather than guessed:
| Threshold | New violation that appears |
|---|---|
| 23 | preflight/gate.py run (24 lines) |
| 20 | release_globs/decide.py unprechecked (21 lines) |
| 19 | preflight/gate.py prepare (20 lines) |
| 18 | cli/resolve-config-extensions.ts withResolvedExtensions (21 lines) |
| 17 | preflight/matrix.py discovered (23 lines); parse_gate_matrix is exactly 17 lines, which is why this pair only fires here |
So the two roots are cleanly separable, and the ceiling steps once per root:
- Split the four
internals/checksfunctions -> repo green at 19 (packages/tsis then the binding constraint). - Split the one
packages/tsfunction -> repo green at 16.
Size: L, not M. Proposed split: two PRs.
One PR carrying all five splits would touch roughly 19 files -- 5 new source modules, 5 new colocated test files, 4 trimmed sources, surgery on 4 existing test files (gate_test.py alone is 339 lines and covers all three functions in play), 3 call-site files, and 5 configs. That is past M by review surface, and #1020 explicitly says file splits are "worth reviewing on their merits, not as collateral of a config bump".
PR 1 -- internals/checks splits, ceiling 24 -> 19
preflight/gate.py: extractrunandprepareto their own modules. Callers:preflight/cli.py(from .gate import default_runner, read_e2e, run), plusartifact_completeness/cli.pyanddeclared_deps/cli.py, which import an unrelated.gate.runfrom their own packages and are untouched.preflight/matrix.py: extractdiscovered. Note itsNoGateMatrixmessage hard-codesinternals/checks/src/checks/preflight/matrix.pyas the place to editREUSABLE;REUSABLEstays inmatrix.py, so that text stays correct, but check it after the move.release_globs/decide.py: extractunprechecked. Callerrelease_globs/gate.pyuses an absolutefrom checks.release_globs.decide import ...; the new import should be relative per AGENTS.md.- Then set
max_lines = 19in all six occurrences. - No changelog fragment (repo tooling, not package source), no e2e attestation, no PARITY change.
PR 2 -- packages/ts split, ceiling 19 -> 16
cli/resolve-config-extensions.ts: both functions exceed 16, so either moves. Extracting the private helperconfigPathsFromArgvintocli/config-paths-from-argv.ts(kebab-case) withcli/config-paths-from-argv.test.tsis the smaller change; it requires exporting it, and the argv-scanning cases in the existing 228-lineresolve-config-extensions.test.tsmove with it.- Only call site of
withResolvedExtensionsiscli/main.ts, unchanged either way. - Then set
max_lines = 16in all six occurrences. - This is
packages/ts/src, so it needs apackages/ts/changelog.d/fragment and, per thee2e-verifyscope, apackages/ts/e2e-attestations/<branch>.jsonreceipt (just e2e-attest-ts). Not a migration: the extracted symbol is private today and the public surface is unchanged.
Sequential or stacked both work; stacked (PR 2 based on PR 1) avoids idling, and the diff-scoped gates measure each layer against its own base.
Files to edit (both PRs, same six lines)
testing-conventions.toml-- two occurrences,[python]and[typescript]packages/python/testing-conventions.tomlpackages/ts/testing-conventions.tomlinternals/checks/testing-conventions.tomlplugins/dirsql-plugin-embeddings/testing-conventions.toml
internals/distcheck passes no config: to the reusable workflow, so it reads the root file -- that is why the root [python] table cannot be skipped.
Rust stays out of scope
Unchanged from #1020 and #1033: upstream defaults the rule off for Rust because a Rust file is a module. Opting Rust in is a separate design call, not a step on this ratchet.
Notes for whoever picks this up
- Exemptions are not an option. The repo's exemption count for this gate is zero and stays there; every violation is fixed by moving the function.
- Re-verify against current
mainbefore editing -- the margins here are one and two lines wide. - Gates that will judge the splits: 100% unit-coverage floor (python and typescript),
unit mutationon changed lines (fix survivors with a new assertion, never by weakening a test),unit lintisolation (each new unit test mocks every collaborator), and the co-change check. Runjust preflightbefore pushing.
Activity