commitguardian 0.1.0__py3-none-any.whl

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 (197) hide show
  1. commitguard/__init__.py +26 -0
  2. commitguard/__main__.py +6 -0
  3. commitguard/api/__init__.py +18 -0
  4. commitguard/api/app.py +1376 -0
  5. commitguard/api/governance.py +1085 -0
  6. commitguard/api/hosting.py +196 -0
  7. commitguard/api/http.py +252 -0
  8. commitguard/api/settings.py +169 -0
  9. commitguard/audit/__init__.py +13 -0
  10. commitguard/audit/logger.py +34 -0
  11. commitguard/audit/models.py +222 -0
  12. commitguard/audit/storage.py +59 -0
  13. commitguard/ci/__init__.py +7 -0
  14. commitguard/ci/context.py +60 -0
  15. commitguard/cli/__init__.py +6 -0
  16. commitguard/cli/app.py +74 -0
  17. commitguard/cli/commands/__init__.py +1 -0
  18. commitguard/cli/commands/benchmark.py +441 -0
  19. commitguard/cli/commands/check.py +100 -0
  20. commitguard/cli/commands/ci.py +165 -0
  21. commitguard/cli/commands/dashboard.py +141 -0
  22. commitguard/cli/commands/doctor.py +533 -0
  23. commitguard/cli/commands/github.py +449 -0
  24. commitguard/cli/commands/hook.py +156 -0
  25. commitguard/cli/commands/init.py +137 -0
  26. commitguard/cli/commands/install.py +152 -0
  27. commitguard/cli/commands/policy.py +36 -0
  28. commitguard/cli/commands/report.py +39 -0
  29. commitguard/cli/commands/reproduce.py +123 -0
  30. commitguard/cli/commands/scan.py +47 -0
  31. commitguard/cli/common.py +44 -0
  32. commitguard/cli/output.py +89 -0
  33. commitguard/cli/render.py +367 -0
  34. commitguard/config/__init__.py +6 -0
  35. commitguard/config/defaults.py +53 -0
  36. commitguard/config/enforcement.py +53 -0
  37. commitguard/config/loader.py +174 -0
  38. commitguard/config/schema.py +105 -0
  39. commitguard/config/sources.py +183 -0
  40. commitguard/controlplane/__init__.py +24 -0
  41. commitguard/controlplane/access.py +231 -0
  42. commitguard/controlplane/commands.py +393 -0
  43. commitguard/controlplane/errors.py +88 -0
  44. commitguard/controlplane/identity.py +478 -0
  45. commitguard/controlplane/members.py +219 -0
  46. commitguard/controlplane/notifications.py +787 -0
  47. commitguard/controlplane/pagination.py +146 -0
  48. commitguard/controlplane/policies.py +1204 -0
  49. commitguard/controlplane/queries.py +1814 -0
  50. commitguard/controlplane/results.py +909 -0
  51. commitguard/controlplane/rules.py +184 -0
  52. commitguard/controlplane/views.py +799 -0
  53. commitguard/core/__init__.py +6 -0
  54. commitguard/core/context.py +31 -0
  55. commitguard/core/decision.py +58 -0
  56. commitguard/core/engine.py +82 -0
  57. commitguard/core/result.py +177 -0
  58. commitguard/detectors/__init__.py +6 -0
  59. commitguard/detectors/base.py +58 -0
  60. commitguard/detectors/bot.py +87 -0
  61. commitguard/detectors/coauthor.py +86 -0
  62. commitguard/detectors/identity.py +76 -0
  63. commitguard/detectors/registry.py +72 -0
  64. commitguard/detectors/trailer.py +211 -0
  65. commitguard/exceptions/__init__.py +33 -0
  66. commitguard/exceptions/base.py +9 -0
  67. commitguard/exceptions/configuration.py +22 -0
  68. commitguard/exceptions/detection.py +11 -0
  69. commitguard/exceptions/git.py +41 -0
  70. commitguard/exceptions/service.py +25 -0
  71. commitguard/git/__init__.py +12 -0
  72. commitguard/git/commands.py +101 -0
  73. commitguard/git/commit.py +97 -0
  74. commitguard/git/diff.py +36 -0
  75. commitguard/git/hooks.py +527 -0
  76. commitguard/git/push.py +93 -0
  77. commitguard/git/ranges.py +71 -0
  78. commitguard/git/repository.py +447 -0
  79. commitguard/github/__init__.py +34 -0
  80. commitguard/github/actions.py +163 -0
  81. commitguard/github/app.py +935 -0
  82. commitguard/github/auth.py +217 -0
  83. commitguard/github/check_runs.py +172 -0
  84. commitguard/github/checks.py +210 -0
  85. commitguard/github/client.py +844 -0
  86. commitguard/github/enforcement_status.py +209 -0
  87. commitguard/github/errors.py +129 -0
  88. commitguard/github/events.py +563 -0
  89. commitguard/github/identifiers.py +90 -0
  90. commitguard/github/installations.py +566 -0
  91. commitguard/github/markdown.py +19 -0
  92. commitguard/github/permissions.py +70 -0
  93. commitguard/github/pull_requests.py +53 -0
  94. commitguard/github/queue.py +47 -0
  95. commitguard/github/recovery.py +124 -0
  96. commitguard/github/repositories.py +305 -0
  97. commitguard/github/server.py +52 -0
  98. commitguard/github/settings.py +174 -0
  99. commitguard/github/storage.py +2315 -0
  100. commitguard/github/webhooks.py +129 -0
  101. commitguard/github/worker.py +628 -0
  102. commitguard/github/workflow.py +286 -0
  103. commitguard/governance/__init__.py +26 -0
  104. commitguard/governance/bulk.py +765 -0
  105. commitguard/governance/cache.py +88 -0
  106. commitguard/governance/common.py +216 -0
  107. commitguard/governance/exceptions.py +861 -0
  108. commitguard/governance/groups.py +448 -0
  109. commitguard/governance/inventory.py +386 -0
  110. commitguard/governance/posture.py +1272 -0
  111. commitguard/governance/resolver.py +632 -0
  112. commitguard/governance/rollouts.py +760 -0
  113. commitguard/governance/rules.py +371 -0
  114. commitguard/governance/schedules.py +663 -0
  115. commitguard/governance/service.py +120 -0
  116. commitguard/governance/settings.py +365 -0
  117. commitguard/governance/simulation.py +618 -0
  118. commitguard/governance/workflow.py +734 -0
  119. commitguard/notifications/__init__.py +2 -0
  120. commitguard/notifications/channels/__init__.py +1 -0
  121. commitguard/notifications/channels/base.py +22 -0
  122. commitguard/notifications/channels/email.py +110 -0
  123. commitguard/notifications/channels/in_app.py +74 -0
  124. commitguard/notifications/channels/sink.py +58 -0
  125. commitguard/notifications/channels/webhook.py +233 -0
  126. commitguard/notifications/deduplication.py +57 -0
  127. commitguard/notifications/dispatcher.py +201 -0
  128. commitguard/notifications/models.py +439 -0
  129. commitguard/notifications/outbox.py +106 -0
  130. commitguard/notifications/preferences.py +224 -0
  131. commitguard/notifications/retry.py +282 -0
  132. commitguard/notifications/service.py +128 -0
  133. commitguard/notifications/settings.py +167 -0
  134. commitguard/notifications/templates.py +108 -0
  135. commitguard/observability/__init__.py +5 -0
  136. commitguard/observability/logging.py +161 -0
  137. commitguard/observability/metrics.py +105 -0
  138. commitguard/policies/__init__.py +6 -0
  139. commitguard/policies/defaults.py +48 -0
  140. commitguard/policies/evaluator.py +66 -0
  141. commitguard/policies/governance.py +498 -0
  142. commitguard/policies/loader.py +23 -0
  143. commitguard/policies/mandatory.py +52 -0
  144. commitguard/policies/model.py +46 -0
  145. commitguard/provenance/__init__.py +9 -0
  146. commitguard/provenance/author.py +146 -0
  147. commitguard/provenance/committer.py +16 -0
  148. commitguard/provenance/normalization.py +158 -0
  149. commitguard/provenance/signatures.py +34 -0
  150. commitguard/provenance/trailers.py +256 -0
  151. commitguard/research/__init__.py +26 -0
  152. commitguard/research/compare.py +231 -0
  153. commitguard/research/datasets.py +1484 -0
  154. commitguard/research/detection.py +183 -0
  155. commitguard/research/environment.py +185 -0
  156. commitguard/research/gitenv.py +108 -0
  157. commitguard/research/hooks.py +247 -0
  158. commitguard/research/metrics.py +85 -0
  159. commitguard/research/performance.py +194 -0
  160. commitguard/research/platform.py +288 -0
  161. commitguard/research/report.py +372 -0
  162. commitguard/research/repository.py +111 -0
  163. commitguard/research/reproduction.py +297 -0
  164. commitguard/research/results.py +94 -0
  165. commitguard/rules/__init__.py +11 -0
  166. commitguard/rules/data/ai-domains.yaml +51 -0
  167. commitguard/rules/data/ai-identities.yaml +131 -0
  168. commitguard/rules/data/bot-identities.yaml +53 -0
  169. commitguard/rules/data/patterns.yaml +52 -0
  170. commitguard/rules/loader.py +102 -0
  171. commitguard/rules/matcher.py +212 -0
  172. commitguard/rules/models.py +269 -0
  173. commitguard/security/__init__.py +5 -0
  174. commitguard/security/hashing.py +30 -0
  175. commitguard/security/rate_limit.py +33 -0
  176. commitguard/security/safe_yaml.py +69 -0
  177. commitguard/security/sanitization.py +85 -0
  178. commitguard/security/secrets.py +169 -0
  179. commitguard/security/validation.py +89 -0
  180. commitguard/services/__init__.py +15 -0
  181. commitguard/services/analysis.py +119 -0
  182. commitguard/services/audit.py +95 -0
  183. commitguard/services/ci.py +383 -0
  184. commitguard/services/enforcement.py +102 -0
  185. commitguard/services/hooks.py +254 -0
  186. commitguard/services/remediation.py +99 -0
  187. commitguard/services/reports.py +146 -0
  188. commitguard/services/scan.py +172 -0
  189. commitguard/utils/__init__.py +1 -0
  190. commitguard/utils/filesystem.py +72 -0
  191. commitguard/utils/platform.py +35 -0
  192. commitguard/utils/subprocess.py +84 -0
  193. commitguardian-0.1.0.dist-info/METADATA +694 -0
  194. commitguardian-0.1.0.dist-info/RECORD +197 -0
  195. commitguardian-0.1.0.dist-info/WHEEL +4 -0
  196. commitguardian-0.1.0.dist-info/entry_points.txt +2 -0
  197. commitguardian-0.1.0.dist-info/licenses/LICENSE +21 -0
