Coverage for src / documint_mcp / check_runs.py: 0%
30 statements
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 22:30 -0400
« prev ^ index » next coverage.py v7.13.5, created at 2026-03-30 22:30 -0400
1"""
2GitHub Check Runs API integration for Documint.
3Posts non-blocking drift annotations on PRs (neutral conclusion — the Codecov model).
4"""
6from __future__ import annotations
8from dataclasses import dataclass, field
10from .github import _github_request, get_installation_access_token
13@dataclass
14class DriftAnnotation:
15 path: str # "docs/API_REFERENCE.md"
16 start_line: int # line in the doc that mentions the changed symbol
17 end_line: int
18 annotation_level: str # "warning" (non-blocking)
19 message: str # "add_memory() gained parameter session_id: str — review §3.2"
20 title: str # "Documint: Documentation drift detected"
21 raw_details: str = field(default="") # full diff context (optional)
24class CheckRunManager:
25 """
26 Manages GitHub Check Runs for drift detection.
28 Flow:
29 1. create_check_run() — called when PR is opened (status=in_progress)
30 2. complete_check_run() — called when drift analysis finishes (conclusion=neutral)
32 The existing GitHub client in github.py uses synchronous httpx calls via the
33 module-level _github_request() helper and get_installation_access_token().
34 This class wraps those helpers directly.
35 """
37 def create_check_run(
38 self,
39 owner: str,
40 repo: str,
41 head_sha: str,
42 installation_id: str,
43 ) -> int:
44 """
45 Create a check run in 'in_progress' state.
46 Returns the check run ID.
47 """
48 token = get_installation_access_token(installation_id)
49 resp = _github_request(
50 "POST",
51 f"/repos/{owner}/{repo}/check-runs",
52 token=token,
53 json_body={
54 "name": "Documint",
55 "head_sha": head_sha,
56 "status": "in_progress",
57 "output": {
58 "title": "Checking documentation drift...",
59 "summary": "Documint is analyzing your changes for documentation drift.",
60 },
61 },
62 )
63 return int(resp.json()["id"])
65 def complete_check_run(
66 self,
67 owner: str,
68 repo: str,
69 check_run_id: int,
70 installation_id: str,
71 findings_count: int,
72 annotations: list[DriftAnnotation],
73 ) -> None:
74 """
75 Update check run with results.
77 Always uses conclusion="neutral" (non-blocking — informational only).
78 Annotation level is "warning" (not "failure").
79 """
80 if findings_count == 0:
81 title = "No documentation drift detected"
82 summary = "All documentation artifacts are synchronized with their source files."
83 else:
84 plural = "s" if findings_count > 1 else ""
85 title = f"{findings_count} documentation artifact{plural} may be outdated"
86 summary = (
87 f"Documint detected structural changes in {findings_count} artifact definition(s). "
88 "Review and update documentation to keep your agent context accurate.\n\n"
89 "This check is informational and does not block merging."
90 )
92 # GitHub API limits: max 50 annotations per update
93 annotation_payload = [
94 {
95 "path": a.path,
96 "start_line": a.start_line,
97 "end_line": a.end_line,
98 "annotation_level": a.annotation_level,
99 "message": a.message,
100 "title": a.title,
101 "raw_details": a.raw_details,
102 }
103 for a in annotations[:50]
104 ]
106 token = get_installation_access_token(installation_id)
107 _github_request(
108 "PATCH",
109 f"/repos/{owner}/{repo}/check-runs/{check_run_id}",
110 token=token,
111 json_body={
112 "status": "completed",
113 "conclusion": "neutral",
114 "output": {
115 "title": title,
116 "summary": summary,
117 "annotations": annotation_payload,
118 },
119 },
120 )
122 def fail_check_run(
123 self,
124 owner: str,
125 repo: str,
126 check_run_id: int,
127 installation_id: str,
128 error_message: str,
129 ) -> None:
130 """Mark check run as failed (internal error, not drift)."""
131 token = get_installation_access_token(installation_id)
132 _github_request(
133 "PATCH",
134 f"/repos/{owner}/{repo}/check-runs/{check_run_id}",
135 token=token,
136 json_body={
137 "status": "completed",
138 "conclusion": "neutral", # still neutral — don't block for our own errors
139 "output": {
140 "title": "Documint check encountered an error",
141 "summary": f"Internal error during drift analysis: {error_message}",
142 },
143 },
144 )