# HyperI CI — Architecture and Design

## Overview

HyperI CI is a polyglot CI/CD tool that replaces a legacy system of ~100
shell scripts, 50+ composite actions, and a six-layer dispatch hierarchy
with a single Python CLI. It supports Python, Rust, TypeScript, and Go
projects. The same tool runs identically on developer laptops and in
GitHub Actions.

The system has two sides that work together:

1. **GitHub Actions side** — reusable workflow templates that orchestrate
   CI pipeline stages (quality, test, build, release)
2. **CLI side** — `hyperi-ci` Python tool that executes each stage's
   actual logic (linting, testing, building, publishing)

```
┌─────────────────────────────────────────────────────────────┐
│                    GitHub Actions                           │
│                                                             │
│  Consumer repo                    hyperi-ci repo            │
│  .github/workflows/ci.yml  ───►  .github/workflows/        │
│  (5 lines, calls reusable)       ├── python-ci.yml          │
│                                  ├── rust-ci.yml            │
│                                  ├── ts-ci.yml              │
│                                  └── go-ci.yml              │
│                                       │                     │
│                                       ▼                     │
│                              uvx hyperi-ci run <stage>      │
└────────────────────────────────────┬────────────────────────┘
                                     │
┌────────────────────────────────────▼────────────────────────┐
│                    hyperi-ci CLI                             │
│                                                             │
│  cli.py ──► dispatch.py ──► languages/<lang>/<stage>.py     │
│                    │                                        │
│                    ├── detect.py    (language detection)     │
│                    ├── config.py    (config cascade)         │
│                    └── common.py    (logging, subprocess)    │
│                                                             │
│  Each handler calls language tools via subprocess:          │
│    Python:  ruff, pytest, uv build, uv publish              │
│    Rust:    cargo fmt/clippy/test/build/publish              │
│    TS:      eslint, vitest/jest, npm build/publish           │
│    Go:      go vet, go test, go build                       │
└─────────────────────────────────────────────────────────────┘
```

## GitHub Actions Side

### Reusable Workflow Templates

Four language-specific reusable workflows live in
`.github/workflows/` in the hyperi-ci repo itself:

| Workflow | Language | Toolchain Setup |
|----------|----------|-----------------|
| `python-ci.yml` | Python | `astral-sh/setup-uv@v7`, `uv python install` |
| `rust-ci.yml` | Rust | `dtolnay/rust-toolchain@master` (clippy, rustfmt) |
| `ts-ci.yml` | TypeScript | `actions/setup-node@v6`, detect package manager |
| `go-ci.yml` | Go | `actions/setup-go@v6` (go-version-file) |

### Job Structure

Every reusable workflow has the same four-job structure:

```
quality ──┐
           ├──► build ──► release
test ─────┘
```

1. **quality** (ubuntu-latest) — runs `uvx hyperi-ci run quality`
2. **test** (matrix: ubuntu + macOS) — runs `uvx hyperi-ci run test`
3. **build** (needs quality + test) — runs `uvx hyperi-ci run build`
   - Skipped on fork PRs (`github.event.pull_request.head.repo.fork != true`)
4. **release** (needs all three, main push only) — runs `semantic-release`
   - Only on `refs/heads/main` push events

### Consumer Project Setup

A consumer project needs three files, all generated by `hyperi-ci init`:

**`.github/workflows/ci.yml`** — 5 lines:
```yaml
name: CI
on: { push: { branches: [main] }, pull_request: { branches: [main] } }
jobs:
  ci:
    uses: hyperi-io/hyperi-ci/.github/workflows/python-ci.yml@main
    secrets: inherit
```

**`.hyperi-ci.yaml`** — project configuration (language, build targets,
publish settings).

**`Makefile`** — wraps CLI for local use:
```makefile
quality:
	hyperi-ci run quality
test:
	hyperi-ci run test
build:
	hyperi-ci run build
```

### Workflow Inputs

All workflows accept common inputs:

| Input | Default | Purpose |
|-------|---------|---------|
| `publish-target` | `"internal"` | Where artifacts go: `internal`, `oss`, or `both` |
| `test-matrix` | `'["ubuntu-latest", "macos-latest"]'` | OS matrix for test job |

Plus language-specific inputs (e.g. `python-version`, `rust-toolchain`,
`node-version`, `go-version-file`).

### Fork Safety

Fork PRs run quality and test only — no build, no publish, no secrets.
GitHub's built-in "Require approval for first-time contributors" handles
the approval gate. No custom code needed.

## CLI Side

### Entry Point

```
hyperi-ci run <stage>     Run CI stage (setup, quality, test, build, publish)
hyperi-ci init            Generate project scaffolding
hyperi-ci detect          Show detected language
hyperi-ci config          Show merged configuration
hyperi-ci trigger         Trigger GitHub Actions workflow
hyperi-ci watch           Watch a run to completion
hyperi-ci logs            Fetch and filter run logs
```

### Stage Dispatch

When `hyperi-ci run quality` is invoked:

1. **detect.py** identifies the project language from file markers
   (`Cargo.toml` → rust, `pyproject.toml` → python, etc.) or from
   `.hyperi-ci.yaml` / `HYPERI_CI_LANGUAGE` env var override
2. **config.py** loads and merges configuration (defaults → project
   config → env vars)
3. **dispatch.py** imports `hyperi_ci.languages.<lang>.<stage>` and
   calls its `run(config, extra_env)` function

```python
# Handler interface — every language/stage module exports this
def run(config: CIConfig, extra_env: dict[str, str] | None = None) -> int:
    """Run the stage. Returns 0 on success."""
```

### Language Handlers

```
src/hyperi_ci/languages/
├── python/
│   ├── quality.py    ruff check, ruff format, ty, bandit, pip-audit
│   ├── test.py       pytest with coverage
│   ├── build.py      uv build (wheel + sdist), Nuitka (native binary)
│   └── publish.py    uv publish → PyPI or JFrog
├── rust/
│   ├── quality.py    cargo fmt, clippy, cargo audit, cargo deny
│   ├── test.py       cargo nextest / cargo test
│   ├── build.py      cargo build --release (multi-target, cross-compile)
│   └── publish.py    cargo publish → crates.io or JFrog
├── typescript/
│   ├── quality.py    eslint, prettier, tsc, npm audit
│   ├── test.py       vitest / jest (auto-detected), Playwright E2E
│   ├── build.py      npm run build / pnpm build
│   └── publish.py    npm publish → npmjs or JFrog
└── golang/
    ├── quality.py    gofmt, go vet, golangci-lint, gosec, govulncheck
    ├── test.py       go test -race -cover
    ├── build.py      go build (multi-target cross-compile, CGO support)
    └── publish.py    go proxy (automatic), gh release upload (binaries)
```

### Configuration Cascade

Priority (highest wins):

```
CLI flags → ENV vars (HYPERCI_*) → .hyperi-ci.yaml → config/defaults.yaml → hardcoded
```

The `.hyperi-ci.yaml` in each project is the single source of truth for
project-specific settings. `config/defaults.yaml` in the hyperi-ci
package provides sensible defaults for all languages.

### Config Schema

```python
@dataclass
class CIConfig:
    language: str
    publish_target: str          # "internal", "oss", or "both"
    _raw: dict[str, Any]         # Full merged config for nested access

    def get(key: str) -> Any     # Dot-notation access: "quality.python.ruff"
    def destination_for(type)    # Publish routing: ["jfrog-pypi"] or ["pypi"]
```

### Publish Target Routing

A single config value (`publish.target`) controls where artifacts go:

| Target | Python | Rust | TypeScript | Go |
|--------|--------|------|------------|-----|
| `internal` | JFrog PyPI | JFrog Cargo | JFrog npm | JFrog Go |
| `oss` | pypi.org | crates.io | npmjs.com | proxy.golang.org |
| `both` | Both | Both | Both | Both |

Set via GitHub org/repo variable `PUBLISH_TARGET`, which maps to
`HYPERCI_PUBLISH_TARGET` env var in workflows.

Organisation-specific URLs (JFrog domain, registry paths) are in
`config/org.yaml` and loaded into `OrgConfig`:

```python
@dataclass
class OrgConfig:
    jfrog_domain: str        # "hypersec.jfrog.io"
    pypi_publish_url: str    # Derived: "{base}/api/pypi/hyperi-pypi-local"
    cargo_publish_url: str   # Derived: "{base}/api/cargo/hyperi-cargo-local"
    npm_url: str             # Derived: "{base}/api/npm/hyperi-npm"
    # ... etc
```

## Configuration Boundaries: org.yaml vs GitHub Vars vs Secrets

Configuration lives in three places with clear, non-overlapping boundaries:

### 1. `config/org.yaml` — CI Logic and Routing (version-controlled)

Everything that affects CI behaviour, artifact routing, or registry URLs.
Changes go through PRs with review. Testable in unit tests.

| What | Examples |
|------|---------|
| Registry URLs | JFrog domain, PyPI publish URL, npm registry |
| Destination mappings | `python: jfrog-pypi`, `python: pypi` |
| Org identifiers | GitHub org, GHCR org, JFrog prefix |
| Publish defaults | Default target, binary repo name |
| Deprecated patterns | Old CI repo URLs for migration detection |

### 2. GitHub Vars — Platform Infrastructure (UI-managed)

Runner labels and GitHub-specific infrastructure config that varies by
org or repo. Rarely changes. Not testable in unit tests (platform-specific).

| Variable | Scope | Purpose |
|----------|-------|---------|
| `GH_RUNNER_DEFAULT` | org | Default self-hosted runner label |
| `GH_RUNNER_RUST` | org | Runner for Rust jobs (may need more resources) |
| `GH_RUNNER_PYTHON` | org | Runner for Python jobs |
| `GH_RUNNER_TYPESCRIPT` | org | Runner for TypeScript jobs |
| `GH_RUNNER_GOLANG` | org | Runner for Go jobs |
| `PUBLISH_TARGET` | org/repo | Override publish target per repo |

### 3. GitHub Secrets — Credentials (UI-managed, encrypted)

Authentication tokens. Never in code, never in vars. Scoped to the
minimum set of repos that need them via `selected` visibility.

| Secret | Visibility | Purpose |
|--------|-----------|---------|
| `JFROG_TOKEN` | selected | JFrog Artifactory authentication |
| `NPM_TOKEN` | selected | npmjs.com publish token |
| `CRATES_TOKEN` | selected | crates.io publish token |
| `GIT_TOKEN` | selected | Cross-repo access (workflow dispatch) |

### Decision Rule

> If it affects CI logic or routing → `org.yaml` (reviewable, testable).
> If it's platform infrastructure → GitHub Vars (UI-managed).
> If it's a credential → GitHub Secrets (encrypted, scoped).

### Version Pinning: `config/versions.yaml`

All tool and action versions are documented in `config/versions.yaml`
as a single source of truth. When updating a version, change the SSOT
file first, then grep and update the workflow files to match.

Versions are pinned at major (actions) or minor (runtimes) level.
Runtime versions (e.g. Python 3.12) are deliberate choices — don't
auto-bump without testing. Action versions (e.g. checkout@v6) should
track latest major.

### What Does NOT Go in GitHub Vars

- Registry URLs (use `org.yaml` — version-controlled, testable)
- Destination mappings (use `config/defaults.yaml` — part of config cascade)
- Tool versions (use `config/versions.yaml` — reviewable)

## How They Work Together

### Local Development

Developer runs `make quality` or `hyperi-ci run test` directly.
The CLI detects language, loads config, and invokes language tools
via subprocess. No GitHub Actions involvement.

### CI Pipeline

1. Developer pushes to branch or opens PR
2. GitHub Actions triggers consumer's `ci.yml`
3. Consumer workflow calls reusable template (e.g. `python-ci.yml@main`)
4. Reusable workflow sets up language toolchain + uv
5. Each job runs `uvx hyperi-ci run <stage>`
6. `hyperi-ci` CLI does the actual work — same code path as local
7. On main push: semantic-release analyses commits, creates tag, triggers
   publish via `hyperi-ci run publish`

### Key Insight

GitHub Actions handles **orchestration** (job ordering, matrix, caching,
secrets, permissions). The CLI handles **execution** (what tools to run,
how to invoke them, what flags to pass). This separation means:

- Workflow files stay small (no bash logic in YAML)
- Language tool invocation is tested locally before CI
- Adding a new quality check = Python code change, not workflow YAML
- Same behaviour everywhere — no "works locally but fails in CI"

## Runner Modes

CI supports two execution modes: **self-hosted** (ARC runners on the
DevEx cluster) and **free** (GitHub-hosted `ubuntu-latest`).

| Mode | Runners | Cache | Toolchain |
|------|---------|-------|-----------|
| `self-hosted` | ARC runners (DevEx cluster) | Persistent NFS sccache/ccache | Pre-installed in runner image |
| `free` | GitHub-hosted `ubuntu-latest` (4 vCPU, 16 GB) | None between runs | Installed per-job via setup actions |

