@kernel-sig/console 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (198) hide show
  1. package/.env.example +50 -0
  2. package/LICENSE +127 -0
  3. package/Makefile +66 -0
  4. package/README.md +279 -0
  5. package/backend/.dockerignore +12 -0
  6. package/backend/Dockerfile +43 -0
  7. package/backend/alembic/env.py +75 -0
  8. package/backend/alembic/script.py.mako +25 -0
  9. package/backend/alembic/versions/0001_initial.py +88 -0
  10. package/backend/alembic/versions/350e2d9b6553_add_pr_comment_table.py +48 -0
  11. package/backend/alembic/versions/4f8342e727fe_sig_info_roster_fields_and_branch_.py +39 -0
  12. package/backend/alembic/versions/5a31ade90136_add_repository_credential_pull_request_.py +293 -0
  13. package/backend/alembic/versions/87a008142f17_add_classification_attention_and_ai_.py +436 -0
  14. package/backend/alembic/versions/a1c7d90e4b52_branch_belongs_to_a_repository.py +72 -0
  15. package/backend/alembic/versions/b41c9d7e5f28_extend_pr_kind_categories.py +43 -0
  16. package/backend/alembic/versions/c8e4f2a71b93_add_release_kind.py +40 -0
  17. package/backend/alembic/versions/d1275091dfdc_add_needs_detail_flag_to_pull_request.py +44 -0
  18. package/backend/alembic/versions/e5b27c9d3a41_downgrade_cve_without_ids.py +54 -0
  19. package/backend/alembic/versions/ee3159a4eff0_add_sig_meeting_member_and_release_.py +164 -0
  20. package/backend/alembic/versions/fcf3c186d63b_attention_rule_subscriptions.py +42 -0
  21. package/backend/alembic.ini +40 -0
  22. package/backend/app/__init__.py +0 -0
  23. package/backend/app/api/__init__.py +0 -0
  24. package/backend/app/api/deps.py +74 -0
  25. package/backend/app/api/v1/__init__.py +0 -0
  26. package/backend/app/api/v1/ai.py +386 -0
  27. package/backend/app/api/v1/analytics.py +48 -0
  28. package/backend/app/api/v1/attention.py +142 -0
  29. package/backend/app/api/v1/auth.py +106 -0
  30. package/backend/app/api/v1/classification.py +192 -0
  31. package/backend/app/api/v1/health.py +29 -0
  32. package/backend/app/api/v1/issues.py +143 -0
  33. package/backend/app/api/v1/pulls.py +388 -0
  34. package/backend/app/api/v1/repositories.py +273 -0
  35. package/backend/app/api/v1/router.py +32 -0
  36. package/backend/app/api/v1/sig.py +565 -0
  37. package/backend/app/api/v1/users.py +87 -0
  38. package/backend/app/api/v1/webhooks.py +135 -0
  39. package/backend/app/core/__init__.py +0 -0
  40. package/backend/app/core/config.py +72 -0
  41. package/backend/app/core/crypto.py +74 -0
  42. package/backend/app/core/db.py +79 -0
  43. package/backend/app/core/exceptions.py +60 -0
  44. package/backend/app/core/logging.py +69 -0
  45. package/backend/app/core/permissions.py +87 -0
  46. package/backend/app/core/queue.py +57 -0
  47. package/backend/app/core/security.py +69 -0
  48. package/backend/app/domain/__init__.py +0 -0
  49. package/backend/app/domain/attention.py +618 -0
  50. package/backend/app/domain/classification.py +689 -0
  51. package/backend/app/domain/meeting.py +408 -0
  52. package/backend/app/domain/release.py +163 -0
  53. package/backend/app/domain/review.py +416 -0
  54. package/backend/app/domain/sig.py +178 -0
  55. package/backend/app/domain/sig_info.py +201 -0
  56. package/backend/app/integrations/__init__.py +0 -0
  57. package/backend/app/integrations/atomgit/__init__.py +0 -0
  58. package/backend/app/integrations/atomgit/client.py +505 -0
  59. package/backend/app/integrations/atomgit/models.py +311 -0
  60. package/backend/app/integrations/llm/__init__.py +0 -0
  61. package/backend/app/integrations/llm/prompts.py +222 -0
  62. package/backend/app/integrations/llm/provider.py +326 -0
  63. package/backend/app/main.py +242 -0
  64. package/backend/app/middleware/__init__.py +0 -0
  65. package/backend/app/middleware/audit.py +129 -0
  66. package/backend/app/middleware/request_context.py +42 -0
  67. package/backend/app/models/__init__.py +112 -0
  68. package/backend/app/models/ai.py +214 -0
  69. package/backend/app/models/attention.py +131 -0
  70. package/backend/app/models/attention_settings.py +61 -0
  71. package/backend/app/models/audit.py +53 -0
  72. package/backend/app/models/base.py +35 -0
  73. package/backend/app/models/classification.py +144 -0
  74. package/backend/app/models/credential.py +56 -0
  75. package/backend/app/models/issue.py +132 -0
  76. package/backend/app/models/meeting.py +189 -0
  77. package/backend/app/models/pull_request.py +288 -0
  78. package/backend/app/models/repository.py +196 -0
  79. package/backend/app/models/sig.py +194 -0
  80. package/backend/app/models/user.py +44 -0
  81. package/backend/app/schemas/__init__.py +0 -0
  82. package/backend/app/schemas/ai.py +120 -0
  83. package/backend/app/schemas/attention.py +43 -0
  84. package/backend/app/schemas/auth.py +26 -0
  85. package/backend/app/schemas/classification.py +58 -0
  86. package/backend/app/schemas/common.py +43 -0
  87. package/backend/app/schemas/pull_request.py +214 -0
  88. package/backend/app/schemas/repository.py +94 -0
  89. package/backend/app/schemas/sig.py +246 -0
  90. package/backend/app/schemas/user.py +79 -0
  91. package/backend/app/services/__init__.py +0 -0
  92. package/backend/app/services/ai_service.py +569 -0
  93. package/backend/app/services/analytics_service.py +390 -0
  94. package/backend/app/services/attention_queue.py +451 -0
  95. package/backend/app/services/attention_service.py +432 -0
  96. package/backend/app/services/auth_service.py +74 -0
  97. package/backend/app/services/classification_service.py +658 -0
  98. package/backend/app/services/credential_service.py +105 -0
  99. package/backend/app/services/pull_query.py +273 -0
  100. package/backend/app/services/release_service.py +446 -0
  101. package/backend/app/services/repository_service.py +132 -0
  102. package/backend/app/services/sig_service.py +385 -0
  103. package/backend/app/services/sync_service.py +752 -0
  104. package/backend/app/services/user_service.py +68 -0
  105. package/backend/app/worker.py +389 -0
  106. package/backend/entrypoint.sh +10 -0
  107. package/backend/pyproject.toml +68 -0
  108. package/backend/tests/test_analysis_api.py +154 -0
  109. package/backend/tests/test_atomgit_client.py +360 -0
  110. package/backend/tests/test_atomgit_models.py +257 -0
  111. package/backend/tests/test_attention.py +291 -0
  112. package/backend/tests/test_audit_middleware.py +135 -0
  113. package/backend/tests/test_classification.py +498 -0
  114. package/backend/tests/test_config.py +41 -0
  115. package/backend/tests/test_crypto.py +68 -0
  116. package/backend/tests/test_exceptions.py +62 -0
  117. package/backend/tests/test_health.py +63 -0
  118. package/backend/tests/test_llm_provider.py +320 -0
  119. package/backend/tests/test_meeting_domain.py +169 -0
  120. package/backend/tests/test_permissions.py +69 -0
  121. package/backend/tests/test_pull_query_wiring.py +66 -0
  122. package/backend/tests/test_pull_schemas.py +82 -0
  123. package/backend/tests/test_release_domain.py +82 -0
  124. package/backend/tests/test_review_parser.py +301 -0
  125. package/backend/tests/test_schemas_user.py +97 -0
  126. package/backend/tests/test_security.py +92 -0
  127. package/cli/index.js +338 -0
  128. package/compose.yaml +105 -0
  129. package/frontend/.dockerignore +4 -0
  130. package/frontend/Dockerfile +27 -0
  131. package/frontend/index.html +14 -0
  132. package/frontend/nginx.conf +47 -0
  133. package/frontend/package-lock.json +5020 -0
  134. package/frontend/package.json +35 -0
  135. package/frontend/src/api/ai.ts +124 -0
  136. package/frontend/src/api/analytics.ts +66 -0
  137. package/frontend/src/api/attention.ts +181 -0
  138. package/frontend/src/api/auth.ts +74 -0
  139. package/frontend/src/api/classification.ts +170 -0
  140. package/frontend/src/api/issues.ts +90 -0
  141. package/frontend/src/api/pulls.ts +233 -0
  142. package/frontend/src/api/repositories.ts +95 -0
  143. package/frontend/src/api/sig.ts +232 -0
  144. package/frontend/src/app/antd-theme.ts +94 -0
  145. package/frontend/src/app/providers.tsx +59 -0
  146. package/frontend/src/app/router.tsx +411 -0
  147. package/frontend/src/app/search.ts +30 -0
  148. package/frontend/src/components/ClassificationBadge.tsx +58 -0
  149. package/frontend/src/components/GateBadge.tsx +13 -0
  150. package/frontend/src/components/SeverityBadge.tsx +20 -0
  151. package/frontend/src/components/layout/AppShell.tsx +16 -0
  152. package/frontend/src/components/layout/AuthLayout.tsx +32 -0
  153. package/frontend/src/components/layout/Sidebar.tsx +223 -0
  154. package/frontend/src/components/layout/TopBar.tsx +47 -0
  155. package/frontend/src/components/pulls/DiscussionTimeline.tsx +145 -0
  156. package/frontend/src/components/pulls/FacetRail.tsx +199 -0
  157. package/frontend/src/components/pulls/LabelChips.tsx +87 -0
  158. package/frontend/src/components/ui/alert.tsx +30 -0
  159. package/frontend/src/components/ui/badge.tsx +53 -0
  160. package/frontend/src/components/ui/button.tsx +51 -0
  161. package/frontend/src/components/ui/card.tsx +64 -0
  162. package/frontend/src/components/ui/chart-theme.ts +65 -0
  163. package/frontend/src/components/ui/data-table.tsx +39 -0
  164. package/frontend/src/components/ui/echart.tsx +70 -0
  165. package/frontend/src/components/ui/empty-state.tsx +22 -0
  166. package/frontend/src/components/ui/input.tsx +39 -0
  167. package/frontend/src/components/ui/lazy-chart.tsx +21 -0
  168. package/frontend/src/components/ui/skeleton.tsx +19 -0
  169. package/frontend/src/hooks/use-current-repository.ts +44 -0
  170. package/frontend/src/hooks/use-current-user.ts +38 -0
  171. package/frontend/src/lib/api-client.ts +93 -0
  172. package/frontend/src/lib/css-color.ts +60 -0
  173. package/frontend/src/lib/utils.ts +45 -0
  174. package/frontend/src/main.tsx +22 -0
  175. package/frontend/src/pages/attention/AttentionQueuePage.tsx +316 -0
  176. package/frontend/src/pages/attention/RuleSettingsPanel.tsx +177 -0
  177. package/frontend/src/pages/branches/BranchDetailPage.tsx +632 -0
  178. package/frontend/src/pages/branches/BranchListPage.tsx +308 -0
  179. package/frontend/src/pages/dashboard/DashboardPage.tsx +657 -0
  180. package/frontend/src/pages/issues/IssueListPage.tsx +284 -0
  181. package/frontend/src/pages/login/LoginPage.tsx +95 -0
  182. package/frontend/src/pages/meetings/MeetingDetailPage.tsx +403 -0
  183. package/frontend/src/pages/meetings/MeetingListPage.tsx +264 -0
  184. package/frontend/src/pages/members/MembersPage.tsx +534 -0
  185. package/frontend/src/pages/pulls/PullDetailPage.tsx +833 -0
  186. package/frontend/src/pages/pulls/PullListPage.tsx +399 -0
  187. package/frontend/src/pages/settings/AiSettingsPage.tsx +449 -0
  188. package/frontend/src/pages/settings/SettingsPage.tsx +321 -0
  189. package/frontend/src/pages/setup/SetupPage.tsx +144 -0
  190. package/frontend/src/styles/globals.css +147 -0
  191. package/frontend/tsconfig.json +22 -0
  192. package/frontend/vite.config.ts +57 -0
  193. package/package.json +48 -0
  194. package/scripts/e2e-auth-flow.py +131 -0
  195. package/scripts/e2e-pr-detail.py +128 -0
  196. package/scripts/e2e-verify.py +773 -0
  197. package/scripts/verify-ai-pipeline.py +616 -0
  198. package/scripts/verify-analysis-pipeline.py +303 -0
