Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); refactor(coverage): split src/coverage.sh into a src/coverage/ module by Chemaclass · Pull Request #928 · TypedDevs/bashunit · GitHub
Skip to content

refactor(coverage): split src/coverage.sh into a src/coverage/ module - #928

Merged
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module
Jul 31, 2026
Merged

refactor(coverage): split src/coverage.sh into a src/coverage/ module#928
Chemaclass merged 2 commits into
mainfrom
refactor/925-split-coverage-module

Conversation

@Chemaclass

Copy link
Copy Markdown
Member

🤔 Background

Related #925

src/coverage.sh was 2548 lines and 59 functions — the largest file in src/. Same treatment as #927. The responsibility map was posted on #925 and agreed before any code moved.

💡 Changes

  • Twelve cohesive modules behind a thin aggregator, layered leaves-first and acyclic: config · paths · lines · functions → engine · stats · branches → report_text · report_lcov · report_html → html_index · html_file. Namespace unchanged, no function renamed, bashunit:76 keeps its single source.
  • .editorconfig decision (narrow, deliberate): the old whole-file max_line_length = unset is replaced by the same rule scoped to html_index.sh + html_file.sh, which hold 50 of the 56 over-120 lines. Every other coverage module now honours the global 120 — a net tightening. The remaining 6 lines were reflowed by hand, and the two building shared strings (_NONEXEC_PATTERN, _XTRACE_PS4) were verified to evaluate byte-identically.
  • .gitignore fix, required by the move:coverage/ was unanchored, so it matched the new src/coverage/ source directory and silently excluded all twelve modules from git. Anchored to /coverage/.
  • Dropped the stale file-wide # shellcheck disable=SC2094 — shellcheck does not raise it on the original file either.
  • branches.sh stays one file on purpose: the _branch_* helpers mutate extract_branches's locals via dynamic scoping.

✅ Verification

Relocation proven by comparing the non-blank line multiset before/after — only differences are the 6 reflowed lines and the stale directive. 59 functions before and after; aggregator holds zero non-source statements.

Green: sequential · --parallel · --parallel --simple --strict · --coverage --parallel · make sa · make lint · fork-budget acceptance tests · bash build.sh bin -v✅ Build verified ✅.

Pre-existing and not fixed here: --coverage reports an arithmetic error from the lcov function-record loop on an empty function span. Reproduces identically on main, so it stays out of a pure-move PR.

No CHANGELOG entry — internal, no user-visible behaviour change.

