[project]
name = "llmdebug"
version = "2.15.0"
description = "Structured debug snapshots for LLM-assisted debugging"
readme = "README.md"
license = { text = "MIT" }
requires-python = ">=3.10"
authors = [{ name = "Nicolas Schuler", email = "schuler.nicolas@proton.me" }]
keywords = ["debugging", "llm", "crash-reporting", "pytest"]
classifiers = [
    "Development Status :: 4 - Beta",
    "Intended Audience :: Developers",
    "License :: OSI Approved :: MIT License",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.10",
    "Programming Language :: Python :: 3.11",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Topic :: Software Development :: Debuggers",
    "Framework :: Pytest",
]
dependencies = [
    "filelock>=3.0",
    "polars>=1.12.0",
    "scikit-learn>=1.4.0",
    "scipy>=1.13",
]

[project.optional-dependencies]
cli = ["click>=8.0", "rich>=13.0"]
mcp = ["mcp>=1.0"]
toon = ["toons>=0.1"]
jupyter = ["ipython>=8.0"]
evals = [
    "datasets>=2.0",      # HuggingFace datasets for importers
    "swebench==4.1.0",    # Pin official evaluator CLI for reproducible publication scoring
    "testcontainers>=4.13.2",
]
dev = [
    "bandit>=1.8.0",
    "click>=8.0",
    "deptry>=0.22.0",
    "diff-cover>=9.2",
    "import-linter>=2.0",
    "ipython>=8.0",
    "mcp>=1.0",
    "mutmut>=3.2",
    "numpy>=1.20",
    "pip-audit>=2.9.0",
    "pyright>=1.1.390",
    "pytest>=9.0",
    "pytest-asyncio>=0.25.0",
    "pytest-benchmark>=4.0",
    "pytest-cov>=6.0",
    "python-semantic-release>=9.0",
    "radon>=6.0",
    "rich>=13.0",
    "ruff>=0.12.0",
    "toons>=0.1",
    "vulture>=2.14",
    "xenon>=0.9.3",
]

[dependency-groups]
dev = [
    "bandit>=1.8.0",
    "click>=8.0",
    "deptry>=0.22.0",
    "diff-cover>=9.2",
    "import-linter>=2.0",
    "ipython>=8.0",
    "mcp>=1.0",
    "mutmut>=3.2",
    "numpy>=1.20",
    "pip-audit>=2.9.0",
    "pyright>=1.1.390",
    "pytest>=9.0",
    "pytest-asyncio>=0.25.0",
    "pytest-benchmark>=4.0",
    "pytest-cov>=6.0",
    "python-semantic-release>=9.0",
    "radon>=6.0",
    "rich>=13.0",
    "ruff>=0.12.0",
    "toons>=0.1",
    "vulture>=2.14",
    "xenon>=0.9.3",
]

[project.urls]
Homepage = "https://github.com/NicolasSchuler/llmdebug"
Repository = "https://github.com/NicolasSchuler/llmdebug"

[project.scripts]
llmdebug = "llmdebug.cli:main"
llmdebug-mcp = "llmdebug.mcp_server:main"

[project.entry-points.pytest11]
llmdebug = "llmdebug.pytest_plugin"

[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"

[tool.hatch.build]
exclude = [".venv", ".venv*/"]

[tool.hatch.build.targets.wheel]
packages = ["src/llmdebug"]

[tool.ruff]
line-length = 100
target-version = "py310"
exclude = ["evals/cases", "evals/artifacts"]
force-exclude = true

[tool.ruff.lint]
select = ["E", "W", "F", "I", "UP", "B", "SIM", "C4", "RUF", "PERF", "FURB"]
ignore = [
    "E501",    # line length — formatter handles it
    "SIM105",  # contextlib.suppress — less readable than try/except pass
    "PERF203", # try-except in loop — common pattern for error recovery in I/O loops
]

[tool.ruff.lint.per-file-ignores]
"tests/**/*.py" = [
    "S101",    # assert is fine in tests
    "F401",    # unused imports (test fixtures, re-exports)
    "RUF012",  # ClassVar annotations in test helper classes
]
"evals/**/*.py" = [
    "S101",  # assert in eval scripts
]

[tool.ruff.lint.isort]
known-first-party = ["llmdebug"]

[tool.ruff.lint.mccabe]
max-complexity = 50

[tool.pyright]
include = ["src/llmdebug"]
exclude = ["tests", ".venv", ".venv*", "evals"]
pythonVersion = "3.10"
typeCheckingMode = "strict"
reportMissingTypeStubs = false
reportUnusedImport = false

[tool.pytest.ini_options]
testpaths = ["tests"]
markers = [
    "slow: marks tests as slow",
    "integration: marks integration tests",
]

[tool.coverage.run]
source = ["src/llmdebug"]
omit = ["*/tests/*", "*/__pycache__/*", "*/snapshot_types.py"]

[tool.coverage.report]
show_missing = true
fail_under = 75
exclude_lines = [
    "pragma: no cover",
    "if __name__",
    "if TYPE_CHECKING",
    "raise NotImplementedError",
    "@overload",
    "\\.\\.\\.",
]

[tool.bandit]
exclude_dirs = ["tests", ".venv", "evals"]
skips = [
    "B101",  # assert — used extensively, not a security concern
    "B311",  # random for non-crypto — we don't use random for security
    "B404",  # import subprocess — needed for git/process operations
    "B603",  # subprocess without shell=True — intentional and safe
]

[tool.deptry]
exclude = [".venv", "tests", "evals"]
pep621_dev_dependency_groups = ["dev"]

[tool.deptry.per_rule_ignores]
DEP001 = ["torch", "jax", "pandas", "toons", "IPython"]
DEP002 = ["filelock", "datasets", "swebench", "testcontainers", "scipy", "polars", "scikit-learn"]
DEP004 = ["pytest", "numpy"]

[tool.semantic_release]
version_toml = ["pyproject.toml:project.version"]
version_variables = ["src/llmdebug/__init__.py:__version__"]
branch = "main"
build_command = "uv build --no-sources"
commit_message = "chore(release): {version}"
major_on_zero = false

[tool.semantic_release.changelog]
changelog_file = "CHANGELOG.md"

[tool.semantic_release.commit_parser_options]
allowed_tags = ["build", "chore", "ci", "docs", "feat", "fix", "perf", "refactor", "style", "test"]
minor_tags = ["feat"]
patch_tags = ["fix", "perf"]
