{"id":50927816,"url":"https://github.com/three-cubes/fitness-engine","last_synced_at":"2026-06-17T01:02:31.543Z","repository":{"id":364704796,"uuid":"1268131902","full_name":"three-cubes/fitness-engine","owner":"three-cubes","description":"three-cubes-fitness: the shared fitness-function engine (runner, lib, CORE_RULES, mutation generator) for three-cubes repos (#499 Phase 4)","archived":false,"fork":false,"pushed_at":"2026-06-14T05:02:41.000Z","size":60,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-06-14T07:05:55.083Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/three-cubes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-06-13T07:13:50.000Z","updated_at":"2026-06-14T05:02:41.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/three-cubes/fitness-engine","commit_stats":null,"previous_names":["three-cubes/fitness-engine"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/three-cubes/fitness-engine","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/three-cubes%2Ffitness-engine","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/three-cubes%2Ffitness-engine/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/three-cubes%2Ffitness-engine/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/three-cubes%2Ffitness-engine/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/three-cubes","download_url":"https://codeload.github.com/three-cubes/fitness-engine/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/three-cubes%2Ffitness-engine/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34429493,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-16T02:00:06.860Z","response_time":126,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2026-06-17T01:02:27.320Z","updated_at":"2026-06-17T01:02:31.523Z","avatar_url":"https://github.com/three-cubes.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"# three-cubes-fitness\n\nShared architecture-fitness primitives for Three Cubes repositories\n(`kairix`, `tc-agent-zone`). This package is the **single source** for the\nhelper code those repos' fitness-function checks previously maintained as two\nparallel, slowly-drifting copies. Consuming it means a fix or a behaviour change\nlands once, not twice.\n\nIt ships these modules:\n\n- **`tc_fitness.lib`** — the merged check helpers:\n  - **baseline gating** (from kairix `scripts/checks/_arch_lib.py`):\n    `gate()`, `python_files()`, `main_entry()`, `repo_relative()`, `REPO_ROOT`.\n  - **agent-actionable emit / YAML** (from tc-agent-zone `scripts/checks/_lib/`):\n    `actionable()`, `emit_failures()`, `emit_pass()`, `load_yaml()`, `missing_keys()`.\n- **`tc_fitness.ratchet`** — the unified ratchet grammar: one override\n  min-length, one marker parser, one suppression grammar (see\n  *Drift reconciliation* below).\n- **`tc_fitness.runner`** *(v0.3.0)* — the catalogue-driven, repo-agnostic check\n  **runner**: in-process dispatch for python checks + guarded (optionally\n  parallel) subprocess dispatch for shell checks, the named verdict ledger,\n  `--all` / `--gate` / `--staged` modes, and the thin-consumer `main_cli` /\n  programmatic `run` API. Supported by `tc_fitness.catalogue` (the `RuleEntry`\n  schema), `tc_fitness.context` (the shared file-index + AST parse/walk cache),\n  and `tc_fitness.staged` (the sound per-rule staged selection). See *The\n  runner (v0.3.0)* below.\n\n## What's in the box\n\n```python\nfrom tc_fitness import (\n    # baseline gating (kairix surface)\n    gate, gate_keys, python_files, main_entry, repo_relative, REPO_ROOT,\n    # agent-actionable emit / YAML (tc-agent-zone surface)\n    actionable, remediation, emit_failures, emit_pass, load_yaml, missing_keys,\n    # unified ratchet primitives\n    OVERRIDE_MIN_REASON_LEN, make_override_re, parse_overrides, Override,\n    COVERAGE_OVERRIDE_RE, MUTATION_OVERRIDE_RE,\n    is_vague_reason, VAGUE_OVERRIDE_RE,\n    SUPPRESSION_PATTERNS, BARE_SUPPRESSION_PATTERNS,\n    contains_suppression, is_bare_suppression,\n)\n```\n\n\u003e **v0.2.0 is an additive, backward-compatible superset of v0.1.0.** Every\n\u003e v0.1.0 signature and behaviour is unchanged when the new optional parameters\n\u003e are left at their defaults. A consumer pinned to `@v0.1.0` keeps working\n\u003e unmodified; the additions (`gate_keys`, `remediation`, `actionable(..., run=)`,\n\u003e `is_vague_reason(..., min_len=)`, `parse_overrides(..., min_len=)`) exist to\n\u003e cover tc-agent-zone's check surface. See *What v0.2.0 adds* below.\n\n### Baseline gating\n\n```python\nfrom pathlib import Path\nfrom tc_fitness import gate, main_entry\n\n# Low-level: gate a pre-computed violation set against\n# .architecture/baseline/\u003cname\u003e-files.txt\nexit_code = gate(\"f26-core-no-provider-imports\", violations, REMEDIATION)\n\n# Convenience: scan roots, call a per-file predicate, gate the union.\ndef file_has_violation(path: Path) -\u003e bool: ...\nexit_code = main_entry(file_has_violation, \"f26\", REMEDIATION, \"kairix\")\n```\n\n`REPO_ROOT` defaults to the current working directory (the repo root when checks\nrun from `safe-commit.sh` / pre-commit / CI). Every gating helper also accepts an\nexplicit `repo_root=` keyword for test isolation or monorepo sub-trees.\n\n### Agent-actionable output\n\n```python\nfrom tc_fitness import actionable, emit_failures, emit_pass\n\nfails = [actionable(\"kairix/x.py:12 leaks a secret\", \"redact it\", \"re-run check_f15.py\")]\nif fails:\n    emit_failures(\"f15-no-secret-logging\", fails)  # → stderr\nelse:\n    emit_pass(\"PASS f15-no-secret-logging\")        # → stdout\n```\n\n### YAML loading\n\n```python\nfrom tc_fitness import load_yaml, missing_keys\n\ndata, err = load_yaml(Path(\"manifest.yaml\"))   # (data, None) | (None, \"error\")\nif err is None:\n    absent = missing_keys(data, (\"name\", \"version\"))\n```\n\n`load_yaml` imports PyYAML lazily and returns `(None, \"PyYAML missing\")` when it\nisn't installed, so the dependency is optional — install the `yaml` extra only if\nyou call it.\n\n## What v0.2.0 adds\n\nv0.2.0 extends the surface to cover tc-agent-zone's 116-check fleet — additively,\nso kairix's `@v0.1.0` pin needs no change. Four additions:\n\n### `actionable(what, fix, nxt, run=None)` — optional 3-marker form\n\n59 tc-agent-zone checks emit a `fix:/next:/run:` triple. `actionable` now takes an\noptional fourth `run` argument; supplying it appends `; run: \u003crun\u003e`. With `run`\nomitted (the default), the output is **byte-identical** to v0.1.0's 2-marker\n`\u003cwhat\u003e; fix: \u003cfix\u003e; next: \u003cnxt\u003e`.\n\n```python\nactionable(\"X broke\", \"do Y\", \"rerun Z\")                  # X broke; fix: do Y; next: rerun Z\nactionable(\"X broke\", \"do Y\", \"rerun Z\", \"python check.py\")  # ...; next: rerun Z; run: python check.py\n```\n\n### `remediation(fix, nxt, run, *, passing=None, forbidden=None)` — multiline block\n\n30 tc-agent-zone checks emit a multiline F21-shape remediation block: the three\naction markers on their own lines, optionally followed by a `Pass` and a\n`Forbidden` example. `remediation` formats that block (no trailing newline),\nready to `print()`.\n\n```python\nprint(remediation(\n    \"redact the secret\", \"re-run the check\", \"python scripts/checks/check_f15.py\",\n    passing='logger.info(\"token redacted\")',\n    forbidden='logger.info(f\"token={token}\")',\n))\n# fix: redact the secret\n# next: re-run the check\n# run: python scripts/checks/check_f15.py\n# Pass: logger.info(\"token redacted\")\n# Forbidden: logger.info(f\"token={token}\")\n```\n\n### `gate_keys(name, current, remediation, *, baseline_suffix=\"-ids.txt\")` — string-keyed ratchet\n\n13 tc-agent-zone checks ratchet a baseline whose KEY is a logical id (`-ids.txt`,\ne.g. `F30:my_tool`) or a path-glob (`-paths.txt`, e.g. `kairix/**/web/static/*`),\nNOT a working-tree file path. `gate()` keys on `Path` objects and *relativises\nabsolute paths* under `repo_root` — wrong for opaque string keys. `gate_keys` is\nits string-keyed sibling: same net-new-fails / shrinks-only / grandfather\nsemantics and the same exit-code contract, but keys are treated as opaque\nstrings (no `Path` coercion). `baseline_suffix` selects `-ids.txt` (default) or\n`-paths.txt`.\n\n```python\nexit_code = gate_keys(\"f30\", {\"F30:my_new_tool\"}, REMEDIATION)                     # → f30-ids.txt\nexit_code = gate_keys(\"f89\", static_globs, REMEDIATION, baseline_suffix=\"-paths.txt\")  # → f89-paths.txt\n```\n\n### `min_len` floor override on the ratchet vagueness check\n\n`is_vague_reason` and `parse_overrides` now take an optional keyword-only\n`min_len`, defaulting to `OVERRIDE_MIN_REASON_LEN` (=40). tc-agent-zone's shell\ndirectives use a 10-char floor, so its checks call `min_len=10`. The constant is\nunchanged and the default-arg behaviour is byte-identical to v0.1.0 — the lower\nfloor is a per-call choice, never a mutation of the shared default kairix depends\non.\n\n```python\nis_vague_reason(\"x\" * 10)               # True  — vague at the default 40-floor\nis_vague_reason(\"x\" * 10, min_len=10)   # False — clears taz's 10-floor\n```\n\n### Discovery helpers (`REPO_ROOT` / `python_files` / `repo_relative`) cover taz unchanged\n\ntc-agent-zone reimplements `REPO_ROOT = Path(__file__).resolve().parents[2]` inline\nin each check. The package's CWD-anchored `REPO_ROOT = Path.cwd()` is the correct\nshared replacement: it resolves to the consumer repo root in the `safe-commit.sh`\n/ pre-commit / CI invocation paths (where checks run *from* the repo root), and\nevery gating helper accepts an explicit `repo_root=` for the rare case that\nassumption doesn't hold. No additive gap was found here — `python_files`,\n`repo_relative`, and `main_entry` already cover taz's `.py` discovery.\n\n## How repositories consume it\n\nPin to a tag in your `pyproject.toml` (git install — no PyPI publish):\n\n```toml\n[project.optional-dependencies]\ndev = [\n  # kairix stays on v0.1.0 (the additions are a no-op for it); tc-agent-zone\n  # pins v0.2.0 for the gate_keys / remediation / run-marker / min_len surface.\n  \"three-cubes-fitness @ git+https://github.com/three-cubes/fitness-engine.git@v0.2.0\",\n]\n```\n\nor, equivalently, on the command line:\n\n```bash\npip install \"three-cubes-fitness @ git+https://github.com/three-cubes/fitness-engine.git@v0.2.0\"\n```\n\nAlways pin a tag, never `@main` — the version is the contract the gates depend on.\nBecause v0.2.0 is an additive superset, a consumer already pinned to `@v0.1.0`\nkeeps working unchanged; bump to `@v0.2.0` only when you need the new surface.\n\n## The runner (v0.3.0)\n\nv0.3.0 adds a **single, common, repo-agnostic check runner** that both kairix\nand tc-agent-zone point their `run_checks.py` at — the structural keystone of\n\"one common fitness process for all repos\". It is purely additive: the v0.1.0 /\nv0.2.0 lib + ratchet surface is untouched.\n\n### Thin-consumer API\n\nA consumer repo declares its own `tuple[RuleEntry, ...]` catalogue and its check\nmodules, then its `run_checks.py` collapses to:\n\n```python\nfrom tc_fitness.runner import main_cli\nfrom .catalogue import RULES\nraise SystemExit(main_cli(RULES))\n```\n\n`main_cli` parses `--all` / `--staged` / `--gate \u003cid\u003e` and returns the process\nexit code. For tests and embedding there is a programmatic\n`run(rules, *, mode, staged_files=None, repo_root=None, ...) -\u003e Verdicts`.\n\n### What the runner does\n\n- **In-process dispatch** for python checks (`check_\u003cx\u003e.py` exposing\n  `main() -\u003e int`): the module is imported and `main()` is called inside one\n  process, sharing a single `CheckContext` whose AST cache parses every file at\n  most once. A check that raises is isolated into a FAIL — one crash never\n  aborts the ledger.\n- **Guarded subprocess dispatch** for `*.sh` shell detectors. Sequential by\n  default (byte-identical interleaving with kairix's runner); pass\n  `parallel_subprocess=True` to run them on a `ThreadPoolExecutor` with output\n  buffered and replayed in catalogue order (tc-agent-zone's parallelism).\n- **The named verdict ledger** — a `run [id]` line and a `PASS [id]` / `FAIL\n  [id]` verdict per rule, then the aggregate verdict; the format kairix's F83\n  gate-runner contract depends on.\n- **`--all`** (dispatchable AND `run_all`), **`--gate \u003cid\u003e`** (one rule), and\n  **`--staged`** — the *sound* per-rule staged selection (file-local /\n  relational / always-run), single-sourced on each `RuleEntry`. The hard\n  invariant is **no false negative on a staged change**: when scope can't be\n  resolved, the rule runs (fail-safe).\n- A **paved-road footer hook** so a failing rule can point an agent at the\n  consumer's own query surface.\n\n### Repo-agnostic by injection\n\nThe runner never imports `kairix` or `tc-agent-zone`. Repo-specific behaviour is\ninjected through `RunnerConfig` seams:\n\n| Seam | Purpose |\n|---|---|\n| `repo_root` / `checks_dir` | where the repo + its check scripts live |\n| `scope_resolver` | derive a rule's staged scope from its check script (the repo's FitnessRule-aware hook) when `staged_scope` is unset |\n| `enumeration_narrower` | the repo's extra file-index narrowing for file-local staged runs, layered on top of the package-level `tc_fitness.python_files` narrowing |\n| `conditional_check` | govern a `subprocess_arg_env` rule's runtime arg + exact skip text (e.g. a coverage check that needs a Cobertura XML) |\n| `paved_road_footer` | the affordance line printed under a FAIL |\n| `parallel_subprocess` | run shell checks on a thread pool |\n\n`RuleEntry.id` is id-agnostic — it accepts kairix's `\"F26\"` and tc-agent-zone's\n`\"no-duplicate-string\"` style equally; the runner only uses it as a ledger label\nand the `--gate` selector. `category` / `scope` are open `str` fields each repo\ncurates its own closed vocabulary for.\n\n### Drop-in for kairix's local runner\n\nThe runner is byte-identical to kairix's current `scripts/checks/run_checks.py`:\nwiring kairix's `_check_context` / `_staged_selection` / `_rule_catalogue` into\n`main_cli` via the seams above reproduces the **same verdicts and the same\nnamed-ledger text** for `--all`, `--gate`, and `--staged` (verified by diffing\nthe two runners' output over the full catalogue, including file-local staged\nnarrowing). kairix's migration is therefore mechanical: translate its `RuleEntry`\nrows to the package schema, pass its three helper modules as hooks, and collapse\n`run_checks.py` to the three-line form.\n\n## Drift reconciliation\n\nBoth repos independently grew the same ratchet gates (coverage, mutation-survival,\nsonar-quality) and drifted on three details. This package resolves each to one\nbehaviour. The merged version is the **superset-correct** choice — it satisfies\nevery call pattern either repo relied on.\n\n### 1. Override-rationale minimum length → **40 chars, strictly-less-than**\n\ntc-agent-zone's coverage ratchet treated a rationale as \"vague\" below **20**\nchars; its mutation ratchet used **40**. The *remediation text both gates printed\nto operators already said \"≥40 chars\"* — so the 20-char path was a latent bug\n(code disagreed with its own message). Reconciled to `OVERRIDE_MIN_REASON_LEN =\n40`, and `len(reason) \u003c 40` is vague. Stricter of the two, and matches the\ndocumented contract. **Mutation's behaviour won.**\n\n### 2. Suppression-pattern list → **the superset, one grammar**\n\ntc-agent-zone added `NOSONAR` (and the `//` C-style variants) to the marker set\nkairix originally tracked, and the regex copies had possessive-quantifier\nvariations. Reconciled to the **union** of every marker any repo tracked:\n`SUPPRESSION_PATTERNS` (substring markers for \"flag any line containing one\") and\n`BARE_SUPPRESSION_PATTERNS` (end-of-line regexes for \"bare suppression, no\nrationale\"). `NOSONAR` is in both. **The superset won** — dropping any marker\nwould silently un-gate a suppression one repo was catching.\n\n### 3. Override-marker separator → **em-dash *and* hyphen both accepted**\n\ntc-agent-zone's override-line regex accepted an em-dash **or** an ASCII hyphen as\nthe path↔reason separator (`[—-]++`); some kairix copies were em-dash-only.\nReconciled to **accept both** (`make_override_re` builds the parser; the\nseparator class is `[—-]++`, possessive to avoid backtracking). A commit that\nwrote `coverage-ratchet-acknowledged: path - reason` with a plain hyphen must keep\nclearing the ratchet, and so must the em-dash form. **The superset (tc-agent-zone's\nlooser parse) won.**\n\n## Development\n\n```bash\npip install -e \".[dev]\"\npython -m pytest tests/ -q\n```\n\nThe test suite is the proof the merge is behaviour-preserving: `tests/test_lib.py`\npins the call patterns each repo's checks depend on, and `tests/test_ratchet.py`\npins the three reconciled drift decisions (40-char threshold; em-dash AND hyphen;\n`NOSONAR` in the suppression set).\n\nThe package is self-contained: pure stdlib at runtime, with PyYAML as an optional\nextra. It must never import from `kairix` or `tc-agent-zone` — it is the shared\ncore both depend on.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthree-cubes%2Ffitness-engine","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fthree-cubes%2Ffitness-engine","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fthree-cubes%2Ffitness-engine/lists"}