[project]
name = "weakincentives"
dynamic = ["version"]
description = "Tools for developing and optimizing side effect free background agents"
readme = "llms.md"
authors = [
    { name = "Andrei Savu", email = "andrei@weakincentives.com" }
]
requires-python = ">=3.12"
dependencies = []
license = { text = "Apache-2.0" }
keywords = [
    "ai",
    "agents",
    "background-agents",
    "side-effect-free",
    "optimization",
    "weak-incentives",
]
classifiers = [
    "Development Status :: 3 - Alpha",
    "Intended Audience :: Developers",
    "Intended Audience :: Science/Research",
    "License :: OSI Approved :: Apache Software License",
    "Operating System :: OS Independent",
    "Programming Language :: Python :: 3",
    "Programming Language :: Python :: 3.12",
    "Programming Language :: Python :: 3.13",
    "Programming Language :: Python :: 3.14",
    "Topic :: Scientific/Engineering :: Artificial Intelligence",
    "Topic :: Software Development :: Libraries :: Python Modules",
    "Typing :: Typed",
]

[project.urls]
Homepage = "https://weakincentives.com/"
Documentation = "https://github.com/weakincentives/weakincentives/blob/main/guides/README.md"
Repository = "https://github.com/weakincentives/weakincentives"
"Issue Tracker" = "https://github.com/weakincentives/weakincentives/issues"

[project.optional-dependencies]
skills = [
    "pyyaml>=6.0.0",
]
asteval = [
    "asteval>=1.0.7",
]
openai = [
    "openai>=2.8.0",
]
litellm = [
    "litellm>=1.79.3",
]
podman = [
    "podman>=5.6.0",
]
redis = [
    "redis>=5.0.0",
]
claude-agent-sdk = [
    "claude-agent-sdk>=0.1.15",
]
wink = [
    "fastapi>=0.115.0",
    "markdown-it-py[linkify]>=4.0.0",
    "uvicorn>=0.30.0",
]

[project.scripts]
wink = "weakincentives.cli.wink:main"

[build-system]
requires = ["hatchling>=1.27.0", "hatch-vcs>=0.5.0"]
build-backend = "hatchling.build"

[tool.hatch.version]
source = "vcs"
tag-pattern = "v(?P<version>.+)"

[tool.hatch.version.raw-options]
version_scheme = "no-guess-dev"
local_scheme = "no-local-version"

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

[tool.hatch.build.targets.sdist]
exclude = ["examples", "src/examples"]

[tool.hatch.build.hooks.custom]
path = "hatch_build.py"

[dependency-groups]
dev = [
    "weakincentives[skills]",
    "bandit>=1.8.6",
    "deptry>=0.24.0",
    "pytest>=9.0.1",
    "pytest-timeout>=2.4.0",
    "pytest-cov>=7.0.0",
    "pytest-randomly>=4.0.1",
    "pytest-rerunfailures>=16.1",
    "pip-audit>=2.9.0",
    "ruff>=0.14.5",
    "pyright>=1.1.407",
    "ty==0.0.1a35",
    "mdformat>=1.0.0",
    "hypothesis>=6.100.0",
    "vulture>=2.14",
]

[tool.ruff]
line-length = 88
target-version = "py312"
extend-exclude = ["test-repositories"]

[tool.ruff.lint]
extend-select = [
    "I",
    "UP",
    "B",
    "SIM",
    "C4",
    "C90",
    "ANN",
    "RET",
    "RSE",
    "PTH",
    "ISC",
    "CPY",
    "A",
    "RUF",
    "TRY",
    "PLR",
    "PERF",
]
extend-ignore = ["TRY003", "RUF067"]
fixable = ["I001", "F401"]

[tool.ruff.lint.isort]
known-first-party = ["weakincentives"]
combine-as-imports = true

[tool.ruff.lint.per-file-ignores]
# Tests rely on readable inline status codes, simple fixtures, and helper signatures.
# C901 is ignored because test functions can legitimately have higher complexity.
"tests/**" = ["C901", "PLR2004", "PLR0913", "PLR0914", "PLR6301", "SIM117", "RUF029", "ANN201"]
"integration-tests/**" = ["C901", "PLR2004", "PLR0904", "PLR0913", "PLR0914", "PLR6301", "SIM117", "RUF029"]
# Examples may use higher complexity for illustration purposes.
"examples/**" = ["C901", "CPY001", "ANN201", "ANN204"]
"src/weakincentives/prompt/overrides/validation.py" = ["PLR0913"]
"src/weakincentives/prompt/prompt.py" = ["PLR0913", "PLR6301"]
"src/weakincentives/prompt/protocols.py" = ["PLR0913"]
"src/weakincentives/adapters/core.py" = ["PLR0913", "PLR6301"]
"src/weakincentives/adapters/openai.py" = ["PLR0913"]
"src/weakincentives/dbc/__init__.py" = ["PLR0913"]
"src/weakincentives/prompt/_enabled_predicate.py" = ["PLR6201"]
"src/weakincentives/prompt/composition.py" = ["PLR6301"]
"src/weakincentives/prompt/markdown.py" = ["PLR0913"]
"src/weakincentives/prompt/rendering.py" = ["PLR6301"]
"src/weakincentives/prompt/section.py" = ["PLR0913", "PLR6301"]
"src/weakincentives/prompt/structured_output.py" = ["PLR0913"]
"src/weakincentives/prompt/tool_result.py" = ["PLR0911"]
"src/weakincentives/serde/parse.py" = ["PLR0911", "PLR0912", "PLR0913", "PLR0914", "PLR0915"]
"src/weakincentives/serde/schema.py" = ["PLR0912", "PLR0914", "PLR0915", "PLR2004"]
"src/weakincentives/tools/asteval.py" = ["PLR0912", "PLR0914", "PLR0915", "PLR2004"]
"src/weakincentives/tools/planning.py" = ["PLR6301"]
"src/weakincentives/adapters/claude_agent_sdk/_bridge.py" = ["PLR0913", "TRY300"]
"src/weakincentives/adapters/claude_agent_sdk/adapter.py" = ["PLR0913", "PLR6301", "ANN401", "TRY300"]
"src/weakincentives/runtime/session/session.py" = ["PLR0904", "PLR0913"]
"src/weakincentives/contrib/mailbox/_redis.py" = ["PLR0914", "PLR6301", "C901", "TRY300", "TRY301", "SIM108"]
"src/weakincentives/formal/__init__.py" = ["C901", "PLR0912", "PLR0913", "PLR0914", "PLR0915", "A002", "B010", "PERF401"]
"src/weakincentives/formal/testing.py" = ["PLR0914"]
"formal-tests/**" = ["PLR2004"]

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