@@ -0,0 +1,694 @@
1
+ Metadata-Version: 2.5
2
+ Name: commitguardian
3
+ Version: 0.1.0
4
+ Summary: Block AI, bot and agent attribution in Git commits, locally and on GitHub.
5
+ Project-URL: Homepage, https://github.com/oyinlola-tech/commitguard
6
+ Project-URL: Repository, https://github.com/oyinlola-tech/commitguard
7
+ Project-URL: Documentation, https://github.com/oyinlola-tech/commitguard/blob/main/docs/README.md
8
+ Project-URL: Changelog, https://github.com/oyinlola-tech/commitguard/blob/main/CHANGELOG.md
9
+ Project-URL: Issues, https://github.com/oyinlola-tech/commitguard/issues
10
+ Author: Oluwayemi Oyinlola
11
+ License-Expression: MIT
12
+ License-File: LICENSE
13
+ Keywords: ai-attribution,git,git-hooks,policy,provenance,security
14
+ Classifier: Development Status :: 2 - Pre-Alpha
15
+ Classifier: Environment :: Console
16
+ Classifier: Intended Audience :: Developers
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Programming Language :: Python :: 3 :: Only
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Programming Language :: Python :: 3.13
22
+ Classifier: Topic :: Security
23
+ Classifier: Topic :: Software Development :: Version Control :: Git
24
+ Classifier: Typing :: Typed
25
+ Requires-Python: >=3.12
26
+ Requires-Dist: pydantic<3,>=2.7
27
+ Requires-Dist: pyyaml>=6.0.1
28
+ Requires-Dist: typer>=0.12
29
+ Requires-Dist: tzdata>=2024.1; sys_platform == 'win32'
30
+ Provides-Extra: app
31
+ Requires-Dist: cryptography>=42; extra == 'app'
32
+ Provides-Extra: dev
33
+ Requires-Dist: cryptography>=42; extra == 'dev'
34
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
35
+ Requires-Dist: mypy>=1.11; extra == 'dev'
36
+ Requires-Dist: pytest>=8.2; extra == 'dev'
37
+ Requires-Dist: ruff>=0.6; extra == 'dev'
38
+ Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
39
+ Description-Content-Type: text/markdown
40
+
41
+ <div align="center">
42
+
43
+ <picture>
44
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/banner-dark.png">
45
+ <img alt="CommitGuard: Git commit provenance and contribution policy enforcement" src="docs/images/banner-light.png" width="100%">
46
+ </picture>
47
+
48
+ <p>
49
+ <a href="https://github.com/oyinlola-tech/commitguard/actions/workflows/ci.yml"><img alt="CI" src="https://github.com/oyinlola-tech/commitguard/actions/workflows/ci.yml/badge.svg"></a>
50
+ <a href="https://github.com/oyinlola-tech/commitguard/actions/workflows/security.yml"><img alt="Security" src="https://github.com/oyinlola-tech/commitguard/actions/workflows/security.yml/badge.svg"></a>
51
+ <a href="LICENSE"><img alt="License: MIT" src="https://img.shields.io/badge/license-MIT-101816?style=flat"></a>
52
+ <img alt="Python 3.12+" src="https://img.shields.io/badge/python-3.12%2B-101816?style=flat&logo=python&logoColor=white">
53
+ <img alt="Status: pre-alpha" src="https://img.shields.io/badge/status-pre--alpha-855700?style=flat">
54
+ </p>
55
+
56
+ <p>
57
+ <a href="#see-it-work"><strong>See it work</strong></a> ·
58
+ <a href="#quick-start"><strong>Quick start</strong></a> ·
59
+ <a href="#run-the-demo-locally"><strong>Run the demo</strong></a> ·
60
+ <a href="#test-evidence"><strong>Test evidence</strong></a> ·
61
+ <a href="#documentation"><strong>Docs</strong></a>
62
+ </p>
63
+
64
+ <a href="#built-with"><img alt="Python, TypeScript, React, Vite, Vitest, SQLite, Node.js, Git, GitHub Actions" src="https://skillicons.dev/icons?i=py,ts,react,vite,vitest,sqlite,nodejs,git,githubactions&perline=9" height="40"></a>
65
+
66
+ </div>
67
+
68
+ <br>
69
+
70
+ > [!NOTE]
71
+ > **Pre-alpha, Phase 10: measured, reproducible and open to external evaluation.**
72
+ > Local Git hooks stop violations during `git commit` / `git push`; a GitHub
73
+ > Actions check and a webhook-driven GitHub App run the same engine on pull
74
+ > requests, pushes and merge queues; a web dashboard explains what was scanned,
75
+ > what is blocked and why, notifies the people who need to act, and lets an
76
+ > organization govern policy across hundreds of repositories: groups,
77
+ > approvals, scoped exceptions, staged rollouts and an explicit security posture.
78
+ > Detection accuracy, performance and bypass resistance are **measured against a
79
+ > versioned labelled dataset**, and every result - including the runs that failed
80
+ > and exposed real bypasses - is published and reproducible with
81
+ > [`commitguard reproduce`](docs/cli/reproduce.md).
82
+
83
+ > [!IMPORTANT]
84
+ > **A failing GitHub check blocks merges only when branch protection requires it.**
85
+ > CommitGuard never configures that for you. The CLI cannot see it at all; the
86
+ > GitHub App reports what GitHub's read-only endpoints show and says `unknown`
87
+ > when they show nothing. See [What works today](#what-works-today).
88
+
89
+ ## See it work
90
+
91
+ Everything below was produced by running CommitGuard, not typed by hand: the
92
+ terminal images come from real commands in a fresh repository, and the dashboard
93
+ screenshots come from the demo stack, which replays a full lifecycle through
94
+ the real services (see [Run the demo locally](#run-the-demo-locally)).
95
+
96
+ **1. A local hook stops the commit.** Nothing is committed; the message is kept.
97
+
98
+ <img alt="git commit blocked by the CommitGuard commit-msg hook" src="docs/images/cli-commit-blocked.png" width="820">
99
+
100
+ **2. The same engine scans a commit range, as the GitHub check does.** Exit code 1 fails CI.
101
+
102
+ <img alt="commitguard check and scan output for a blocked commit" src="docs/images/cli-scan-blocked.png" width="820">
103
+
104
+ **3. The dashboard explains what was blocked, and why.**
105
+
106
+ <picture>
107
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/overview-dark.png">
108
+ <img alt="CommitGuard dashboard overview" src="docs/images/overview.png" width="100%">
109
+ </picture>
110
+
111
+ <table>
112
+ <tr>
113
+ <td width="50%"><img alt="Violation detail with evidence" src="docs/images/violation.png"><br><sub><b>Violation</b>: the exact trailer, the matched rule data and remediation; never file contents.</sub></td>
114
+ <td width="50%"><img alt="Scan executions after a GitHub re-run" src="docs/images/scan-executions.png"><br><sub><b>Re-runs</b>: a GitHub "Re-run" is a numbered execution; earlier results are kept.</sub></td>
115
+ </tr>
116
+ <tr>
117
+ <td><img alt="Notification center" src="docs/images/notifications.png"><br><sub><b>Notifications</b>: blocked violations, policy changes, rollbacks and installation outages.</sub></td>
118
+ <td><img alt="Repository enforcement evidence" src="docs/images/repository.png"><br><sub><b>Repositories</b>: protection is shown only with evidence from GitHub.</sub></td>
119
+ </tr>
120
+ <tr>
121
+ <td><img alt="Policy version history and rollback" src="docs/images/policy-rollback.png"><br><sub><b>Policy rollback</b>: immutable versions; a rollback is a new, audited version.</sub></td>
122
+ <td><img alt="Merge queue validation" src="docs/images/merge-queue.png"><br><sub><b>Merge queue</b>: the merge group commit itself is validated.</sub></td>
123
+ </tr>
124
+ </table>
125
+
126
+ ## What CommitGuard is
127
+
128
+ CommitGuard analyses Git commit metadata and decides, according to a
129
+ repository's policy, whether a commit is acceptable. It is built as a general
130
+ *provenance and contribution policy engine*: independent detectors report what
131
+ a commit claims about its origin, and a separate policy layer decides what to
132
+ do about it.
133
+
134
+ The first policy is **AI agent attribution**. This commit is blocked by default:
135
+
136
+ ```text
137
+ feat: implement authentication
138
+
139
+ Co-authored-by: Claude <noreply@anthropic.com>
140
+ ```
141
+
142
+ This one is not:
143
+
144
+ ```text
145
+ feat: implement authentication
146
+
147
+ Co-authored-by: John Doe <john@example.com>
148
+ ```
149
+
150
+ ## What it detects — and what it does not
151
+
152
+ CommitGuard detects **explicit attribution and identity evidence** in commit
153
+ metadata:
154
+
155
+ | Rule | Detector | Example | Default |
156
+ |---|---|---|---|
157
+ | `ai_coauthor` | `coauthor` | `Co-authored-by: Claude <noreply@anthropic.com>` | block |
158
+ | `ai_identity` | `identity` | author/committer `Copilot <…+Copilot@users.noreply.github.com>` | block |
159
+ | `ai_trailer` | `trailer` | `Generated-by: Claude Code`, `Generated with [Claude Code](…)` (with or without a leading pictographic symbol) | block |
160
+ | `malformed_trailer` | `trailer` | `Co-authored-by Claude noreply@anthropic.com` | warn |
161
+ | `bot_identity` | `bot` | author `dependabot[bot]` (a bot, **not** an AI) | warn |
162
+
163
+ It does **not**:
164
+
165
+ - determine whether code was written by an AI. A finding means *"this commit
166
+ contains an identity or attribution associated with an AI agent"*, never
167
+ *"this code was written by AI"*;
168
+ - guess from wording (`feat: use AI service for recommendations` is not evidence);
169
+ - analyse diffs or file contents, call any AI/LLM API, or use the network;
170
+ - prove authorship: metadata is self-asserted and can simply be removed.
171
+
172
+ ## Why it exists
173
+
174
+ AI coding agents increasingly write commits, and many record themselves in
175
+ commit metadata. Some organisations and projects need to control that for
176
+ licensing or contributor-agreement reasons, accurate provenance records, or a
177
+ consistent contribution policy. Checking by hand does not scale, and naive
178
+ string matching is both easy to evade (casing, malformed trailers, look-alike
179
+ Unicode, escape codes that hide a line) and prone to false positives (a human
180
+ named Claude, an employee with an `@anthropic.com` address).
181
+
182
+ ## How AI attribution is identified
183
+
184
+ ```text
185
+ Git ─▶ Commit parser ─▶ Commit (author, committer, message, trailers)
186
+
187
+ Detection engine (4 detectors, pure, offline)
188
+ │ rules/*.yaml ─▶ identity matcher
189
+
190
+ Findings (rule, severity, confidence, evidence)
191
+
192
+ Policy evaluator (.commitguard.yaml)
193
+
194
+ Decision: ALLOW / WARN / BLOCK ─▶ exit code 0 / 0 / 1
195
+ ```
196
+
197
+ - **Rules are data** (`rules/ai-identities.yaml`, `ai-domains.yaml`,
198
+ `bot-identities.yaml`, `patterns.yaml`), not code.
199
+ - **Matching is deliberate:** exact comparison after case/width/whitespace
200
+ normalisation, removal of invisible characters and folding of Cyrillic/Greek
201
+ look-alikes. No substring or fuzzy matching: `Claude` never matches
202
+ `Claudette` or `Claude Dupont`.
203
+ - **Evidence is combined:** an exact AI email, GitHub bot login or distinctive
204
+ name prefix is strong evidence; a bare alias like `Claude` is medium
205
+ confidence; a vendor domain alone (`jane@anthropic.com`) is never enough.
206
+ - **Explicit evidence > weak inference.** An agent not listed in the rules is
207
+ not guessed; add a rule instead.
208
+
209
+ Details: [docs/detection-engine.md](docs/detection-engine.md).
210
+
211
+ ## Detection vs. policy
212
+
213
+ | | Detector | Policy |
214
+ |---|---|---|
215
+ | Question | *Does this commit list an AI co-author? What is the evidence?* | *Is that allowed here?* |
216
+ | Output | `Finding` (rule, severity, confidence, evidence, remediation) | `Decision` (allow / warn / block, with reasons) |
217
+ | Knows about | a single commit and rule data | configuration |
218
+ | Side effects | none | none |
219
+
220
+ Precedence is deterministic: **block > warn > allow**, independent of detector
221
+ order. A detector that crashes blocks (fail closed). See
222
+ [docs/policy-engine.md](docs/policy-engine.md).
223
+
224
+ ## Install
225
+
226
+ ```bash
227
+ pipx install commitguardian
228
+ ```
229
+
230
+ > [!IMPORTANT]
231
+ > **The PyPI name is `commitguardian`; the command is `commitguard`.**
232
+ > `commitguard` and `commitguard-cli` on PyPI are two *unrelated* projects by
233
+ > other authors, so `pip install commitguard` installs someone else's code
234
+ > ([why](docs/adr/009-published-to-pypi-as-commitguardian.md)).
235
+
236
+ ```bash
237
+ # in a virtual environment
238
+ python -m pip install commitguardian
239
+
240
+ # pinned to an exact commit, if you prefer provenance over convenience
241
+ pipx install "git+https://github.com/oyinlola-tech/commitguard@<commit-sha>"
242
+
243
+ # from a clone, for development
244
+ git clone https://github.com/oyinlola-tech/commitguard && cd commitguard
245
+ python -m pip install -e ".[dev]"
246
+ ```
247
+
248
+ Requires Python 3.12+ and Git 2.31+. Full walkthrough:
249
+ [5-minute quick start](docs/getting-started/quickstart.md).
250
+
251
+ ## Quick start
252
+
253
+ ```bash
254
+ cd my-project
255
+ commitguard init --install-hooks # .commitguard.yaml + pre-commit, commit-msg, pre-push
256
+ commitguard doctor # Status: HEALTHY
257
+
258
+ git add .
259
+ git commit -m "implement authentication" # checked automatically
260
+ git push # every outgoing commit checked
261
+ ```
262
+
263
+ Server side (GitHub):
264
+
265
+ ```bash
266
+ commitguard init --github --action-repository OWNER/commitguard --action-ref <commit sha>
267
+ commitguard github setup # required check name + branch protection steps
268
+ ```
269
+
270
+ Then require the `commitguard` status check on protected branches.
271
+
272
+ ## CLI usage
273
+
274
+ ```bash
275
+ commitguard init # write .commitguard.yaml with secure defaults
276
+ commitguard install # install hooks (existing hooks are preserved and chained)
277
+ commitguard uninstall # remove only CommitGuard's hooks, restore previous ones
278
+ commitguard scan # explain findings for HEAD
279
+ commitguard scan origin/main..HEAD # every commit in a range
280
+ commitguard scan --format json # structured report (schema_version 1)
281
+ commitguard check # machine-friendly result for HEAD
282
+ commitguard check --quiet origin/main..HEAD
283
+ commitguard check --message-file .git/COMMIT_EDITMSG # a commit that does not exist yet
284
+ commitguard check --verbose # check with full human-readable evidence
285
+ commitguard policy list # effective policies and config layers
286
+ commitguard doctor # installation, config, engine and hook health
287
+ commitguard hook pre-commit|commit-msg <file>|pre-push # called by installed hooks
288
+ commitguard ci github # GitHub Actions check (reads $GITHUB_EVENT_PATH)
289
+ commitguard github setup # workflow status, check name, setup guidance
290
+ ```
291
+
292
+ Blocked `scan` output (abridged):
293
+
294
+ ```text
295
+ CommitGuard
296
+ ✗ BLOCKED: policy violation detected
297
+
298
+ AI coauthor detected
299
+ Commit: 4f71c92
300
+ Detector: coauthor
301
+ Rule: ai_coauthor
302
+ Severity: high
303
+ Confidence: high
304
+ Action: block (policy ai_coauthor)
305
+ Evidence: Claude <noreply@anthropic.com>
306
+ Source: Co-authored-by trailer, line 4
307
+ Matched: email "noreply@anthropic.com" (ai-identities.yaml#claude)
308
+ Remediation: Remove the AI co-author attribution from the commit message ...
309
+
310
+ Result: BLOCK
311
+ ```
312
+
313
+ `check` output is one tab-separated line per finding followed by a summary:
314
+
315
+ ```text
316
+ BLOCK 4f71c92 coauthor ai_coauthor Claude <noreply@anthropic.com>
317
+ result=BLOCK commits=1 block=1 warn=0 allow=0
318
+ ```
319
+
320
+ ### Exit codes
321
+
322
+ | Code | Meaning |
323
+ |---|---|
324
+ | `0` | allowed (no findings, or only `allow`/`warn` findings) |
325
+ | `1` | blocked by policy (a finding or detector failure evaluated to `block`) |
326
+ | `2` | error: invalid configuration/rules, Git error, bad arguments, unexpected failure (hooks block on errors) |
327
+
328
+ CommitGuard never modifies commits or rewrites history; remediation is always
329
+ left to the developer.
330
+
331
+ ## Configuration
332
+
333
+ ```yaml
334
+ # .commitguard.yaml
335
+ version: 1
336
+ policies:
337
+ ai_coauthor:
338
+ enabled: true
339
+ action: block
340
+ bot_identity:
341
+ enabled: true
342
+ action: warn
343
+ ```
344
+
345
+ Layers, lowest precedence first: built-in defaults → global
346
+ `~/.config/commitguard/config.yaml` → repository `.commitguard.yaml` →
347
+ `--config PATH`. Omitted policies keep secure defaults; invalid configuration
348
+ is an error (exit 2). Which hooks enforce is set under `enforcement:`
349
+ (all enabled by default). See [docs/configuration.md](docs/configuration.md).
350
+
351
+ ## Enforcement layers: local + GitHub
352
+
353
+ ```text
354
+ Local Hooks
355
+
356
+
357
+ CommitGuard Core (detection engine · policy engine · ScanService)
358
+
359
+ ├───────────────► GitHub Actions commitguard ci github → job status "commitguard"
360
+
361
+ └───────────────► GitHub App commitguard github serve
362
+
363
+
364
+ Webhooks (signed, deduplicated)
365
+
366
+
367
+ Scan Service (metadata-only fetch, trusted policy)
368
+
369
+
370
+ GitHub Checks "commitguard-app"
371
+ ```
372
+
373
+ In more detail, for the Action:
374
+
375
+ ```text
376
+ Developer ─▶ git commit / push ─▶ Local hooks (Phase 3) ──── fast feedback, bypassable
377
+
378
+
379
+ GitHub
380
+ │ pull_request / merge_group / push
381
+
382
+ GitHub Actions: commitguard ci github (Phase 4)
383
+ │ same detectors + policy evaluator
384
+ ┌─────┴─────┐
385
+ PASS FAIL
386
+ │ │
387
+ check passes check fails ─▶ merge blocked
388
+ (when branch protection
389
+ requires "commitguard")
390
+ ```
391
+
392
+ | Layer | Purpose | Can be bypassed by the contributor? |
393
+ |---|---|---|
394
+ | Local hooks | stop accidents before they leave the machine | yes (`--no-verify`, deleting hooks) |
395
+ | GitHub Actions check | server-side validation of every commit a PR introduces | no, but it only *reports* on its own |
396
+ | Branch protection / rulesets | make the check a merge requirement | no (repository admins configure it) |
397
+
398
+ Why both: hooks give immediate feedback without waiting for CI; the GitHub
399
+ check makes local bypasses visible and, with branch protection, unmergeable.
400
+
401
+ **Local hooks** (`commitguard install`): pre-commit checks the pending identity,
402
+ commit-msg the message, pre-push every outgoing commit. Existing hooks are
403
+ preserved and chained; failures block. See [docs/git-hooks.md](docs/git-hooks.md).
404
+
405
+ **GitHub check** (`commitguard ci github`, `action.yml`):
406
+
407
+ - scans every commit a pull request introduces (`head ^base`), not just the
408
+ latest, plus merge queue entries and pushes (new branches, deletions, tags,
409
+ force pushes);
410
+ - evaluates with the policy from the **base commit**, so a pull request cannot
411
+ relax `.commitguard.yaml` to approve itself; rules come from the installed
412
+ CommitGuard, never the repository;
413
+ - needs only `contents: read`, no secrets, works for fork pull requests;
414
+ - fails closed (exit 2) when policy cannot be evaluated;
415
+ - a push-triggered run happens **after** commits reach GitHub: prevention needs
416
+ protected branches, required pull requests and the required check.
417
+
418
+ See [docs/github-enforcement.md](docs/github-enforcement.md).
419
+
420
+ **GitHub App** (`commitguard github serve`; the App extra adds `cryptography`:
421
+ `python -m pip install "commitguardian[app]"`):
422
+
423
+ - a centralised service you deploy: install it once on an account or
424
+ organisation and it scans pull requests and pushes of the selected
425
+ repositories from signed webhooks;
426
+ - publishes the Check Run `commitguard-app` (queued → in progress → completed)
427
+ on the exact commit it scanned;
428
+ - least privilege: Checks write, and read-only Contents, Metadata and Pull
429
+ requests. Tokens are down-scoped to one repository per scan;
430
+ - never checks out or runs repository code, and never modifies a repository;
431
+ - supports an optional **mandatory policy** that repositories cannot weaken;
432
+ - `commitguard github validate` checks configuration, authentication,
433
+ permissions and installations.
434
+
435
+ Actions or App? Actions: simplest, no service. App: organisation-wide,
436
+ webhook-driven, central policy, but you operate it. Both can run together. See
437
+ [docs/github-app.md](docs/github-app.md) and [docs/deployment.md](docs/deployment.md).
438
+
439
+ ## Dashboard
440
+
441
+ ```text
442
+ CommitGuard core ──► ScanResult ──┬──► CLI (terminal)
443
+ ├──► GitHub Check (GitHub enforces it)
444
+ └──► /api/v1 ──► Dashboard (explains it)
445
+ ```
446
+
447
+ The GitHub App service also serves a web dashboard (`web/`) and its API:
448
+
449
+ - **Overview** — monitored repositories, scans in a period, blocked scans,
450
+ open and critical violations, explicit security checks (no invented score);
451
+ - **Repositories** — protection shown only with evidence from GitHub, and
452
+ separate signals for the App, Actions, required check, latest check and
453
+ local hooks (reported as not verifiable);
454
+ - **Scans** — every result with its commit range, findings, evidence and the
455
+ policy, rules and CommitGuard versions that produced it; scan again;
456
+ - **Violations** — open, acknowledged or resolved, where resolution happens
457
+ only when the commit is no longer present; remediation guidance; history kept;
458
+ - **Policies** — versioned, immutable organization floors that repositories
459
+ cannot lower, with confirmation for weakening changes, a structured diff and
460
+ audited **rollback** to an earlier version; **Rules**; **Audit log**;
461
+ **GitHub installations**;
462
+ - **Notifications** — blocked violations, policy changes and rollbacks,
463
+ installation disconnects, merge queue and re-run failures, deduplicated, in
464
+ the dashboard and optionally by e-mail and signed webhooks; **Settings**
465
+ (sessions, members, notification preferences).
466
+
467
+ - **Organization** — security posture as explicit states with reasons (no
468
+ score), "N of M required repositories satisfy all mandatory controls", a
469
+ repository security matrix, repository groups and bulk onboarding;
470
+ organization, group and repository **policies** with mandatory and default
471
+ strength, drafts, **simulation** against recorded scans, approval with
472
+ separation of duties, **staged rollouts**; scoped, expiring **exceptions**;
473
+ per-rule provenance of the effective policy; organization rules; scheduled
474
+ scans; compliance reports (JSON/CSV, explicitly not a certification).
475
+
476
+ Sign-in uses the GitHub App's user authorization; roles (viewer, security
477
+ manager, admin, owner) are granted in CommitGuard, and users only see
478
+ repositories GitHub lets them see. See [docs/dashboard.md](docs/dashboard.md).
479
+
480
+ ## What works today
481
+
482
+ - `scan`, `check` (including `--message-file`, `--format json`, `--quiet`,
483
+ `--verbose`), `init`, `policy list`, with documented exit codes
484
+ - Git hook enforcement: `install`/`uninstall` (per repository, or `--global`
485
+ via a Git template directory), `hook pre-commit|commit-msg|pre-push`,
486
+ chaining of existing hooks, integrity checksums, fail-closed wrappers
487
+ - `doctor` with hook presence, integrity, interpreter, enforcement and GitHub
488
+ workflow checks (branch protection is reported as unverifiable)
489
+ - GitHub enforcement: `ci github` (pull_request, merge_group, push), trusted
490
+ policy source (base commit), bundled rules only, annotations, job summary,
491
+ step outputs, JSON report; composite `action.yml` with SHA-pinned actions and
492
+ hash-pinned dependencies; `init --github`; `github setup`
493
+ - Commit model with parsed trailers; lenient, bounded trailer parser that
494
+ records malformed and evasive variants instead of crashing
495
+ - Four detectors (`coauthor`, `identity`, `trailer`, `bot`) driven by YAML rules
496
+ - Detector registry, detection engine (fail closed), policy evaluator
497
+ - Layered, strictly validated configuration
498
+ - Hardened read-only Git access (no shell, `--end-of-options`, no mailmap /
499
+ replace objects, batched reads)
500
+ - Terminal-safe output and ASCII-only JSON
501
+ - GitHub App service: signed webhooks with replay protection, installation
502
+ lifecycle, repository authorization, down-scoped installation tokens,
503
+ metadata-only Git mirrors, Check Runs with stale-write protection,
504
+ mandatory policy floor, policy-weakening detection, SQLite state with
505
+ retention, audit events, structured logs with correlation IDs,
506
+ `/health` and `/ready`, `github validate`, `github webhook-test`, `github serve`
507
+
508
+ - Dashboard and `/api/v1`: GitHub sign-in, roles and tenant isolation,
509
+ overview, repositories with enforcement evidence, scans, violations with a
510
+ lifecycle, versioned organization policy, rules, audit log, installations
511
+ and sync, sessions and members; `commitguard dashboard members`
512
+ - Notifications: transactional outbox, in-app notification center,
513
+ organization and personal preferences, deduplication, SMTP e-mail and
514
+ HMAC-signed webhooks with bounded retries, delivery records and audit
515
+ - GitHub App merge queue validation (`merge_group`), GitHub "Re-run" and
516
+ "Re-run all checks" handling with numbered scan executions, stale re-run
517
+ protection, event processing records with safe redelivery, automatic retry
518
+ of infrastructure failures
519
+ - Organization policy rollback (immutable versions, rollback lineage, diff,
520
+ optimistic concurrency, audit and notification in one transaction)
521
+ - Organization governance: security settings and baseline, repository
522
+ onboarding (enforce / monitor mode), repository groups, background bulk
523
+ operations, organization/group/repository policies resolved per rule with
524
+ provenance and conflicts, effective policy cache with transactional
525
+ invalidation and propagation status, policy drafts and approvals, emergency
526
+ publication, read-only policy simulation, staged rollouts with automatic
527
+ halt and rollback, scoped expiring exceptions, organization identity rules
528
+ (data only), scheduled default-branch scans, posture, drift, trends, alert
529
+ digests, search and compliance reports
530
+
531
+ Not yet: PR comments, SARIF, signature verification, secret detection. The App and dashboard have not yet
532
+ been tested against github.com itself (only an offline model of the API and
533
+ OAuth flow plus real Git).
534
+
535
+ ## Run the demo locally
536
+
537
+ The demo stack runs the real API, services and dashboard against an offline
538
+ model of GitHub, then replays a complete lifecycle: a blocked pull request and
539
+ its fix, a GitHub re-run, a merge group, a scan error, a policy change and its
540
+ rollback, an App suspension and reconnection, and notification delivery.
541
+
542
+ ```bash
543
+ ./scripts/install-dev.sh && source .venv/bin/activate
544
+ cd web && npm ci && npm run build && cd ..
545
+ python tests/e2e/dashboard_harness.py --demo # http://localhost:4173
546
+ ```
547
+
548
+ Open <http://localhost:4173> and choose **Continue with GitHub** (signs in as
549
+ the owner, `alice`), or sign in as another role:
550
+
551
+ | User | Role | Sign-in link |
552
+ |---|---|---|
553
+ | `alice` | owner | <http://localhost:4173/demo/sign-in?user=501> |
554
+ | `ada` | admin | <http://localhost:4173/demo/sign-in?user=504> |
555
+ | `sam` | security manager | <http://localhost:4173/demo/sign-in?user=503> |
556
+ | `victor` | viewer | <http://localhost:4173/demo/sign-in?user=502> |
557
+
558
+ The demo keeps its data in a temporary directory, never contacts github.com,
559
+ and records e-mail and webhook deliveries in memory instead of sending them.
560
+
561
+ ## Test evidence
562
+
563
+ <picture>
564
+ <source media="(prefers-color-scheme: dark)" srcset="docs/images/tests-dark.png">
565
+ <img alt="Test evidence: pytest, Vitest and Playwright results with static checks" src="docs/images/tests-light.png" width="100%">
566
+ </picture>
567
+
568
+ The card is rendered from [docs/evidence/tests.json](docs/evidence/tests.json),
569
+ which [scripts/readme_evidence.py](scripts/readme_evidence.py) writes from the
570
+ real test runs and checks; the live status of every push is the
571
+ [CI badge](https://github.com/oyinlola-tech/commitguard/actions/workflows/ci.yml).
572
+ To regenerate the evidence, terminal images and screenshots:
573
+
574
+ ```bash
575
+ python scripts/readme_evidence.py # runs all suites and checks, writes docs/evidence/
576
+ cd web && npm run build && npm run screenshots # renders docs/images/
577
+ ```
578
+
579
+ ## Built with
580
+
581
+ | | |
582
+ |---|---|
583
+ | **Core and GitHub App** | ![Python](https://img.shields.io/badge/Python_3.12+-3776AB?style=flat-square&logo=python&logoColor=white) ![SQLite](https://img.shields.io/badge/SQLite-003B57?style=flat-square&logo=sqlite&logoColor=white) ![Git](https://img.shields.io/badge/Git-F05032?style=flat-square&logo=git&logoColor=white) ![GitHub Apps](https://img.shields.io/badge/GitHub_App-181717?style=flat-square&logo=github&logoColor=white) ![GitHub Actions](https://img.shields.io/badge/GitHub_Actions-2088FF?style=flat-square&logo=githubactions&logoColor=white) |
584
+ | **Dashboard** | ![TypeScript](https://img.shields.io/badge/TypeScript-3178C6?style=flat-square&logo=typescript&logoColor=white) ![React](https://img.shields.io/badge/React_19-20232A?style=flat-square&logo=react&logoColor=61DAFB) ![Vite](https://img.shields.io/badge/Vite-646CFF?style=flat-square&logo=vite&logoColor=white) ![TanStack Query](https://img.shields.io/badge/TanStack_Query-FF4154?style=flat-square&logo=reactquery&logoColor=white) |
585
+ | **Quality** | ![pytest](https://img.shields.io/badge/pytest-0A9EDC?style=flat-square&logo=pytest&logoColor=white) ![Vitest](https://img.shields.io/badge/Vitest-6E9F18?style=flat-square&logo=vitest&logoColor=white) ![Playwright](https://img.shields.io/badge/Playwright_+_axe-2EAD33?style=flat-square) ![Ruff](https://img.shields.io/badge/Ruff-D7FF64?style=flat-square&logo=ruff&logoColor=black) ![mypy](https://img.shields.io/badge/mypy_strict-2A6DB2?style=flat-square) |
586
+
587
+ No AI or LLM API is called anywhere: detection is deterministic, offline and
588
+ driven by rule data.
589
+
590
+ ## Development
591
+
592
+ Requires Python 3.12+ and Git 2.31+.
593
+
594
+ ```bash
595
+ ./scripts/install-dev.sh
596
+ source .venv/bin/activate
597
+ pytest
598
+ ruff check . && ruff format --check .
599
+ mypy
600
+
601
+ # Dashboard (Node.js 20.19+)
602
+ cd web && npm ci
603
+ npm test && npm run typecheck && npm run lint
604
+ npm run build && npm run e2e # browser tests against the full stack
605
+ ```
606
+
607
+ ## Roadmap
608
+
609
+ | Phase | Scope | Status |
610
+ |---|---|---|
611
+ | **1. Foundation** | project structure · CLI · configuration · Git abstraction · commit model · detector and policy interfaces · testing foundation | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
612
+ | **2. AI attribution detection** | `Co-authored-by` parsing · AI identity and domain rules · identity detection · findings · blocking decisions | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
613
+ | **3. Git enforcement** | `pre-commit` · `commit-msg` · `pre-push` · hook installation and management · local enforcement | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
614
+ | **4. GitHub enforcement** | GitHub Actions check · pull request, merge queue and push scanning · trusted policy source · branch protection guidance | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
615
+ | **5. GitHub App** | webhooks · App authentication · installation lifecycle · Check Runs · ScanService · EnforcementService · audit events · mandatory policy | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
616
+ | **6. Security dashboard and control plane** | GitHub sign-in · roles and tenant isolation · enforcement evidence · scans · violation lifecycle · versioned organization policy · audit log · `/api/v1` | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
617
+ | **7. Notifications, merge queue, re-runs, recovery** | notification outbox · in-app, e-mail and signed webhooks · deduplication and retries · merge group validation · scan executions · policy rollback and diff | ![done](https://img.shields.io/badge/done-147a4b?style=flat-square) |
618
+ | **8. Organization governance** | organization policy hierarchy · repository groups · approvals and separation of duties · policy simulation · staged rollouts · scoped exceptions · security posture · compliance reports | ![current](https://img.shields.io/badge/current-1a5bb3?style=flat-square) |
619
+ | **Later: security intelligence** | advanced bot detection · signed commit verification · secret detection · provenance analysis · SARIF | ![planned](https://img.shields.io/badge/planned-55605c?style=flat-square) |
620
+
621
+ ## Documentation
622
+
623
+ - [Architecture](docs/architecture.md)
624
+ - [Detection engine](docs/detection-engine.md)
625
+ - [Policy engine](docs/policy-engine.md)
626
+ - [Configuration](docs/configuration.md)
627
+ - [Git hooks](docs/git-hooks.md)
628
+ - [GitHub enforcement (Actions)](docs/github-enforcement.md)
629
+ - [GitHub App](docs/github-app.md)
630
+ - [Dashboard and API](docs/dashboard.md)
631
+ - [Notifications](docs/notifications.md)
632
+ - [Merge queue](docs/merge-queue.md)
633
+ - [Policy management and rollback](docs/policy-management.md)
634
+ - [Organization governance](docs/organization-governance.md)
635
+ - [Policy inheritance](docs/policy-inheritance.md)
636
+ - [Policy simulation](docs/policy-simulation.md)
637
+ - [Policy exceptions](docs/policy-exceptions.md)
638
+ - [Staged policy rollouts](docs/policy-rollouts.md)
639
+ - [Repository management](docs/repository-management.md)
640
+ - [Security posture](docs/security-posture.md)
641
+ - [Compliance reporting](docs/compliance-reporting.md)
642
+ - [Recovery and failure handling](docs/recovery.md)
643
+ - [Deployment](docs/deployment/) — local, GitHub Actions, GitHub App, organization, production
644
+
645
+ **Getting started and reference**
646
+
647
+ - [5-minute quick start](docs/getting-started/quickstart.md)
648
+ - [Examples](examples/) — each one verified by a test against the real engine
649
+ - [CLI reference](docs/cli/) — every command, option and exit code
650
+ - [API reference](docs/api/)
651
+ - [Troubleshooting](docs/deployment/troubleshooting.md) · [Operations runbook](docs/operations/runbook.md)
652
+ - [Compatibility matrix](docs/support/compatibility.md)
653
+
654
+ **Security**
655
+
656
+ - [Security documentation](docs/security/) — threat model, boundaries, authorization matrix, review guide
657
+ - [Vulnerability response](docs/security/vulnerability-response.md) · [Incident response](docs/security/incident-response.md)
658
+ - [Supply chain](docs/security/supply-chain.md) — including the gaps
659
+
660
+ **Research and evidence**
661
+
662
+ - [Research index](docs/research/) — question, methodology, evaluations, limitations
663
+ - [Detection evaluation](docs/research/detection-evaluation.md) — accuracy, and the bypasses these benchmarks found
664
+ - [Performance evaluation](docs/research/performance-evaluation.md) · [Bypass resistance](docs/research/bypass-resistance.md)
665
+ - [Reproducibility](docs/research/reproducibility.md) — re-run all of it yourself
666
+ - [Limitations](docs/research/limitations.md) — read this one
667
+ - [Decision records](docs/adr/) · [Evidence timeline](docs/evidence/timeline.md) · [Validation matrix](docs/evidence/validation-matrix.md)
668
+
669
+ **Project**
670
+
671
+ - [Roadmap](ROADMAP.md) · [Maintainer docs](docs/maintainers/) · [Community](docs/community/)
672
+
673
+ ## Evidence, in one command
674
+
675
+ ```bash
676
+ commitguard reproduce all --evidence-dir evidence/security --results benchmarks/results
677
+ commitguard report security --results benchmarks/results --evidence evidence/security
678
+ ```
679
+
680
+ Rebuilds the labelled dataset, verifies its fingerprint against the published
681
+ files, re-measures detection, runs 350 security tests, and regenerates the reports.
682
+ A step that cannot run reports `SKIPPED` with a reason — never a pass.
683
+
684
+ Measured 2026-09-17 on this machine: **0 false negatives and 0 false positives on
685
+ 9,174 labelled cases**, after three real bypasses that these benchmarks found were
686
+ fixed. [All thirteen runs, including the failures](benchmarks/results/), are kept.
687
+
688
+ ## Contributing, security, license
689
+
690
+ - [CONTRIBUTING.md](CONTRIBUTING.md) · [good first issues](docs/community/good-first-issues.md)
691
+ - [SECURITY.md](SECURITY.md) — please report vulnerabilities and detection bypasses privately
692
+ - [External evaluation](docs/community/external-evaluation.md) — the most useful thing you can contribute
693
+ - [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
694
+ - [MIT License](LICENSE) · [CITATION.cff](CITATION.cff)