- Python 3.12+,
uv - Node.js 22+, npm
- Docker (for Postgres+pgvector, Redis, Prometheus, Grafana)
- A GitHub personal access token (
reposcope) if you're testing the PR-generation path against a real repo - An OpenAI API key
git clone <this repo>
cd Sentinel
cp .env.example .env # fill in OPENAI_API_KEY, GITHUB_TOKEN, LANGCHAIN_API_KEY
docker compose up -d # postgres+pgvector, redis, prometheus, grafana
uv sync
cd frontend
cp .env.local.example .env.local
npm install# backend CLI
uv run sentinel ingest --repo .
uv run sentinel audit --repo .
# orchestrator API + WS
uv run uvicorn sentinel.api:app --port 8000 --reload
# frontend
cd frontend && npm run dev
# optional: queue workers (only needed for /api/audits/enqueue, not the default inline path)
uv run rq worker sentinel-audits --worker-class rq.SimpleWorker --url redis://localhost:6379/0Open http://localhost:3000 — paste a GitHub URL or a local repo path, click Run audit.
Never point Sentinel at a repo you care about while testing changes to pr_node,
fixer.py, or git_actions.py — those write real branches/commits/PRs. Use a
disposable repo (see sentinel-project-brief.md / roadmap.md for how
sentinel-test-target was built and why).
- Python:
uv+pyproject.toml, norequirements.txt. Format/lint withruff(already a dependency —uv run ruff check .). - TypeScript: standard Next.js/ESLint conventions from
create-next-app; shadcn/ui components live infrontend/src/components/ui/and are generated vianpx shadcn add <component>, not hand-written. - Logging: structured JSON via
structlog(sentinel/logging.py). Every log call in a request/audit path should carryaudit_idif one exists in scope — it's the correlation ID that ties a log line to a specific run across the queue, agents, and git actions. - Config: env vars only, read via
sentinel/config.py'sSettingsclass. Never hardcode an endpoint, model name, or credential — add a newSettingsfield with a sensible default instead. - Commits: descriptive, explain why not just what. This project's own commit history (not covered by this doc) is the reference for tone/format.
Sentinel does not yet have a test suite for its own codebase — the Test Analyst has
correctly flagged this on every self-audit throughout development (see
design_decisions.md). If you're adding a testable, non-trivial function (especially in
sentinel/tools.py, sentinel/cache.py, sentinel/dedup.py, or anything with a validated
input boundary like sentinel/repos.py's URL regex), adding a tests/ directory with
pytest tests for it is genuinely valuable — there's no existing suite that would make
this redundant.
For validating behavioral changes to the agent pipeline itself, the practical approach used throughout this project's own development was:
- Test the specific function/tool in isolation first (
uv run python -c "...") before wiring it into an agent — cheaper and faster to debug. - Run
sentinel audit --repo .(self-audit) to sanity-check the full pipeline. - Run against
sentinel-test-target(or another disposable repo) to validate anything touching real GitHub actions. - For dedup/idempotency/lock changes specifically: run the same audit twice in a row and
diff the
pr_statusfields — this is how every dedup regression in this project was actually caught.
- Add tools in
sentinel/tools.py(or a new module) following the existing pattern: amake_*_tool(repo_path: Path)factory returning a@tool-decorated closure. Validate any path/input the tool touches — see_within_repointools.pyand the URL validation inrepos.pyfor the pattern. - Add a system prompt in
sentinel/analysts.pyfollowing the existing three: state the grounding tools to use first, an explicit scope boundary, and appendsentinel.agent.CONVERGENCE_RULE. - Add a
*_node(state: AuditState) -> dictfunction calling_run_analyst(...)with your tools/prompt, returning{"<name>_findings": findings}. - Wire it into
build_graph()insentinel/graph.py: add the node, an edge fromscope, and an edge todiagnose. Updatediagnose_node's risk-tier assignment for the new analyst. - Update
sentinel/state.py'sAuditStatewith the new findings key.
sentinel/fixer.py's FIXABLE_ANALYSTS set controls which analysts' findings get a
proposed fix at all. If you're extending it (e.g. to include test findings once
test-writing is implemented), the constraint that matters most: old_snippet must
remain something verifiably unique-and-exact in the file, checked before the fix is ever
proposed as usable (see design_decisions.md's note on snippet-replace patches over raw
diffs) — don't relax that check to make a new fix category easier to implement.
See docs/architecture.md for the full repository layout and component breakdown.