Skip to content

refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

Description

@Chemaclass

Summary

After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
and well named; as a listing they read as leftovers, and the root stops communicating
structure. Directories convey layering, a flat pile does not.

Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

Measured on main (8f2a1c3).

Groups

Each is backed by a real dependency cluster, not name similarity.

src/system/ — what this machine has

check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
clock.sh (203 → check_os, dependencies, math) — 383 lines

The true bottom layer. Nothing in it touches test state, config, console or runner.

src/util/ — pure computation

str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

One outbound edge (mathdependencies) which just places util above system. Acyclic.

src/api/ — the test-authoring surface

globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
188 lines

What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
set_test_title, and the custom-assert facade (assert_that, assert_once,
assertion_failed).

Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
2300 lines has earned its own module. The api/index.sh comment must say so, or the split
looks arbitrary.

src/config/ — run-scoped configuration and persisted state

env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
env.sh's 33 is_* predicates.

Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
the build for.

env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
concern). It just moves.

src/benchmark/ — the weakest of the set, stated plainly

benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

It is a distinct feature — runner/bench.sh is the file/function loop, this is the
implementation (annotation parsing, running, result printing). But three files of ~60 lines is
thin, and this is the one group where "everything in modules" costs ceremony to buy
consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

main.sh

Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
out of scope here
and should follow this work, not precede it — see the note on #948.

Process

One group per PR, same as #931 and #940:

  1. Post the mapping on this issue before moving code.
  2. git mv so renames are recorded as renames.
  3. index.sh aggregator, entrypoint source line updated.
  4. Grep for hardcoded paths before committing.

Suggested order: systemutilapiconfigbenchmark. System first because
everything sits on it; api third because its naming deserves a second look before it is
cemented.

Constraints

All of ADR-010's, every one of which has drawn blood at least once:

Verification, per PR

Relocation proof: 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 ✅.

Acceptance criteria (per group PR)

  • Mapping posted here before code moves
  • git diff shows only relocations — no renamed functions, no changed logic
  • Aggregator is src/<group>/index.sh, only source lines and comments
  • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
  • git check-ignore -v confirms the directory is not ignored
  • All suites green, including bash build.sh bin -v
  • .claude/rules/architecture-map.md updated
  • No CHANGELOG entry — internal, no user-visible behaviour change

Do not

Metadata

Metadata

Assignees