[tool.ruff.lint.flake8-copyright]
notice-rgx = '''(?s)\A(?:#!/[^\n]+\n)?# Licensed under the Apache License, Version 2.0 \(the "License"\);
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
#     http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.

'''

[tool.ruff.format]
quote-style = "double"
indent-style = "space"

[tool.pytest.ini_options]
addopts = "--cov=src/weakincentives --cov-report=term-missing --cov-fail-under=100"
markers = [
    "core: marks fast, high-value tests that always run in CI.",
    "integration: marks tests that exercise live provider integrations.",
    "timeout(timeout): set per-test timeout in seconds via pytest-timeout.",
    "threadstress: repeat tests under randomized thread-pool conditions.",
    "podman: marks tests that exercise the Podman tool against a live daemon.",
    "redis: marks all Redis integration tests (standalone and cluster).",
    "redis_standalone: marks tests using standalone Redis.",
    "redis_cluster: marks tests using Redis Cluster.",
    "slow: marks tests that are slow and may be skipped in quick test runs.",
]
norecursedirs = ["test-repositories", ".uv-cache", ".venv", ".hypothesis"]
reruns = 2
reruns_delay = 0.5

[tool.coverage.run]
branch = true
source = ["src/weakincentives"]
omit = [
    # Redis mailbox requires integration tests with a real Redis server
    "src/weakincentives/contrib/mailbox/_redis.py",
    # Docs directory contains bundled example files for documentation, not library code
    "src/weakincentives/docs/*",
]

[tool.coverage.report]
fail_under = 100
exclude_lines = [
    "pragma: no cover",
    "if TYPE_CHECKING:",
    "@overload",
    "@abstractmethod",
    "^\\s*\\.\\.\\.\\s*$",
    ":\\s*\\.\\.\\.\\s*$",
]

[tool.deptry]

[tool.deptry.per_rule_ignores]
DEP002 = ["asteval", "claude-agent-sdk", "litellm", "mcp", "openai", "podman", "pyyaml", "redis"]

[tool.pyright]
typeCheckingMode = "strict"
pythonVersion = "3.12"
include = ["src"]
exclude = [
    "test-repositories",
    ".git",
    ".uv-cache",
    ".venv",
    ".pytest_cache",
    ".ruff_cache",
    "dist",
    "src/weakincentives/adapters/claude_agent_sdk",
    "src/weakincentives/docs",
]
deprecateTypingAliases = true
reportCallInDefaultInitializer = "error"
reportMissingTypeStubs = "error"
reportImplicitOverride = "error"
reportImplicitStringConcatenation = "error"
reportImportCycles = "error"
reportPrivateUsage = "error"
reportMissingSuperCall = "error"
reportPropertyTypeMismatch = "error"
reportUnknownMemberType = "error"
reportUninitializedInstanceVariable = "error"
reportUnnecessaryTypeIgnoreComment = "error"
reportUnreachable = "error"
reportUnusedCallResult = "error"
reportUnusedExpression = "error"
# Tier 1: High-impact rules
reportInconsistentConstructor = "error"
reportMatchNotExhaustive = "error"
reportUnnecessaryComparison = "error"
reportDeprecated = "error"
# Tier 2: Medium-impact rules
reportUnnecessaryIsInstance = "error"
reportUnnecessaryCast = "error"
reportUnnecessaryContains = "error"
reportConstantRedefinition = "error"
# Tier 3: Style rules
reportUntypedNamedTuple = "error"
reportTypeCommentUsage = "error"

[tool.vulture]
paths = ["src/weakincentives"]
min_confidence = 80
ignore_names = [
    "__dir__",
    "__getattr__",
    "visit_*",
    "expression",  # Protocol method parameter in InterpreterProtocol.eval
    "stream",  # Protocol method parameter in _YAMLModule.safe_load
]

[tool.ty]

[tool.ty.environment]
python-version = "3.12"

[tool.ty.rules]
ambiguous-protocol-member = "error"
deprecated = "error"
division-by-zero = "error"
invalid-ignore-comment = "error"
possibly-missing-import = "error"
possibly-unresolved-reference = "error"
# redundant-cast disabled: false positives on cast(Self, ...) pattern
# which is common for working around type system limitations
redundant-cast = "ignore"
undefined-reveal = "error"
unresolved-global = "error"
# unsupported-base disabled: false positives on dynamic class creation
# patterns like `class Foo(cls):` inside __class_getitem__
unsupported-base = "ignore"
unused-ignore-comment = "error"
useless-overload-body = "error"
# High-impact additions
possibly-missing-attribute = "error"
ignore-comment-unknown-rule = "error"
