Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 29 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ jobs:
pip install pytest-timeout

- name: Lint (ruff)
# --exit-zero removed Track A2 (0 standing violations measured 2026-07-29,
# see LEDGER.md's Squish-Lint-Job-Fix-1) — this step now genuinely blocks.
run: |
ruff check squish/ tests/ --output-format github --exit-zero
ruff check squish/ tests/ --output-format github

- name: Verify version consistency
run: |
Expand Down Expand Up @@ -138,6 +140,19 @@ jobs:

lint-only:
# Runs on Linux for fast feedback on PRs from forks (no Apple Silicon needed)
#
# Both steps below used to be structurally unable to fail: ruff check carried
# --exit-zero (forces exit 0 regardless of findings) and mypy check was wrapped in
# `|| true` (swallows its exit code). Neither shape contains the string
# "continue-on-error", so a naive audit of konjo-gate.yml alone missed both — this
# job was the repo's actual decorative lint gate. Fixed Track A2 (squish's first
# kiban connection): ruff check is real and blocking now (0 standing violations
# measured 2026-07-29, so flipping it costs nothing); mypy check is ratcheted
# against its measured 215-error baseline (.konjo/mypy-ceiling.txt) via the same
# generic ratchet gate lopi's coverage-floor gate pioneered
# (.konjo/scripts/ratchet_check.py) rather than flipped hard on day one, which
# would just red every PR against a 215-error backlog no single PR caused. See
# LEDGER.md's Squish-Lint-Job-Fix-1.
name: Lint (ubuntu)
runs-on: ubuntu-latest
steps:
Expand All @@ -152,10 +167,17 @@ jobs:
run: pip install ruff mypy

- name: ruff check
run: ruff check squish/ tests/ --output-format github --exit-zero
run: ruff check squish/ tests/ --output-format github

- name: mypy check
run: mypy squish/ --ignore-missing-imports --no-error-summary || true
- name: mypy check (ratcheted against the measured baseline)
run: |
set +e
mypy squish/ --ignore-missing-imports --no-error-summary 2>&1 | tee mypy-out.log
COUNT=$(grep -c ": error:" mypy-out.log || true)
set -e
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name mypy-errors --measured "$COUNT" \
--file .konjo/mypy-ceiling.txt

test-linux:
name: Test Linux (Python ${{ matrix.python-version }})
Expand All @@ -181,8 +203,10 @@ jobs:
pip install pytest-timeout

- name: Lint (ruff)
# --exit-zero removed Track A2 (0 standing violations measured 2026-07-29,
# see LEDGER.md's Squish-Lint-Job-Fix-1) — this step now genuinely blocks.
run: |
ruff check squish/ tests/ --output-format github --exit-zero
ruff check squish/ tests/ --output-format github

- name: Run tests (Linux — MLX tests auto-skipped)
# MLX-dependent tests skip automatically on Linux via pytest.importorskip.
Expand Down
141 changes: 114 additions & 27 deletions .github/workflows/konjo-gate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,28 @@ jobs:

