CI/CD Integration

Docsfy already has strong automation building blocks for CI, but they are not yet wired into a repository-managed CI pipeline. The project currently relies on tox for tests and pre-commit for linting, typing, and secret scanning.

Warning: No CI workflow definitions are currently checked in (for example, no .github/workflows, .gitlab-ci.yml, or Jenkinsfile). Until a pipeline is added, enforcement depends on developers running checks locally.

Current Automation Posture

Test execution is defined in tox

skipsdist = true

envlist = ["unittests"]

[env.unittests]
deps = ["uv"]
commands = [["uv", "run", "--extra", "dev", "pytest", "-n", "auto", "tests"]]
  • One tox environment exists: unittests
  • Tests run with pytest-xdist (-n auto) for parallel execution
  • skipsdist = true means tox does not build/install the package before testing

Note: With skipsdist = true, CI validates source-tree behavior but not wheel/sdist installability.

Python, pytest, and mypy defaults are centralized in pyproject.toml

[project]
requires-python = ">=3.12"

[project.optional-dependencies]
dev = ["pytest", "pytest-asyncio", "pytest-xdist", "httpx"]

[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
pythonpath = ["src"]

[tool.mypy]
check_untyped_defs = true
disallow_any_generics = true
disallow_incomplete_defs = true
disallow_untyped_defs = true
no_implicit_optional = true
show_error_codes = true
warn_unused_ignores = true
strict_equality = true
extra_checks = true
warn_unused_configs = true
warn_redundant_casts = true
  • CI runners should use Python 3.12+
  • Async testing is first-class (pytest-asyncio)
  • Mypy is configured in strict mode

Lint, formatting, typing, and security checks are encoded in .pre-commit-config.yaml

ci:
  autofix_prs: false
  autoupdate_commit_msg: "ci: [pre-commit.ci] pre-commit autoupdate"
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v6.0.0
    hooks:
      - id: check-added-large-files
      - id: check-docstring-first
      - id: check-executables-have-shebangs
      - id: check-merge-conflict
      - id: check-symlinks
      - id: detect-private-key
      - id: mixed-line-ending
      - id: debug-statements
      - id: trailing-whitespace
        args: [--markdown-linebreak-ext=md]
      - id: end-of-file-fixer
      - id: check-ast
      - id: check-builtin-literals
      - id: check-toml
# flake8 retained for RedHatQE M511 plugin; ruff handles standard linting
- repo: https://github.com/PyCQA/flake8
  rev: 7.3.0
  hooks:
    - id: flake8
      args: [--config=.flake8]
      additional_dependencies:
        [git+https://github.com/RedHatQE/flake8-plugins.git, flake8-mutable]

- repo: https://github.com/astral-sh/ruff-pre-commit
  rev: v0.15.2
  hooks:
    - id: ruff
    - id: ruff-format

- repo: https://github.com/Yelp/detect-secrets
  rev: v1.5.0
  hooks:
    - id: detect-secrets

- repo: https://github.com/gitleaks/gitleaks
  rev: v8.30.0
  hooks:
    - id: gitleaks

- repo: https://github.com/pre-commit/mirrors-mypy
  rev: v1.19.1
  hooks:
    - id: mypy
      exclude: (tests/)
  • ruff + ruff-format handle general lint/format checks
  • flake8 is retained for rule M511 via plugin
  • detect-secrets and gitleaks provide layered secret scanning
  • mypy runs as a hook and excludes tests/
[flake8]
select=M511
[extend]
useDefault = true

[allowlist]
paths = [
    '''tests/test_repository\.py''',
]

Warning: The flake8 hook intentionally pulls RedHatQE/flake8-plugins from Git, so CI reproducibility depends on that upstream repository state unless you pin a commit.

Deployment Readiness Signals Already in Code

The repo already contains deploy-friendly health checks in both app and container config:

@app.get("/health")
async def health() -> dict[str, str]:
    return {"status": "ok"}
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
    CMD curl -f http://localhost:8000/health || exit 1

ENTRYPOINT ["uv", "run", "--no-sync", "uvicorn", "docsfy.main:app", "--host", "0.0.0.0", "--port", "8000"]
healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
  interval: 30s
  timeout: 10s
  retries: 3

Use the existing repository configuration as the source of truth:

  1. Setup
  2. Use Python 3.12 runner
  3. Install pre-commit, tox, and uv

  4. Quality & Security Gate

  5. Run all hooks from .pre-commit-config.yaml
  6. Enforces linting, formatting, type checks, and secret scanning

  7. Test Gate

  8. Run tox unittests env from tox.toml
  9. Executes pytest -n auto tests through uv

  10. Build Gate (main/release branches)

  11. Build container from Dockerfile
  12. Preserves runtime assumptions already encoded in the image

  13. Smoke Gate

  14. Start the built image and check /health
  15. Fail fast before deployment if health probe fails

  16. Deploy

  17. Deploy only after all prior gates succeed

Tip: Keep CI logic thin by reusing tox.toml and .pre-commit-config.yaml directly, instead of duplicating check logic in pipeline YAML.

Why This Works Well for Docsfy

Tests are already written to run without external AI services by mocking expensive/external operations:

with patch.dict(os.environ, {"ADMIN_KEY": TEST_ADMIN_KEY}):
    get_settings.cache_clear()
    await storage.init_db()
    ...

with (
    patch("docsfy.main.check_ai_cli_available", return_value=(True, "")),
    patch("docsfy.main.clone_repo", return_value=(tmp_path / "repo", "abc123")),
    patch("docsfy.main.run_planner", return_value=sample_plan),
    patch(
        "docsfy.main.generate_all_pages",
        return_value={"introduction": "# Intro\n\nWelcome!"},
    ),
):
    ...

This makes CI runs deterministic and suitable for pull-request validation without requiring real provider credentials.