When `mode` is `free`, `GH_RUNNER_*` org variables are ignored and
all jobs run on `ubuntu-latest`. This means any GitHub organisation
can use hyperi-ci's reusable workflows without self-hosted
infrastructure — builds just take longer without the persistent cache
and pre-installed toolchain.

### How Runner Mode Is Resolved

Mode is controlled at three levels (highest wins):

1. **Workflow input `runner-mode`** — consumer repo passes it explicitly
   in their `ci.yml` (per-repo control)
2. **GitHub variable `GH_RUNNER_MODE`** — set at org or repo level
   (org-wide default)
3. **Fallback** — if neither is set, `GH_RUNNER_*` vars are checked,
   ultimately falling back to `ubuntu-latest`

Every `runs-on` expression in the reusable workflows evaluates:

```
(inputs.runner-mode || vars.GH_RUNNER_MODE) == 'free'
  → 'ubuntu-latest'
  → inputs.runner-<job> || vars.GH_RUNNER_<LANG> || vars.GH_RUNNER_DEFAULT || 'ubuntu-latest'
```

Consumer project examples:

```yaml
# Free mode (explicit)
jobs:
  ci:
    uses: hyperi-io/hyperi-ci/.github/workflows/rust-ci.yml@main
    with:
      runner-mode: free
    secrets: inherit

# Self-hosted mode (default — uses GH_RUNNER_* org vars)
jobs:
  ci:
    uses: hyperi-io/hyperi-ci/.github/workflows/rust-ci.yml@main
    secrets: inherit
```

The `config/defaults.yaml` documents the intended org-wide default
(`runners.mode: self-hosted`) but actual enforcement is at the
workflow expression level via `GH_RUNNER_MODE` and `runner-mode`.

### uv Caching

The `setup-uv` action's `enable-cache` is only enabled in `python-ci.yml`
where there are actual Python dependencies to cache (`uv.lock`,
`pyproject.toml`). In non-Python workflows (Rust, TypeScript, Go), uv
is used solely as a delivery mechanism for `uvx hyperi-ci` — there is
no Python project to cache, so caching is disabled to avoid spurious
"no cache files found" warnings.

### Cross-Compilation and ARM Runners

Most languages can cross-compile from x64 to arm64 on a single runner:

| Language | Cross-compile x64 → arm64 | ARM runner needed? |
|----------|--------------------------|-------------------|
| Rust | Yes — `cargo build --target aarch64-unknown-linux-gnu` | No |
| Go | Yes — `GOARCH=arm64 go build` | No |
| TypeScript | N/A — produces platform-agnostic JS | No |
| Python (wheel) | N/A — pure Python or manylinux wheels | No |
| Python (Nuitka) | **No** — compiles to native C binary | **Yes** |

Nuitka compiles Python to native machine code via C and cannot
cross-compile. Projects using Nuitka to produce binaries for both
x64 and arm64 require two build runners — one per architecture.
In free mode this means `ubuntu-latest` (x64) and
`ubuntu-latest-arm` (arm64, free for public repos). In self-hosted
mode, separate ARC runner tiers for each architecture.

The Nuitka build uses the OSS version from PyPI (`pip install nuitka`),
not the commercial Nuitka licence. The compilation is for performance
only — all HyperI Python projects also publish standard wheels.

### Self-Hosted Mode (ARC Runners)

When `GH_RUNNER_*` variables point to ARC runner labels, CI runs on
self-hosted runners deployed to the DevEx Kubernetes cluster. Four
runner tiers are available:

| Tier | CPUs | RAM | maxRunners | Typical Use |
|------|------|-----|------------|-------------|
| 2cpu | 2 | 4Gi | 20 | Lint, test, publish, semantic-release |
| 4cpu | 4 | 8Gi | 10 | Python, Node.js builds, small Rust crates |
| 8cpu | 8 | 16Gi | 5 | Medium Rust/C++ builds, integration tests |
| 16cpu | 16 | 28Gi | 3 | Large Rust/C++ release builds, ClickHouse |

Runners are **ephemeral** — each pod is destroyed after one job and the
scale sets idle at zero. The runner image (built from
`hyperi-infra/containers/arc-runner/Dockerfile`) includes the full
toolchain: LLVM/Clang 19+20, GCC, Rust stable+nightly, Python 3.12+uv,
Node.js 22, Go, mold linker, and security scanners.

