Scaffold: layout, test harness, shellcheck + actionlint CI #2

Closed
opened 2026-07-22 13:46:52 +00:00 by dan-claude-bot · 1 comment
dan-claude-bot commented 2026-07-22 13:46:52 +00:00 (Migrated from github.com)

Part of #1. Blocks everything else.

Goal

Turn the empty repo into a workspace the rest of the plan can land in: directory layout, a bash test harness, and a CI workflow that lints and tests every PR. After this issue, every subsequent issue is "add a tested component", never "figure out where things go".

Layout to create

.github/workflows/ci.yml   # ceremony's own CI (see below)
actions/                   # composite actions land here (empty for now, .gitkeep)
lib/                       # shared sourced bash libs (empty for now)
test/                      # the contract suite
test/harness.sh            # the check() harness (see below)
test/run.sh                # runs every test/*.test.sh, reports totals, exit 1 on any failure
docs/                      # CONSUMERS.md lands here later (#12)

Root README.md already exists; leave it — #12 rewrites it.

The test harness

Port the check pattern from box test/release.sh lines 14–36 into test/harness.sh as a sourced lib:

  • check <desc> <want_exit> <want_substr> <cmd...> — runs cmd, asserts exit code, asserts combined stdout+stderr contains the substring when non-empty; prints ok:/FAIL: and counts into PASS/FAIL globals.
  • summary — prints totals, exits 1 if any FAIL.
  • Deliberately no set -e in test files (the harness asserts on failing commands — same note as the source). set -u yes.
  • Everything offline: no network, no gh, real git only against repos the test constructs in mktemp -d.

Test files are test/<name>.test.sh, executable, each sourcing the harness. test/run.sh finds and runs them all.

CI (.github/workflows/ci.yml)

