You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
src/main.sh is the last large flat file: 1427 lines, 24 functions. It was carried out of #931 deliberately — it is the dispatcher and the riskiest file in src/, so it deserves its
own issue rather than being that issue's tail end.
Measured on main (8f2a1c3), after #931 and #940 left src/ at 11 modules + 15 flat files.
What is in it
Function
Lines
Concern
cmd_test
485
flag parsing for the default subcommand
exec_tests
189
the run lifecycle: dispatch, aggregate, report, exit code
Not pre-decided. Produce the responsibility map first, as every module under #931 did — but
the seams that look real from the function list:
validation — the five *_or_exit / abort_* helpers, called by every parser
the assert subcommand — six functions, 213 lines, entirely self-contained
watch loop — two functions, already delegating to src/cli/watch.sh
flag parsing — cmd_*, which is most of the bulk
run lifecycle — exec_tests, exec_benchmarks, cleanup, handle_stop_on_failure_sync
Open question worth answering in the map: cmd_doc, cmd_init, cmd_learn, cmd_watch and cmd_upgrade are flag parsing for subcommands whose implementations already live in src/cli/. Should each parser move next to its implementation, or stay together as one
parsing layer? Both are defensible; decide deliberately and record it.
Why this one is riskier than the previous seven
It is the only caller of src/cli/*, src/learn/, src/benchmark.sh and the runner, so a
mistake here breaks every subcommand at once rather than one module.
bashunit sources src/main.shlast, after everything it dispatches to. Any split must
preserve that position.
cmd_test at 485 lines is a single while/case over ~60 flags. Cutting it means cutting
a case statement, which is the one shape where a mechanical line-range split is unsafe.
Constraints
All of ADR-010's, which by now have all drawn blood at least once:
Aggregator (src/main/index.sh) holds only source lines and comments.
Prove it is a relocation: the non-blank line multiset differs only by new shebangs, module
headers and source lines; function count unchanged; the built artifact's sorted code content
identical.
Then: ./bashunit tests/ · --parallel · --parallel --simple --strict · make sa · make lint · CI-mode ShellCheck · bash build.sh bin -v printing ✅ Build verified ✅.
Because this file owns dispatch, also smoke-test every subcommand end to end against the built binary, not just dev mode: test, assert, doc, init, learn, watch, upgrade, bench.
Acceptance criteria
Responsibility map posted here and agreed before any code moves
The cmd_*-next-to-implementation question answered explicitly
git diff shows only relocations — no renamed functions, no changed logic
src/main/index.sh holds only source lines and comments
bashunit still sources the module last
Every subcommand smoke-tested against the built binary
All suites above green, including bash build.sh bin -v
.claude/rules/architecture-map.md updated
No CHANGELOG entry — internal, no user-visible behaviour change
Do not
Do not split cmd_test's case statement across files
Do not rename functions
Do not move main.sh earlier in the entrypoint's source order
Summary
src/main.shis the last large flat file: 1427 lines, 24 functions. It was carried out of#931 deliberately — it is the dispatcher and the riskiest file in
src/, so it deserves itsown issue rather than being that issue's tail end.
Measured on
main(8f2a1c3), after #931 and #940 leftsrc/at 11 modules + 15 flat files.What is in it
cmd_testexec_testscmd_benchcmd_docexec_multi_assert·exec_assert·handle_assert_exit_code·cmd_assert·is_assertion_function·is_exit_code_assertionbashunit assertpathwatch_loop·watch_get_checksum--watchpolling loopvalidate_config_or_exit·set_shard_or_exit·require_non_negative_int_or_exit·require_writable_path_or_exit·abort_unknown_optioncmd_init·cmd_learn·cmd_watch·cmd_upgradesrc/cli/exec_benchmarks·cleanup·handle_stop_on_failure_syncProposed shape
Not pre-decided. Produce the responsibility map first, as every module under #931 did — but
the seams that look real from the function list:
*_or_exit/abort_*helpers, called by every parsersrc/cli/watch.shcmd_*, which is most of the bulkexec_tests,exec_benchmarks,cleanup,handle_stop_on_failure_syncOpen question worth answering in the map:
cmd_doc,cmd_init,cmd_learn,cmd_watchandcmd_upgradeare flag parsing for subcommands whose implementations already live insrc/cli/. Should each parser move next to its implementation, or stay together as oneparsing layer? Both are defensible; decide deliberately and record it.
Why this one is riskier than the previous seven
src/cli/*,src/learn/,src/benchmark.shand the runner, so amistake here breaks every subcommand at once rather than one module.
build::embed_docsmarkers and entrypointordering;
cmd_docsits directly on that path.bashunitsourcessrc/main.shlast, after everything it dispatches to. Any split mustpreserve that position.
cmd_testat 485 lines is a singlewhile/caseover ~60 flags. Cutting it means cuttinga
casestatement, which is the one shape where a mechanical line-range split is unsafe.Constraints
All of ADR-010's, which by now have all drawn blood at least once:
src/main/index.sh) holds onlysourcelines and comments.^}$brace — a braceinside a heredoc or a
casearm ends the segment early and silently splits a function(refactor(learn): split src/learn.sh into a src/learn/ module #938).
git check-ignore -vthe new directory before committing (refactor(coverage): split src/coverage.sh into a src/coverage/ module #928 hid twelve files).-x; reproduce locally withSHELLCHECK_OPTS="-e SC1091 -e SC2155 -e SC2016".# shellcheck disable=moves only to the files that need it — and check whetherit is stale first (refactor(coverage): split src/coverage.sh into a src/coverage/ module #928 and refactor(console): split results.sh into six single-purpose files #947 both found dead ones).
src/main.shpaths intests/,build.sh,Makefile,.github/,.editorconfig,.gitignorebefore committing. refactor(learn): split src/learn.sh into a src/learn/ module #938 and refactor(state): split src/state.sh into a src/state/ module #946 both shipped red or vacuoustests from a missed path; in refactor(state): split src/state.sh into a src/state/ module #946 a layering contract passed while checking nothing.
Verification
Prove it is a relocation: the non-blank line multiset differs only by new shebangs, module
headers and
sourcelines; function count unchanged; the built artifact's sorted code contentidentical.
Then:
./bashunit tests/·--parallel·--parallel --simple --strict·make sa·make lint· CI-mode ShellCheck ·bash build.sh bin -vprinting✅ Build verified ✅.Because this file owns dispatch, also smoke-test every subcommand end to end against the
built binary, not just dev mode:
test,assert,doc,init,learn,watch,upgrade,bench.Acceptance criteria
cmd_*-next-to-implementation question answered explicitlygit diffshows only relocations — no renamed functions, no changed logicsrc/main/index.shholds onlysourcelines and commentsbashunitstill sources the module lastbash build.sh bin -v.claude/rules/architecture-map.mdupdatedDo not
cmd_test'scasestatement across filesmain.shearlier in the entrypoint's source ordershfmt -w;make lintis the format gate