Container-first pipeline for collecting repository metrics with:
- CK (static code metrics)
- CM (change metrics)
- GitHub API metadata
- SonarQube metrics (
sonarextractor)
configs/— YAML configuration files for runsconfigs/repositories/— small example repository lists for e2e runsresults/— output directory (mounted from host)josseph/— Python package and pipeline codedocker-compose.yml—sonarqube+jossephservicesDockerfile— runtime image forjosseph
- Docker Desktop / Docker Engine with Compose
- A GitHub token is needed only when using the
githubextractor:
export GITHUB_TOKEN=your_token_here- Linux only: SonarQube requires a kernel parameter increase for Elasticsearch:
sudo sysctl -w vm.max_map_count=524288To make it persistent across reboots, add vm.max_map_count=524288 to /etc/sysctl.conf. This is not required on macOS or Windows (Docker Desktop handles it automatically).
The checked-in quick-start config runs CK and CM on a pinned revision of
junit-team/junit4. It does not need a GitHub token or SonarQube:
docker compose run --rm --build --no-deps josseph configs/quickstart.yamlThe config is intentionally small and explicit:
tools:
- ck
- cm
workers: 1
repositories: repositories/quickstart.yamlOn success, results/junit-team@junit4/ contains paired .parquet and .json
artifacts for both tools, and results/runs/<run-id>/summary.json reports
"status": "success". See the checked-in sample run.
The default config runs all four extractors. Copy the environment template,
set GITHUB_TOKEN, and run it; Compose builds the image and starts SonarQube:
cp .env.example .env
# Edit GITHUB_TOKEN in .env, then:
docker compose run --rm --build josseph configs/config.yamlFor a fresh local SonarQube container, docker-compose.yml uses a two-step
bootstrap:
SONAR_ADMIN_DEFAULT_PASSWORD=adminis the upstream factory default for a new local containerSONAR_ADMIN_PASSWORD=Admin#Password12345is the policy-compliant password JOSSeph sets on first run
Override these values in .env only for your local container lifecycle. Keep
SONAR_ADMIN_DEFAULT_PASSWORD aligned with the current admin password of that
local SonarQube instance, and ensure SONAR_ADMIN_PASSWORD satisfies the
current SonarQube password policy.
The checked-in full config is configs/config.yaml:
tools:
- ck
- cm
- github
- sonar
workers: 1
repositories: repositories/one-repo.yamlThe container reads /app/configs/config.yaml by default.
Supported keys:
tools: optional list of extractors (ck,cm,github,sonar); omitted means allextractor_settings: optional mapping of extractor name to extractor-specific settingsworkers: optional positive integer; omitted means CPU countgithub_token: optional token value; if omitted,GITHUB_TOKENfrom the environment is usedrepositories: path to a YAML file whose root is a sequence of repository entries; entries may include an optionalcommit
Path in repositories is resolved relative to the YAML file.
Pinned commits are only supported when they are reachable from the repository's
default branch.
Results are written to:
results/<owner>@<repo>/ck.parquetresults/<owner>@<repo>/cm.parquetresults/<owner>@<repo>/github.parquetresults/<owner>@<repo>/sonar.parquetresults/<owner>@<repo>/*.json(metadata withcommit_hash,requested_commit_hash,metric_binding, andcollected_at_utc)results/runs/<run-id>/summary.json(pipeline-level run metadata)
Results are stored per repository, not per repository revision. A later run for the same repository can overwrite earlier artifacts; the requested commit is captured in per-tool metadata and in the run summary.
A metric is considered complete only when both files exist:
results/<owner>@<repo>/<tool>.parquetresults/<owner>@<repo>/<tool>.json
- Rebuild image after code changes:
docker compose build josseph- Stop SonarQube:
docker compose stop sonarqube- Remove SonarQube container/network:
docker compose down- This setup is container-first for reproducibility.
- Cloned repositories live in
workspace/projects/by default and are mounted into the container from./workspace. - Analysis helpers and operational scripts may live outside the repo in a sibling tools directory.
GITHUB_TOKENis passed from host environment intojossephviadocker-compose.yml.sonaranalysis may be slower on large repositories.- Sonar Scanner is downloaded from the official SonarSource CDN at Docker build time, pinned to version
7.0.2.4839(seethird_party/sonar-scanner/README.md).
- Runtime dependencies are pinned in
requirements.txt. - Unknown tool names fail fast (
tools:validates against the registered extractors). - The process exit code is strict:
0: all repositories processed without top-level failures1: one or more repositories failed during analysis2: invalid user input/configuration (for example, unknown tool)
- Cached results are reused only when both
<tool>.parquetand<tool>.jsonare present; for revision-bound extractors with a pinned commit, thecommit_hashrecorded in<tool>.jsonmust also match the requested commit. observation-boundextractors are still cacheable by file presence; use--forceto recollect them.
JOSSeph relies on the following third-party tools (CK and CM are vendored; Sonar Scanner is downloaded at image build time; SonarQube runs as a Docker service). All licenses are compatible with the project's MIT license.
| Tool | Version | License | Source |
|---|---|---|---|
| CK | 0.7.1 | Apache 2.0 | github.com/mauricioaniche/ck |
| CM | — | Apache 2.0 | github.com/mauricioaniche/change-metrics |
| Sonar Scanner | 7.0.2.4839 | LGPL 3.0 | github.com/SonarSource/sonar-scanner-cli |
| SonarQube (Docker) | 25.5.0.107428-community | LGPL 3.0 | github.com/SonarSource/sonarqube |
To add a new metrics source:
- Add a new module under
josseph/metrics/extractors/, for examplemy_extractor.py. - In that module:
- define
EXTRACTOR_NAME = "my_extractor" - implement
build_extractor(context, settings) - implement an extractor class that subclasses
MetricExtractor
- define
- List the extractor name under
tools:in the YAML config. - Set
metric_bindingon the extractor class:- default is
revision-bound - use
observation-boundfor time-dependent extractors
- default is
- Pass extractor-specific parameters under
extractor_settings:when needed.
Example:
tools:
- github
- my_extractor
extractor_settings:
my_extractor:
threshold: 10Minimal extractor module:
from josseph.metrics.abstract_extractor import MetricExtractor
EXTRACTOR_NAME = "my_extractor"
class MyExtractor(MetricExtractor):
requires_checkout = False
metric_binding = "revision-bound"
def __init__(self, threshold: int) -> None:
self.threshold = threshold
def run(self, target):
return [{"threshold": self.threshold, "repo": target.project_name}]
def build_extractor(context, settings):
threshold = int(settings.get("threshold", 10))
return MyExtractor(threshold=threshold)