[build-system] requires = ["setuptools>=61.0.0", "wheel"] build-backend = "setuptools.build_meta" [project] name = "palinode" version = "0.21.0" description = "Inspectable, correctable project memory in git-versioned Markdown for AI agents." authors = [ {name = "Paul Kyle", email = "paul@phasespace.co"} ] readme = "README.md" license = {text = "MIT"} requires-python = ">=3.11" keywords = ["ai", "agents", "memory", "mcp", "llm", "rag", "knowledge-management"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", "Programming Language :: Python :: 3.13", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Topic :: Scientific/Engineering :: Artificial Intelligence", "Topic :: Software Development :: Libraries :: Python Modules", ] dependencies = [ # Lower bounds set to versions that exclude all known CVEs against these packages # as of 2026-04-30. See PR description for CVE-to-version mapping. "sqlite-vec>=0.1.6", # CVE-2024-46488 fixed in 0.1.6 "watchdog>=4.0", "pyyaml>=6.0,<7.0", # 5.x has multiple RCE/parser CVEs "httpx>=0.27", # older versions had input-validation CVEs "python-frontmatter>=1.1", "fastapi>=0.115,<0.137", # older: CSRF/ReDoS CVEs. Cap <0.137: 0.137.1 regressed include_router (routes not added to app → routeless API). 0.136.x is the last good line. "uvicorn>=0.32", # 0.11.x had log-injection / response-splitting CVEs "pydantic>=2.7,<3.0", # 1.x and pre-2.4 had ReDoS / infinity-loop CVEs # Upper bound is load-bearing: mcp 2.0.0 (2026-07-28) removed both # Server.list_tools and Server.call_tool, the decorators palinode/mcp.py is # built on, so an uncapped range installs a server that cannot start. # Lower bound: earlier versions carry DNS rebinding / FastMCP DoS / # Streamable HTTP DoS CVEs. Lift the cap only with the 2.x migration. "mcp>=2,<3", # 2.x server API. Upper bound is deliberate: 1.10 was # declared unbounded and pip resolved it to a 2.0 that # removed the decorators, breaking every fresh install. "rich>=13.7", "click>=8.1", # The consolidation parser's recovery path for malformed model JSON # (`op_parse.parse_result`). Was an undeclared import inside an `except` # branch, so the documented recovery had never once run in production — # every malformed proposal fell through to "no operations". Pure # Python, ~25 KB, MIT, no required transitive deps. "json-repair>=0.30", "jinja2>=3.1.5", # provenance UI templating; 3.1.5 fixes sandbox-escape CVEs "markdown-it-py>=3.0", # provenance UI: render memory bodies (html disabled) "nh3>=0.2.18", # provenance UI: HTML sanitizer (ammonia); XSS backstop ] [project.urls] "Homepage" = "https://github.com/phasespace-labs/palinode" "Documentation" = "https://github.com/phasespace-labs/palinode/tree/main/docs" "Repository" = "https://github.com/phasespace-labs/palinode" "Issues" = "https://github.com/phasespace-labs/palinode/issues" "Changelog" = "https://github.com/phasespace-labs/palinode/blob/main/docs/CHANGELOG.md" "Privacy" = "https://github.com/phasespace-labs/palinode/blob/main/PRIVACY.md" [project.scripts] palinode-watcher = "palinode.indexer.watcher:main" palinode-api = "palinode.api.server:main" palinode = "palinode.cli:main" palinode-mcp = "palinode.mcp:main" palinode-mcp-http = "palinode.mcp:main_http" palinode-mcp-sse = "palinode.mcp:main_sse" # deprecated alias for main_http [project.optional-dependencies] dev = [ "pytest", "pytest-asyncio", "pytest-timeout", "ruff", "bandit" ] [tool.ruff.lint] # Deliberately narrow: bug-classes only, no formatting opinions. CI runs # `ruff check palinode/ tests/ scripts/` and gates on it, so this set has to # stay at zero — every rule here is one that catches a defect, not a style. # # F pyflakes — undefined names, unused imports/vars (found the # `palinode config edit` NameError this config landed with) # B bugbear — mutable defaults, loop-variable closures, zip() misuse # E7 statements — bare `except:`, ambiguous names like `l` # E9 runtime — syntax/IO errors that break collection # # NOT enabled, with reasons — revisit deliberately, not by widening `select`: # E501 line-too-long (1373 hits) — this codebase uses long lines for MCP # schema descriptions on purpose; a formatter's job, not a linter's. # I001 unsorted-imports (220) — pure churn; would collide with the # `merge=union` CHANGELOG/diff discipline for no defect caught. # W291/W293 whitespace (253) — same; belongs to `ruff format` if adopted. # E402 import-not-at-top (43) — several are deliberate lazy imports that # keep CLI startup fast. # UP pyupgrade (~100) — modernization, not correctness. Separate PR. select = ["F", "B", "E7", "E9"] # B904 (raise ... from err) is 39 sites needing per-site judgment about which # exceptions to chain. Worth doing; not worth blocking this PR on. ignore = ["B904"] [tool.bandit] # Bandit static security analysis configuration. CI runs `bandit -r palinode/ -ll` # (medium+ severity); these settings make local-invocation behavior match CI. # All in-tree findings are triaged inline with `# nosec B: ` — # do NOT add wholesale rule skips here without a specific justification. exclude_dirs = ["tests", ".venv", "build", "dist"] [tool.pytest.ini_options] testpaths = ["tests"] # Exclude live tests from default runs (they need a running server) addopts = "--ignore=tests/live" pythonpath = ["."] markers = [ "slow: marks tests that need Ollama or take >5s", "doctor_real_search_roots: allow a test to exercise doctor's built-in filesystem root discovery", ] asyncio_mode = "strict" timeout = 60 timeout_method = "thread" [tool.setuptools] packages = [ "palinode", "palinode.api", "palinode.api.routers", "palinode.api.ui", "palinode.cli", "palinode.consolidation", "palinode.core", "palinode.diagnostics", "palinode.diagnostics.checks", "palinode.import_", "palinode.indexer", "palinode.ingest", "palinode.lint", "palinode.migration", "palinode.prompts", ] [tool.setuptools.package-data] # Ship the provenance UI's Jinja2 templates and static assets inside the wheel # (offline-first — no CDN). Without this, an installed wheel has the UI Python # but no templates/CSS and the router 500s at first render. "palinode.api.ui" = ["templates/*.html", "static/*.css"] # Ship the consolidation prompts. `specs/prompts/*.md` is the source of truth # in the repo but `specs/` is not a package and never entered the wheel, so a # PyPI install had no prompts at all: `palinode init` wrote none into the store # and the first `palinode consolidate` died on a missing file. The copies under # palinode/prompts/ are pinned byte-identical to specs/prompts/ by # tests/test_packaged_prompts_match_source.py. shipped-hashes.json is the # per-release hash record `palinode prompt sync` uses to tell a stale store # copy from an operator-edited one. "palinode.prompts" = ["*.md", "shipped-hashes.json"]