refactor: Decompose program-lib.sh into portable modules (#35) - #50
refactor: Decompose program-lib.sh into portable modules (#35)#50rubambiza wants to merge 11 commits into
Conversation
Design for splitting the ~985-line program-lib.sh into four balanced, self-contained, bash-3.2-safe modules (core/github-api/fork/org) behind an unchanged aggregator entrypoint, plus a portability contract and a Linux+macOS CI matrix. Pure refactor; positioned so the agent-skills vendor copies (#2149) get clean modular files. Module boundaries are data-driven (function-usage audit across the 9 consumers); flat layout matches how agent-skills already vendors scripts; self-sourcing modules with load-once guards keep subset-copying safe. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Captures the 23-function public surface of program-lib.sh so the decomposition refactor (rossoctl#35) can prove no function is lost or renamed as functions move into modules. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Move workspace/date/json/diff/report helpers into scripts/core.sh with a load-once guard; program-lib.sh sources it. Verbatim move, no behavior change; inventory test green. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Move the gh backoff wrapper and issue read/close/pr-check helpers into scripts/github-api.sh with a load-once guard. Verbatim move, no behavior change; inventory test green. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Move fork/PR creation, issue-field validation, and candidate scoring into scripts/fork.sh with a load-once guard. These helpers reference no other module (gh is called directly), so fork.sh is guard-only. Verbatim move, no behavior change; inventory test green. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Move org-identity + core-repo helpers into scripts/org.sh (guard-only, no cross-module deps). program-lib.sh is now a thin aggregator sourcing the four modules; the entrypoint the 9 consumers use is unchanged. Also splits an unparseable inline shellcheck directive on canonical_repo_for_dir into a comment line plus a clean directive (same effect). Verbatim move, no behavior change; inventory test + full suite green. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Proves each library module sources on its own (self-sourcing guards + relative dep resolution) and defines its functions -- the property that makes a module safe to vendor as a subset. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
link-health-scanner used an associative array (ISSUE_COUNTS), which fails on macOS's default bash 3.2. Replace with a newline-delimited repo<TAB>count accumulator plus _ic_get/_ic_incr helpers. Verified count parity (3/1/0) and no bash-4 constructs remain; no output change. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Add a test workflow with an ubuntu-latest + macos-latest matrix. The macOS leg exercises BSD coreutils and the bash-3.2 floor, enforcing the portability contract (catches declare -A / mapfile / GNU-only flags). shellcheck targets the five library modules at warning severity; broader consumer-script linting is tracked as a separate follow-up. Pins actions/checkout to a SHA with least-privilege permissions. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
Add a README section describing the four library modules, the aggregator entrypoint, and the bash-3.2+ portability contract enforced by CI. Update the shared-helpers note to point at the module split. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
clawgenti
left a comment
There was a problem hiding this comment.
Decomposes program-lib.sh into four focused, vendorable modules (core.sh, github-api.sh, fork.sh, org.sh) behind a thin aggregator, fixes the declare -A bash-4 portability blocker in link-health-scanner.sh, and adds a matrix CI workflow (Linux + macOS) with a function-inventory equivalence test. The design is well-executed with load-once guards, correct $BASH_SOURCE-relative dep resolution, and thorough inline documentation.
Finding: fork.sh is missing the self-sourcing of core.sh and github-api.sh specified in the design document (§ "Dependency loading", lines 89–97), which shows those . source lines as part of the module header. The current implementation works because fork.sh’s functions only invoke gh/git directly rather than calling gh_with_backoff or other library helpers — but the discrepancy between spec and implementation may cause confusion for anyone vendoring fork.sh under the assumption it is self-contained with respect to its stated deps. Either add the source lines (matching the spec) or update the design doc to reflect that fork.sh intentionally has no intra-library dependencies.
Reviewed by clawgenti using the github-pr-review skill
| # ## Portability | ||
| # Targets bash 3.2+ (macOS default) through modern bash. | ||
| [ -n "${_FORK_SH_LOADED:-}" ] && return | ||
| _FORK_SH_LOADED=1 |
There was a problem hiding this comment.
The design doc (§ Dependency loading) shows fork.sh sourcing core.sh and github-api.sh here:
_LIB_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
. "$_LIB_DIR/core.sh"
. "$_LIB_DIR/github-api.sh"The current implementation omits these — which is fine because none of fork.sh’s functions actually call gh_with_backoff or other library helpers (they use raw gh/git directly). But it leaves the spec and the code out of sync. Suggest either adding the source lines to match the spec, or adding a # No intra-library deps comment so the omission is intentional and documented for vendoring consumers.
The design doc §"Dependency loading" showed fork.sh (and by category github-api.sh, org.sh) self-sourcing core.sh/github-api.sh. The implementation deliberately deviated: empirical measurement of each module's function bodies found no cross-module helper calls, so all four modules are guard-only with no source lines. Update the spec to reflect the implemented, measured reality — modules self-source only dependencies they actually reference, and the current split has none — so a vendored fork.sh is genuinely self-contained and no module declares an unused dependency. Resolves the PR rossoctl#50 review finding. Assisted-By: Claude Code (Anthropic AI) <noreply@anthropic.com> Signed-off-by: Gloire Rubambiza <gloire@ibm.com>
|
Thanks — good catch on the spec/implementation drift. Resolved by updating the design doc (commit a58ea67) rather than adding the source lines. The implementation is intentional: I verified empirically that no module (github-api.sh, fork.sh, org.sh) calls a core.sh or github-api.sh helper — every function invokes gh/git/jq/builtins directly — so all four modules are guard-only. Adding unused source lines would declare dependencies the code does not have and cut against the vendoring goal (a copied fork.sh should be genuinely self-contained). The spec now states the load list is driven by measured call-sites, documents the self-source pattern to add only if a real cross-module call is later introduced, and notes the current split has no intra-library dependencies. Assisted-By: Claude Code |
clawgenti
left a comment
There was a problem hiding this comment.
Solid refactor: clean module split with load-once guards, bash-3.2 portability fix for declare -A, equivalence verified by function-inventory + smoke tests, and CI green on both Linux and macOS.
docs/specs/…(latest commit): The CI section says "runshellcheckoverscripts/*.sh" but.github/workflows/tests.ymltargets only the five library modules (scripts/core.sh scripts/github-api.sh scripts/fork.sh scripts/org.sh scripts/program-lib.sh), not the full glob. Minor doc/code discrepancy — the narrower scope is intentional (consumer-script SC2034 tracked in #52), so the spec should say "library modules" rather thanscripts/*.sh.
Reviewed by clawgenti using the github-pr-review skill
|
|
||
| - **Matrix:** `ubuntu-latest` + `macos-latest`. The macOS runner exercises BSD `date`/`sed`/`grep` | ||
| and an older-bash environment, catching the `declare -A` class of defect automatically. | ||
| - **Steps:** run every `tests/*.sh`; run `shellcheck` over `scripts/*.sh`. |
There was a problem hiding this comment.
The CI step shellchecks only the five library modules (not scripts/*.sh) — consumer-script linting is intentionally deferred to #52. Consider updating this line to say "library modules" rather than scripts/*.sh to match what the workflow actually does.
What
Decompose the ~985-line
scripts/program-lib.shshell library into four flat, self-contained modules behind an unchanged aggregator entrypoint. Pure refactor — no behavior change.core.shgithub-api.shghwrapper, issue read/close/PR-checkfork.shorg.shprogram-lib.shEach module has a load-once guard and self-sources only the dependencies it actually references (verified:
github-api.sh,fork.sh, andorg.shreference no other module, so they are guard-only; only the aggregator sources all four). The 9 consumer scripts keepsource "$SCRIPT_DIR/program-lib.sh"unchanged.Why
The monolith mixes unrelated concerns and forces wholesale copying when the scripts are vendored per-skill (the Agent Spec does not support referencing external scripts). Focused modules are easier to read, review, and vendor as a subset.
Also in this PR
link-health-scanner.shuseddeclare -A ISSUE_COUNTS(bash 4), which fails on macOS's default bash 3.2. Replaced with a newline-delimitedrepo<TAB>countaccumulator. No output change..github/workflows/tests.ymlruns the test suite onubuntu-latest+macos-latest(the macOS leg exercises BSD coreutils and the bash-3.2 floor) and shellchecks the library modules.Equivalence verification
tests/test-lib-inventory.sh): the 23-function public surface is unchanged — none lost or renamed.tests/test-lib-modules.sh): each module sources on its own with its functions defined.ORG=rossoctl, extract-broken-links, parse-diff-map).# shellcheck disable=SC2086 -- commentoncanonical_repo_for_dirinto a comment line plus a clean directive (same effect).Notes
--severity=warning. Broader consumer-script linting (a pre-existingSC2034arg-parsing idiom) is tracked as a separate follow-up (ci: Make consumer scripts shellcheck-clean and widen lint scope #52), out of scope here per scope discipline.Fixes #35
Assisted-By: Claude Code