Skip to content

CI

CI #97

Workflow file for this run

name: CI
on:
push:
branches: [main]
pull_request:
workflow_dispatch: {}
jobs:
build-test:
runs-on: ubuntu-latest
strategy:
matrix:
# 20 is the dev-toolchain floor (vitest needs ≥20); 24 is current. The
# published bundle's Node 18 floor is covered by runtime-node-floor.
node-version: [20, 24]
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: ${{ matrix.node-version }}
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Typecheck
run: pnpm run typecheck
- name: Lint
run: pnpm run lint
- name: Build is reproducible
run: pnpm run check:build
- name: Test (with coverage)
run: pnpm run test:coverage
- name: Smoke-run the committed bundle (offline render + check)
run: pnpm run demo
- name: Assert the SRD was produced and is structurally complete
run: |
test -f /tmp/construct-demo/SRD.json
test -f /tmp/construct-demo/requirements/FUNCTIONAL.md
test -f /tmp/construct-demo/SRD.md
# The gate has to work in BOTH directions, so prove both.
- name: The gate REFUSES an un-authored complex scaffold
run: |
rm -rf /tmp/construct-scaffold
mkdir -p /tmp/construct-scaffold/evidence
cp tests/fixtures/sample-brief.json /tmp/construct-scaffold/brief.json
cp tests/fixtures/sample-evidence.json /tmp/construct-scaffold/evidence/evidence.json
node scripts/construct.mjs render --out /tmp/construct-scaffold --level complex
if node scripts/construct.mjs check --out /tmp/construct-scaffold; then
echo "::error::check accepted a complex scaffold whose acceptance criteria are still the renderer's"
exit 1
fi
- name: The shipped example SRD is in sync with its manifest and passes the gate
run: |
node scripts/construct.mjs render --out assets/example-srd --from-srd
git diff --exit-code -- assets/example-srd
node scripts/construct.mjs check --out assets/example-srd --min-grounding 66
install-bundle:
runs-on: ubuntu-latest
name: install-bundle (skill installs whole, not just SKILL.md)
steps:
- uses: actions/checkout@v6
- uses: pnpm/action-setup@v6
- uses: actions/setup-node@v6
with:
node-version: 24
cache: pnpm
- name: Install dependencies
run: pnpm install --frozen-lockfile
- name: Build the embedded bundle
run: pnpm run build
- name: Verify the install-bundle shape (no root SKILL.md, engine in sync)
run: pnpm run verify:bundle
- name: End-to-end — `skills add` bundles the engine + references, not SKILL.md alone
run: |
WORK="$(mktemp -d)"
cd "$WORK"
npx --yes skills add "$GITHUB_WORKSPACE" --skill construct --agent claude-code --copy -y
DIR=.claude/skills/construct
test -f "$DIR/SKILL.md" || { echo "::error::SKILL.md not installed"; exit 1; }
test -f "$DIR/scripts/construct.mjs" || { echo "::error::engine not bundled — skills add dropped scripts/"; exit 1; }
test -d "$DIR/references" || { echo "::error::references/ not bundled"; exit 1; }
# The stack is the engine's. A copy here would be a second source of
# truth that goes stale silently, so its ABSENCE is the assertion —
# and the replacement is proving the engine writes one out instead.
test ! -e "$DIR/docker-compose.yml" || { echo "::error::stale docker-compose.yml in the installed skill — the stack lives in the engine"; exit 1; }
test ! -e "$DIR/docker" || { echo "::error::stale docker/ in the installed skill — the stack lives in the engine"; exit 1; }
node "$DIR/scripts/construct.mjs" --help > /dev/null || { echo "::error::installed engine does not run"; exit 1; }
export CONSTRUCT_CACHE_DIR="$WORK/cache"
node "$DIR/scripts/construct.mjs" firecrawl status > /dev/null 2>&1 || true
test -f "$CONSTRUCT_CACHE_DIR/compose/docker-compose.yml" \
|| { echo "::error::the installed skill could not materialise the compose file — the stack is inoperable as-installed"; exit 1; }
test -f "$CONSTRUCT_CACHE_DIR/compose/docker/searxng/settings.yml" \
|| { echo "::error::searxng settings not materialised — the SearXNG mount would fail"; exit 1; }
test -f "$CONSTRUCT_CACHE_DIR/compose/docker/firecrawl/firecrawl.env" \
|| { echo "::error::firecrawl env not materialised — env_file: would fail before docker is reached"; exit 1; }
grep -q "construct semantic up" "$CONSTRUCT_CACHE_DIR/compose/docker-compose.yml" \
|| { echo "::error::the materialised compose names another tool's command"; exit 1; }
echo "install-bundle: skill installed whole, the engine runs, and it writes its own stack out."
runtime-node-floor:
runs-on: ubuntu-latest
name: runtime (Node 18 floor, zero-dep bundle)
steps:
- uses: actions/checkout@v6
- uses: actions/setup-node@v6
with:
node-version: 18
# This job exists to prove the zero-dep bundle RUNS on the Node floor — not
# to assert an SRD is gate-clean. It renders at --level light for the same
# reason `pnpm demo` does: a raw `complex` scaffold is deliberately NOT
# gate-clean (its acceptance criteria are still the renderer's). Both
# directions of that gate are proved in build-test, against the authored
# example and against a bare scaffold.
- name: The committed bundle renders + checks an SRD on Node 18 (no install)
run: |
mkdir -p "$RUNNER_TEMP/run18/evidence"
cp tests/fixtures/sample-brief.json "$RUNNER_TEMP/run18/brief.json"
cp tests/fixtures/sample-evidence.json "$RUNNER_TEMP/run18/evidence/evidence.json"
node scripts/construct.mjs render --out "$RUNNER_TEMP/run18" --level light --merge
node scripts/construct.mjs check --out "$RUNNER_TEMP/run18"
test -f "$RUNNER_TEMP/run18/SRD.json"
test -f "$RUNNER_TEMP/run18/TRACEABILITY.md"
- name: The example SRD also passes on the Node 18 floor
run: node scripts/construct.mjs check --out assets/example-srd --min-grounding 66