### Persistent Build Cache (Rust, C/C++)

Compilation caches persist across ephemeral runner pods via a shared
NFS-backed PersistentVolume. This is the primary mechanism for keeping
Rust and C++ builds fast — without it, every job would start from a
cold compile.

**Infrastructure** (defined in `hyperi-infra`):

- **250Gi NFS PV** on NVMe SSD (`storage.devex.hyperi.io:/nas/ssd/runner-cache`)
- **PVC** `runner-cache-pvc` in `arc-runners` namespace, mounted at `/mnt/cache`
- **NFS tuned for cache workloads**: `async` (speed over durability for
  disposable data), `soft` + `timeo=30` (fail-fast, no D-state hangs),
  `nolock`, 1MB I/O blocks, 60s attribute cache

**Cache environment variables** (set on all runner tiers):

| Variable | Value | Purpose |
|----------|-------|---------|
| `SCCACHE_DIR` | `/mnt/cache/sccache` | sccache compilation cache (NFS) |
| `SCCACHE_CACHE_SIZE` | `100G` | Max cache size (LRU eviction) |
| `SCCACHE_IDLE_TIMEOUT` | `0` | Keep sccache daemon alive for job duration |
| `CCACHE_DIR` | `/mnt/cache/ccache` | ccache C/C++ compilation cache (NFS) |
| `CCACHE_MAXSIZE` | `100G` | Max cache size |
| `RUSTC_WRAPPER` | `sccache` | Route all rustc invocations through sccache |
| `CARGO_INCREMENTAL` | `0` | Disabled — incompatible with sccache |
| `CARGO_REGISTRIES_CRATES_IO_PROTOCOL` | `sparse` | Faster registry metadata |
| `LDFLAGS` | `-fuse-ld=mold` | mold linker (replaces slow ld.bfd) |

**Package manager caches** (Cargo registry, uv, pip, npm) use local
`emptyDir` volumes — their metadata-heavy I/O patterns perform poorly
over NFS and they re-populate quickly.

### Why This Matters

Without the persistent cache, a clean Rust build of a project with C
dependencies (e.g. librdkafka via `rdkafka-sys`) takes 15–20 minutes.
With a warm sccache, the same build completes in 1–3 minutes. C++
projects (e.g. ClickHouse contributor builds) see similar improvements
with ccache.

The cache is disposable — if the NFS volume is lost or corrupted, the
only impact is slower first builds while caches repopulate. No data
integrity requirements, which is why NFS `async` mode is used.

### Configuration Files (in `hyperi-infra`)

| File | Purpose |
|------|---------|
| `k8s/runner-cache-pv.yaml` | PV/PVC definition (NFS mount options) |
| `k8s/arc-runner-set-values.yaml` | Base Helm values (volumes, env, auth) |
| `k8s/arc-runner-{2,4,8,16}cpu.yaml` | Per-tier resource limits and scaling |
| `containers/arc-runner/Dockerfile` | Runner image with full toolchain |
| `ansible/playbooks/k8s-arc-runners.yml` | Helm deployment playbook |
| `ansible/roles/storage_server/` | NFS server config and SSD cache dirs |

## Cross-Compilation (Rust + C/C++)

For projects with C/C++ dependencies (e.g. librdkafka via `rdkafka`
crate with `cmake-build` feature), the build handler uses a **private
sysroot approach** ported from the old CI. This avoids installing
cross-arch `-dev` packages system-wide (many are NOT Multi-Arch safe
and would replace the native versions, breaking the native build).

### How it works

1. **Target ordering** — builds native target first, then cross targets,
   to avoid Multi-Arch package conflicts.

2. **Cross-compiler installation** — installs only the cross-compiler
   toolchain system-wide (`gcc-aarch64-linux-gnu`, `g++-aarch64-linux-gnu`,
   `libc6-dev:arm64`) as these ARE Multi-Arch safe.

3. **Private sysroot** — auto-detects native `-dev` packages with
   pkg-config files, resolves their cross-arch equivalents + transitive
   library dependencies (breadth-first, up to 20 levels deep), downloads
   `.deb` files, and extracts to `/tmp/cross-sysroot/<arch>/`. No sudo
   needed for this step. Handles usrmerge and GNU LD script path patching.