No one assigned

    Labels

    refactoringRefactoring or cleaning related

    Type

    No type

    Projects

    • Status
      Done

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
    Skip to content

    refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

    Description

    @Chemaclass

    Summary

    After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
    and well named; as a listing they read as leftovers, and the root stops communicating
    structure. Directories convey layering, a flat pile does not.

    Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

    Measured on main (8f2a1c3).

    Groups

    Each is backed by a real dependency cluster, not name similarity.

    src/system/ — what this machine has

    check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
    clock.sh (203 → check_os, dependencies, math) — 383 lines

    The true bottom layer. Nothing in it touches test state, config, console or runner.

    src/util/ — pure computation

    str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

    One outbound edge (mathdependencies) which just places util above system. Acyclic.

    src/api/ — the test-authoring surface

    globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
    188 lines

    What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
    set_test_title, and the custom-assert facade (assert_that, assert_once,
    assertion_failed).

    Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
    2300 lines has earned its own module. The api/index.sh comment must say so, or the split
    looks arbitrary.

    src/config/ — run-scoped configuration and persisted state

    env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

    Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
    rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
    across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
    env.sh's 33 is_* predicates.

    Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
    and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
    the build for.

    env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
    concern). It just moves.

    src/benchmark/ — the weakest of the set, stated plainly

    benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

    It is a distinct feature — runner/bench.sh is the file/function loop, this is the
    implementation (annotation parsing, running, result printing). But three files of ~60 lines is
    thin, and this is the one group where "everything in modules" costs ceremony to buy
    consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

    main.sh

    Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
    or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
    out of scope here
    and should follow this work, not precede it — see the note on #948.

    Process

    One group per PR, same as #931 and #940:

    1. Post the mapping on this issue before moving code.
    2. git mv so renames are recorded as renames.
    3. index.sh aggregator, entrypoint source line updated.
    4. Grep for hardcoded paths before committing.

    Suggested order: systemutilapiconfigbenchmark. System first because
    everything sits on it; api third because its naming deserves a second look before it is
    cemented.

    Constraints

    All of ADR-010's, every one of which has drawn blood at least once:

    Verification, per PR

    Relocation proof: 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 ✅.

    Acceptance criteria (per group PR)

    • Mapping posted here before code moves
    • git diff shows only relocations — no renamed functions, no changed logic
    • Aggregator is src/<group>/index.sh, only source lines and comments
    • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
    • git check-ignore -v confirms the directory is not ignored
    • All suites green, including bash build.sh bin -v
    • .claude/rules/architecture-map.md updated
    • No CHANGELOG entry — internal, no user-visible behaviour change

    Do not

    Metadata

    Metadata

    Assignees

    No one assigned

      Labels

      refactoringRefactoring or cleaning related

      Type

      No type

      Projects

      • Status
        Done

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
      Skip to content

      refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

      Description

      @Chemaclass

      Summary

      After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
      and well named; as a listing they read as leftovers, and the root stops communicating
      structure. Directories convey layering, a flat pile does not.

      Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

      Measured on main (8f2a1c3).

      Groups

      Each is backed by a real dependency cluster, not name similarity.

      src/system/ — what this machine has

      check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
      clock.sh (203 → check_os, dependencies, math) — 383 lines

      The true bottom layer. Nothing in it touches test state, config, console or runner.

      src/util/ — pure computation

      str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

      One outbound edge (mathdependencies) which just places util above system. Acyclic.

      src/api/ — the test-authoring surface

      globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
      188 lines

      What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
      set_test_title, and the custom-assert facade (assert_that, assert_once,
      assertion_failed).

      Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
      2300 lines has earned its own module. The api/index.sh comment must say so, or the split
      looks arbitrary.

      src/config/ — run-scoped configuration and persisted state

      env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

      Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
      rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
      across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
      env.sh's 33 is_* predicates.

      Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
      and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
      the build for.

      env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
      concern). It just moves.

      src/benchmark/ — the weakest of the set, stated plainly

      benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

      It is a distinct feature — runner/bench.sh is the file/function loop, this is the
      implementation (annotation parsing, running, result printing). But three files of ~60 lines is
      thin, and this is the one group where "everything in modules" costs ceremony to buy
      consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

      main.sh

      Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
      or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
      out of scope here
      and should follow this work, not precede it — see the note on #948.

      Process

      One group per PR, same as #931 and #940:

      1. Post the mapping on this issue before moving code.
      2. git mv so renames are recorded as renames.
      3. index.sh aggregator, entrypoint source line updated.
      4. Grep for hardcoded paths before committing.

      Suggested order: systemutilapiconfigbenchmark. System first because
      everything sits on it; api third because its naming deserves a second look before it is
      cemented.

      Constraints

      All of ADR-010's, every one of which has drawn blood at least once:

      Verification, per PR

      Relocation proof: 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 ✅.

      Acceptance criteria (per group PR)

      • Mapping posted here before code moves
      • git diff shows only relocations — no renamed functions, no changed logic
      • Aggregator is src/<group>/index.sh, only source lines and comments
      • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
      • git check-ignore -v confirms the directory is not ignored
      • All suites green, including bash build.sh bin -v
      • .claude/rules/architecture-map.md updated
      • No CHANGELOG entry — internal, no user-visible behaviour change

      Do not

      Metadata

      Metadata

      Assignees

      No one assigned

        Labels

        refactoringRefactoring or cleaning related

        Type

        No type

        Projects

        • Status
          Done

        Milestone

        No milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
        Skip to content

        refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

        Description

        @Chemaclass

        Summary

        After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
        and well named; as a listing they read as leftovers, and the root stops communicating
        structure. Directories convey layering, a flat pile does not.

        Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

        Measured on main (8f2a1c3).

        Groups

        Each is backed by a real dependency cluster, not name similarity.

        src/system/ — what this machine has

        check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
        clock.sh (203 → check_os, dependencies, math) — 383 lines

        The true bottom layer. Nothing in it touches test state, config, console or runner.

        src/util/ — pure computation

        str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

        One outbound edge (mathdependencies) which just places util above system. Acyclic.

        src/api/ — the test-authoring surface

        globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
        188 lines

        What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
        set_test_title, and the custom-assert facade (assert_that, assert_once,
        assertion_failed).

        Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
        2300 lines has earned its own module. The api/index.sh comment must say so, or the split
        looks arbitrary.

        src/config/ — run-scoped configuration and persisted state

        env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

        Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
        rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
        across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
        env.sh's 33 is_* predicates.

        Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
        and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
        the build for.

        env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
        concern). It just moves.

        src/benchmark/ — the weakest of the set, stated plainly

        benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

        It is a distinct feature — runner/bench.sh is the file/function loop, this is the
        implementation (annotation parsing, running, result printing). But three files of ~60 lines is
        thin, and this is the one group where "everything in modules" costs ceremony to buy
        consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

        main.sh

        Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
        or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
        out of scope here
        and should follow this work, not precede it — see the note on #948.

        Process

        One group per PR, same as #931 and #940:

        1. Post the mapping on this issue before moving code.
        2. git mv so renames are recorded as renames.
        3. index.sh aggregator, entrypoint source line updated.
        4. Grep for hardcoded paths before committing.

        Suggested order: systemutilapiconfigbenchmark. System first because
        everything sits on it; api third because its naming deserves a second look before it is
        cemented.

        Constraints

        All of ADR-010's, every one of which has drawn blood at least once:

        Verification, per PR

        Relocation proof: 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 ✅.

        Acceptance criteria (per group PR)

        • Mapping posted here before code moves
        • git diff shows only relocations — no renamed functions, no changed logic
        • Aggregator is src/<group>/index.sh, only source lines and comments
        • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
        • git check-ignore -v confirms the directory is not ignored
        • All suites green, including bash build.sh bin -v
        • .claude/rules/architecture-map.md updated
        • No CHANGELOG entry — internal, no user-visible behaviour change

        Do not

        Metadata

        Metadata

        Assignees

        No one assigned

          Labels

          refactoringRefactoring or cleaning related

          Type

          No type

          Projects

          • Status
            Done

          Milestone

          No milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
          Skip to content

          refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

          Description

          @Chemaclass

          Summary

          After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
          and well named; as a listing they read as leftovers, and the root stops communicating
          structure. Directories convey layering, a flat pile does not.

          Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

          Measured on main (8f2a1c3).

          Groups

          Each is backed by a real dependency cluster, not name similarity.

          src/system/ — what this machine has

          check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
          clock.sh (203 → check_os, dependencies, math) — 383 lines

          The true bottom layer. Nothing in it touches test state, config, console or runner.

          src/util/ — pure computation

          str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

          One outbound edge (mathdependencies) which just places util above system. Acyclic.

          src/api/ — the test-authoring surface

          globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
          188 lines

          What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
          set_test_title, and the custom-assert facade (assert_that, assert_once,
          assertion_failed).

          Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
          2300 lines has earned its own module. The api/index.sh comment must say so, or the split
          looks arbitrary.

          src/config/ — run-scoped configuration and persisted state

          env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

          Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
          rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
          across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
          env.sh's 33 is_* predicates.

          Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
          and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
          the build for.

          env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
          concern). It just moves.

          src/benchmark/ — the weakest of the set, stated plainly

          benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

          It is a distinct feature — runner/bench.sh is the file/function loop, this is the
          implementation (annotation parsing, running, result printing). But three files of ~60 lines is
          thin, and this is the one group where "everything in modules" costs ceremony to buy
          consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

          main.sh

          Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
          or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
          out of scope here
          and should follow this work, not precede it — see the note on #948.

          Process

          One group per PR, same as #931 and #940:

          1. Post the mapping on this issue before moving code.
          2. git mv so renames are recorded as renames.
          3. index.sh aggregator, entrypoint source line updated.
          4. Grep for hardcoded paths before committing.

          Suggested order: systemutilapiconfigbenchmark. System first because
          everything sits on it; api third because its naming deserves a second look before it is
          cemented.

          Constraints

          All of ADR-010's, every one of which has drawn blood at least once:

          Verification, per PR

          Relocation proof: 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 ✅.

          Acceptance criteria (per group PR)

          • Mapping posted here before code moves
          • git diff shows only relocations — no renamed functions, no changed logic
          • Aggregator is src/<group>/index.sh, only source lines and comments
          • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
          • git check-ignore -v confirms the directory is not ignored
          • All suites green, including bash build.sh bin -v
          • .claude/rules/architecture-map.md updated
          • No CHANGELOG entry — internal, no user-visible behaviour change

          Do not

          Metadata

          Metadata

          Assignees

          No one assigned

            Labels

            refactoringRefactoring or cleaning related

            Type

            No type

            Projects

            • Status
              Done

            Milestone

            No milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
            Skip to content

            refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

            Description

            @Chemaclass

            Summary

            After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
            and well named; as a listing they read as leftovers, and the root stops communicating
            structure. Directories convey layering, a flat pile does not.

            Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

            Measured on main (8f2a1c3).

            Groups

            Each is backed by a real dependency cluster, not name similarity.

            src/system/ — what this machine has

            check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
            clock.sh (203 → check_os, dependencies, math) — 383 lines

            The true bottom layer. Nothing in it touches test state, config, console or runner.

            src/util/ — pure computation

            str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

            One outbound edge (mathdependencies) which just places util above system. Acyclic.

            src/api/ — the test-authoring surface

            globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
            188 lines

            What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
            set_test_title, and the custom-assert facade (assert_that, assert_once,
            assertion_failed).

            Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
            2300 lines has earned its own module. The api/index.sh comment must say so, or the split
            looks arbitrary.

            src/config/ — run-scoped configuration and persisted state

            env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

            Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
            rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
            across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
            env.sh's 33 is_* predicates.

            Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
            and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
            the build for.

            env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
            concern). It just moves.

            src/benchmark/ — the weakest of the set, stated plainly

            benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

            It is a distinct feature — runner/bench.sh is the file/function loop, this is the
            implementation (annotation parsing, running, result printing). But three files of ~60 lines is
            thin, and this is the one group where "everything in modules" costs ceremony to buy
            consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

            main.sh

            Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
            or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
            out of scope here
            and should follow this work, not precede it — see the note on #948.

            Process

            One group per PR, same as #931 and #940:

            1. Post the mapping on this issue before moving code.
            2. git mv so renames are recorded as renames.
            3. index.sh aggregator, entrypoint source line updated.
            4. Grep for hardcoded paths before committing.

            Suggested order: systemutilapiconfigbenchmark. System first because
            everything sits on it; api third because its naming deserves a second look before it is
            cemented.

            Constraints

            All of ADR-010's, every one of which has drawn blood at least once:

            Verification, per PR

            Relocation proof: 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 ✅.

            Acceptance criteria (per group PR)

            • Mapping posted here before code moves
            • git diff shows only relocations — no renamed functions, no changed logic
            • Aggregator is src/<group>/index.sh, only source lines and comments
            • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
            • git check-ignore -v confirms the directory is not ignored
            • All suites green, including bash build.sh bin -v
            • .claude/rules/architecture-map.md updated
            • No CHANGELOG entry — internal, no user-visible behaviour change

            Do not

            Metadata

            Metadata

            Assignees

            No one assigned

              Labels

              refactoringRefactoring or cleaning related

              Type

              No type

              Projects

              • Status
                Done

              Milestone

              No milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions

              , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
              Skip to content

              refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

              Description

              @Chemaclass

              Summary

              After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
              and well named; as a listing they read as leftovers, and the root stops communicating
              structure. Directories convey layering, a flat pile does not.

              Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

              Measured on main (8f2a1c3).

              Groups

              Each is backed by a real dependency cluster, not name similarity.

              src/system/ — what this machine has

              check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
              clock.sh (203 → check_os, dependencies, math) — 383 lines

              The true bottom layer. Nothing in it touches test state, config, console or runner.

              src/util/ — pure computation

              str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

              One outbound edge (mathdependencies) which just places util above system. Acyclic.

              src/api/ — the test-authoring surface

              globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
              188 lines

              What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
              set_test_title, and the custom-assert facade (assert_that, assert_once,
              assertion_failed).

              Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
              2300 lines has earned its own module. The api/index.sh comment must say so, or the split
              looks arbitrary.

              src/config/ — run-scoped configuration and persisted state

              env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

              Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
              rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
              across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
              env.sh's 33 is_* predicates.

              Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
              and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
              the build for.

              env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
              concern). It just moves.

              src/benchmark/ — the weakest of the set, stated plainly

              benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

              It is a distinct feature — runner/bench.sh is the file/function loop, this is the
              implementation (annotation parsing, running, result printing). But three files of ~60 lines is
              thin, and this is the one group where "everything in modules" costs ceremony to buy
              consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

              main.sh

              Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
              or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
              out of scope here
              and should follow this work, not precede it — see the note on #948.

              Process

              One group per PR, same as #931 and #940:

              1. Post the mapping on this issue before moving code.
              2. git mv so renames are recorded as renames.
              3. index.sh aggregator, entrypoint source line updated.
              4. Grep for hardcoded paths before committing.

              Suggested order: systemutilapiconfigbenchmark. System first because
              everything sits on it; api third because its naming deserves a second look before it is
              cemented.

              Constraints

              All of ADR-010's, every one of which has drawn blood at least once:

              Verification, per PR

              Relocation proof: 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 ✅.

              Acceptance criteria (per group PR)

              • Mapping posted here before code moves
              • git diff shows only relocations — no renamed functions, no changed logic
              • Aggregator is src/<group>/index.sh, only source lines and comments
              • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
              • git check-ignore -v confirms the directory is not ignored
              • All suites green, including bash build.sh bin -v
              • .claude/rules/architecture-map.md updated
              • No CHANGELOG entry — internal, no user-visible behaviour change

              Do not

              Metadata

              Metadata

              Assignees

              No one assigned

                Labels

                refactoringRefactoring or cleaning related

                Type

                No type

                Projects

                • Status
                  Done

                Milestone

                No milestone

                Relationships

                None yet

                Development

                No branches or pull requests

                Issue actions

                , '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(src): group the remaining flat files into modules (system, util, api, config, benchmark) · Issue #949 · TypedDevs/bashunit · GitHub
                Skip to content

                refactor(src): group the remaining flat files into modules (system, util, api, config, benchmark) #949

                Description

                @Chemaclass

                Summary

                After #931 and #940, src/ is 11 modules plus 15 loose files. Individually each is small
                and well named; as a listing they read as leftovers, and the root stops communicating
                structure. Directories convey layering, a flat pile does not.

                Group the rest so that src/ holds modules and nothing else (bar main.sh, see below).

                Measured on main (8f2a1c3).

                Groups

                Each is backed by a real dependency cluster, not name similarity.

                src/system/ — what this machine has

                check_os.sh (107, leaf) · dependencies.sh (42, leaf) · io.sh (31 → dependencies) ·
                clock.sh (203 → check_os, dependencies, math) — 383 lines

                The true bottom layer. Nothing in it touches test state, config, console or runner.

                src/util/ — pure computation

                str.sh (156, leaf) · math.sh (104 → dependencies) — 260 lines

                One outbound edge (mathdependencies) which just places util above system. Acyclic.

                src/api/ — the test-authoring surface

                globals.sh (109, leaf) · skip_todo.sh (21) · test_title.sh (5) · bashunit.sh (53) —
                188 lines

                What a user's test file calls: temp_file/temp_dir/current_dir/data_set, skip/todo,
                set_test_title, and the custom-assert facade (assert_that, assert_once,
                assertion_failed).

                Assertions are the other half of this surface and stay in src/assert/, which at 11 files and
                2300 lines has earned its own module. The api/index.sh comment must say so, or the split
                looks arbitrary.

                src/config/ — run-scoped configuration and persisted state

                env.sh (754) · parallel.sh (63) · rerun.sh (124) — 941 lines

                Not a junk drawer; there are real internal edges. env → rerun, parallel → env, and
                rerun::is_enabled is called fromenv.sh. parallel::is_enabled is called from 11 files
                across runner, coverage, console and main — a cross-cutting mode predicate, the same shape as
                env.sh's 33 is_* predicates.

                Note src/parallel.sh is a different concern from src/runner/parallel.sh (job-slot waiting
                and the spinner, runner-internal). Same basename, different jobs — the collision #923 fixed
                the build for.

                env.sh is not split here; #931 recorded it as deliberately whole (51 functions but one
                concern). It just moves.

                src/benchmark/ — the weakest of the set, stated plainly

                benchmark.sh (191, 5 functions) → annotations.sh / run.sh / report.sh

                It is a distinct feature — runner/bench.sh is the file/function loop, this is the
                implementation (annotation parsing, running, result printing). But three files of ~60 lines is
                thin, and this is the one group where "everything in modules" costs ceremony to buy
                consistency. Accept it or leave benchmark.sh flat; decide deliberately and say which.

                main.sh

                Stays at the root of src/ for now. Splitting it is #948, and whether it becomes src/main/
                or remains a root dispatcher is that issue's call. Renaming it to index.sh is explicitly
                out of scope here
                and should follow this work, not precede it — see the note on #948.

                Process

                One group per PR, same as #931 and #940:

                1. Post the mapping on this issue before moving code.
                2. git mv so renames are recorded as renames.
                3. index.sh aggregator, entrypoint source line updated.
                4. Grep for hardcoded paths before committing.

                Suggested order: systemutilapiconfigbenchmark. System first because
                everything sits on it; api third because its naming deserves a second look before it is
                cemented.

                Constraints

                All of ADR-010's, every one of which has drawn blood at least once:

                Verification, per PR

                Relocation proof: 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 ✅.

                Acceptance criteria (per group PR)

                • Mapping posted here before code moves
                • git diff shows only relocations — no renamed functions, no changed logic
                • Aggregator is src/<group>/index.sh, only source lines and comments
                • Hardcoded path references updated (grep tests/ build.sh Makefile .github/ .editorconfig .gitignore)
                • git check-ignore -v confirms the directory is not ignored
                • All suites green, including bash build.sh bin -v
                • .claude/rules/architecture-map.md updated
                • No CHANGELOG entry — internal, no user-visible behaviour change

                Do not

                Metadata

                Metadata

                Assignees

                No one assigned

                  Labels

                  refactoringRefactoring or cleaning related

                  Type

                  No type

                  Projects

                  • Status
                    Done

                  Milestone

                  No milestone

                  Relationships

                  None yet

                  Development

                  No branches or pull requests

                  Issue actions