English | 中文
PR risk advisor and ecosystem auditor.
Feed it a PR, get back a structured risk assessment report. Not a bug finder — it tells you whether this PR will blow up and when it can be merged.
# Install
pip install -e .
# or
uv sync
# Review a GitHub PR
export GITHUB_TOKEN=***
export MIMO_API_KEY=***
codesentinel review https://github.com/owner/repo/pull/123
# Review a GitLab MR (supports nested namespaces)
export GITLAB_TOKEN=***
codesentinel review https://gitlab.com/group/subgroup/project/-/merge_requests/456
# Output to file
codesentinel review <pr_url> --output report.md
# Skip LLM deep review (faster, no API key needed)
codesentinel review <pr_url> --skip-llm
# With local repo (enables git history + CODEOWNERS)
codesentinel review <pr_url> --repo-path ./my-repoThree-level funnel model:
PR → [Level 1: Risk Scoring] → Low risk: log only
→ Medium risk: +supply chain audit +engineering impact
→ High risk: +LLM deep review (issues + evidence + test skeletons)
Deterministic rule engine, no LLM dependency. 19 built-in rules covering:
- Change scale (file count, line count, hunk count)
- Sensitive paths (payment/, auth/, security/)
- Dependency changes (additions, upgrades, removals)
- Author experience (first-time contributor, module familiarity)
- Project critical paths (customizable via .codesentinel/rules.toml)
Output: risk level (low/medium/high) + contribution breakdown:
Risk Level: ⚠️ Medium
Risk Points: 8
+5 sensitive path (payment/)
+3 large PR (25 files)
- Supply Chain Audit: Queries OSV database for dependency CVEs and license risks
- Engineering Impact Assessment: Estimates build time changes, test coverage impact, module coupling
- Dependency Scan: Three-layer architecture (detect → patch parse → full diff), supports 8 package manager formats
Only triggered for high-risk PRs. Uses risk-aware file selection:
- Sensitive paths: +50
- Config/dependency files: +40
- Project critical paths: +weight
- High-defect modules: +30
- No corresponding tests: +20
- Production paths: +15
- Deleted code: +15
- Permission logic: +25
- Error handling: +15
Output: issue description + evidence + executable test skeleton.
# Generate project-specific rules
codesentinel init
codesentinel init --minimal # Only rules.toml
codesentinel init --force # Overwrite existingGenerates a .codesentinel/ directory:
.codesentinel/
rules.toml # Risk rules + critical paths
project_profile.md # Project context (injected into LLM prompt)
review_policy.md # Review principles (injected into LLM prompt)
[settings]
low_risk_max = 3
medium_risk_max = 6
[[rules]]
name = "payment_change"
description = "Changes to payment module"
condition = "touches('payment/')"
score_delta = 5
tag = "security"
[project]
critical_paths = [
{path = "src/core/", weight = 50, reason = "Core engine"},
{path = "payment/", weight = 40, reason = "Payment flow"},
].codesentinel/ configuration is always read from the base branch, not the PR head.
If a PR modifies .codesentinel/, the report will show:
⚠️ Review policy was modified by this PR — this review uses the base branch config; the new policy takes effect after merge.
This prevents malicious PRs from injecting prompts via config changes.
Every step has status tracking, visible in the report:
| Step | Status | Detail |
|-------------------|--------|---------------------------------|
| PR Data | ✅ ok | PR data collected |
| Dependency Scan | ⚠️ partial | patch-only, 2 deps found |
| Risk Scoring | ✅ ok | Risk: HIGH (score=12) |
| OSV Audit | ✅ ok | 0 vulnerabilities found |
| Impact Assessment | ✅ ok | Build impact: +90s |
| LLM Deep Review | ✅ ok | 3 findings |
| Project Context | ✅ ok | Loaded 2 file(s) |
CodeSentinel can be used as a Python library — no CLI required.
import asyncio
from code_sentinel import review, ReviewOptions
async def main():
# GitHub PR
result = await review(
"https://github.com/owner/repo/pull/123",
options=ReviewOptions(provider="mimo"),
)
print(f"Risk: {result.risk.level} ({result.risk.score} points)")
# GitLab MR (supports nested namespaces)
result = await review(
"https://gitlab.com/group/subgroup/project/-/merge_requests/456",
options=ReviewOptions(provider="mimo"),
)
print(f"Risk: {result.risk.level} ({result.risk.score} points)")
asyncio.run(main())from code_sentinel import review_sync, ReviewOptions
result = review_sync(
"https://github.com/owner/repo/pull/123",
options=ReviewOptions(skip_llm=True),
)
print(result.summary)All fields are optional — anything left as None is resolved from config file and environment variables.
ReviewOptions(
provider="mimo", # LLM provider (mimo, deepseek)
model="mimo-v2.5-pro", # Model name
api_key="...", # LLM API key
github_token="...", # GitHub API token
rules_path="rules.toml", # Custom rules file
repo_path=".", # Local repo path (enables git history)
skip_llm=False, # Skip LLM deep review
timeout_seconds=120, # Pipeline timeout
strict=False, # Raise on any pipeline error
auditors=[], # Custom auditor plugins
reporters=[], # Custom reporter plugins
)result.risk.level # "low" / "medium" / "high"
result.risk.score # int
result.risk.contributions # list[RiskContribution]
result.llm_review.findings # list[Finding]
result.dependencies # DependencySummary
result.supply_chain # SupplyChainSummary
result.impact # ImpactSummary
result.attention # list[AttentionFile]
result.pipeline.steps # list[PipelineStep]
result.reports # dict[str, str] (markdown, json, pr-comment)
result.summary # one-line human-readable string
result.is_high_risk # bool
result.has_findings # bool
result.to_dict() # serialize to plain dictCodeSentinel has a plugin system for auditors and reporters.
Add custom analysis steps to the pipeline. Subclass AuditorPlugin and implement audit():
from code_sentinel.plugins import AuditorPlugin, AuditContext, AuditResult
class MyAuditor(AuditorPlugin):
name = "my_auditor"
async def audit(self, context: AuditContext) -> AuditResult:
result = AuditResult(name=self.name)
# Access: context.changeset, context.raw_diff, context.dep_changes, ...
result.status = "ok"
result.findings.append({"type": "custom", "severity": "medium", "message": "..."})
return resultPass it via ReviewOptions(auditors=[MyAuditor()]).
Add custom output formats. Subclass ReporterPlugin and implement render():
from code_sentinel.plugins import ReporterPlugin
from code_sentinel.result import ReviewResult
class SlackReporter(ReporterPlugin):
name = "slack"
def render(self, result: ReviewResult) -> str:
return f"Risk: {result.risk.level} ({result.risk.score})"Pass it via ReviewOptions(reporters=[SlackReporter()]). The rendered output is available in result.reports["slack"].
| Plugin | Type | Name | Description |
|---|---|---|---|
MarkdownReporter |
Reporter | markdown |
Full Markdown report |
JsonReporter |
Reporter | json |
JSON serialization |
PrCommentReporter |
Reporter | pr-comment |
GitHub PR comment format |
SupplyChainAuditor |
Auditor | supply_chain |
OSV vulnerability scan |
ImpactAuditor |
Auditor | impact |
Build/test impact assessment |
DeepReviewAuditor |
Auditor | deep_review |
LLM deep review (high-risk only) |
See the examples/ directory:
| File | Description |
|---|---|
examples/basic.py |
Async review with one line |
examples/basic_sync.py |
Synchronous usage |
examples/custom_rules.py |
Project-specific rules.toml |
examples/custom_reporter.py |
Custom Slack reporter plugin |
examples/custom_auditor.py |
Custom SQL injection & credential auditor |
examples/ci_pipeline.py |
CI/CD integration with exit codes |
Run any example:
python examples/basic.pyCodeSentinel uses a three-level funnel with a plugin harness:
PR URL
│
├─ Collect PR Data (GitHub API)
├─ Parse Diff → ChangeSet
├─ Dependency Scan (3-layer: detect → patch → full diff)
├─ Load Project Context (.codesentinel/)
│
├─ Level 1: Risk Scoring (deterministic rules)
│ └─ 19 built-in rules + custom rules.toml
│
├─ Level 2: Auditors (plugin harness)
│ ├─ SupplyChainAuditor → OSV vulnerabilities
│ ├─ ImpactAuditor → build/test impact
│ └─ [Custom AuditorPlugins]
│
├─ Level 3: LLM Deep Review (high-risk only)
│ └─ risk-aware file selection → findings + test skeletons
│
├─ Reporters (plugin harness)
│ ├─ MarkdownReporter
│ ├─ JsonReporter
│ ├─ PrCommentReporter
│ └─ [Custom ReporterPlugins]
│
└─ ReviewResult (unified output)
The harness pattern:
- AuditorPlugin.audit(context) → AuditResult — each auditor gets an
AuditContextwith all PR data and returns anAuditResultwith findings/warnings. - ReporterPlugin.render(result) → str — each reporter converts a
ReviewResultinto a formatted string. - Pipeline orchestrator runs auditors in order, collects results, then runs reporters. Partial failures are tolerated unless
strict=True.
# .github/workflows/audit.yml
name: CodeSentinel
on:
pull_request:
types: [opened, synchronize, reopened]
jobs:
audit:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: astral-sh/setup-uv@v5
- run: uv python install 3.11
- run: uv tool install git+https://github.com/corrie2/CodeSentinel.git@main
- run: codesentinel review "${{ github.event.pull_request.html_url }}" --output report.md
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
MIMO_API_KEY: ${{ secrets.MIMO_API_KEY }}# Start webhook server
codesentinel serve --port 8080
# GitHub webhook: POST /webhook/github
# GitLab webhook: POST /webhook/gitlabThe webhook server runs the full pipeline:
- Receives PR/MR events from GitHub or GitLab
- Runs risk scoring, dependency audit, and LLM deep review
- Posts review results back as a PR/MR comment
For the comment to be posted, the token needs write access:
- GitHub:
GITHUB_TOKENwithissues:writepermission - GitLab:
GITLAB_TOKENwithapiscope
Environment variables (priority: CODESENTINEL_ prefix > no prefix):
| Variable | Description |
|---|---|
GITHUB_TOKEN |
GitHub API Token |
MIMO_API_KEY |
MiMo LLM API Key (default) |
DEEPSEEK_API_KEY |
DeepSeek API Key (alternative) |
GITLAB_TOKEN |
GitLab API Token (optional) |
codesentinel review <pr_url> # Review a PR/MR (GitHub or GitLab)
codesentinel serve # Start webhook server
codesentinel init # Generate .codesentinel/ template
codesentinel config show # Show current config
codesentinel config set <k> <v> # Set config value
Supported URL formats:
- GitHub:
https://github.com/{owner}/{repo}/pull/{number} - GitLab:
https://{host}/{project_path}/-/merge_requests/{number}- Supports nested namespaces:
group/subgroup/project
- Supports nested namespaces:
src/code_sentinel/
cli.py # CLI entry point
config.py # Configuration management
server.py # FastAPI webhook server
git_provider/ # GitHub + GitLab API
collector/ # diff_parser, dep_scanner, codeowners, git_history
risk/ # scorer, rules, file_ranker, memory
auditor/ # supply_chain, impact, deep_review
reporter/ # formatter (Markdown/JSON/PR comment)
llm/ # LLM client + prompts
rules/ # Default risk rules
templates/ # Report templates
MIT