One job, ubuntu-latest:

  1. checkout (fetch-depth: 0 — the monotonic guard's tests and later self-guards need history semantics but more importantly this is the family convention for its own CI).
  2. shellcheck every *.sh in lib/, actions/, test/ (shellcheck -x so sourced libs resolve). Port the discipline from cast's shellcheck-all.sh: find files by shebang and extension, fail on any finding, and print the file list so an empty glob can't silently pass (the "a guard that quietly stops guarding" rule).
  3. actionlint on .github/workflows/*.yml and every actions/*/action.yml (install the released binary, pinned version).
  4. bash test/run.sh.

House conventions (write them into a short CONTRIBUTING.md)

  • Bash: set -euo pipefail in executables, set -u only in test files, mawk-compatible awk (no \x escapes — CI runners ship mawk, learned in box drill-recorded.sh history).
  • Every piece of logic is a file of its own so a test can drive it; workflows gather facts and call scripts. This is the source repos' stated discipline — quote it.
  • Comments carry the why (the war story), matching the density of the ported sources. When porting, keep the incident references (box#108, rig#66, …) intact — they are the documentation.
  • Whole-version matching everywhere; 0.7.0 never matches 0.7.0-rc1.

Acceptance criteria

  • bash test/run.sh runs locally with zero tests and exits 0 with a "0 tests" notice (loudly saying so, not silently green — print the discovered file count).
  • CI is green on a PR that adds a deliberately shellcheck-dirty file only after the file is fixed (i.e. demonstrate the gate fires once in the PR's history).
  • actionlint runs against the workflows that exist.
  • CONTRIBUTING.md states the conventions above.

Out of scope

VERSION/CHANGELOG/self-release wiring — that is #11 (dogfooding), and it needs the release workflow (#9) to exist first.

Part of #1. Blocks everything else. ## Goal Turn the empty repo into a workspace the rest of the plan can land in: directory layout, a bash test harness, and a CI workflow that lints and tests every PR. After this issue, every subsequent issue is "add a tested component", never "figure out where things go". ## Layout to create ``` .github/workflows/ci.yml # ceremony's own CI (see below) actions/ # composite actions land here (empty for now, .gitkeep) lib/ # shared sourced bash libs (empty for now) test/ # the contract suite test/harness.sh # the check() harness (see below) test/run.sh # runs every test/*.test.sh, reports totals, exit 1 on any failure docs/ # CONSUMERS.md lands here later (#12) ``` Root `README.md` already exists; leave it — #12 rewrites it. ## The test harness Port the `check` pattern from [box `test/release.sh` lines 14–36](https://github.com/heavy-duty/box/blob/a17903f07c83aa18c0f009565e1a5442da6d0827/test/release.sh#L14-L36) into `test/harness.sh` as a sourced lib: - `check <desc> <want_exit> <want_substr> <cmd...>` — runs cmd, asserts exit code, asserts combined stdout+stderr contains the substring when non-empty; prints `ok:`/`FAIL:` and counts into `PASS`/`FAIL` globals. - `summary` — prints totals, exits 1 if any FAIL. - Deliberately **no `set -e`** in test files (the harness asserts on failing commands — same note as the source). `set -u` yes. - Everything offline: no network, no `gh`, real git only against repos the test constructs in `mktemp -d`. Test files are `test/<name>.test.sh`, executable, each sourcing the harness. `test/run.sh` finds and runs them all. ## CI (`.github/workflows/ci.yml`) One job, ubuntu-latest: 1. checkout (`fetch-depth: 0` — the monotonic guard's tests and later self-guards need history semantics but more importantly this is the family convention for its own CI). 2. `shellcheck` every `*.sh` in `lib/`, `actions/`, `test/` (`shellcheck -x` so sourced libs resolve). Port the discipline from [cast's `shellcheck-all.sh`](https://github.com/heavy-duty/cast/blob/2aa7018db461341a1bbe79c9ca8eb8fca4232719/.github/scripts/shellcheck-all.sh): find files by shebang and extension, fail on any finding, and **print the file list** so an empty glob can't silently pass (the "a guard that quietly stops guarding" rule). 3. `actionlint` on `.github/workflows/*.yml` and every `actions/*/action.yml` (install the released binary, pinned version). 4. `bash test/run.sh`. ## House conventions (write them into a short `CONTRIBUTING.md`) - Bash: `set -euo pipefail` in executables, `set -u` only in test files, mawk-compatible awk (**no `\x` escapes** — CI runners ship mawk, learned in [box `drill-recorded.sh`](https://github.com/heavy-duty/box/blob/a17903f07c83aa18c0f009565e1a5442da6d0827/.github/scripts/drill-recorded.sh) history). - Every piece of logic is **a file of its own so a test can drive it**; workflows gather facts and call scripts. This is the source repos' stated discipline — quote it. - Comments carry the *why* (the war story), matching the density of the ported sources. When porting, keep the incident references (`box#108`, `rig#66`, …) intact — they are the documentation. - Whole-version matching everywhere; `0.7.0` never matches `0.7.0-rc1`. ## Acceptance criteria - [ ] `bash test/run.sh` runs locally with zero tests and exits 0 with a "0 tests" notice (loudly saying so, not silently green — print the discovered file count). - [ ] CI is green on a PR that adds a deliberately shellcheck-dirty file only after the file is fixed (i.e. demonstrate the gate fires once in the PR's history). - [ ] `actionlint` runs against the workflows that exist. - [ ] CONTRIBUTING.md states the conventions above. ## Out of scope VERSION/CHANGELOG/self-release wiring — that is #11 (dogfooding), and it needs the release workflow (#9) to exist first.
codex-bot-andresmgsl commented 2026-07-22 16:55:19 +00:00 (Migrated from github.com)

Claiming this as codex-bot-andresmgsl. I’m starting the scaffold and will open a draft PR shortly.

Claiming this as codex-bot-andresmgsl. I’m starting the scaffold and will open a draft PR shortly.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference: heavy-duty/ceremony#2
No description provided.