@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
package/.env.example ADDED
@@ -0,0 +1,50 @@
1
+ # ============================================================
2
+ # Kernel SIG Console 配置
3
+ # 复制为 .env 后按需修改:cp .env.example .env
4
+ # ============================================================
5
+
6
+ # ---- 必填 ----
7
+ # 凭据加密(AES-256-GCM)与 JWT 签名密钥,至少 32 字符。
8
+ # 生成:openssl rand -hex 32
9
+ # 注意:此密钥一旦更换,已加密存储的 AtomGit token / LLM API Key 将无法解密。
10
+ KSC_SECRET_KEY=please-change-me-to-a-random-32-plus-char-string
11
+
12
+ # ---- 镜像源 ----
13
+ # 部分网络环境直连 Docker Hub 与 pypi.org 极慢或不可达,默认走国内镜像源。
14
+ # 如在其他网络环境,改回 docker.io / https://pypi.org/simple 即可。
15
+ DOCKER_REGISTRY=docker.m.daocloud.io
16
+ PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
17
+ NPM_REGISTRY=https://registry.npmjs.org
18
+
19
+ # ---- 端口 ----
20
+ # 默认避开本机常见的 80/443/3000/8000/6379,避免与既有服务冲突
21
+ KSC_WEB_PORT=18080
22
+ KSC_API_PORT=18000
23
+ KSC_POSTGRES_PORT=15432
24
+ KSC_VALKEY_PORT=16379
25
+
26
+ # ---- 数据库 ----
27
+ KSC_DB_NAME=kernel_sig
28
+ KSC_DB_USER=kernel_sig
29
+ KSC_DB_PASSWORD=kernel_sig
30
+
31
+ # ---- 运行环境 ----
32
+ KSC_ENVIRONMENT=production
33
+ KSC_LOG_LEVEL=INFO
34
+ # 通过 HTTPS 访问时设为 true(要求 Secure Cookie)
35
+ KSC_COOKIE_SECURE=false
36
+
37
+ # ---- 首次启动引导管理员(可选)----
38
+ # 设置后,首次启动会自动创建管理员,无需打开浏览器初始化。
39
+ # 创建完成后建议清空此变量并重启。
40
+ KSC_BOOTSTRAP_ADMIN_USERNAME=admin
41
+ KSC_BOOTSTRAP_ADMIN_PASSWORD=
42
+ KSC_BOOTSTRAP_ADMIN_EMAIL=admin@localhost
43
+
44
+ # ---- 首次启动纳管默认仓库(可选)----
45
+ # 首次启动时自动纳管 `owner/name`,省得进界面手动添加。留空则跳过。
46
+ # 没配下面那个 token 也照样纳管,只是同步不跑(界面上会标明未配置凭据)。
47
+ KSC_BOOTSTRAP_REPOSITORY=openeuler/kernel
48
+ # AtomGit token,首次启动时导入并加密存库。不填也可以,之后在
49
+ # 「设置 → 凭据」里加。填了就会开始拉数据。
50
+ KSC_ATOMGIT_TOKEN=
package/LICENSE ADDED
@@ -0,0 +1,127 @@
1
+ 木兰宽松许可证, 第2版
2
+
3
+ 木兰宽松许可证, 第2版
4
+ 2020年1月 http://license.coscl.org.cn/MulanPSL2
5
+
6
+
7
+ 您对“软件”的复制、使用、修改及分发受木兰宽松许可证,第2版(“本许可证”)的如下条款的约束:
8
+
9
+ 0. 定义
10
+
11
+ “软件”是指由“贡献”构成的许可在“本许可证”下的程序和相关文档的集合。
12
+
13
+ “贡献”是指由任一“贡献者”许可在“本许可证”下的受版权法保护的作品。
14
+
15
+ “贡献者”是指将受版权法保护的作品许可在“本许可证”下的自然人或“法人实体”。
16
+
17
+ “法人实体”是指提交贡献的机构及其“关联实体”。
18
+
19
+ “关联实体”是指,对“本许可证”下的行为方而言,控制、受控制或与其共同受控制的机构,此处的控制是指有受控方或共同受控方至少50%直接或间接的投票权、资金或其他有价证券。
20
+
21
+ 1. 授予版权许可
22
+
23
+ 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的版权许可,您可以复制、使用、修改、分发其“贡献”,不论修改与否。
24
+
25
+ 2. 授予专利许可
26
+
27
+ 每个“贡献者”根据“本许可证”授予您永久性的、全球性的、免费的、非独占的、不可撤销的(根据本条规定撤销除外)专利许可,供您制造、委托制造、使用、许诺销售、销售、进口其“贡献”或以其他方式转移其“贡献”。前述专利许可仅限于“贡献者”现在或将来拥有或控制的其“贡献”本身或其“贡献”与许可“贡献”时的“软件”结合而将必然会侵犯的专利权利要求,不包括对“贡献”的修改或包含“贡献”的其他结合。如果您或您的“关联实体”直接或间接地,就“软件”或其中的“贡献”对任何人发起专利侵权诉讼(包括反诉或交叉诉讼)或其他专利维权行动,指控其侵犯专利权,则“本许可证”授予您对“软件”的专利许可自您提起诉讼或发起维权行动之日终止。
28
+
29
+ 3. 无商标许可
30
+
31
+ “本许可证”不提供对“贡献者”的商品名称、商标、服务标志或产品名称的商标许可,但您为满足第4条规定的声明义务而必须使用除外。
32
+
33
+ 4. 分发限制
34
+
35
+ 您可以在任何媒介中将“软件”以源程序形式或可执行形式重新分发,不论修改与否,但您必须向接收者提供“本许可证”的副本,并保留“软件”中的版权、商标、专利及免责声明。
36
+
37
+ 5. 免责声明与责任限制
38
+
39
+ “软件”及其中的“贡献”在提供时不带任何明示或默示的担保。在任何情况下,“贡献者”或版权所有者不对任何人因使用“软件”或其中的“贡献”而引发的任何直接或间接损失承担责任,不论因何种原因导致或者基于何种法律理论,即使其曾被建议有此种损失的可能性。
40
+
41
+ 6. 语言
42
+ “本许可证”以中英文双语表述,中英文版本具有同等法律效力。如果中英文版本存在任何冲突不一致,以中文版为准。
43
+
44
+ 条款结束
45
+
46
+ 如何将木兰宽松许可证,第2版,应用到您的软件
47
+
48
+ 如果您希望将木兰宽松许可证,第2版,应用到您的新软件,为了方便接收者查阅,建议您完成如下三步:
49
+
50
+ 1, 请您补充如下声明中的空白,包括软件名、软件的首次发表年份以及您作为版权人的名字;
51
+
52
+ 2, 请您在软件包的一级目录下创建以“LICENSE”为名的文件,将整个许可证文本放入该文件中;
53
+
54
+ 3, 请将如下声明文本放入每个源文件的头部注释中。
55
+
56
+ Copyright (c) [Year] [name of copyright holder]
57
+ [Software Name] is licensed under Mulan PSL v2.
58
+ You can use this software according to the terms and conditions of the Mulan PSL v2.
59
+ You may obtain a copy of Mulan PSL v2 at:
60
+ http://license.coscl.org.cn/MulanPSL2
61
+ THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
62
+ See the Mulan PSL v2 for more details.
63
+
64
+
65
+ Mulan Permissive Software License,Version 2
66
+
67
+ Mulan Permissive Software License,Version 2 (Mulan PSL v2)
68
+ January 2020 http://license.coscl.org.cn/MulanPSL2
69
+
70
+ Your reproduction, use, modification and distribution of the Software shall be subject to Mulan PSL v2 (this License) with the following terms and conditions:
71
+
72
+ 0. Definition
73
+
74
+ Software means the program and related documents which are licensed under this License and comprise all Contribution(s).
75
+
76
+ Contribution means the copyrightable work licensed by a particular Contributor under this License.
77
+
78
+ Contributor means the Individual or Legal Entity who licenses its copyrightable work under this License.
79
+
80
+ Legal Entity means the entity making a Contribution and all its Affiliates.
81
+
82
+ Affiliates means entities that control, are controlled by, or are under common control with the acting entity under this License, ‘control’ means direct or indirect ownership of at least fifty percent (50%) of the voting power, capital or other securities of controlled or commonly controlled entity.
83
+
84
+ 1. Grant of Copyright License
85
+
86
+ Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable copyright license to reproduce, use, modify, or distribute its Contribution, with modification or not.
87
+
88
+ 2. Grant of Patent License
89
+
90
+ Subject to the terms and conditions of this License, each Contributor hereby grants to you a perpetual, worldwide, royalty-free, non-exclusive, irrevocable (except for revocation under this Section) patent license to make, have made, use, offer for sale, sell, import or otherwise transfer its Contribution, where such patent license is only limited to the patent claims owned or controlled by such Contributor now or in future which will be necessarily infringed by its Contribution alone, or by combination of the Contribution with the Software to which the Contribution was contributed. The patent license shall not apply to any modification of the Contribution, and any other combination which includes the Contribution. If you or your Affiliates directly or indirectly institute patent litigation (including a cross claim or counterclaim in a litigation) or other patent enforcement activities against any individual or entity by alleging that the Software or any Contribution in it infringes patents, then any patent license granted to you under this License for the Software shall terminate as of the date such litigation or activity is filed or taken.
91
+
92
+ 3. No Trademark License
93
+
94
+ No trademark license is granted to use the trade names, trademarks, service marks, or product names of Contributor, except as required to fulfill notice requirements in Section 4.
95
+
96
+ 4. Distribution Restriction
97
+
98
+ You may distribute the Software in any medium with or without modification, whether in source or executable forms, provided that you provide recipients with a copy of this License and retain copyright, patent, trademark and disclaimer statements in the Software.
99
+
100
+ 5. Disclaimer of Warranty and Limitation of Liability
101
+
102
+ THE SOFTWARE AND CONTRIBUTION IN IT ARE PROVIDED WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED. IN NO EVENT SHALL ANY CONTRIBUTOR OR COPYRIGHT HOLDER BE LIABLE TO YOU FOR ANY DAMAGES, INCLUDING, BUT NOT LIMITED TO ANY DIRECT, OR INDIRECT, SPECIAL OR CONSEQUENTIAL DAMAGES ARISING FROM YOUR USE OR INABILITY TO USE THE SOFTWARE OR THE CONTRIBUTION IN IT, NO MATTER HOW IT’S CAUSED OR BASED ON WHICH LEGAL THEORY, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGES.
103
+
104
+ 6. Language
105
+
106
+ THIS LICENSE IS WRITTEN IN BOTH CHINESE AND ENGLISH, AND THE CHINESE VERSION AND ENGLISH VERSION SHALL HAVE THE SAME LEGAL EFFECT. IN THE CASE OF DIVERGENCE BETWEEN THE CHINESE AND ENGLISH VERSIONS, THE CHINESE VERSION SHALL PREVAIL.
107
+
108
+ END OF THE TERMS AND CONDITIONS
109
+
110
+ How to Apply the Mulan Permissive Software License,Version 2 (Mulan PSL v2) to Your Software
111
+
112
+ To apply the Mulan PSL v2 to your work, for easy identification by recipients, you are suggested to complete following three steps:
113
+
114
+ i Fill in the blanks in following statement, including insert your software name, the year of the first publication of your software, and your name identified as the copyright owner;
115
+
116
+ ii Create a file named “LICENSE” which contains the whole context of this License in the first directory of your software package;
117
+
118
+ iii Attach the statement to the appropriate annotated syntax at the beginning of each source file.
119
+
120
+
121
+ Copyright (c) [Year] [name of copyright holder]
122
+ [Software Name] is licensed under Mulan PSL v2.
123
+ You can use this software according to the terms and conditions of the Mulan PSL v2.
124
+ You may obtain a copy of Mulan PSL v2 at:
125
+ http://license.coscl.org.cn/MulanPSL2
126
+ THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
127
+ See the Mulan PSL v2 for more details.
package/Makefile ADDED
@@ -0,0 +1,66 @@
1
+ SHELL := /bin/bash
2
+ COMPOSE := docker compose
3
+
4
+ .DEFAULT_GOAL := help
5
+ .PHONY: help up down logs ps build restart migrate revision test test-backend test-frontend test-e2e verify-analysis verify-ai lint fmt typecheck shell clean
6
+
7
+ help: ## 显示所有可用目标
8
+ @grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
9
+ | awk 'BEGIN {FS = ":.*?## "}; {printf " \033[36m%-16s\033[0m %s\n", $$1, $$2}'
10
+
11
+ up: ## 启动全部服务
12
+ $(COMPOSE) up -d --build
13
+
14
+ down: ## 停止全部服务
15
+ $(COMPOSE) down
16
+
17
+ logs: ## 跟踪服务日志
18
+ $(COMPOSE) logs -f --tail=100
19
+
20
+ ps: ## 查看服务状态
21
+ $(COMPOSE) ps
22
+
23
+ build: ## 重新构建镜像
24
+ $(COMPOSE) build
25
+
26
+ restart: down up ## 重启全部服务
27
+
28
+ migrate: ## 执行数据库迁移
29
+ $(COMPOSE) exec backend alembic upgrade head
30
+
31
+ revision: ## 生成迁移(用法: make revision m="描述")
32
+ $(COMPOSE) exec backend alembic revision --autogenerate -m "$(m)"
33
+
34
+ test: test-backend ## 运行全部测试
35
+
36
+ test-backend: ## 运行后端测试
37
+ $(COMPOSE) exec backend pytest -q
38
+
39
+ test-e2e: ## 浏览器端端到端验证(需 pip install playwright && playwright install chromium)
40
+ python3 scripts/e2e-verify.py
41
+
42
+ # 以下两个脚本跑在真实数据库上,因此用本机 venv 而非容器 ——
43
+ # 它们断言的是数据的性质(覆盖率、幂等、不打回 AI 判定),
44
+ # 而这些性质只有当数据真实存在时才可验证。
45
+ verify-analysis: ## 校验分类与关注项流水线(需本机 backend/.venv)
46
+ cd backend && KSC_SECRET_KEY="$$(grep -m1 '^KSC_SECRET_KEY=' ../.env | cut -d= -f2-)" \
47
+ .venv/bin/python ../scripts/verify-analysis-pipeline.py
48
+
49
+ verify-ai: ## 校验 AI 链路(本地桩端点,需本机 backend/.venv)
50
+ cd backend && KSC_SECRET_KEY="$$(grep -m1 '^KSC_SECRET_KEY=' ../.env | cut -d= -f2-)" \
51
+ .venv/bin/python ../scripts/verify-ai-pipeline.py
52
+
53
+ lint: ## 后端静态检查
54
+ $(COMPOSE) exec backend ruff check .
55
+
56
+ fmt: ## 后端格式化
57
+ $(COMPOSE) exec backend ruff format .
58
+
59
+ typecheck: ## 前端类型检查
60
+ cd frontend && npx tsc --noEmit
61
+
62
+ shell: ## 进入后端容器
63
+ $(COMPOSE) exec backend bash
64
+
65
+ clean: ## 停止并删除数据卷(危险:会清空数据库)
66
+ $(COMPOSE) down -v
package/README.md ADDED
@@ -0,0 +1,279 @@
1
+ # Kernel SIG Console
2
+
3
+ openEuler Kernel SIG 的本地自托管协作平台,用于管理 AtomGit 上的 PR 与 Issue。
4
+
5
+ ## 为什么需要它
6
+
7
+ openEuler 内核仓库当前有 **1000+ 个开放的 PR**,评审状态由 `lgtm-*` / `approved` /
8
+ `ci_*` 标签承载,没有统一视图。Maintainer 难以快速识别优先级、统计评审 SLA、
9
+ 发现被淹没的重要 PR。
10
+
11
+ 本平台把这些信息结构化,并提供 AI 辅助分析。
12
+
13
+ ## 功能
14
+
15
+ | 模块 | 说明 | 状态 |
16
+ |------|------|------|
17
+ | 认证与权限 | 五级 RBAC(观察者/评审者/提交者/维护者/管理员),审计留痕 | 已实现 |
18
+ | PR 管理 | 门禁阶段筛选、标题/编号检索、分支与排序、详情含提交/文件 diff/评审时间线 | 已实现 |
19
+ | Issue 管理 | 按 AtomGit 原生类型与优先级筛选、状态着色 | 已实现 |
20
+ | 评审跟踪 | 从评论解析评审事件,归属评审人工作量;合并门禁四段推导 | 已实现 |
21
+ | 数据同步 | 分层增量同步(列表/详情/评论),幂等,支持 webhook 触发 | 已实现 |
22
+ | 定时轮询 | ARQ worker 按任务错峰调度:列表 / 详情 / 分类 / 关注项 / AI 补判 | 已实现 |
23
+ | 统计分析 | 积压时长、合并周期、门禁分布、分支分布、评审人排行 | 已实现 |
24
+ | 凭据管理 | AES-256-GCM 加密存储、指纹展示、有效性校验 | 已实现 |
25
+ | 自动分类 | 规则优先、AI 兜底,识别 CVE / Backport / 子系统归属,支持人工指定 | 已实现 |
26
+ | 关注队列 | 13 条规则收敛成可认领的待办,每条附判定依据 | 已实现 |
27
+ | AI 对接 | OpenAI 兼容协议、按任务路由模型、类型补判 / 摘要 / 风险审查 / backport 核对 | 已实现 |
28
+
29
+ ## 实测数据(openeuler/kernel)
30
+
31
+ 平台在真实仓库上验证,以下为某次同步的实际结果:
32
+
33
+ | 指标 | 数值 |
34
+ |------|------|
35
+ | 未合并 PR | 1,133 |
36
+ | 已合并 PR | 4,000 |
37
+ | 未关闭 Issue | 2,031 |
38
+ | 可立即合入 | 179 |
39
+ | 待评审 | 772 |
40
+ | 超 30 天未评审 | 457 |
41
+ | 合并周期中位数 | 2 天 |
42
+
43
+ > 「可合入」指 CLA 已签署、CI 通过、有 LGTM 且已关联 Issue ——
44
+ > 在这之前,这些信息散落在上千个 PR 的标签里,无法一眼看出。
45
+
46
+ ### 关于评审状态的两个反直觉事实
47
+
48
+ 实现过程中通过真实数据确认,与直觉相反,且都会导致维护者看到错误信息:
49
+
50
+ 1. **CI 失败标签是 `ci_failed`**,而非 `ci_must_go_failed`(实测前者 36 次、后者 1 次)。
51
+ 2. **`approved` 不是合入前的前置门禁**:200 个 open PR 中 0 个带此标签,
52
+ 而 200 个已合并 PR 中 199 个带 —— 它是合入时补记的标记。
53
+ 把它当作前置条件会让「可合入」状态永不可能出现。
54
+ 3. **评审人身份在评论里,不在标签里**:PR 上用的是整体 `lgtm` 标签,
55
+ 具体是谁评审的,记录在人类的 `/lgtm` 评论中。
56
+
57
+ ## 自动分类、关注队列与 AI
58
+
59
+ 三者是同一条链路上的三环:**规则能判的判定,判不出的交给模型,需要人看的排成待办**。
60
+
61
+ ### 分类:规则优先,AI 兜底,人工最高
62
+
63
+ 规则是确定性的(标题里有 `CVE-\d{4}-\d{4,}` 就是 CVE),因此先跑规则。
64
+ 实测 3164 个 open PR/Issue 上规则覆盖率约 **54%** —— 剩余部分不是规则写少了,
65
+ 而是内核补丁标题描述的是「改了什么」而非「属于哪一类」。这部分交给模型补判。
66
+
67
+ 三档优先级严格有序,且每条记录都保留判定来源与依据:
68
+
69
+ | 来源 | 何时写入 | 会被谁覆盖 |
70
+ |------|----------|-----------|
71
+ | 规则 | 每次同步后重算 | 规则、AI、人工 |
72
+ | AI | 规则判不出时补判 | 人工;**规则判不出来时不覆盖** |
73
+ | 人工 | 维护者显式指定 | 只有维护者自己撤销 |
74
+
75
+ 第二条约束不是可选项。规则每 10 分钟全量重算一次;若允许它把 AI 的结论打回
76
+ `unknown`,那条记录下一轮又会被挑去补判 —— 模型会被反复调用同一个对象,
77
+ 既不收敛也持续计费。
78
+
79
+ ### 关注队列:只给结论是没有用的
80
+
81
+ 13 条规则覆盖 PR 与 Issue 两侧(评审超期、CI 失败、CVE 未推进、缺签名、
82
+ Issue 无人认领……)。每条命中都带**证据**:等了几天、哪个文件是二进制、
83
+ 缺几个签名、涉及哪些 CVE。维护者据此可以直接决定下一步动作,
84
+ 而不是只看到一个「有问题」的标记。
85
+
86
+ 状态是保留而非覆盖的:命中时保留 `first_detected_at`,不再命中的置
87
+ `resolved_at` 而不删除 —— 「这条挂了多久才被处理」本身就是要看的信息。
88
+
89
+ ### AI:唯一按次计费的部分
90
+
91
+ - **按任务配模型**:分类这类轻任务用便宜模型,代码风险审查才用强模型
92
+ - **内容指纹幂等**:输入没变就不重复调用,`force=true` 才强制重跑
93
+ - **失败不中断**:单条上游失败只落状态,不让整轮定时任务停摆;
94
+ 但**未配置模型**属于部署问题,直接抛错,不留失败记录污染用量统计
95
+ - **Prompt 内嵌领域规则**:KABI 兼容性约束、补丁格式要求、backport 核对方法,
96
+ 而不是让它「通用地评审一段代码」
97
+
98
+ ## 快速开始
99
+
100
+ 前提只有一个:本机装了 Docker。
101
+
102
+ ```bash
103
+ npx @kernel-sig/console # 起一整套实例,首次要构建镜像、几分钟
104
+ npx @kernel-sig/console --open # 顺手打开浏览器
105
+ ```
106
+
107
+ 首次运行会自动生成 `~/.kernel-sig/.env`(含随机的 `KSC_SECRET_KEY`)、构建镜像、
108
+ 起容器,就绪后打印地址。访问 <http://localhost:18080> 会引导创建管理员账号。
109
+
110
+ 装成全局之后命令短得多,日常用这个:
111
+
112
+ ```bash
113
+ npm i -g @kernel-sig/console
114
+ ksc # 再次启动,秒级
115
+ ksc logs # 看日志
116
+ ksc status # 看容器状态
117
+ ksc down # 停止;数据留在 docker 卷里,下次启动仍在
118
+ ```
119
+
120
+ 新实例**默认已纳管 `openeuler/kernel`**。给个 AtomGit token 就开始拉数据:
121
+
122
+ ```bash
123
+ ksc --token <你的 AtomGit token>
124
+ ```
125
+
126
+ 不给也能起,只是没有数据 —— 界面上的仓库卡片会标明「未配置凭据」并指向凭据设置,
127
+ 之后随时在「设置 → 凭据」里补。
128
+
129
+ > 注意:这是**在你本机跑一个独立实例**,不是连到别人的服务;和已有的实例各有各的
130
+ > 数据库。另外平台的定时同步需要机器常驻,合上笔记本就停了 —— 适合试用与评估,
131
+ > 正式给团队用仍然建议部署到服务器(见下一节)。
132
+
133
+ ### 从源码部署
134
+
135
+ 服务器上部署走这条,`ksc` 只是它的封装:
136
+
137
+ ```bash
138
+ cp .env.example .env
139
+ # 生成密钥并写入 .env 的 KSC_SECRET_KEY
140
+ openssl rand -hex 32
141
+
142
+ make up
143
+ ```
144
+
145
+ ## 常用命令
146
+
147
+ ```bash
148
+ make help # 查看全部命令
149
+ make up # 启动
150
+ make down # 停止
151
+ make logs # 查看日志
152
+ make migrate # 执行数据库迁移
153
+ make test # 运行测试
154
+ make lint # 静态检查
155
+ ```
156
+
157
+ ## 配置
158
+
159
+ 所有配置通过环境变量提供,见 [`.env.example`](.env.example)。
160
+
161
+ | 变量 | 说明 | 默认值 |
162
+ |------|------|--------|
163
+ | `KSC_SECRET_KEY` | 凭据加密与 JWT 签名密钥(≥32 字符),**必填** | 无 |
164
+ | `DOCKER_REGISTRY` | 镜像源前缀 | `docker.m.daocloud.io` |
165
+ | `PIP_INDEX_URL` | 构建时的 PyPI 源 | 清华源 |
166
+ | `KSC_WEB_PORT` | Web 访问端口 | `18080` |
167
+ | `KSC_ENVIRONMENT` | `development` / `production` | `production` |
168
+ | `KSC_BOOTSTRAP_REPOSITORY` | 首次启动自动纳管的仓库(`owner/name`) | `openeuler/kernel` |
169
+ | `KSC_ATOMGIT_TOKEN` | 首次启动导入成凭据;不填则在界面上添加 | 空 |
170
+
171
+ > **端口说明**:默认使用 18080 / 18000 / 15432 / 16379,
172
+ > 主动避开常见的 80 / 443 / 3000 / 8000 / 6379,以免与本机既有服务冲突。
173
+
174
+ > **镜像源说明**:部分网络环境直连 Docker Hub 与 pypi.org 极慢或不可达,
175
+ > 因此默认走国内镜像源。其他网络环境改回官方源即可。
176
+
177
+ ## 架构
178
+
179
+ ```
180
+ 浏览器 → Nginx (:18080) → FastAPI (:8000) → PostgreSQL
181
+
182
+ ARQ Worker → AtomGit API / LLM
183
+ ↘ Valkey(队列)
184
+ ```
185
+
186
+ 后端分层为 API / Service / Model / Core,依赖单向:`api → services → models`,
187
+ `integrations` 不依赖任何内部模块,保证领域逻辑能被 worker 与测试独立复用。
188
+
189
+ worker 与 API 复用同一镜像、只换启动命令。迁移由 API 进程负责执行,
190
+ worker 等它就绪再接活,避免两个进程同时跑 Alembic。
191
+
192
+ ### 定时任务节奏
193
+
194
+ 分钟点刻意错开:全部压在整点会让上游在瞬间收到成倍请求,
195
+ 自己的数据库也会同时承受多个全量重算。
196
+
197
+ | 任务 | 频率 | 超时 |
198
+ |------|------|------|
199
+ | `sync_pulls` | 每 10 分钟 | 600s |
200
+ | `sync_issues` | 每 30 分钟 | 600s |
201
+ | `sync_pull_details` | 每 5 分钟(一批 50 个) | 900s |
202
+ | `classify_repository` | 每 10 分钟 | 600s |
203
+ | `compute_attention` | 每 15 分钟 | 600s |
204
+ | `run_ai_analysis` | 每 30 分钟(一批 5 个) | 1800s |
205
+
206
+ 详情同步刻意小批量高频:单次请求量大时上游会限流,
207
+ 而列表同步必须先跑完 —— 关注项规则读的是它写下的派生列。
208
+
209
+ ## 安全
210
+
211
+ - AtomGit token 与 LLM API Key 使用 **AES-256-GCM** 加密落库,
212
+ 密钥由 `KSC_SECRET_KEY` 经 HKDF-SHA256 派生(不直接复用配置密钥)
213
+ - 界面**不回显**凭据明文,仅展示指纹
214
+ - 密码使用 **Argon2id** 哈希
215
+ - 会话令牌存 **HttpOnly + SameSite** Cookie
216
+ - 所有写操作记入审计日志,凭据类字段自动脱敏
217
+
218
+ ## 开发
219
+
220
+ ```bash
221
+ # 仅启动依赖服务
222
+ docker compose up -d postgres valkey
223
+
224
+ # 后端(本机)
225
+ cd backend
226
+ python3 -m venv .venv
227
+ .venv/bin/pip install -i https://pypi.tuna.tsinghua.edu.cn/simple -e ".[dev]"
228
+ .venv/bin/uvicorn app.main:app --reload --port 18000
229
+
230
+ # 前端(本机)
231
+ cd frontend && npm install && npm run dev # http://localhost:15173
232
+ ```
233
+
234
+ ### 验证脚本
235
+
236
+ 除单元测试外,仓库内有三个跑在真实数据 / 真实浏览器上的校验脚本。
237
+ 单元测试证明代码符合预期,这三个脚本证明系统在真实环境下确实工作。
238
+
239
+ ```bash
240
+ # 浏览器端到端(100+ 项断言,覆盖全部页面与交互)
241
+ # 需要 playwright:pip install playwright && playwright install chromium
242
+ python3 scripts/e2e-verify.py
243
+
244
+ # 分类与关注项:跑在真实数据库上,验证覆盖率与重算幂等
245
+ cd backend && KSC_SECRET_KEY=... .venv/bin/python ../scripts/verify-analysis-pipeline.py
246
+
247
+ # AI 链路:本地桩服务冒充 OpenAI 兼容端点,验证请求头/payload/
248
+ # JSON 解析/幂等/失败降级/人工覆盖优先
249
+ cd backend && KSC_SECRET_KEY=... .venv/bin/python ../scripts/verify-ai-pipeline.py
250
+ ```
251
+
252
+ > 后两个脚本读的是本机活实例的数据库,断言不假设环境为空;
253
+ > 它们验证的是数据的性质(覆盖率、幂等、不打回 AI 判定),
254
+ > 这些性质只有数据真实存在时才可验证。
255
+
256
+ ## 发布到 npm(维护者)
257
+
258
+ `package.json` 顶部的 `files` 是**显式白名单**,不是可选项。实测 npm 10.9.8 会把
259
+ 白名单里列出的目录**原样整目录**收进包 —— `frontend/node_modules`(290 MB)
260
+ 就是这么做进去的,`.npmignore` 拦不住。所以新增构建产物类的文件时,要同步更新这份
261
+ 名单,不要指望忽略文件。
262
+
263
+ 发布前先看一遍清单,尤其确认里面**没有 `.env`**(那是明文密钥):
264
+
265
+ ```bash
266
+ npm pack --dry-run # 看清单
267
+ npm pack # 出 tarball,可先用它本地验一遍
268
+ npm publish --access public --otp=<六位码>
269
+ ```
270
+
271
+ `name@version` 一旦发布就永远不能重用(即使 `npm unpublish` 也不行),
272
+ 所以先用 `npm exec --package=./kernel-sig-console-0.1.0.tgz -- ksc up` 跑通再发。
273
+
274
+ ## 许可
275
+
276
+ [木兰宽松许可证,第 2 版](LICENSE)(Mulan Permissive Software License, Version 2,
277
+ SPDX 标识符 `MulanPSL-2.0`)。
278
+
279
+ Copyright (c) 2026 Xie XiuQi
@@ -0,0 +1,12 @@
1
+ .venv/
2
+ __pycache__/
3
+ *.py[cod]
4
+ .pytest_cache/
5
+ .mypy_cache/
6
+ .ruff_cache/
7
+ htmlcov/
8
+ .coverage
9
+ *.egg-info/
10
+ build/
11
+ dist/
12
+ .env
@@ -0,0 +1,43 @@
1
+ ARG DOCKER_REGISTRY=docker.io
2
+ FROM ${DOCKER_REGISTRY}/library/python:3.12-slim
3
+
4
+ # 部分网络环境下 pypi.org 直连极慢,构建参数允许切换到镜像源
5
+ ARG PIP_INDEX_URL=https://pypi.tuna.tsinghua.edu.cn/simple
6
+ ENV PIP_INDEX_URL=${PIP_INDEX_URL} \
7
+ PIP_DISABLE_PIP_VERSION_CHECK=1 \
8
+ PIP_NO_CACHE_DIR=1 \
9
+ PYTHONDONTWRITEBYTECODE=1 \
10
+ PYTHONUNBUFFERED=1
11
+
12
+ WORKDIR /app
13
+
14
+ RUN apt-get update \
15
+ && apt-get install -y --no-install-recommends curl \
16
+ && rm -rf /var/lib/apt/lists/*
17
+
18
+ # 先装依赖再拷源码,让依赖层在源码变更时仍可复用
19
+ COPY pyproject.toml ./
20
+ RUN python -m pip install --upgrade pip \
21
+ && python -m pip install -e ".[dev]"
22
+
23
+ COPY . .
24
+
25
+ # 上面那次 -e 安装时 app/ 还没进来,`packages.find` 一无所获,
26
+ # 生成的映射是空的 —— 于是 import app 只在 cwd 正好是 /app 时成立。
27
+ # 服务本身跑在 /app 下看不出问题,但 `pytest` 控制台脚本不把 cwd
28
+ # 放进 sys.path:`make test-backend` 会报满屏 ModuleNotFoundError,
29
+ # 看着像代码坏了。补跑一次,此时包已就位;--no-deps 保证不重解依赖,
30
+ # 上面的依赖层仍然复用。
31
+ RUN python -m pip install -e . --no-deps
32
+
33
+ RUN useradd --create-home --uid 10001 appuser \
34
+ && chmod +x entrypoint.sh \
35
+ && chown -R appuser:appuser /app
36
+ USER appuser
37
+
38
+ EXPOSE 8000
39
+
40
+ HEALTHCHECK --interval=30s --timeout=5s --start-period=30s --retries=3 \
41
+ CMD curl -fsS http://localhost:8000/api/v1/healthz || exit 1
42
+
43
+ CMD ["./entrypoint.sh"]
@@ -0,0 +1,75 @@
1
+ """Alembic 环境配置:URL 从应用 Settings 读取,不落配置文件。"""
2
+
3
+ import asyncio
4
+ import os
5
+ from logging.config import fileConfig
6
+
7
+ from sqlalchemy import pool
8
+ from sqlalchemy.engine import Connection
9
+ from sqlalchemy.ext.asyncio import async_engine_from_config
10
+
11
+ from alembic import context
12
+
13
+ # 迁移本身不需要签名/加密密钥。若调用方只跑迁移(如 CI 的 schema 校验),
14
+ # 不应被迫提供 KSC_SECRET_KEY。应用启动时仍会强制校验该变量。
15
+ os.environ.setdefault("KSC_SECRET_KEY", "migration-only-placeholder-key-0000000000")
16
+
17
+ from app.core.config import get_settings
18
+ from app.models import Base
19
+
20
+ config = context.config
21
+
22
+ if config.config_file_name is not None:
23
+ fileConfig(config.config_file_name)
24
+
25
+ target_metadata = Base.metadata
26
+
27
+
28
+ def _database_url() -> str:
29
+ return get_settings().database_url
30
+
31
+
32
+ def run_migrations_offline() -> None:
33
+ context.configure(
34
+ url=_database_url(),
35
+ target_metadata=target_metadata,
36
+ literal_binds=True,
37
+ dialect_opts={"paramstyle": "named"},
38
+ compare_type=True,
39
+ )
40
+ with context.begin_transaction():
41
+ context.run_migrations()
42
+
43
+
44
+ def do_run_migrations(connection: Connection) -> None:
45
+ context.configure(
46
+ connection=connection,
47
+ target_metadata=target_metadata,
48
+ compare_type=True,
49
+ compare_server_default=True,
50
+ )
51
+ with context.begin_transaction():
52
+ context.run_migrations()
53
+
54
+
55
+ async def run_async_migrations() -> None:
56
+ configuration = config.get_section(config.config_ini_section, {})
57
+ configuration["sqlalchemy.url"] = _database_url()
58
+ connectable = async_engine_from_config(
59
+ configuration,
60
+ prefix="sqlalchemy.",
61
+ poolclass=pool.NullPool,
62
+ )
63
+ async with connectable.connect() as connection:
64
+ await connection.run_sync(do_run_migrations)
65
+ await connectable.dispose()
66
+
67
+
68
+ def run_migrations_online() -> None:
69
+ asyncio.run(run_async_migrations())
70
+
71
+
72
+ if context.is_offline_mode():
73
+ run_migrations_offline()
74
+ else:
75
+ run_migrations_online()
@@ -0,0 +1,25 @@
1
+ """${message}
2
+
3
+ Revision ID: ${up_revision}
4
+ Revises: ${down_revision | comma,n}
5
+ Create Date: ${create_date}
6
+ """
7
+
8
+ from collections.abc import Sequence
9
+
10
+ import sqlalchemy as sa
11
+ from alembic import op
12
+ ${imports if imports else ""}
13
+
14
+ revision: str = ${repr(up_revision)}
15
+ down_revision: str | None = ${repr(down_revision)}
16
+ branch_labels: str | Sequence[str] | None = ${repr(branch_labels)}
17
+ depends_on: str | Sequence[str] | None = ${repr(depends_on)}
18
+
19
+
20
+ def upgrade() -> None:
21
+ ${upgrades if upgrades else "pass"}
22
+
23
+
24
+ def downgrade() -> None:
25
+ ${downgrades if downgrades else "pass"}