Developer Guide
May 11, 2026 ยท View on GitHub
This guide is for contributors working on the codebase.
Local Setup
Install dependencies:
uv sync
Run tests:
uv run pytest
Run lint checks:
uv run ruff check app tests
Run the web app locally:
uv run uvicorn app.main:app --reload --host 127.0.0.1 --port 9999
Project Layout
app/
main.py FastAPI app, routes, URL/PDF/text ingestion
auth.py Basic auth dependency
config.py Pydantic settings loaded from environment
schema.py CTISummary schema and grounding cache helpers
summarizer.py LLM provider construction and structured summarization
rendering.py Markdown and JSON rendering helpers
cli.py CLI entry point
templates/
index.html Simple web UI
static/ CSS and static assets
tests/ Pytest suite
docs/ Project documentation
Architecture
The service accepts report input through the web UI or CLI. URL and HTML inputs are converted with BeautifulSoup. PDF input is converted with PyMuPDF. The resulting text is sent to CTISummarizer.
CTISummarizer builds a Pydantic AI model from LLMSettings, requests a structured CTISummary, validates the result, and returns operational metadata such as duration and usage when available.
Rendered markdown is produced locally from the validated schema. The LLM is not expected to produce final HTML.
Configuration Changes
Add new environment-backed settings in app/config.py using Pydantic settings. Mirror stable variables in env.example and document them in Configuration.
When adding settings, include tests in tests/test_config.py.
Provider Changes
Provider construction lives in app/summarizer.py.
When adding or changing provider behavior:
- keep provider-specific code isolated in
_build_pydantic_ai_model - add tests in
tests/test_summarizer.py - avoid breaking OpenAI-compatible APIs when adding OpenAI-specific features
- keep timeout, retry, and structured output behavior explicit
Schema Changes
The public structured output is CTISummary in app/schema.py.
When adding fields:
- update the Pydantic model
- update rendering in
app/rendering.py - update tests
- update prompt/config documentation when the LLM should fill the field
- keep optional fields optional when reports may not support them
Prompt Changes
The default prompt is configured by SYSTEM_PROMPT. The code also appends grounding hints and guardrails in CTISummarizer._build_instructions.
Prompt changes should preserve these rules:
- extract only report-supported facts
- do not infer or enrich missing details
- prefer MITRE ATT&CK and Malpedia canonical values when supported
- keep YARA rules empty unless the report contains enough concrete evidence
CLI
The cti-summarizer script is defined in pyproject.toml and implemented in app/cli.py.
Example:
uv run cti-summarizer --file sample-data.txt --return-json
Testing Expectations
Run at least:
uv run pytest
uv run ruff check app tests
For deployment-related changes, also run:
docker compose config --quiet
Add tests for every meaningful behavior change. Tests should cover both success and failure paths where practical.
Known Gaps And Planned Work
- OTEL metrics/tracing configuration is not implemented yet. The documentation has a placeholder in Configuration.
- Reverse proxy examples only cover Traefik.
- The user guide is intentionally short because the UI is expected to change.
- The README has been modernized into an entry point; detailed operational content belongs in
docs/. - Some legacy files remain for reference or compatibility. Prefer
pyproject.tomlanduvoverrequirements.txt.