[build-system] requires = ["hatchling"] build-backend = "hatchling.build" [project] name = "zettelforge" version = "2.8.0" description = "ZettelForge: Agentic Memory System with vector search, knowledge graph, and synthesis" readme = "README.md" license = "MIT" requires-python = ">=3.10" authors = [ {name = "Patrick Roland", email = "patrick@groland.com"} ] keywords = ["cybersecurity", "threat-intelligence", "cti", "stix", "agentic-memory", "knowledge-graph", "mcp-server", "ai-agent", "rag", "llm-memory"] 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 :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Security", "Topic :: Software Development :: Libraries :: Python Modules", ] # Core dependencies (Community edition — MIT licensed) dependencies = [ "lancedb>=0.5.0", "pyarrow>=14.0.0", "pydantic>=2.0.0", "numpy>=1.24.0", "requests>=2.31.0", "tantivy>=0.11.0", "fastembed>=0.8.0", "httpx>=0.25.0", "structlog>=24.0.0", # Detection rules (Phase 1c): Sigma + YARA first-class "PyYAML>=6.0", "jsonschema>=4.0", "plyara>=2.0", ] [project.optional-dependencies] # Local LLM: in-process llama-cpp-python (GGUF models) # pip install zettelforge[local] local = [ "llama-cpp-python>=0.3.0", ] # Local LLM: in-process ONNX Runtime GenAI (ONNX models, RFC-011) # pip install zettelforge[local-onnx] local-onnx = [ "onnxruntime-genai>=0.4.0", ] # All local backends combined (convenience) # pip install zettelforge[local-all] local-all = [ "zettelforge[local]", "zettelforge[local-onnx]", ] # LiteLLM — unified routing to 100+ LLM providers (RFC-012) # pip install zettelforge[litellm] litellm = [ "litellm>=1.60.0", ] # PII detection via Microsoft Presidio (RFC-013, optional) # pip install zettelforge[pii] # Enables governance.pii section in config. Automatically downloads # a spaCy model (~12MB) on first use. pii = [ "presidio-analyzer>=2.2.0", "presidio-anonymizer>=2.2.0", "spacy>=3.5.0", ] # Extensions: install the separate zettelforge-enterprise package # pip install zettelforge-enterprise extensions = [ "zettelforge-enterprise>=2.1.0", ] # Web UI (ZettelForge) web = [ # Exclude 0.136.3 while PyPI advisory MAL-2026-4750 is active. "fastapi>=0.100.0,!=0.136.3,<0.139.1", "uvicorn>=0.20.0", "psutil>=5.9.0", "jinja2>=3.0.0", ] # LangChain integration langchain = [ "langchain-core>=0.2.0", ] # CrewAI integration — exposes ZettelForge memory as CrewAI BaseTool subclasses. # Pin to >=1.14.0: the integration imports BaseTool from crewai.tools, a path # only guaranteed in CrewAI's 1.x line. Earlier 0.x releases that satisfied a # looser pin do not expose that import path and would surface as an obscure # ImportError at first use rather than at install time. # pip install zettelforge[crewai] crewai = [ "crewai>=1.14.0", ] # RFC-016 OSINT layer runtime deps. Phase 1 collectors (DNS, WHOIS, # crt.sh) need these to function; tests need them to construct realistic # mocks (e.g. raise dns.resolver.NXDOMAIN). Optional so non-OSINT users # don't pay the cost. CI's [dev] install pulls these in via the line # below. osint = [ "dnspython>=2.4.0", "python-whois>=0.9.0", "ipwhois>=1.2.0", # AGE-120 enrichers. maigret/sherlock are both MIT (AGE-118 approved); they # back username_to_social. Lazy-imported and fail-closed in the collector, # so absence at runtime is non-fatal. HIBP and the block explorer use the # already-present httpx (no new dep). "maigret>=0.4.0", "sherlock-project>=0.14.0", ] dev = [ "pytest>=7.0.0", "pytest-asyncio>=0.21.0", "pytest-cov>=4.0.0", "mypy>=1.0.0", "ruff>=0.4.0", "langchain-core>=0.2.0", # Exclude 0.136.3 while PyPI advisory MAL-2026-4750 is active. "fastapi>=0.100.0,!=0.136.3,<0.139.1", # for test_web_api.py "uvicorn>=0.20.0", # for test_web_api.py "psutil>=5.9.0", # for test_web_api.py "jinja2>=3.0.0", # for test_web_api.py "zettelforge[osint]", # OSINT collectors need dnspython etc. for tests ] [project.urls] Homepage = "https://github.com/rolandpg/zettelforge" Documentation = "https://docs.threatrecall.ai" Repository = "https://github.com/rolandpg/zettelforge" Issues = "https://github.com/rolandpg/zettelforge/issues" Changelog = "https://github.com/rolandpg/zettelforge/blob/master/CHANGELOG.md" [project.scripts] zettelforge = "zettelforge.__main__:main" zettelforge-mcp = "zettelforge.mcp.server:run_stdio" [tool.hatch.build.targets.wheel] packages = ["src/zettelforge"] [tool.mypy] python_version = "3.10" warn_return_any = true warn_unused_configs = true ignore_missing_imports = true disallow_untyped_defs = true [tool.ruff] line-length = 100 [tool.ruff.lint] # GOV-003 §"Tooling and Automation" mandates the full set # {E, F, I, S, B, UP, SIM, RUF, N, ANN, T20}. We ratchet incrementally: # T20 + B + UP + SIM + RUF added earlier 2026-04-25; S (bandit-ish # security) added in this batch. Per-line `# noqa: SXXX` annotations # document each accepted exception (best-effort fallbacks, non-crypto # RNG, ?-bound SQL with constant column lists). Only ANN remains; it # will be ratcheted per-module to avoid a 1000+ finding flood. select = ["E", "F", "I", "W", "N", "T20", "B", "UP", "SIM", "RUF", "S"] ignore = [ "E501", # line too long — handled by formatter "E402", # module-level import not at top — needed for conditional imports "RUF002", # ambiguous unicode in docstrings — en-dash and × are intentional "RUF003", # ambiguous unicode in comments — en-dash and × are intentional ] [tool.ruff.lint.per-file-ignores] "src/zettelforge/__init__.py" = ["F401", "E402"] # re-exports and conditional imports # S104: web host binding to 0.0.0.0 is intentional (RFC-015 web UI) "src/zettelforge/config.py" = ["S104"] # T20 (print) is forbidden in production code (GOV-003), but every CLI # entrypoint legitimately uses print() for stdout output. The patterns # below scope T20 to library code only. "src/zettelforge/__main__.py" = ["T20"] "src/zettelforge/demo.py" = ["T20"] "src/zettelforge/scripts/*.py" = ["T20"] "src/zettelforge/sigma/cli.py" = ["T20"] "src/zettelforge/yara/cli.py" = ["T20"] "src/zettelforge/vector_memory.py" = ["T20"] # has argparse CLI at module bottom [tool.ruff.format] quote-style = "double" [tool.pytest.ini_options] testpaths = ["tests"]