Skip to content

CI hardening: add ruff/mypy/coverage + a Python matrix, run docs --strict on PRs, and unify action pins #16

Description

@Shirofune-Security

The CI setup for mecha-hayabusa is minimal and has a few concrete gaps worth hardening. The test workflow runs unit tests on a single interpreter with no linting, formatting, type-checking, or coverage, and does not exercise any Python version matrix despite requires-python = ">=3.13". Separately, the docs workflow runs a --strict MkDocs build (which correctly fails on broken links/nav) only on push to main, so a PR that breaks the strict build is caught only after it merges. Finally, the two workflows pin the same actions to different major versions (actions/checkout@v4 in test.yml vs @v7 in docs.yml), which is an inconsistency that should be unified. None of these are bugs in the tool itself — this is CI hardening.

Problem

1. test.yml does only bare unit tests, on one interpreter

.github/workflows/test.yml (full file):

name: tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  unit:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
      - name: Install dependencies
        run: uv sync --frozen
      - name: Run unit tests
        run: uv run python -m unittest discover -s tests -v
  • There is no lint (ruff check), no format check (ruff format --check), no type check (mypy), and no coverage step. The repository also has no [tool.ruff] / [tool.mypy] configuration anywhere — pyproject.toml is 11 lines and contains only [project] metadata plus dependencies (confirmed: no ruff/mypy/flake8/tox config files in the repo).
  • There is no Python version matrix. pyproject.toml:6 declares requires-python = ">=3.13", an open-ended lower bound, yet CI runs on exactly one interpreter (ubuntu-latest's default). Any incompatibility with a newer interpreter — or with 3.12 if the floor is ever lowered — goes untested.
  • astral-sh/setup-uv@v5 is used with no cache configured, so dependency resolution/download is repeated on every run.

For a repo whose core is a ~99 KB server.py (the MCP server exposing Hayabusa CSV to an LLM) plus a substantial test suite (9 test modules under tests/), the absence of a lint/type gate means style regressions and type errors can land on main unnoticed.

2. docs.yml runs --strict only on main, not on PRs

.github/workflows/docs.yml:6-12:

on:
  push:
    branches: [main]
    paths:
      - "website/**"
      - ".github/workflows/docs.yml"
  workflow_dispatch:

The strict build itself is good — .github/workflows/docs.yml:42:

      - name: Build site (strict)
        run: mkdocs build --strict --config-file website/mkdocs.yml

mkdocs build --strict fails on broken links and nav problems, which is exactly what you want. But because the workflow only triggers on push to main (and workflow_dispatch), and never on pull_request, a PR that edits website/** and breaks the strict build passes CI, merges, and only then fails — on main, where it also blocks the Pages deploy. The breakage is discovered at the worst possible time instead of during review.

3. Action major versions disagree between the two workflows

The same action is pinned to different majors depending on the file:

  • .github/workflows/test.yml:13actions/checkout@v4
  • .github/workflows/docs.yml:28actions/checkout@v7

docs.yml additionally uses actions/setup-python@v6 (line 33), actions/configure-pages@v6 (line 45), actions/upload-pages-artifact@v5 (line 48), and actions/deploy-pages@v5 (line 61). The concrete, unambiguous problem is the checkout@v4 vs checkout@v7 split — there's no reason for two workflows in the same repo to pin actions/checkout to different majors. Pick one verified major and use it in both.

Impact

Low severity, maintainer-facing only. This tool runs locally for a single DFIR analyst; none of these gaps affect runtime behavior or security of an investigation. The practical costs are:

  • Regressions slip onto main: lint/type/format issues and single-interpreter blind spots aren't caught before merge.
  • Doc breakage is caught late: a --strict failure introduced in a PR isn't visible until after merge, where it also blocks the Pages deploy and requires a follow-up fix on main.
  • Inconsistent, drifting action pins: harder to reason about and maintain; the v4 pin is stale relative to v7 used next door.

No user data or investigation output is at risk. This is purely CI/maintainability hardening.

Suggested fix

All changes are confined to .github/workflows/. This does not touch the skill/investigate/ or skill/investigate_jp/ trees, so no EN/JP duplication is required for this fix.

(a) Add lint + format-check (and optionally type-check + coverage) to test.yml, and add a Python matrix. Since astral-sh/uv is already the toolchain, run the tools through uv:

name: tests

on:
  push:
    branches: [main]
  pull_request:

jobs:
  unit:
    runs-on: ubuntu-latest
    timeout-minutes: 10
    strategy:
      fail-fast: false
      matrix:
        python-version: ["3.13"]   # add "3.12" here if the floor is lowered
    steps:
      - uses: actions/checkout@v4
      - name: Install uv
        uses: astral-sh/setup-uv@v5
        with:
          enable-cache: true          # cache uv downloads across runs
      - name: Install dependencies
        run: uv sync --frozen
      - name: Lint (ruff)
        run: uvx ruff check .
      - name: Format check (ruff)
        run: uvx ruff format --check .
      - name: Type check (mypy)          # optional
        run: uvx mypy server.py state.py report.py
      - name: Run unit tests (with coverage)
        run: uv run python -m coverage run -m unittest discover -s tests -v
      - name: Coverage report              # optional
        run: uv run python -m coverage report

Add a minimal [tool.ruff] section to pyproject.toml so lint rules are explicit and reproducible locally, e.g.:

[tool.ruff]
target-version = "py313"
line-length = 100

(If mypy/coverage steps are kept, add them to a dev dependency group or rely on uvx as shown. Start lenient — enabling ruff in check-only mode first — to avoid a large one-time reformat.)

(b) Run the strict docs build on PRs (build-only, no deploy). Add a pull_request trigger scoped to the same paths and guard the deploy so it stays main-only. Either extend docs.yml with a pull_request: trigger and gate the deploy job with if: github.event_name == 'push', or add a small dedicated PR check:

name: docs-strict (PR)

on:
  pull_request:
    paths:
      - "website/**"
      - ".github/workflows/docs.yml"

jobs:
  build:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: actions/setup-python@v6
        with:
          python-version: "3.x"
          cache: pip
      - run: pip install -r website/requirements.txt
      - name: Build site (strict, no deploy)
        run: mkdocs build --strict --config-file website/mkdocs.yml

(c) Unify action pins. Choose one verified major for actions/checkout and use it in both workflows (e.g. bump test.yml:13 from @v4 to match docs.yml's @v7, after confirming v7 is a real released major). Keep the Pages actions (configure-pages, upload-pages-artifact, deploy-pages, setup-python) consistent as well.

Verification

  • Lint/format/type/matrix: open a draft PR that (1) introduces a deliberate lint violation (e.g. an unused import) and confirm the ruff check step fails; (2) revert it and confirm the job passes on each matrix entry. Confirm the astral-sh/setup-uv cache is populated on the second run (visible in the step log).

  • Docs on PR: on a branch, introduce a broken internal link in website/docs/** and open a PR; the new PR docs job must fail with a --strict error before merge. Fix the link and confirm it goes green. Confirm the deploy job does not run on pull_request events (only on push to main).

  • Action pins: after unifying, grep the workflows to confirm a single actions/checkout major:

    grep -rn "actions/checkout@" .github/workflows/

    Both lines should report the same major, and both workflows should still complete successfully on a trial run.


Filed as part of a full architecture & code review of the repository; each finding was independently re-verified against the current code before filing.

Metadata

Metadata

Labels

enhancementNew feature or requestlow priorityNice to have — hygiene/polish

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions