# CI Requirements — FabledScribe > Spec lives in [`docs/process.md`](https://git.fabledsword.com/bvandeusen/CI-runner/src/branch/main/docs/process.md) > in the CI-Runner repo. ## Runtime image ``` git.fabledsword.com/bvandeusen/ci-python:3.14 ``` Used by all six jobs in `.forgejo/workflows/ci.yml`: typecheck (Vue/TS), plugin (hook checks), lint (ruff), test (pytest), integration (pytest + real Postgres), build (docker buildx). ## Image deps used - python 3.14 - node 24 (used for `npm ci` + `vue-tsc` in the typecheck job, and as the frontend builder stage inside the production `Dockerfile`) - ruff (lint job runs `ruff check src/` with zero install overhead) - uv (test + integration jobs run `uv sync --locked`; installed in the image since the ci-python Dockerfile started pip-installing it) - docker CLI + buildx (build job pushes the production image to the Forgejo registry) ## Per-job tool installs Anything CI installs at job time that isn't in the image. Promotion candidates if more than one project needs them. - `jq` + `shellcheck` — apt-installed in the **plugin** job, which lints the four Claude Code hook scripts and runs their fail-open smoke test. Per `docs/process.md`'s decision checkpoint, single-consumer deps stay per-job until a second consumer wants them; Scribe is the only one so far. Both are small (jq ~1 MB, shellcheck ~20 MB) and would be promotion candidates the moment another project lints shell. **jq is load-bearing for the smoke test specifically**: every hook starts with `command -v jq || exit 0`, so without it the test passes while exercising nothing. ## Notes - Production runtime image (`Dockerfile`) also tracks Python 3.14 — the CI image and runtime image stay aligned by design so test results are representative. - Build wall time: dominated by `pytest` (full async test suite). Cold ci-python pulls add ~30s; not a blocker. - Registry-backed BuildKit layer cache (`type=registry,ref=…:cache,mode=max`) gives ~80% speedup on warm builds — see the build job comment. - `pyproject.toml` pins `requires-python = ">=3.14"` to match the CI + runtime target; lockfile (`uv.lock`) is committed and resolves against Python 3.14.