@@ -0,0 +1,311 @@
1
+ """AtomGit API 响应的数据模型。
2
+
3
+ 字段严格对应 **实测响应**(见 `tests/fixtures/atomgit/`),而非官方文档 ——
4
+ 文档描述的是 GitCode 企业版超集,与 atomgit.com 实例存在出入。
5
+
6
+ 实测发现的结构差异(与 GitHub 风格 API 的直觉不同):
7
+ - ``pulls/{n}/files`` 的 ``patch`` 是**对象**(含 ``diff``/``old_path``/``too_large``),
8
+ 不是字符串
9
+ - commit 的 message 嵌套在 ``commit.{author,committer,message}``
10
+ - ``mergeable_state`` 是含冲突检测明细的**对象**,不是状态字符串
11
+ - 时间字段在"未发生"时返回空字符串而非 null
12
+ - Issue 的 ``number`` 是字符串,PR 的是整数
13
+ - PR 对象不含评审关系,评审状态由 ``labels`` 承载
14
+ """
15
+
16
+ from datetime import datetime
17
+ from typing import Annotated, Any, ClassVar
18
+
19
+ from pydantic import BaseModel, BeforeValidator, ConfigDict, Field
20
+
21
+
22
+ def _blank_to_none(value: Any) -> Any:
23
+ """AtomGit 用空字符串表示"时间未设置",需归一化为 None。"""
24
+ if isinstance(value, str) and not value.strip():
25
+ return None
26
+ return value
27
+
28
+
29
+ def _str_to_int(value: Any) -> Any:
30
+ """Issue 编号以字符串返回,统一转为整数以与 PR 共用编号空间。"""
31
+ if isinstance(value, str) and value.isdigit():
32
+ return int(value)
33
+ return value
34
+
35
+
36
+ def _wrap_string_as_diff(value: Any) -> Any:
37
+ """``patch`` 可能是对象,也可能是纯 diff 字符串,统一成对象形态。"""
38
+ if isinstance(value, str):
39
+ return {"diff": value}
40
+ return value
41
+
42
+
43
+ OptionalDateTime = Annotated[datetime | None, BeforeValidator(_blank_to_none)]
44
+ NumericId = Annotated[int, BeforeValidator(_str_to_int)]
45
+
46
+
47
+ class AtomGitBase(BaseModel):
48
+ model_config = ConfigDict(extra="ignore", populate_by_name=True)
49
+
50
+
51
+ class AtomGitUser(AtomGitBase):
52
+ login: str
53
+ id: Any = None
54
+ name: str | None = None
55
+ avatar_url: str | None = None
56
+
57
+
58
+ class AtomGitLabel(AtomGitBase):
59
+ id: Any = None
60
+ name: str
61
+ color: str | None = None
62
+ description: str | None = None
63
+
64
+
65
+ class AtomGitBranchRef(AtomGitBase):
66
+ ref: str | None = None
67
+ sha: str | None = None
68
+ label: str | None = None
69
+
70
+
71
+ class AtomGitMergeCheck(AtomGitBase):
72
+ """``mergeable_state`` 实测为合并前置检查的明细对象。
73
+
74
+ 比单一的 mergeable 布尔值信息量大得多 —— 可直接用于关注项规则,
75
+ 例如 ``conflict_passed == False`` 表示存在合并冲突。
76
+ """
77
+
78
+ model_config = ConfigDict(extra="allow")
79
+
80
+ merge_request_id: int | None = None
81
+ state: bool | None = None
82
+ status_without_user_auth: bool | None = None
83
+ conflict_passed: bool | None = None
84
+ branch_missing_passed: bool | None = None
85
+ non_ff_passed: bool | None = None
86
+ mr_state_passed: bool | None = None
87
+ merged_by_user_passed: bool | None = None
88
+
89
+
90
+ class AtomGitPullRequest(AtomGitBase):
91
+ """PR 列表与详情共用。详情接口的字段是列表接口的子集扩充。"""
92
+
93
+ number: int
94
+ id: Any = None
95
+ iid: int | None = None
96
+ project_id: Any = None
97
+ title: str
98
+ body: str | None = None
99
+ state: str
100
+ draft: bool = False
101
+ locked: bool = False
102
+
103
+ user: AtomGitUser | None = None
104
+ labels: list[AtomGitLabel] = Field(default_factory=list)
105
+ assignees: list[AtomGitUser] = Field(default_factory=list)
106
+ testers: list[AtomGitUser] = Field(default_factory=list)
107
+ approval_reviewers: list[AtomGitUser] = Field(default_factory=list)
108
+
109
+ target_branch: str | None = None
110
+ source_branch: str | None = None
111
+ source_project_id: Any = None
112
+
113
+ added_lines: int = 0
114
+ removed_lines: int = 0
115
+ # AtomGit 的 notes 字段直接给出评论数,可用于变更检测
116
+ notes: int = 0
117
+
118
+ mergeable: bool | None = None
119
+ mergeable_state: AtomGitMergeCheck | None = None
120
+ can_merge_check: bool | None = None
121
+ close_related_issue: Any = None
122
+
123
+ created_at: OptionalDateTime = None
124
+ updated_at: OptionalDateTime = None
125
+ merged_at: OptionalDateTime = None
126
+ closed_at: OptionalDateTime = None
127
+
128
+ html_url: str | None = None
129
+ web_url: str | None = None
130
+
131
+ head: AtomGitBranchRef | None = None
132
+ base: AtomGitBranchRef | None = None
133
+
134
+ @property
135
+ def label_names(self) -> list[str]:
136
+ return [label.name for label in self.labels]
137
+
138
+ @property
139
+ def author_login(self) -> str | None:
140
+ return self.user.login if self.user else None
141
+
142
+ @property
143
+ def permalink(self) -> str | None:
144
+ """规范 PR 链接。html_url 实测指向 /merge_requests/,故优先 web_url。"""
145
+ return self.web_url or self.html_url
146
+
147
+ @property
148
+ def has_merge_conflict(self) -> bool:
149
+ """是否冲突。优先用明细对象,回退到 mergeable 布尔值。"""
150
+ if self.mergeable_state is not None and self.mergeable_state.conflict_passed is not None:
151
+ return not self.mergeable_state.conflict_passed
152
+ return self.mergeable is False
153
+
154
+
155
+ class AtomGitIssue(AtomGitBase):
156
+ number: NumericId
157
+ id: Any = None
158
+ title: str
159
+ body: str | None = None
160
+ state: str
161
+ comments: int = 0
162
+
163
+ # AtomGit 原生结构化字段,比用标签猜测更可靠
164
+ issue_type: str | None = None
165
+ issue_state: str | None = None
166
+ priority: int | None = None
167
+ issue_priority_detail: dict[str, Any] | None = None
168
+ issue_type_detail: dict[str, Any] | None = None
169
+ issue_state_detail: dict[str, Any] | None = None
170
+
171
+ labels: list[AtomGitLabel] = Field(default_factory=list)
172
+ user: AtomGitUser | None = None
173
+ assignee: AtomGitUser | None = None
174
+ assignees: list[AtomGitUser] = Field(default_factory=list)
175
+
176
+ created_at: OptionalDateTime = None
177
+ updated_at: OptionalDateTime = None
178
+ finished_at: OptionalDateTime = None
179
+ html_url: str | None = None
180
+
181
+ @property
182
+ def label_names(self) -> list[str]:
183
+ return [label.name for label in self.labels]
184
+
185
+ @property
186
+ def priority_label(self) -> str | None:
187
+ if self.issue_priority_detail:
188
+ title = self.issue_priority_detail.get("title")
189
+ if isinstance(title, str):
190
+ return title
191
+ return None
192
+
193
+
194
+ class AtomGitComment(AtomGitBase):
195
+ """PR 与 Issue 的评论。
196
+
197
+ 实测字段含 ``comment_type`` 与 ``discussion_id``;
198
+ 后者可用于把评论归入同一条讨论线程。
199
+ """
200
+
201
+ id: Any
202
+ body: str = ""
203
+ comment_type: str | None = None
204
+ discussion_id: str | None = None
205
+ user: AtomGitUser | None = None
206
+ created_at: OptionalDateTime = None
207
+ updated_at: OptionalDateTime = None
208
+
209
+ @property
210
+ def author_login(self) -> str | None:
211
+ return self.user.login if self.user else None
212
+
213
+
214
+ class AtomGitFilePatch(AtomGitBase):
215
+ """``pulls/{n}/files`` 中 ``patch`` 字段的实际结构。"""
216
+
217
+ diff: str | None = None
218
+ old_path: str | None = None
219
+ new_path: str | None = None
220
+ a_mode: str | None = None
221
+ b_mode: str | None = None
222
+ new_file: bool = False
223
+ deleted_file: bool = False
224
+ renamed_file: bool = False
225
+ too_large: bool = False
226
+ added_lines: int = 0
227
+ removed_lines: int = 0
228
+
229
+
230
+ class AtomGitFile(AtomGitBase):
231
+ filename: str
232
+ status: str | None = None
233
+ additions: int = 0
234
+ deletions: int = 0
235
+ changes: int = 0
236
+ sha: str | None = None
237
+ blob_url: str | None = None
238
+ raw_url: str | None = None
239
+ patch: Annotated[AtomGitFilePatch | None, BeforeValidator(_wrap_string_as_diff)] = None
240
+
241
+ # 二进制文件的判定依据:Git 在 diff 中给出的是这个标记而非文本差异
242
+ BINARY_MARKERS: ClassVar[tuple[str, ...]] = (
243
+ "Binary files differ",
244
+ "GIT binary patch",
245
+ )
246
+
247
+ @property
248
+ def diff_text(self) -> str | None:
249
+ return self.patch.diff if self.patch else None
250
+
251
+ @property
252
+ def is_binary(self) -> bool:
253
+ text = self.diff_text
254
+ if text is None:
255
+ return False
256
+ return any(marker in text for marker in self.BINARY_MARKERS)
257
+
258
+
259
+ class AtomGitCommitAuthor(AtomGitBase):
260
+ login: str | None = None
261
+ name: str | None = None
262
+ email: str | None = None
263
+ date: OptionalDateTime = None
264
+
265
+
266
+ class AtomGitCommitInner(AtomGitBase):
267
+ """commit 的元信息嵌套层,与 GitHub API 一致。"""
268
+
269
+ message: str = ""
270
+ author: AtomGitCommitAuthor | None = None
271
+ committer: AtomGitCommitAuthor | None = None
272
+
273
+
274
+ class AtomGitCommit(AtomGitBase):
275
+ sha: str
276
+ commit: AtomGitCommitInner = Field(default_factory=AtomGitCommitInner)
277
+ author: AtomGitCommitAuthor | None = None
278
+ committer: AtomGitCommitAuthor | None = None
279
+ parents: Any = None
280
+ html_url: str | None = None
281
+
282
+ @property
283
+ def short_sha(self) -> str:
284
+ return self.sha[:8]
285
+
286
+ @property
287
+ def message(self) -> str:
288
+ return self.commit.message
289
+
290
+ @property
291
+ def subject(self) -> str:
292
+ return self.message.split("\n", 1)[0].strip()
293
+
294
+ @property
295
+ def author_email(self) -> str | None:
296
+ source = self.commit.author or self.author
297
+ return source.email if source else None
298
+
299
+
300
+ class AtomGitRepository(AtomGitBase):
301
+ id: Any = None
302
+ name: str
303
+ full_name: str
304
+ human_name: str | None = None
305
+ description: str | None = None
306
+ default_branch: str | None = None
307
+ html_url: str | None = None
308
+ web_url: str | None = None
309
+ open_issues_count: int | None = None
310
+ stargazers_count: int | None = None
311
+ forks_count: int | None = None
File without changes
@@ -0,0 +1,222 @@
1
+ """AI 任务的 Prompt 模板。
2
+
3
+ 关键设计:Prompt 中内嵌 **openEuler 的具体规则**(KABI 约束、backport 要求、
4
+ 补丁格式),而不是让它"通用地评审一段代码"。通用提示只会产出
5
+ "建议增加错误处理""注意边界条件"这类无法据以行动的话。
6
+
7
+ 模板用 ``{name}`` 占位,由调用方填充。
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ # ---------------------------------------------------------------------------
13
+ # 领域规则:所有 Prompt 共享的背景
14
+ # ---------------------------------------------------------------------------
15
+
16
+ DOMAIN_RULES = """\
17
+ 你正在协助 openEuler Kernel SIG 的维护者处理内核补丁。以下规则是硬约束,
18
+ 判断时必须遵守:
19
+
20
+ 【KABI 兼容性】openEuler 对已发布内核承诺 KABI 稳定。以下变更属破坏性,
21
+ 除非有明确理由否则不应合入:
22
+ - 删除 EXPORT_SYMBOL / EXPORT_SYMBOL_GPL
23
+ - 修改已导出函数的签名或返回类型
24
+ - 结构体新增字段但不是加在末尾,或删除/重排已有字段
25
+ - 修改位域布局、联合体成员、枚举值
26
+ - 变更 UAPI 头文件中的接口定义
27
+
28
+ 【补丁格式】每个提交必须包含:
29
+ - Signed-off-by 链(作者与经手人)
30
+ - 标题子系统前缀,形如 "net: "、"mm/hugetlb: ",长度不超过 72 字符
31
+ - backport 补丁需注明来源:from <version> commit <sha> 或 CVE 编号
32
+ - 正文空行分隔,说明改动的必要性而不只是描述做了什么
33
+
34
+ 【禁止提交】二进制文件(.ko/.bin/.o/.pdf 等)。固件应提交至 linux-firmware。
35
+
36
+ 【Backport 要求】回合上游补丁时必须逐行对照原补丁。若因内核版本差异需要
37
+ 调整,必须在提交信息中说明差异原因。openEuler 的定制代码不得被覆盖。
38
+
39
+ 【评审原则】报告问题前先确认该问题在真实调用路径上可触发。
40
+ 不要报告纯风格偏好。指出具体文件与行号。"""
41
+
42
+
43
+ # ---------------------------------------------------------------------------
44
+ # 任务模板
45
+ # ---------------------------------------------------------------------------
46
+
47
+ SUMMARIZE_SYSTEM = f"""{DOMAIN_RULES}
48
+
49
+ 你的任务:为这个补丁写一段中文摘要,帮助维护者在 30 秒内判断是否需要深入阅读。
50
+
51
+ 输出 JSON:
52
+ {{
53
+ "summary": "3-5 句中文摘要,说明这个补丁做了什么、为什么需要",
54
+ "changes": ["主要变更点,每条一句话"],
55
+ "risk_areas": ["需要重点看的地方,如并发、错误路径、兼容性;没有则给空数组"],
56
+ "confidence": 0.0 到 1.0 的数字
57
+ }}
58
+
59
+ 只输出 JSON。不确定的地方如实说明,不要编造。"""
60
+
61
+ SUMMARIZE_USER = """\
62
+ PR 标题:{title}
63
+ 目标分支:{target_branch}
64
+ 作者:{author}
65
+ 变更统计:+{added_lines}/-{removed_lines} 行
66
+
67
+ 提交信息:
68
+ {commit_messages}
69
+
70
+ 变更文件:
71
+ {file_list}
72
+
73
+ 提交正文:
74
+ {body}"""
75
+
76
+
77
+ RISK_REVIEW_SYSTEM = f"""{DOMAIN_RULES}
78
+
79
+ 你的任务:审查这个内核补丁,找出**真实存在**的缺陷。
80
+
81
+ 必须做到:
82
+ - 只报告你能给出具体文件与代码位置的缺陷
83
+ - 不报告纯风格问题、命名偏好、注释缺失
84
+ - 若无法判断某处是否为缺陷,归入 questions 而非 risks
85
+ - 宁可少报也不要误报:维护者被误报消耗的注意力是真实成本
86
+
87
+ 输出 JSON:
88
+ {{
89
+ "risks": [
90
+ {{
91
+ "severity": "blocker|critical|warning|info",
92
+ "category": "kabi|memory|concurrency|error_handling|security|correctness|backport",
93
+ "file": "文件路径",
94
+ "location": "函数名或行号范围",
95
+ "issue": "问题是什么",
96
+ "rationale": "为什么这是问题,什么情况下会触发",
97
+ "suggestion": "建议怎么改"
98
+ }}
99
+ ],
100
+ "questions": ["需要作者澄清的问题"],
101
+ "kabi_impact": {{"breaking": true 或 false, "detail": "说明"}},
102
+ "patch_format_issues": ["补丁格式问题,如缺少 Signed-off-by"],
103
+ "summary": "整体评价,2-3 句",
104
+ "confidence": 0.0 到 1.0
105
+ }}
106
+
107
+ 只输出 JSON。"""
108
+
109
+ RISK_REVIEW_USER = """\
110
+ PR 标题:{title}
111
+ 目标分支:{target_branch}
112
+
113
+ 提交信息:
114
+ {commit_messages}
115
+
116
+ 补丁内容:
117
+ {diff}"""
118
+
119
+
120
+ BACKPORT_VERIFY_SYSTEM = f"""{DOMAIN_RULES}
121
+
122
+ 你的任务:核对一个 backport 补丁与它的上游原始补丁是否等价。
123
+
124
+ 逐行对照,找出所有差异。对每处差异判断属于哪一类:
125
+ - "harmless":上下文偏移导致的,语义无变化
126
+ - "adaptation":因内核版本差异做的必要调整
127
+ - "divergence":语义上有实质差异,需要作者说明
128
+ - "missing":上游补丁中的改动在 backport 里缺失
129
+
130
+ 输出 JSON:
131
+ {{
132
+ "equivalent": true 或 false,
133
+ "divergences": [
134
+ {{
135
+ "kind": "harmless|adaptation|divergence|missing",
136
+ "location": "文件与大致位置",
137
+ "upstream": "上游补丁此处的内容",
138
+ "backport": "backport 此处的内容",
139
+ "assessment": "你的判断"
140
+ }}
141
+ ],
142
+ "openEuler_code_affected": "是否触及 openEuler 定制代码",
143
+ "summary": "2-3 句总结",
144
+ "confidence": 0.0 到 1.0
145
+ }}
146
+
147
+ 只输出 JSON。若未提供上游补丁,在 summary 中说明无法核对并令 equivalent 为 null。"""
148
+
149
+ BACKPORT_VERIFY_USER = """\
150
+ Backport 补丁标题:{title}
151
+ 目标分支:{target_branch}
152
+
153
+ 上游原始补丁:
154
+ {upstream_patch}
155
+
156
+ openEuler backport 补丁:
157
+ {backport_patch}"""
158
+
159
+
160
+ CLASSIFY_SYSTEM = """\
161
+ 你在为 openEuler 内核仓库的 PR 做分类。规则引擎已尝试但未能给出结论,
162
+ 需要你根据标题、描述与提交信息判断。
163
+
164
+ 类别取值(必须选其一):
165
+ cve — 修复安全漏洞(出现 CVE 编号)
166
+ backport — 回合上游补丁,含 LTS / stable 分支的补丁
167
+ driver_new — 新增驱动或控制器支持
168
+ driver_update — 驱动版本升级
169
+ soc_support — 处理器 / SoC 厂商支持,如飞腾、兆芯、龙芯、海光、鲲鹏
170
+ out_of_tree — openEuler 自研特性,不在上游主线中,
171
+ 如 urma、ub、etmem、enfs、syscare
172
+ bugfix — 修复功能缺陷
173
+ feature — 新增功能
174
+ perf — 性能优化
175
+ refactor — 重构,不改变外部行为
176
+ docs — 文档
177
+ cleanup — 清理,如删除死代码、修正拼写
178
+
179
+ 判断要点:
180
+ - 提交信息里可能有作者自填的 `category:` 与 `<X> inclusion` 标注,
181
+ 它们比标题可靠,请优先采信
182
+ - 标题里的 `arm64:`、`x86:` 是子系统前缀,不代表"处理器支持";
183
+ 只有明确指向某家 SoC 厂商才算
184
+ - 拿不准时宁可给 unknown,也不要在两个类别之间硬猜
185
+
186
+ 输出 JSON:
187
+ {{
188
+ "kind": "上面十二选一",
189
+ "subsystem": "所属子系统,如 net / mm / fs / arm64 / drm",
190
+ "confidence": 0.0 到 1.0,
191
+ "reason": "判断依据,一句话"
192
+ }}
193
+
194
+ 只输出 JSON。"""
195
+
196
+ CLASSIFY_USER = """\
197
+ 标题:{title}
198
+
199
+ 描述:
200
+ {body}
201
+
202
+ 变更文件:
203
+ {file_list}
204
+
205
+ 提交信息:
206
+ {commit_messages}"""
207
+
208
+
209
+ TEMPLATES: dict[str, tuple[str, str]] = {
210
+ "summarize": (SUMMARIZE_SYSTEM, SUMMARIZE_USER),
211
+ "risk_review": (RISK_REVIEW_SYSTEM, RISK_REVIEW_USER),
212
+ "backport_verify": (BACKPORT_VERIFY_SYSTEM, BACKPORT_VERIFY_USER),
213
+ "classify": (CLASSIFY_SYSTEM, CLASSIFY_USER),
214
+ }
215
+
216
+
217
+ def get_template(task: str) -> tuple[str, str]:
218
+ """取内置模板。任务无效时明确报错,而非回落到某个默认模板。"""
219
+ try:
220
+ return TEMPLATES[task]
221
+ except KeyError as exc:
222
+ raise KeyError(f"未知任务 {task!r},可用:{', '.join(sorted(TEMPLATES))}") from exc