# ══════════════════════════════════════════════════════════════════════════
# Gate 1 — Static Analysis
#
# Track A2 triage (2026-07-29, see LEDGER.md's Squish-Gate-Triage-1 for the full
# per-step disposition table): every step below used to carry
# `continue-on-error: true`, so this whole job always reported "success" to the
# final konjo-gate job regardless of findings.
# - ruff lint: PROMOTED to real blocking. 0 standing violations measured
# 2026-07-29 (10 pre-existing findings fixed/exempted this same sprint —
# 4 auto-fixed, 6 covered by a new per-file-ignore extending the existing
# tests/**-BLE001 precedent to benchmarks/**, demo/**, scripts/**).
# - ruff format, vulture, bandit: large pre-existing backlogs (332 files / 108
# / 67 findings respectively — mass-reformatting or clearing all of them is
# its own sprint, out of this one's non-goals). RATCHETED instead of left
# soft: each now runs for real and fails only on regression above its
# measured baseline (.konjo/*-ceiling.txt), via the same generic ratchet
# gate lopi's coverage-floor gate pioneered
# (.konjo/scripts/ratchet_check.py). A step that could never fail before now
# genuinely can.
# - bandit's --exclude also had a real, separate bug fixed here: `.venv,venv,
# tests` (no leading `./`) never matched bandit's own `./`-prefixed walk
# paths, so it was silently scanning tests/ too (134 findings, not the real
# 67 in squish/ + benchmarks/ + scripts/ + demo/). Fixed to `./.venv,
# ./venv,./tests`, confirmed locally to actually exclude tests/ now.
# ══════════════════════════════════════════════════════════════════════════
static:
name: "G1 · Static Analysis"
Expand All @@ -26,20 +48,57 @@ jobs:
- name: Install tools
run: pip install ruff mypy vulture bandit --quiet
- name: ruff lint
continue-on-error: true
run: ruff check .
- name: ruff format
continue-on-error: true
run: ruff format --check .
- name: vulture — dead code
continue-on-error: true
run: vulture . --min-confidence 80
- name: bandit — security
continue-on-error: true
run: bandit -r . -ll -q --exclude .venv,venv,tests
- name: ruff format (ratcheted against the measured baseline)
run: |
set +e
COUNT=$(ruff format --check . 2>&1 | grep -c "^Would reformat")
set -e
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name ruff-format-files --measured "$COUNT" \
--file .konjo/ruff-format-ceiling.txt
- name: vulture — dead code (ratcheted against the measured baseline)
run: |
set +e
COUNT=$(vulture . --min-confidence 80 | wc -l)
set -e
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name vulture-findings --measured "$COUNT" \
--file .konjo/vulture-ceiling.txt
- name: bandit — security (ratcheted against the measured baseline)
# --exclude paths must be `./`-prefixed to match bandit's own walk paths
# (see the job-level comment above — plain "tests" never matched).
run: |
set +e
COUNT=$(bandit -r . -ll -q --exclude ./.venv,./venv,./tests 2>&1 | grep -c "^>> Issue")
set -e
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name bandit-findings --measured "$COUNT" \
--file .konjo/bandit-ceiling.txt

# ══════════════════════════════════════════════════════════════════════════
# Gate 2 — Tests + Coverage (≥ 80%)
#
# Track A2 triage (2026-07-29): KEPT SOFT, not promoted — owner: squish
# maintainers, revisit-by 2026-09-30. This job is a duplicate of ci.yml's real
# `coverage` job (macos-14, MLX-aware, publishes the badge) with two concrete
# bugs that would make promoting it today just red every PR on a false signal,
# not a real one:
# 1. It runs on ubuntu-latest with no mlx install and none of the
# Metal-unguarded-import exclusions ci.yml's own `test`/`test-linux`/
# `coverage` jobs carry (tests/test_sqint2_linear.py etc.) — confirmed by
# reading tests/conftest.py's own Layer-2 comment: those files import
# mlx.core at module level with no guard, which collection-errors the
# instant GITHUB_ACTIONS is set and mlx isn't installed.
# 2. `--cov=.` measures the whole repo (benchmarks/, demo/, scripts/, tests/
# themselves) instead of `--cov=squish` the way ci.yml's real coverage job
# correctly scopes it, so even a clean run would report a materially
# different, misleadingly low percentage against the same 80% bar.
# Fixing both is real, scoped work for a maintainer to decide deliberately
# (rewrite the ignore list, add an mlx install, rescope --cov) or to delete this
# duplicate outright in favor of ci.yml's real one — not a call this sprint
# makes unilaterally per its own non-goal ("connect what exists, don't rebuild
# CI"). Recorded here instead of silently left implying it blocks.
# ══════════════════════════════════════════════════════════════════════════
coverage:
name: "G2 · Tests + Coverage"
Expand All @@ -56,6 +115,8 @@ jobs:
pip install -e ".[test]" --quiet 2>/dev/null || \
pip install -e . --quiet 2>/dev/null || true
- name: Run tests with coverage
# KEEP SOFT — see the job-level comment above (owner: squish maintainers,
# revisit-by 2026-09-30). Known-broken as configured on ubuntu-latest.
continue-on-error: true
run: |
python -m pytest tests/ \
Expand All @@ -65,6 +126,7 @@ jobs:
-x -q
- name: Coverage gate
if: always()
# KEEP SOFT — tied 1:1 to "Run tests with coverage" above; same reason.
continue-on-error: true
run: |
python3 -c "
Expand All @@ -84,6 +146,19 @@ jobs:

# ══════════════════════════════════════════════════════════════════════════
# Gate 3 — Mutation Testing (PRs only)
#
# Track A2 triage (2026-07-29): KEPT SOFT — owner: squish maintainers,
# revisit-by 2026-09-30. Unlike the other soft steps in this file, this one's
# `continue-on-error` was already load-bearing for a documented, sound reason
# (see the step's own comment below): a full mutmut run on this codebase's size
# routinely exceeds the GitHub Actions 8-minute step timeout, and a SIGKILL'd
# step fails the job even under continue-on-error, so the 420s internal cap +
# partial-results tail is the real mitigation — continue-on-error just covers
# the remaining risk of a genuine mutation survival past the cap. Promoting
# this to hard-blocking needs a real mutation-survival threshold measured on a
# completed run first, which this soft step's own timeout risk prevents
# getting today; that measurement is real, scoped follow-up work, not
# something to guess at here.
# ══════════════════════════════════════════════════════════════════════════
mutation:
name: "G3 · Mutation Testing"
Expand All @@ -99,6 +174,7 @@ jobs:
pip install mutmut pytest --quiet
pip install -e . --quiet 2>/dev/null || true
- name: Run mutation testing
# KEEP SOFT — see the job-level comment above.
continue-on-error: true
timeout-minutes: 8
run: |
Expand All @@ -111,6 +187,12 @@ jobs:

# ══════════════════════════════════════════════════════════════════════════
# Gate 4 — Complexity + Size + DRY
#
# Track A2 triage (2026-07-29): Complexity gate, DRY check, and Documentation
# gate all RATCHETED against their measured baselines (146 / 99 / 31.4%
# respectively) instead of left soft — same reasoning and mechanism as G1
# above (.konjo/scripts/ratchet_check.py). File size gate was already the one
# step in this whole file with no continue-on-error and needed no change.
# ══════════════════════════════════════════════════════════════════════════
complexity:
name: "G4 · Complexity + Size + DRY"
Expand All @@ -122,23 +204,21 @@ jobs:
python-version: "3.11"
- name: Install tools
run: pip install radon interrogate --quiet
- name: Complexity gate
continue-on-error: true
- name: Complexity gate (ratcheted against the measured baseline)
run: |
set +e
COUNT=$(radon cc . -n C --json 2>/dev/null \
| python3 -c "
import json, sys
d = json.load(sys.stdin)
total = sum(len(v) for v in d.values())
print(total)
" 2>/dev/null || echo 0)
set -e
echo "Functions with cyclomatic complexity > 10: $COUNT"
if [ "$COUNT" -gt 0 ]; then
radon cc . -n C -s
echo "::error::$COUNT function(s) above complexity grade C."
exit 1
fi
echo "Complexity: all functions grade C or better ✓"
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name complexity-grade-c-plus --measured "$COUNT" \
--file .konjo/complexity-ceiling.txt
- name: File size gate
run: |
# BLOCKING for new files; existing oversized files are grandfathered
Expand All @@ -161,22 +241,29 @@ jobs:
exit 1
fi
echo "File sizes: all new files within 500-line limit ✓ (allowlisted legacy files exempt)"
- name: DRY check
continue-on-error: true
- name: DRY check (ratcheted against the measured baseline)
run: |
python3 .konjo/scripts/dry_check.py \
--threshold 0.85 \
--min-lines 20 \
--warn-only \
--report dry_report.json 2>&1
COUNT=$(python3 -c "import json; d=json.load(open('dry_report.json')); print(d['count'])")
if [ "$COUNT" -gt 0 ]; then
echo "::error::$COUNT DRY violation(s). Abstract into shared functions."
exit 1
python3 .konjo/scripts/ratchet_check.py \
--mode ceiling --name dry-violations --measured "$COUNT" \
--file .konjo/dry-ceiling.txt
- name: Documentation gate (ratcheted against the measured baseline)
run: |
set +e
PCT=$(interrogate . 2>&1 | grep -oE "actual: [0-9.]+" | grep -oE "[0-9.]+")
set -e
if [ -z "$PCT" ]; then
echo "::error::Could not parse interrogate's actual coverage percent from its output."
exit 2
fi
echo "DRY check: no violations ✓"
- name: Documentation gate
continue-on-error: true
run: interrogate . --fail-under 80 -q 2>/dev/null || true
python3 .konjo/scripts/ratchet_check.py \
--mode floor --name docstring-coverage --measured "$PCT" \
--file .konjo/docstring-floor.txt

# Gate 5 — Adversarial Review — DISABLED in CI
# Run locally: git diff HEAD~1 | python3 .konjo/scripts/konjo_review.py
Expand Down
59 changes: 59 additions & 0 deletions .github/workflows/konjo-gates.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
# .github/workflows/konjo-gates.yml
# squish's first connection to kiban's own `konjo-gates` orchestrator (Track A2,
# 2026-07-29). Follows kiban's templates/repo-ci.yml pattern (the same shape vectro's
# real, genuinely-blocking konjo-gates.yml uses) rather than konjo-gate.yml's own
# repo-native G1-G4 jobs, which stay repo-native by design — see LEDGER.md's
# Squish-Gate-Triage-1. This is net-new: adds a job, deletes nothing that already
# existed in squish's CI.
#
# CI never reads ~/.konjo; the gate logic and eval cassettes come from the installed,
# version-pinned kiban package. Pin KIBAN_REF to a tag so a kiban change rolls out to
# squish on a deliberate schedule, not the instant a new kiban tag ships — bump this
# together with .konjo/kiban.ref, never one without the other (see CLAUDE.md's Pinning
# section, mirroring lopi's convention).

name: konjo-gates

on:
pull_request:
push:
branches: [main]

env:
KIBAN_REF: "v1.9.0" # matches .konjo/kiban.ref — bump both together.

jobs:
gates:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # konjo-gates diffs against the base ref

- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: "3.11"

# The repo:ruff / repo:ruff-format / repo:mypy / repo:vulture / repo:bandit gates
# shell out to these tools directly (confirmed: konjo_gates_py's _TOOL_SCOPE /
# _TOOL_BIN tables dispatch all five for real under SCOPE_PYTHON) — install them
# on PATH first, the same working pattern konjo-gate.yml's own G1 job already
# uses, so a PR touching squish/** or tests/** doesn't just error "tool not
# installed" on its first run.
- name: Install Python gate tools
run: pip install ruff mypy vulture bandit --quiet

# Install the whole kiban distribution at the pin. It ships the real engine (lib,
# evals) with the konjo-gates entry point and the eval cassettes, so the
# orchestrator imports one source of truth and the replay self-test needs no
# model.
- name: Install pinned kiban
run: pip install "kiban @ git+https://github.com/konjoai/kiban.git@${KIBAN_REF}"

# konjo-gates writes a per-gate progress heartbeat to stderr, so this step is
# never silent even while a slower gate (mutation, prove) runs. Add --verbose to
# also stream the exact scanner argv and each HEAD/base scan pass with its
# duration.
- name: Run gates against the repo profile
run: konjo-gates --profile .konjo/profile.yml --base "origin/${{ github.base_ref || 'main' }}"
10 changes: 10 additions & 0 deletions .konjo/bandit-ceiling.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
# Konjo ratchet ceiling — bandit medium+high findings (-ll, tests/ excluded).
# Never regress above this count; ratchet it down as findings are resolved.
# Seeded 2026-07-29 (Track A2): measured via
# `bandit -r . -ll -q --exclude ./.venv,./venv,./tests`, AFTER fixing the 2
# real High-severity findings this same sprint found (squishd.py _model_key's
# SHA1 and server.py's _system_fingerprint MD5 — both non-cryptographic ID
# hashes, both cleared with usedforsecurity=False, zero behavior change).
# Pre-fix baseline was 69 (3 High before the tests-exclude bug was fixed in
# this sprint's own measurement pass; 2 High in production code after).
67
4 changes: 4 additions & 0 deletions .konjo/complexity-ceiling.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Konjo ratchet ceiling — radon cyclomatic-complexity grade C+ function count.
# Never regress above this count; ratchet it down as functions are simplified.
# Seeded 2026-07-29 (Track A2): measured via `radon cc . -n C --json`.
146
4 changes: 4 additions & 0 deletions .konjo/docstring-floor.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# Konjo ratchet floor — interrogate docstring-coverage percent.
# Never regress below this value; ratchet it up as docstrings are added.
# Seeded 2026-07-29 (Track A2): measured via `interrogate .` (target: 80%).
31.4
5 changes: 5 additions & 0 deletions .konjo/dry-ceiling.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Konjo ratchet ceiling — dry_check.py duplicate-block violation count.
# Never regress above this count; ratchet it down as duplication is removed.
# Seeded 2026-07-29 (Track A2): measured via
# `dry_check.py --threshold 0.85 --min-lines 20`.
99
1 change: 1 addition & 0 deletions .konjo/kiban.ref
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
v1.9.0
8 changes: 8 additions & 0 deletions .konjo/mypy-ceiling.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Konjo ratchet ceiling — mypy error count (squish/, --ignore-missing-imports).
# Never regress above this count; ratchet it down as errors are fixed.
# Seeded 2026-07-29 (Track A2): measured via
# `mypy squish/ --ignore-missing-imports --no-error-summary`.
# This is the backlog that made ci.yml's lint-only job's mypy step
# structurally unable to fail (`|| true`) before this sprint. See
# LEDGER.md's Squish-Lint-Job-Fix-1.
215
Loading
Loading