4. **Linker wrapper** — creates a wrapper script at
   `<sysroot>/bin/<triple>-gcc` that:
   - Forces `-fuse-ld=bfd` (mold can't cross-compile)
   - Adds `-L` flags for sysroot library directories
   - Adds `-Wl,-rpath-link` for transitive `.so` dependency resolution

5. **Environment variables** set per cross target:
   - `CC_<TARGET>`, `CXX_<TARGET>`, `AR_<TARGET>` — cross-compiler
   - `CARGO_TARGET_<TARGET>_LINKER` — linker wrapper path
   - `CFLAGS_<target>`, `CXXFLAGS_<target>` — `-fuse-ld=bfd` + sysroot includes
   - `PKG_CONFIG_PATH`, `PKG_CONFIG_SYSROOT_DIR`, `PKG_CONFIG_ALLOW_CROSS`
   - `CMAKE_PREFIX_PATH` — for cmake-based `-sys` crates
   - `LDFLAGS`, `CFLAGS`, `CXXFLAGS` cleared to prevent host flag leakage

See `docs/CI-LESSONS.md` for the full rationale and known gotchas.

## Developer Commands

Three commands for interacting with GitHub Actions from the terminal:

| Command | Purpose |
|---------|---------|
| `hyperi-ci trigger` | Dispatch a workflow run, optionally watch it |
| `hyperi-ci watch` | Poll a run to completion with exponential backoff |
| `hyperi-ci logs` | Download and filter run logs (by job, step, pattern) |

These wrap the `gh` CLI and replace the old bash scripts that did the
same thing in ~300 lines each.

## Init Command

`hyperi-ci init` replaces the old 1020-line `attach.sh`. It:

1. Detects language (or accepts `--language` override)
2. Reads existing project files to set smart defaults:
   - Python: detects build backend (hatch, setuptools, poetry, flit)
   - Rust: detects workspace vs single crate
   - TypeScript: detects package manager (pnpm, yarn, npm)
3. Generates `.hyperi-ci.yaml`, `Makefile`, `.github/workflows/ci.yml`,
   and `.releaserc.yaml`
4. Skips files that already exist (unless `--force`)
5. Detects deprecated config files (`.hypersec-ci.yaml`) and warns

## Design Principles

1. **No bash** — all logic is Python. `subprocess.run()` with list args
   only. 70% of old CI failures were bash syntax issues.

2. **Semantic release centric** — push to main, semantic-release creates
   tags and publishes. No manual version bumps.

3. **uv for everything** — venv, sync, lock, tool install, build.

4. **Cross-platform** — Linux (CI) and macOS (dev). Uses `pathlib`,
   `shutil.which()`, `sys.platform` — no hardcoded paths.

5. **Self-hosting** — hyperi-ci uses itself for its own CI pipeline.

## File Layout

```
hyperi-ci/
├── .github/workflows/
│   ├── ci.yml                 Self-hosting CI
│   ├── python-ci.yml          Reusable template
│   ├── rust-ci.yml            Reusable template
│   ├── ts-ci.yml              Reusable template
│   └── go-ci.yml              Reusable template
├── src/hyperi_ci/
│   ├── __init__.py            Package root, __version__
│   ├── cli.py                 Typer CLI entry point
│   ├── config.py              CIConfig, OrgConfig, loader
│   ├── detect.py              Language detection
│   ├── dispatch.py            Stage dispatcher
│   ├── common.py              Logging, subprocess, GH Actions helpers
│   ├── init.py                Project scaffolding
│   ├── gh.py                  GitHub CLI helpers
│   ├── trigger.py             Workflow trigger command
│   ├── watch.py               Run watch command
│   ├── logs.py                Log fetch command
│   └── languages/
│       ├── python/            quality, test, build, publish
│       ├── rust/              quality, test, build, publish
│       ├── typescript/        quality, test, build, publish
│       └── golang/            quality, test, build, publish
├── config/
│   ├── defaults.yaml          Default settings for all languages
│   └── org.yaml               Organisation URLs (JFrog, GitHub, GHCR)
├── test-projects/
│   ├── ci-test-rust-minimal/  Rust binary with C/C++ deps (librdkafka)
│   ├── ci-test-python-minimal/
│   ├── ci-test-ts-minimal/
│   └── ci-test-go-minimal/
├── tests/
│   └── unit/                  82 unit tests
├── docs/
│   └── DESIGN.md              This file
├── VERSION                    Source of truth for version
├── pyproject.toml             Package config
├── uv.lock                    Locked dependencies
└── .releaserc.yaml            Semantic release config
```
