# 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 Fabled-Git 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. - `shellcheck` — apt-installed in the **plugin** job, which lints the 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. It is small (~20 MB) and would be a promotion candidate the moment another project lints shell. - `jq` — **no longer installed anywhere, and should not be promoted.** It was installed in two jobs, and this file used to record it as "load- bearing for the smoke test specifically", because every hook opened `command -v jq || exit 0` and the test would otherwise pass while exercising nothing. That was the tail wagging the dog: the hooks ship to users, jq is absent by default on macOS, the Debian/Ubuntu slim images, Alpine and most CI containers, and a machine without it got no context, no rules, no prior art and no process sync in silence. #4107 removed the dependency rather than documenting it, so the smoke test now runs on a bare image — which is the condition it was always meant to assert. The hooks use only POSIX tools (awk, sed, tr, od, cut, head, tail, grep, sort, date, printf) plus `git` and `curl`. ## 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.