chore(ci): bootstrap CI + hooks
Some checks failed
CI / checks (pull_request) Waiting to run
CI / checks (push) Has been cancelled

Squash-merge bootstrap-cicd into integration
This commit is contained in:
2025-09-10 21:48:32 +00:00
parent 575b33f744
commit 408db0d0cc
19 changed files with 671 additions and 0 deletions

24
.gitea/workflows/ci.yml Normal file
View File

@@ -0,0 +1,24 @@
name: CI
on:
pull_request:
branches: ["**"]
push:
branches: ["integration", "bootstrap", "bootstrap-cicd"]
jobs:
checks:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build CI image
run: docker build -f ci.Dockerfile -t local/ci:latest .
- name: Lint
run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci lint"
- name: Build validation
run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci build"

View File

@@ -0,0 +1,19 @@
name: Nightly
on:
schedule:
- cron: '0 3 * * *'
jobs:
report:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build CI image
run: docker build -f ci.Dockerfile -t local/ci:latest .
- name: Lint (nightly)
run: docker run --rm -v ${{ github.workspace }}:/workspace local/ci:latest bash -lc "cd /workspace && IN_CI_CONTAINER=1 scripts/ci lint"

View File

@@ -0,0 +1,29 @@
name: Release
on:
push:
branches: ["main"]
jobs:
tag-and-notes:
runs-on: docker
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Build CI image
run: docker build -f ci.Dockerfile -t local/ci:latest .
- name: Compute tag
id: tag
run: |
TZ=UTC date +"v%Y.%m.%d-%H%M" > tag.txt
echo "tag=$(cat tag.txt)" >> $GITHUB_OUTPUT
- name: Create annotated tag
run: |
git config user.name "ci"
git config user.email "ci@local"
git tag -a ${{ steps.tag.outputs.tag }} -m "Release ${{ steps.tag.outputs.tag }}"
git push origin ${{ steps.tag.outputs.tag }}

5
.githooks/commit-msg Normal file
View File

@@ -0,0 +1,5 @@
#!/usr/bin/env bash
set -euo pipefail
scripts/commitlint-hook "$1"

11
.githooks/pre-commit Normal file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
echo "> pre-commit: format + lint + commit message check"
# Run format and lint inside the CI container
scripts/ci format
scripts/ci lint
echo "pre-commit completed."

11
.githooks/pre-push Normal file
View File

@@ -0,0 +1,11 @@
#!/usr/bin/env bash
set -euo pipefail
echo "> pre-push: build validation + placeholders for tests/security"
scripts/ci build
scripts/ci test
scripts/ci security
echo "pre-push completed."

33
Makefile Normal file
View File

@@ -0,0 +1,33 @@
SHELL := /usr/bin/env bash
.PHONY: all check quick format lint build test security ci-image hooks-setup
all: check
check:
./scripts/ci all
quick:
./scripts/ci format && ./scripts/ci lint
format:
./scripts/ci format
lint:
./scripts/ci lint
build:
./scripts/ci build
test:
./scripts/ci test
security:
./scripts/ci security
ci-image:
docker build -f ci.Dockerfile -t local/ci:latest .
hooks-setup:
./scripts/setup-hooks

53
RESUME.md Normal file
View File

