pyproject.toml
Rama main · réplica de lectura del repositorio original.
[build-system]
requires = ["hatchling"]
build-backend = "hatchling.build"
[project]
name = "morgan-brain"
version = "0.2.0"
description = "A project-scoped memory for your AI tools, consolidated into facts by a local model."
requires-python = ">=3.12"
# An SPDX expression, not a file reference: the text lives at the repository root.
license = "Apache-2.0"
dependencies = [
"pydantic>=2.9",
"pydantic-settings>=2.6",
"httpx>=0.27",
"openai>=1.0",
"structlog>=24.4",
"sqlite-vec>=0.1.9",
# The MCP server is a primary surface, not an extra. Capped below 2.0: mcp 2.0 swaps
# httpx for an incompatible httpx2 package.
"mcp>=1.0,<2.0",
]
[project.optional-dependencies]
dev = [
"pytest>=8.3",
"pytest-asyncio>=0.24",
"hypothesis>=6.0",
# Upper-bounded on purpose: a linter/type-checker minor release adds rules, so an open
# range makes CI fail on someone else's release date. Raise the bound deliberately.
"ruff>=0.16,<0.17",
"mypy>=1.13,<2.0",
]
[project.scripts]
morgan = "morgan_brain.surfaces.cli.__main__:main"
morgan-mcp = "morgan_brain.surfaces.mcp_server:main"
[tool.hatch.build.targets.wheel]
packages = ["morgan_brain"]
[tool.black]
line-length = 100
target-version = ["py312"]
[tool.ruff]
line-length = 100
target-version = "py312"
# Prose is not source: the fenced snippets in the design documents and the agent
# definitions are illustrations, and reformatting them changes what they illustrate.
extend-exclude = ["docs", ".claude"]
# The rule set is declared explicitly on purpose. Ruff's *default* selection
# changes between releases, so a repo that relies on it has a lint gate that
# silently moves under it -- CI turns red on a linter release rather than on a
# code change. Pinning the families here (and the ruff version range in the
# dev extra) makes the gate a property of this repo, not of whichever ruff a
# runner happened to resolve.
[tool.ruff.lint]
select = [
"E", "W", "F", # pycodestyle + pyflakes
"I", # import ordering
"UP", # pyupgrade -- keep the 3.12 idioms current
"B", "BLE", # bugbear + blind-except
"C4", # comprehensions
"DTZ", # naive datetimes: a bi-temporal store must not have any
"ASYNC", # blocking calls on the async hot path
"S", # bandit
"SIM",
"TRY",
"FURB",
"RUF",
"PLE", # pylint errors only; the refactor/convention sets are noise here
]
ignore = [
# FastAPI expresses dependency injection as a call in the default argument.
# It is the framework's documented idiom, not an accidental shared mutable.
"B008",
# Ruff wants every raise to carry a short message and a separate exception
# class. Morgan's errors are read by humans in logs; the long-form message
# is the useful part.
"TRY003",
# `class X(str, Enum)` is deliberate, not a 3.12 oversight. StrEnum changes
# what `str(member)` returns ("user" instead of "Role.USER"), and these enums
# are serialised onto the event bus, into SQLite, and into provider payloads.
# Migrating them is a wire-format change that needs its own tested commit,
# not a lint autofix.
"UP042",
# Ambiguous-unicode. The rule guards against homoglyph smuggling; here it
# only ever fires on deliberate typography -- en dashes in prose, the real
# minus sign in the calibration formulae, x in "4.4x fewer tokens" -- and on
# the Cyrillic fixtures that exist precisely to prove the FTS5 tokeniser
# handles a non-Latin script. Flagging those is noise, not a finding.
"RUF001", "RUF002", "RUF003",
]
[tool.ruff.lint.per-file-ignores]
# Tests assert, spawn subprocesses with a known argv, and bind throwaway
# sockets. Those are the point of the test, not a finding.
"tests/**" = ["S101", "S603", "S607", "S104", "S105", "S106", "S108"]
[tool.mypy]
python_version = "3.12"
strict = true
# pydantic builds a model's __init__ at runtime; the plugin tells mypy what it builds, including
# BaseSettings' own keywords such as _env_file.
plugins = ["pydantic.mypy"]
# The MCP SDK ships no type information of its own.
[[tool.mypy.overrides]]
module = ["mcp.*"]
ignore_missing_imports = true
# The plugin's defaults type every __init__ argument as Any and accept unknown keywords. These
# keep a model's constructor checked as strictly as without the plugin.
[tool.pydantic-mypy]
init_typed = true
init_forbid_extra = true
[tool.pytest.ini_options]
asyncio_mode = "auto"
testpaths = ["tests"]
[tool.bandit]
# Production code only. Bandit reads pytest's idiom as a vulnerability -- `assert` is B101,
# the CLI integration tests' subprocess calls are B603/B607, and the "0.0.0.0" literal in
# the tests that prove Morgan *refuses* that bind is B104.
exclude_dirs = ["tests"]