From fec00ae7f0dca5b47544be64b9b1ad43e7a40441 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Tue, 5 May 2026 00:21:24 +0200 Subject: [PATCH 1/5] ci: add GitHub Actions workflow that runs the unittest suite (closes #13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit There was no CI on this repository — 137 unit tests in tests/ were only ever run when a developer remembered to run them locally. A regression that broke CLI parity, exclusion rules, exporter output, alias inference, or search filtering could land on master with no gate. New workflow `.github/workflows/tests.yml`: - Triggers on every push to master and every pull request. - Single ubuntu-latest runner, Python 3.12. - Installs only what the tests need (flask, fpdf2). pywebview from requirements.txt is the desktop-launcher dep and pulls GTK / Qt system packages — out of scope for the unittest suite, so it is deliberately omitted from the CI install. The unittest suite imports neither. - Runs `python -m unittest discover tests -v`. Local sanity-check with the same command on Python 3.12: 137/137 OK. --- .github/workflows/tests.yml | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 .github/workflows/tests.yml diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml new file mode 100644 index 0000000..6e53a02 --- /dev/null +++ b/.github/workflows/tests.yml @@ -0,0 +1,30 @@ +name: Tests + +on: + push: + branches: [master] + pull_request: + +jobs: + unittest: + name: Unit tests + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Set up Python + uses: actions/setup-python@v5 + with: + python-version: '3.12' + + - name: Install runtime + test dependencies + # Only what the tests actually exercise. `pywebview` from requirements.txt + # is the desktop-launcher dep and pulls GTK / Qt system packages on Linux + # — out of scope for the unittest suite, so it's deliberately omitted here. + run: | + python -m pip install --upgrade pip + python -m pip install 'flask>=3.0' 'fpdf2>=2.7' + + - name: Run unittest suite + run: python -m unittest discover tests -v From 7177d9d3463edb3dd9995ac47620c17a24c0dc6d Mon Sep 17 00:00:00 2001 From: timon0305 Date: Tue, 5 May 2026 00:32:47 +0200 Subject: [PATCH 2/5] ci: pin action versions to immutable commit SHAs (CodeRabbit on PR #14) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Replace @v4 / @v5 tag refs with the matching commit SHAs on actions/checkout and actions/setup-python. Tags are mutable — a compromised maintainer can repoint them, silently swapping the code that runs in our CI runner. SHAs are immutable and remove that class of supply-chain risk. Verified each SHA against the live tag on github.com: gh api repos/actions/checkout/git/ref/tags/v4 \ --jq '.object.sha' # 34e114876b0b11c390a56381ad16ebd13914f8d5 gh api repos/actions/setup-python/git/ref/tags/v5 \ --jq '.object.sha' # a26af69be951a213d495a4c3e4e4022e16d87065 The trailing `# v4` / `# v5` comments preserve the major-version intent so future bumps stay deliberate. The leading comment block documents the bump procedure for the next person. --- .github/workflows/tests.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 6e53a02..0cef4fa 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -11,10 +11,14 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 + # Pinned to immutable commit SHAs (not @v4 / @v5) so a compromised tag + # cannot silently swap the underlying action code on this CI runner. + # When bumping, verify the new SHA via: + # gh api repos/actions//git/ref/tags/ --jq '.object.sha' + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 - name: Set up Python - uses: actions/setup-python@v5 + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: python-version: '3.12' From cc0e9a16d667b21eeeaad6e386decb0dd215077b Mon Sep 17 00:00:00 2001 From: timon0305 Date: Thu, 7 May 2026 23:41:42 +0200 Subject: [PATCH 3/5] ci: expand to multi-OS + multi-Python matrix, add mypy + gitleaks (closes #13) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous shape was a single ubuntu-latest / Python 3.12 unittest job. Expanded to match the broader gate quality the team adopted on the-claw: - unittest: 3 OSes × 3 Pythons = 9 cells (3.11 / 3.12 / 3.13 across ubuntu-latest, macos-latest, windows-latest). Catches Python version drift and the rare path / line-ending issue single-OS hides. fail-fast false so cells run independently. - typecheck: mypy on Python 3.12. Codebase already has 70+ typed functions across 30 .py files, so mypy actually does work. Lenient config (--ignore-missing-imports, --no-strict-optional) + continue-on-error step until the surface is clean. - secret-scan: gitleaks 8.21.2 with checksum verification (mirrors the-claw's setup verbatim). No project-specific .gitleaks.toml; uses defaults for standard credential patterns. Concurrency block added so a new push to the same ref cancels the in-flight run, reducing CI minutes. Action SHAs unchanged from the previous workflow (already pinned). --- .github/workflows/tests.yml | 107 +++++++++++++++++++++++++++++++++--- 1 file changed, 100 insertions(+), 7 deletions(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 0cef4fa..698b73f 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,11 +5,23 @@ on: branches: [master] pull_request: +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: + # ── Unit tests: matrix across OS and Python version ─────────────────────── + # Closes #13. The unittest suite is the merge gate. Multi-OS catches the + # rare path / line-ending issue that a single-OS run hides; multi-Python + # catches API drift across LTS / current / latest interpreters. unittest: - name: Unit tests - runs-on: ubuntu-latest - + name: Unit tests (${{ matrix.os }} / Python ${{ matrix.python-version }}) + runs-on: ${{ matrix.os }} + strategy: + fail-fast: false + matrix: + os: [ubuntu-latest, macos-latest, windows-latest] + python-version: ["3.11", "3.12", "3.13"] steps: # Pinned to immutable commit SHAs (not @v4 / @v5) so a compromised tag # cannot silently swap the underlying action code on this CI runner. @@ -20,15 +32,96 @@ jobs: - name: Set up Python uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 with: - python-version: '3.12' + python-version: ${{ matrix.python-version }} - name: Install runtime + test dependencies - # Only what the tests actually exercise. `pywebview` from requirements.txt - # is the desktop-launcher dep and pulls GTK / Qt system packages on Linux - # — out of scope for the unittest suite, so it's deliberately omitted here. + # Only what the tests actually exercise. `pywebview` from + # requirements.txt is the desktop-launcher dep and pulls GTK / Qt + # system packages on Linux — out of scope for the unittest suite. run: | python -m pip install --upgrade pip python -m pip install 'flask>=3.0' 'fpdf2>=2.7' - name: Run unittest suite run: python -m unittest discover tests -v + + # ── Typecheck: mypy ─────────────────────────────────────────────────────── + # Codebase already has type hints across most of the surface (~70+ typed + # functions). Mypy runs in lenient mode (--ignore-missing-imports for + # untyped third-party deps; no strict-optional) so the gate isn't a wall + # of false positives on first run. continue-on-error keeps findings as + # warnings during the surface-cleanup phase; flip to required by removing + # continue-on-error once the surface is clean. + typecheck: + name: Typecheck (mypy) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + + - name: Set up Python + uses: actions/setup-python@a26af69be951a213d495a4c3e4e4022e16d87065 # v5 + with: + python-version: "3.12" + + - name: Install runtime deps + mypy + run: | + python -m pip install --upgrade pip + python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10' + + - name: Run mypy + continue-on-error: true + run: mypy --ignore-missing-imports --no-strict-optional --pretty . + + # ── Secret scan: gitleaks ───────────────────────────────────────────────── + # Catches accidentally committed credentials. Runs over full git history + # (fetch-depth: 0). No project-specific .gitleaks.toml — defaults cover + # standard credential patterns (API keys, AWS, GitHub tokens, etc.). + secret-scan: + name: Secret scan (gitleaks) + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 # v4 + with: + fetch-depth: 0 + + - name: Install gitleaks + run: | + GITLEAKS_VERSION=8.21.2 + base_url="https://github.com/gitleaks/gitleaks/releases/download/v${GITLEAKS_VERSION}" + tarball="gitleaks_${GITLEAKS_VERSION}_linux_x64.tar.gz" + checksums="gitleaks_${GITLEAKS_VERSION}_checksums.txt" + + # Download tarball and checksums file to temp; retries prevent + # transient 5xx failures. + curl --fail --location --silent --show-error \ + --retry 5 --retry-delay 2 --retry-all-errors \ + -o "/tmp/${tarball}" "${base_url}/${tarball}" + curl --fail --location --silent --show-error \ + --retry 5 --retry-delay 2 --retry-all-errors \ + -o "/tmp/${checksums}" "${base_url}/${checksums}" + + # Verify SHA-256 before extraction; fail and clean up on mismatch. + expected=$(grep " ${tarball}$" "/tmp/${checksums}" | awk '{print $1}') + if [ -z "${expected}" ]; then + echo "::error::No checksum entry found for ${tarball}" >&2 + rm -f "/tmp/${tarball}" "/tmp/${checksums}" + exit 1 + fi + actual=$(sha256sum "/tmp/${tarball}" | awk '{print $1}') + if [ "${expected}" != "${actual}" ]; then + echo "::error::SHA-256 mismatch for ${tarball}: expected ${expected}, got ${actual}" >&2 + rm -f "/tmp/${tarball}" "/tmp/${checksums}" + exit 1 + fi + + tar -xz -f "/tmp/${tarball}" gitleaks + sudo mv gitleaks /usr/local/bin/gitleaks + rm -f "/tmp/${tarball}" "/tmp/${checksums}" + + - name: Run gitleaks + run: | + gitleaks detect \ + --source . \ + --verbose \ + --redact \ + --exit-code 1 From b8df74832a9484b19a063fd7a0c3ed956b4a3ad3 Mon Sep 17 00:00:00 2001 From: timon0305 Date: Thu, 7 May 2026 23:48:38 +0200 Subject: [PATCH 4/5] ci: explicit least-privilege GITHUB_TOKEN permissions (CodeRabbit on PR #19) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds workflow-level `permissions: contents: read` so a compromised action step in any matrix cell can't write back to the repo. None of the jobs (unittest, typecheck, secret-scan) need write access — no commits, PR comments, or release publishes. Read-only is enough. --- .github/workflows/tests.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 698b73f..69230b5 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -5,6 +5,12 @@ on: branches: [master] pull_request: +# Least-privilege GITHUB_TOKEN scope. None of these jobs need write access +# (no commit, no PR comment, no release publish) — read-only is enough. +# A compromised action in any matrix cell can't write back to the repo. +permissions: + contents: read + concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true From 3dd6b206476334a854b5a51c7f7ed8b1b3fee0a3 Mon Sep 17 00:00:00 2001 From: Monkey Dev Date: Fri, 8 May 2026 15:48:57 -0400 Subject: [PATCH 5/5] fix: remove other OS other than ubuntu --- .github/workflows/tests.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 69230b5..3ee7797 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -26,7 +26,7 @@ jobs: strategy: fail-fast: false matrix: - os: [ubuntu-latest, macos-latest, windows-latest] + os: [ubuntu-latest] python-version: ["3.11", "3.12", "3.13"] steps: # Pinned to immutable commit SHAs (not @v4 / @v5) so a compromised tag @@ -75,6 +75,8 @@ jobs: python -m pip install 'flask>=3.0' 'fpdf2>=2.7' 'mypy>=1.10' - name: Run mypy + # Transitional only (maintainer consensus): keeps CI green until `mypy` exits + # zero on this repo — then delete this line so type errors fail the job. continue-on-error: true run: mypy --ignore-missing-imports --no-strict-optional --pretty .