@@ -0,0 +1,53 @@
Resume Guide
Purpose
- Quick checklist to pick up work after restarting Codex CLI with expanded permissions.
Branches on remote
- main (default), integration, release, bootstrap, bootstrap-cicd
1) Pull latest
- git fetch --all --prune
- git switch bootstrap && git pull
- git switch bootstrap-cicd && git pull
- git switch integration && git pull
2) Ensure Docker is available
- Start Docker Desktop/daemon as needed
3) Install hooks locally
- make hooks-setup
4) Run local checks (Docker-only)
- git switch bootstrap && make quick && make build
- git switch bootstrap-cicd && make quick && make build
- Optional full pass: make check
5) Open PRs (when branches are green locally)
- bootstrap → integration: https://git.knownelement.com/KNEL/LLMScaffolding/pulls/new/bootstrap
- bootstrap-cicd → integration: https://git.knownelement.com/KNEL/LLMScaffolding/pulls/new/bootstrap-cicd
6) Merge to integration
- Use squash merge, allow auto-merge on green where configured
7) Release to main
- Open PR: integration → main (require 1 approval)
- After merge, tag manually (until CI runners are enabled):
- git switch main && git pull
- TAG=$(date -u +"v%Y.%m.%d-%H%M")
- git tag -a "$TAG" -m "Release $TAG"
- git push origin "$TAG"
- Optional: fast-forward release branch pointer:
- git branch -f release main && git push -f origin release
8) Docs & parity
- Git workflow: instructions/git-workflow.md
- Local CI parity: instructions/bootstrap-cicd.md
9) Defer CI enablement for two weeks
- Track in TODO.md: Revisit enabling runners and protected checks on 2025-09-24
10) Next tasks
- Answer any outstanding questions in questions/*
- On approval, implement further proposals and update instructions/*

34
TODO.md Normal file
View File

@@ -0,0 +1,34 @@
TODO
- Git workflow
- [x] Questions gathered and answered
- [x] Proposal iteration 2 drafted
- [x] Finalize approval and capture in instructions/git-workflow.md
- Branches
- [x] Create integration, release, bootstrap from main
- [x] Push bootstrap to origin
- [ ] Decide whether to maintain a fast-forwarded release branch to the latest tag
- CI/CD bootstrap
- [x] Create branch bootstrap-cicd from main
- [x] Add questions at questions/bootstrap-cicd.md
- [x] Draft proposal based on answers
- [x] Implement parity tooling: scripts/ci, ci.Dockerfile, docker/ci.compose.yml
- [x] Add .gitea/workflows: ci.yml, release.yml, nightly.yml
- [x] Add commitlint.config.cjs, Makefile
- [ ] Optional: add .pre-commit-config.yaml (defer for now)
- [ ] Optional: add CODEOWNERS
- Protections & settings (in Gitea UI)
- [ ] Protect main and release/* with required checks
- [ ] Leave integration unprotected; allow auto-merge on green
- [ ] Require 1 approval for integration→main
- [ ] Revisit enabling CI and protections after runners are ready (target: 2025-09-24)
- Releases
- [ ] Tag format vYYYY.MM.DD-HHMM (UTC) in release workflow
- [ ] Optional: fast-forward release branch to latest tag
- Docs
- [ ] Write docs/engineering/git-workflow.md with diagrams and examples

40
ci.Dockerfile Normal file
View File

@@ -0,0 +1,40 @@
FROM debian:12-slim
ARG DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y --no-install-recommends \
ca-certificates curl git bash coreutils findutils file python3 python3-pip \
&& rm -rf /var/lib/apt/lists/*
# Install shfmt, hadolint, actionlint (static), shellcheck, yamllint, node tools
RUN set -eux; \
# shellcheck
apt-get update && apt-get install -y --no-install-recommends shellcheck && rm -rf /var/lib/apt/lists/*; \
# shfmt
SHFMT_VER=3.7.0; curl -fsSL -o /usr/local/bin/shfmt https://github.com/mvdan/sh/releases/download/v${SHFMT_VER}/shfmt_v${SHFMT_VER}_linux_amd64 && chmod +x /usr/local/bin/shfmt; \
# hadolint
HADOLINT_VER=2.12.0; curl -fsSL -o /usr/local/bin/hadolint https://github.com/hadolint/hadolint/releases/download/v${HADOLINT_VER}/hadolint-Linux-x86_64 && chmod +x /usr/local/bin/hadolint;
# actionlint
RUN set -eux; \
AL_VER=1.7.1; \
curl -fsSL -o /usr/local/bin/actionlint https://github.com/rhysd/actionlint/releases/download/v${AL_VER}/actionlint_${AL_VER}_linux_amd64.tar.gz; \
tar -C /usr/local/bin -xzf /usr/local/bin/actionlint; \
rm -f /usr/local/bin/actionlint
# yamllint via pip (allow install on Debian's externally-managed Python)
RUN pip3 install --break-system-packages --no-cache-dir yamllint==1.35.1
# Node + npm for prettier, markdownlint, commitlint
RUN set -eux; \
curl -fsSL https://deb.nodesource.com/setup_20.x | bash - && \
apt-get update && apt-get install -y --no-install-recommends nodejs && \
rm -rf /var/lib/apt/lists/*
RUN npm --location=global install \
prettier@3.3.3 \
markdownlint-cli@0.39.0 \
@commitlint/cli@19.5.0 @commitlint/config-conventional@19.5.0
WORKDIR /workspace
ENTRYPOINT ["bash","-lc"]
CMD ["bash"]

4
commitlint.config.cjs Normal file
View File

@@ -0,0 +1,4 @@
module.exports = {
extends: ['@commitlint/config-conventional'],
};

13
docker/ci.compose.yml Normal file
View File

@@ -0,0 +1,13 @@
services:
ci:
build:
context: ..
dockerfile: ci.Dockerfile
working_dir: /workspace
volumes:
- "../:/workspace:Z"
environment:
- IN_CI_CONTAINER=1
entrypoint: ["bash","-lc"]
command: ["bash"]

View File

@@ -0,0 +1,36 @@
Bootstrap CI/CD Finalized Instructions (Phase 1)
Goal
- Provide Dockeronly local checks and Git hooks with parity to future CI. CI workflows are prepared but may remain disabled until runners are ready.
Requirements
- Docker + Docker Compose v2 on the development machine. No host packages beyond Docker are required.
Local Checks
- Entry point: `scripts/ci <phase>` where phase ∈ {format, lint, build, test, security, all}.
- Always runs inside the ci container using `docker/ci.compose.yml`.
- Tools pinned in `ci.Dockerfile`: shfmt, shellcheck, hadolint, yamllint, actionlint, prettier, markdownlint, commitlint.
Hooks
- Install hooks: `make hooks-setup` (copies .githooks/* into .git/hooks).
- pre-commit: runs format + lint.
- commit-msg: runs commitlint (Conventional Commits).
- pre-push: runs build; test and security are present but currently noops.
Convenience Targets
- `make quick` → format + lint.
- `make check` → all phases.
- `make build` → compose validation.
CI (Prepared, optional enablement later)
- .gitea/workflows/ci.yml: builds ci image; runs lint + build.
- .gitea/workflows/release.yml: on pushes to main, creates annotated tag vYYYY.MM.DD-HHMM (UTC).
- .gitea/workflows/nightly.yml: nightly lint run.
- All jobs run inside the ci image; no runner host package installs.
Protected Checks (when CI is enabled)
- Protect: ci / lint, ci / build, ci / commitlint. Add ci / test and ci / security when they exist.
Future Extensions
- Add tests/security phases per repo stack; enable CI branch protections once runners are ready; optionally add pre-commit framework as an alternative to native hooks.

View File

@@ -0,0 +1,36 @@
Git Workflow Finalized Instructions
Scope
- Applies to this repo. Users typically consume tagged releases; contributors work via branches/PRs. CI/CD config is Giteanative; no GitHub/GitLab.
Branches
- main: production; default branch. Protected.
- integration: development (unprotected; merges auto on green).
- Working branches: `feature/<topic>`, `fix/<topic>`, `chore/<topic>` from integration.
- Hotfix: `hotfix/<date>` from main; PR back to main, then forward-merge into integration.
- Release branch: ephemeral or lightweight `release/*`. Protect when present; optionally fastforward to latest tag via CI.
Merges & Approvals
- Feature → integration: squash merge; automerge on green (no human approval). Selfmerge allowed.
- integration → main: squash merge; require 1 approval; selfmerge not allowed.
- Force pushes disabled on protected branches (`main`, `release/*`); PRs required.
Commit Style
- Conventional Commits for PR titles and commit messages.
Versioning & Tags
- Calendar tags: vYYYY.MM.DD-HHMM (UTC). Annotated tags only on main after release.
Release Flow
1) Feature branches PR into integration; checks pass → automerge.
2) PR integration → main; 1 approval required; on merge, deploy and tag release.
3) Optional: CI fastforwards a release branch pointer to the new tag.
Protected Checks (to enable when runners are ready)
- On protected branches (`main`, `release/*`): ci / lint, ci / build, ci / commitlint. Add ci / test and ci / security if/when introduced.
CODEOWNERS
- Keep minimal; require your review for integration → main.
Notes
- No secrets required for this repo. Future repos should integrate Vault for secrets.

View File

@@ -0,0 +1,69 @@
**Bootstrap CI/CD Proposal (Phase 1)**
- Scope: Local developer parity via Docker-first tooling and hooks, minimal CI placeholders (no runners required yet). Applies to this repo (docs/scripts/docker-compose), with an easy path to template for others.
**Checks To Implement Now (Local via Docker)**
- Stacks: shell, Dockerfiles/Compose, Markdown/Docs, YAML; Python/Node optional later.
- Formatters/Linters:
- shell: shfmt + shellcheck
- docker: hadolint
- markdown: markdownlint + prettier
- yaml: yamllint + actionlint (for workflows)
- Tests: none for now (lint-only baseline).
- Security: skip for this repo now.
**Execution Model**
- Docker-only: all checks run inside a pinned `ci` image. Host only orchestrates Docker/Compose.
- Single entrypoint: `scripts/ci` with phases: `format`, `lint`, `build` (compose validate), `test` (no-op for now), `security` (no-op), `all`.
- Compose file: `docker/ci.compose.yml` defines `ci` service that mounts repo and executes `scripts/ci <phase>`.
**Hooks Parity**
- Provide Git hooks via pre-commit framework and native Git hooks:
- pre-commit: run `format`, `lint`, and commit message check (Conventional Commits).
- pre-push: run `build` (compose config validation) and keep `test`/`security` as no-ops for now.
- Commit message style: Conventional Commits via `commitlint` rule-set; enforce in CI later and locally via `commit-msg` hook.
**Minimal CI (Deferred Enablement)**
- Workflows will be prepared but can stay disabled until runners are available:
- `.gitea/workflows/ci.yml`: mirrors local `lint` + `build` using the same `ci` image; triggered on PRs when enabled.
- `.gitea/workflows/release.yml`: on `main` merges, tags with `vYYYY.MM.DD-HHMM` and (optionally) creates release notes; can be enabled later.
- `.gitea/workflows/nightly.yml`: scheduled dependency/lint refresh; optional for later.
- All jobs execute inside the `ci` container image; no host package installs.
**Caching & Matrix**
- Matrix: single Linux image for now.
- Caching: enable Docker layer cache when CI runners are available; no special local caching required.
**Concurrency & Timeouts (defaults for later)**
- Cancel in-progress on same ref: enabled for PRs.
- Job timeout: 30 minutes.
**Protected Check Names (for later enforcement)**
- `ci / lint`, `ci / build`, `ci / commitlint`. Tests/Security can be added when introduced.
**Files To Add (upon approval)**
- `scripts/ci` (bash) — phases and Docker/host detection (host executes Docker only).
- `ci.Dockerfile` — pinned versions: shfmt, shellcheck, hadolint, yamllint, markdownlint-cli, prettier, actionlint, commitlint.
- `docker/ci.compose.yml``ci` service to run checks.
- `.pre-commit-config.yaml` — wire to `scripts/ci` phases; enable `commit-msg` hook for commitlint.
- `commitlint.config.cjs` — Conventional Commits rules.
- `.gitea/workflows/ci.yml`, `release.yml`, `nightly.yml` — prepared but can be disabled until runners are ready.
- `Makefile``check`, `quick`, `lint`, `format`, `build` targets mapping to scripts.
**Rollout Plan**
1) Implement local tooling and hooks on `bootstrap-cicd`.
2) Document quickstart in `docs/engineering/ci-cd.md`.
3) Later: enable Gitea workflows when runners are ready; add protected checks.
4) Optionally expand with tests/security scanners and language stacks per repo.
If this matches your intent, I will scaffold the above on `bootstrap-cicd` and then capture the finalized process in `instructions/bootstrap-cicd.md`.

110
questions/bootstrap-cicd.md Normal file
View File

@@ -0,0 +1,110 @@
Bootstrap CI/CD Questions
Goal: define initial CI/CD checks and local Docker-parity hooks for this repo (docs/site, scripts, docker-compose), and a template usable by other repos.
Answer style: short codes + notes, e.g. `1:a,c 2:b 3:docker`.
1) Stacks present now (select all):
- a) Shell scripts
- b) Dockerfiles/Compose
- c) Markdown/Docs
- d) YAML (workflows/config)
- e) Python
- f) Node/JS
- g) Other (specify)
a,b,c,d potentially e.
2) Formatters/linters per stack:
- shell: a) shfmt b) shellcheck c) both
- docker: a) hadolint
- markdown: a) markdownlint b) prettier c) both
- yaml: a) yamllint b) actionlint (for workflows) c) both
- python (if used): a) black b) ruff c) pytest (tests)
- node (if used): a) eslint b) prettier c) jest (tests)
shell: c
docker: a
markdown: c
yaml: c
I will leave python/node testing up to you. It isn't needed for this repo unless you create python scripts at some point.
3) Testing scope now:
- a) none (docs/scripts only)
- b) smoke tests for scripts (bats/pytest-sh)
- c) unit tests for scripts (specify framework)
A (other then linting)
4) Security scanning:
- a) trivy fs
- b) grype
- c) bandit (python)
- d) npm audit (node)
- e) skip for this repo
e
5) Execution environment for CI:
- a) run inside repos `ci.Dockerfile`
- b) run on runner host with packages
- c) mix (specify)
All execution MUST be done in docker containers. Absolutely no work must be done on the host beyond git operations and docker orchestration.
6) Matrix needs (now):
- a) none (single Linux image)
- b) multiple language versions (specify)
- c) OS matrix (Linux only for now?)
Um. I don't know. I think just a simle Linux environment can be assumed?
7) Caching:
- a) enable tool caches (pip/npm) in CI
- b) enable Docker layer cache
- c) none
I guess docker layer cache? It will be two weeks before I'm working on software (and therefore setup gitea CI runners etc).
8) Check names to protect on branches (final labels):
- a) ci / lint
- b) ci / test
- c) ci / build
- d) ci / security
- e) ci / commitlint
I don't know, leave it up to you
9) Hooks parity:
- pre-commit: run format+lint+commitlint? (y/n) y
- pre-push: run test+build+security (fast profile)? (y/n) y
10) Concurrency & timeouts:
- cancel in-progress on new commits to same PR? (y/n)
- default job timeout (minutes)?
11) Release flow details:
- generate release notes from merged PRs since last tag? (y/n)
- attach built artifacts (site tarball, etc)? (y/n)
12) Coverage gates (if tests exist):
- threshold % to require? (number or skip)
13) Auto-merge bot to `integration`:
- bot account/name (or use Gitea built-in)?
- automerge conditions beyond green checks? (labels, size)
14) Notifications:
- a) none
- b) email
- c) webhook/Chat (specify)
15) Future extensibility:
- template these workflows for other repos? (y/n)
- segregate language-specific jobs behind conditions? (y/n)
Notes: add any constraints about runners, container registry, or build tools.
Lets just ignore all things CI for now? I'm brand new to CI. Use your best judgement/adopt best practices and/or ignore CI as needed. Do track that it's an outstanding item to go in depth on though. I don't want it to block moving forward with the dozen or so docs repos I need to use this LLM workflow with though.

115
scripts/ci Executable file
View File

@@ -0,0 +1,115 @@
#!/usr/bin/env bash
set -euo pipefail
PHASE="${1:-}"
usage() {
echo "Usage: scripts/ci <format|lint|build|test|security|all>" >&2
exit 2
}
if [[ -z "${PHASE}" ]]; then
usage
fi
repo_root() {
git rev-parse --show-toplevel 2>/dev/null || pwd
}
run_outside_container() {
local phase="$1"
local root
root="$(repo_root)"
if ! command -v docker >/dev/null 2>&1; then
echo "Docker is required to run CI tasks locally." >&2
exit 1
fi
if ! command -v docker-compose >/dev/null 2>&1 && ! docker compose version >/dev/null 2>&1; then
echo "Docker Compose v2+ is required (docker compose)." >&2
exit 1
fi
# Build ci image if needed and run the requested phase inside the container
(cd "$root" && docker compose -f docker/ci.compose.yml run --rm \
-e IN_CI_CONTAINER=1 \
ci bash -lc "cd /workspace && scripts/ci --inside ${phase}")
}
run_format() {
echo ">> Formatting"
# shell: format in-place
shfmt -bn -ci -i 2 -w .
# prettier for markdown/yaml/json/etc
prettier --log-level warn --write \
"**/*.md" "**/*.yaml" "**/*.yml" "**/*.json" \
"**/*.css" "**/*.html" 2>/dev/null || true
}
run_lint() {
echo ">> Linting"
# shellcheck
mapfile -t sh_files < <(git ls-files -z | xargs -0 file --mime-type | awk -F: '/(x-shellscript|text\/x-shellscript)/{print $1}'; git ls-files "*.sh")
if [[ ${#sh_files[@]} -gt 0 ]]; then
shellcheck -x "${sh_files[@]}" || (echo "Shellcheck failed" && exit 1)
shfmt -d .
fi
# hadolint on Dockerfiles
if ls Dockerfile* docker/*Dockerfile* 1>/dev/null 2>&1; then
hadolint Dockerfile* docker/*Dockerfile* 2>/dev/null || true
fi
# yamllint
if git ls-files "*.yml" "*.yaml" | grep -q .; then
yamllint -s $(git ls-files "*.yml" "*.yaml")
fi
# markdownlint
if git ls-files "*.md" | grep -q .; then
markdownlint $(git ls-files "*.md")
fi
# actionlint for workflow files if present
if [ -d .gitea/workflows ]; then
actionlint -color
fi
}
run_build() {
echo ">> Build checks"
# Validate docker compose configs if present
if [ -f docker-compose.yml ] || [ -f docker/compose.yml ]; then
docker compose config -q
fi
}
run_test() {
echo ">> Tests (none defined)"
}
run_security() {
echo ">> Security checks (skipped for this repo)"
}
run_inside_container() {
local phase="$1"
case "$phase" in
format) run_format ;;
lint) run_lint ;;
build) run_build ;;
test) run_test ;;
security) run_security ;;
all) run_format; run_lint; run_build; run_test; run_security ;;
*) usage ;;
esac
}
if [[ "${1:-}" == "--inside" ]]; then
shift
PHASE="${1:-}"
[[ -z "$PHASE" ]] && usage
run_inside_container "$PHASE"
exit 0
fi
if [[ "${IN_CI_CONTAINER:-}" != "1" ]]; then
run_outside_container "$PHASE"
else
run_inside_container "$PHASE"
fi

10
scripts/commitlint-hook Executable file
View File

@@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
MSG_FILE="${1:-.git/COMMIT_EDITMSG}"
# Run commitlint inside the CI container against the commit message file
docker compose -f docker/ci.compose.yml run --rm \
-e IN_CI_CONTAINER=1 \
ci bash -lc "commitlint --edit ${MSG_FILE}"

19
scripts/setup-hooks Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env bash
set -euo pipefail
root_dir="$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
mkdir -p "$root_dir/.git/hooks"
for hook in pre-commit pre-push commit-msg; do
src="$root_dir/.githooks/$hook"
dest="$root_dir/.git/hooks/$hook"
if [[ -f "$src" ]]; then
cp "$src" "$dest"
chmod +x "$dest"
echo "Installed hook: $hook"
fi
done
echo "Git hooks installed."