src/coverage.sh was 2548 lines and 59 functions, the largest file in src/.
Apply the same treatment as #927: twelve cohesive modules behind a thin
aggregator, layered leaves-first and acyclic.
config · paths · lines · functions → engine · stats · branches
→ report_text · report_lcov · report_html → html_index · html_file
The namespace is unchanged, no function is renamed, and bashunit:76 keeps its
single source line. The responsibility map was posted on #925 and agreed before
any code moved.
This is a relocation. Verified by comparing the non-blank line multiset of the
twelve new files against the original: the only differences are the six lines
reflowed below and one stale directive. Function count is 59 before and after,
and the aggregator holds zero statements that are not `source` or comment.
branches.sh is deliberately one file: the _branch_* helpers mutate
extract_branches's locals through dynamic scoping, so they cannot be separated.
The justification comment travels with them.
.editorconfig: the old whole-file `max_line_length = unset` is replaced by the
same rule scoped to html_index.sh and html_file.sh, which hold 50 of the 56
lines over 120 chars. Every other coverage module now honours the global limit,
so this is a net tightening rather than a blanket exemption. The remaining six
lines were reflowed by hand; the two that build a shared string
(_NONEXEC_PATTERN, _XTRACE_PS4) were checked to evaluate byte-identically.
Two fixes that the move made unavoidable:
- .gitignore had an unanchored `coverage/`, which matched the new src/coverage/
source directory and silently excluded all twelve modules from git. Anchored
to /coverage/, which is where the generated report actually lives.
- The file-wide `# shellcheck disable=SC2094` was dropped. It is stale:
shellcheck does not raise SC2094 on the original file either, and `make sa`
passes without it.
Not fixed, and pre-existing: `--coverage` reports an arithmetic error from the
lcov function-record loop when a function span is empty. It reproduces
identically on main, so it stays out of a pure-move PR.
Verified: sequential, --parallel, --parallel --simple --strict,
--coverage --parallel, make sa, make lint, fork-budget acceptance tests, and
`bash build.sh bin -v` printing "Build verified".
Closes#925
@ChemaclassChemaclass added the refactoring Refactoring or cleaning related label Jul 31, 2026
@ChemaclassChemaclass self-assigned this Jul 31, 2026
CI runs ShellCheck per file without -x, so it cannot follow `source`. In the
2548-line monolith it saw `line_hits` used in compute_file_coverage and stayed
quiet about the identically named local in report_lcov; once the two functions
landed in different files it correctly reported SC2034.
The variable is genuinely dead: report_lcov declares it in its `local` list and
never reads or writes it. Removing the declaration rather than suppressing the
warning -- a `# shellcheck disable` here would only preserve dead code.
Verified with CI's exact invocation (per file, no -x, same SHELLCHECK_OPTS)
across every tracked shell file.
@Chemaclass
Chemaclass merged commit 7eecc45 into mainJul 31, 2026
37 checks passed
@Chemaclass
Chemaclass deleted the refactor/925-split-coverage-module branch July 31, 2026 21:26
Chemaclass added a commit that referenced this pull request Jul 31, 2026
Four blocks a session can reconstruct on its own, two of which had already
drifted out of date:
- the architecture tree, which is `ls` output and still showed a flat `src/*.sh`
after #927 and #928 added src/runner/, src/coverage/ and src/dev/
- the skills table, which duplicates the skill listing already injected into
every session and had gone stale at 9 of 11 entries (missing /review and
/gh-issues, the most-used skill in the repo)
- Common Commands, whose contents are in the Makefile and whose one non-obvious
note -- that `make lint` is the formatting authority -- is already stated under
Quality Standards
- the Path-Scoped Guidelines bullet lists, which restate bash-style.md and
testing.md; the line explaining how the rules auto-load stays
Everything not derivable is kept: the Bash 3.0 prohibited-features list, the
shfmt/.editorconfig gotcha, the test-pattern pointers, Guardrails, Definition of
Done, Commit Message Format and Prohibited Actions.
6638 -> 4290 chars, roughly 590 fewer resident tokens in every session.
Chemaclass added a commit that referenced this pull request Jul 31, 2026
ADR-010 was written from the runner split (#924) and merged before the coverage
split (#925/#928) landed, so it is missing both an option and the constraints
that second application uncovered.
Considered Options gains the one it never weighed: an `index.sh` inside the
module directory, rather than an aggregator beside it. Decision stays "beside",
now with the reason stated -- `src/assertions.sh` has aggregated the flat
`assert_*.sh` files that way since before module directories existed, so beside
keeps one aggregator convention in src/ instead of two. The case for inside is
recorded rather than dismissed, with a note to revisit it only by amending this
ADR, never per module.
The build section now states a measured fact instead of an assertion:
`build::process_file` is a depth-first walk of `source` statements, not of
directories, so nesting depth and aggregator placement are invisible to it.
Verified with a throwaway three-level module -- all bodies embedded in DFS
order, no `source` lines left in the artifact.
Adds the five traps a split has to respect, four of them found the hard way in
#928: order-dependent file-scope initialisers, an unanchored .gitignore pattern
that silently excluded twelve new files from git, CI's per-file ShellCheck
surfacing what a monolith hid, per-file .editorconfig rules lost by the split,
and inseparable dynamic-scope helper groups. Plus the line-multiset check that
proves a split is a pure relocation.
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

refactoringRefactoring or cleaning related

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant

@Chemaclass