aipr-py 0.2.4__tar.gz
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.
- aipr_py-0.2.4/LICENSE +21 -0
- aipr_py-0.2.4/PKG-INFO +239 -0
- aipr_py-0.2.4/README.md +219 -0
- aipr_py-0.2.4/pyproject.toml +33 -0
- aipr_py-0.2.4/setup.cfg +4 -0
- aipr_py-0.2.4/src/aipr/__init__.py +3 -0
- aipr_py-0.2.4/src/aipr/cache.py +84 -0
- aipr_py-0.2.4/src/aipr/cli.py +448 -0
- aipr_py-0.2.4/src/aipr/detector.py +169 -0
- aipr_py-0.2.4/src/aipr/http.py +84 -0
- aipr_py-0.2.4/src/aipr/py.typed +0 -0
- aipr_py-0.2.4/src/aipr/sarif.py +186 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/PKG-INFO +239 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/SOURCES.txt +26 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/dependency_links.txt +1 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/entry_points.txt +2 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/requires.txt +1 -0
- aipr_py-0.2.4/src/aipr_py.egg-info/top_level.txt +1 -0
- aipr_py-0.2.4/tests/test_batch.py +41 -0
- aipr_py-0.2.4/tests/test_cache.py +109 -0
- aipr_py-0.2.4/tests/test_cli.py +223 -0
- aipr_py-0.2.4/tests/test_concurrent_policy.py +69 -0
- aipr_py-0.2.4/tests/test_detector.py +189 -0
- aipr_py-0.2.4/tests/test_gh_aipr.py +50 -0
- aipr_py-0.2.4/tests/test_redos.py +116 -0
- aipr_py-0.2.4/tests/test_sarif.py +169 -0
- aipr_py-0.2.4/tests/test_ttl_cache.py +156 -0
- aipr_py-0.2.4/tests/test_user_agent.py +64 -0
aipr_py-0.2.4/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Yunare Maia
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
aipr_py-0.2.4/PKG-INFO
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aipr-py
|
|
3
|
+
Version: 0.2.4
|
|
4
|
+
Summary: Read a repository's AI contribution policy before you (or your agent) contribute.
|
|
5
|
+
Author-email: Yunare Maia <yunare@gmail.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/yunaremaia/aipr
|
|
8
|
+
Keywords: ai-policy,open-source,contributing,cli,agents
|
|
9
|
+
Classifier: Development Status :: 4 - Beta
|
|
10
|
+
Classifier: Environment :: Console
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
15
|
+
Requires-Python: >=3.10
|
|
16
|
+
Description-Content-Type: text/markdown
|
|
17
|
+
License-File: LICENSE
|
|
18
|
+
Requires-Dist: regex>=2024.1.0
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# aipr
|
|
22
|
+
|
|
23
|
+
[](https://github.com/yunaremaia/aipr/actions/workflows/ci.yml)
|
|
24
|
+
[](https://www.python.org/downloads/)
|
|
25
|
+
[](https://github.com/yunaremaia/aipr/blob/main/LICENSE)
|
|
26
|
+
|
|
27
|
+
**AI Policy Read** - read an open-source repository's AI contribution policy
|
|
28
|
+
before you (or your agent) contribute.
|
|
29
|
+
|
|
30
|
+
`aipr` fetches the governance files that usually carry AI rules
|
|
31
|
+
(`CONTRIBUTING.md`, `AI_POLICY.md`, `AGENTS.md`, `CLAUDE.md`, ...), classifies
|
|
32
|
+
the repository's stance with weighted phrase matching, and answers one
|
|
33
|
+
question: **can an AI-assisted or autonomous contribution land here?**
|
|
34
|
+
|
|
35
|
+
```
|
|
36
|
+
$ aipr asciimoo/hister
|
|
37
|
+
aipr: asciimoo/hister
|
|
38
|
+
[BLOCKED] human-only policy
|
|
39
|
+
sources: CONTRIBUTING.md, README.md
|
|
40
|
+
confidence: 1.0 score: 19.0
|
|
41
|
+
autonomous contribution: NOT SAFE - require human co-authorship
|
|
42
|
+
[+5.0] ...Issues and PR descriptions must be fully human-written...
|
|
43
|
+
[+5.0] ...AI should never be the main author of the PR...
|
|
44
|
+
|
|
45
|
+
$ aipr apache/maka
|
|
46
|
+
aipr: apache/maka
|
|
47
|
+
[UNKNOWN] no explicit AI policy found
|
|
48
|
+
exit=2
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Why
|
|
52
|
+
|
|
53
|
+
More repositories are publishing explicit AI policies - from "we welcome
|
|
54
|
+
AI-assisted work" to "agents are strictly forbidden". Violating one burns the
|
|
55
|
+
contributor (and, for autonomous agents, the operator): rejected PRs at best,
|
|
56
|
+
blocks at worst. `aipr` makes the check mechanical and cheap, for humans
|
|
57
|
+
deciding where to spend review effort and for agents deciding where to spend
|
|
58
|
+
their quota.
|
|
59
|
+
|
|
60
|
+
## Install
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
# 1. From PyPI
|
|
64
|
+
pip install aipr-py
|
|
65
|
+
|
|
66
|
+
# 2. Standalone from GitHub
|
|
67
|
+
pip install git+https://github.com/yunaremaia/aipr.git
|
|
68
|
+
# requires Python 3.10+; GH_TOKEN recommended (anonymous API calls rate-limit fast)
|
|
69
|
+
export GH_TOKEN=ghp_xxx # classic token with public repo read access
|
|
70
|
+
|
|
71
|
+
# 3. As a GitHub CLI extension (recommended)
|
|
72
|
+
gh extension install yunaremaia/aipr
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The `gh extension install` method is the easiest — after install, `gh aipr OWNER/REPO` works immediately.
|
|
76
|
+
|
|
77
|
+
No dependencies beyond the standard library. `pytest` only to develop.
|
|
78
|
+
|
|
79
|
+
## Usage
|
|
80
|
+
|
|
81
|
+
### Standalone
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
aipr OWNER/REPO # classify a GitHub repository
|
|
85
|
+
aipr --text FILE # classify a local governance file
|
|
86
|
+
aipr --json OWNER/REPO # machine-readable output
|
|
87
|
+
aipr --sarif OWNER/REPO # SARIF 2.1.0 output for GitHub Code Scanning
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### As a GitHub CLI extension
|
|
91
|
+
|
|
92
|
+
After `gh extension install yunaremaia/aipr`, use `gh aipr` identically:
|
|
93
|
+
|
|
94
|
+
```bash
|
|
95
|
+
gh aipr OWNER/REPO
|
|
96
|
+
gh aipr --json OWNER/REPO
|
|
97
|
+
gh aipr --sarif OWNER/REPO
|
|
98
|
+
gh aipr --text FILE
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
Exit codes are preserved (0/1/2) for CI conditionals.
|
|
102
|
+
|
|
103
|
+
### `init` — scaffold AI policy files
|
|
104
|
+
|
|
105
|
+
Generate `AI_POLICY.md` and `AI_TOOL_POLICY.md` in your repo:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
aipr init [--dir .] [--type disclose|permissive|human_only] [--org ORG]
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Presets:
|
|
112
|
+
- `permissive` — explicitly welcomes AI-assisted contributions (aipr-safe)
|
|
113
|
+
- `disclose_ok` (default) — allowed with `Assisted-by: AI` disclosure trailer
|
|
114
|
+
- `human_only` — AI must not be the main author (NOT autonomous-safe)
|
|
115
|
+
|
|
116
|
+
### Verdicts
|
|
117
|
+
|
|
118
|
+
| Verdict | Meaning | Autonomous-safe? |
|
|
119
|
+
|---|---|---|
|
|
120
|
+
| `human_only` | AI must not be the main author / human-written only / bans agents | no |
|
|
121
|
+
| `restrictive` | heavy process: mandatory disclosure + human-in-the-loop requirements | no |
|
|
122
|
+
| `disclose_ok` | allowed with a disclosure trailer (`Assisted-by: AI`) | yes* |
|
|
123
|
+
| `permissive` | explicitly welcomes AI-assisted contributions | yes |
|
|
124
|
+
| `unknown` | no explicit policy found | ask first |
|
|
125
|
+
|
|
126
|
+
\* still follow the disclosure rules - "safe" means *no human co-authorship
|
|
127
|
+
required by policy*, not *no obligations*.
|
|
128
|
+
|
|
129
|
+
### Exit codes (for CI and agents)
|
|
130
|
+
|
|
131
|
+
| Code | Meaning |
|
|
132
|
+
|---|---|
|
|
133
|
+
| 0 | all inspected repos are autonomous-safe |
|
|
134
|
+
| 1 | at least one repo is restricted or human-only |
|
|
135
|
+
| 2 | at least one repo is unknown / no policy found (ranks worse than 1) |
|
|
136
|
+
| 64 | usage error |
|
|
137
|
+
|
|
138
|
+
Batch mode: `aipr owner/repo1 owner/repo2 ...` prints one block per repo
|
|
139
|
+
(JSON array with `--json`) and the exit code reflects the worst result —
|
|
140
|
+
so an unverified repo can never pass a gate silently.
|
|
141
|
+
|
|
142
|
+
Policy fetches are cached on disk for 24h (`~/.cache/aipr`, configurable via
|
|
143
|
+
`AIPR_CACHE_DIR` / `AIPR_CACHE_TTL`), so repeated scans cost zero API calls.
|
|
144
|
+
Use `--no-cache` to force a fresh fetch.
|
|
145
|
+
|
|
146
|
+
## How classification works
|
|
147
|
+
|
|
148
|
+
Weighted regex matching over concatenated governance text. Restrictive phrases
|
|
149
|
+
score positive ("must be fully human-written" +5), permissive ones negative
|
|
150
|
+
("we warmly welcome AI-assisted" -3.5). The strongest signals force the
|
|
151
|
+
verdict; weak mixed signals lean restrictive on purpose - when in doubt, do
|
|
152
|
+
not send a bot.
|
|
153
|
+
|
|
154
|
+
Known limits: English-only patterns; phrase matching cannot understand nuance;
|
|
155
|
+
a repo can carry policy in unusual files we don't probe. Treat UNKNOWN as
|
|
156
|
+
"read it yourself".
|
|
157
|
+
|
|
158
|
+
## CI/CD Integration
|
|
159
|
+
|
|
160
|
+
### Block PRs Violating AI Policy
|
|
161
|
+
|
|
162
|
+
Drop `.github/workflows/aipr.yml` into your repository to automatically block pull requests targeting repos with human-only or restrictive AI policies:
|
|
163
|
+
|
|
164
|
+
```yaml
|
|
165
|
+
# .github/workflows/aipr.yml – block PRs against repos with human-only AI policies
|
|
166
|
+
name: AI Policy Check
|
|
167
|
+
on:
|
|
168
|
+
pull_request:
|
|
169
|
+
branches: [main, master]
|
|
170
|
+
|
|
171
|
+
jobs:
|
|
172
|
+
aipr:
|
|
173
|
+
runs-on: ubuntu-latest
|
|
174
|
+
steps:
|
|
175
|
+
- uses: actions/checkout@v5
|
|
176
|
+
- uses: actions/setup-python@v5
|
|
177
|
+
with:
|
|
178
|
+
python-version: "3.10"
|
|
179
|
+
- run: pip install git+https://github.com/yunaremaia/aipr.git
|
|
180
|
+
- name: Check AI policy
|
|
181
|
+
run: |
|
|
182
|
+
aipr "$GITHUB_REPOSITORY" --json || true
|
|
183
|
+
# Exit 1 = human_only/restrictive (block)
|
|
184
|
+
# Exit 2 = unknown (warn, not block)
|
|
185
|
+
aipr "$GITHUB_REPOSITORY" --json | jq -e '.verdict == "human_only" or .verdict == "restrictive"' && exit 1 || exit 0
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
### GitHub Code Scanning (SARIF)
|
|
189
|
+
|
|
190
|
+
Use `--sarif` to output SARIF 2.1.0 (Static Analysis Results Interchange Format)
|
|
191
|
+
and upload to GitHub Code Scanning. This surfaces AI policy compliance as
|
|
192
|
+
alerts in the GitHub Security tab.
|
|
193
|
+
|
|
194
|
+
```bash
|
|
195
|
+
# Generate SARIF output
|
|
196
|
+
aipr --sarif OWNER/REPO > aipr-results.sarif
|
|
197
|
+
# Upload to GitHub Code Scanning via GitHub Actions:
|
|
198
|
+
# github/codeql-action/upload-sarif with sarif_file: aipr-results.sarif
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
Verdict mapping to SARIF levels:
|
|
202
|
+
- `human_only` / `restrictive` → `error` (blocks contribution)
|
|
203
|
+
- `unknown` → `warning` (needs manual review)
|
|
204
|
+
- `permissive` / `disclose_ok` → `note` (safe to proceed)
|
|
205
|
+
|
|
206
|
+
Example GitHub Actions workflow snippet:
|
|
207
|
+
|
|
208
|
+
```yaml
|
|
209
|
+
name: AI Policy Check (SARIF)
|
|
210
|
+
on:
|
|
211
|
+
pull_request:
|
|
212
|
+
branches: [main]
|
|
213
|
+
|
|
214
|
+
jobs:
|
|
215
|
+
aipr-check:
|
|
216
|
+
runs-on: ubuntu-latest
|
|
217
|
+
permissions:
|
|
218
|
+
security-events: write
|
|
219
|
+
steps:
|
|
220
|
+
- uses: actions/checkout@v4
|
|
221
|
+
- uses: actions/setup-python@v5
|
|
222
|
+
with:
|
|
223
|
+
python-version: '3.11'
|
|
224
|
+
- run: pip install git+https://github.com/yunaremaia/aipr.git
|
|
225
|
+
- run: aipr --sarif ${{ github.event.pull_request.head.repo.full_name }} > aipr-results.sarif
|
|
226
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
227
|
+
with:
|
|
228
|
+
sarif_file: aipr-results.sarif
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
## Status
|
|
232
|
+
|
|
233
|
+
Early beta - battle-tested against a handful of real policies (hister,
|
|
234
|
+
modular, polars, MDAnalysis, maka). Rule additions welcome: open an issue with
|
|
235
|
+
the policy text and the verdict you expected.
|
|
236
|
+
|
|
237
|
+
## License
|
|
238
|
+
|
|
239
|
+
MIT
|
aipr_py-0.2.4/README.md
ADDED
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
# aipr
|
|
2
|
+
|
|
3
|
+
[](https://github.com/yunaremaia/aipr/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.python.org/downloads/)
|
|
5
|
+
[](https://github.com/yunaremaia/aipr/blob/main/LICENSE)
|
|
6
|
+
|
|
7
|
+
**AI Policy Read** - read an open-source repository's AI contribution policy
|
|
8
|
+
before you (or your agent) contribute.
|
|
9
|
+
|
|
10
|
+
`aipr` fetches the governance files that usually carry AI rules
|
|
11
|
+
(`CONTRIBUTING.md`, `AI_POLICY.md`, `AGENTS.md`, `CLAUDE.md`, ...), classifies
|
|
12
|
+
the repository's stance with weighted phrase matching, and answers one
|
|
13
|
+
question: **can an AI-assisted or autonomous contribution land here?**
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
$ aipr asciimoo/hister
|
|
17
|
+
aipr: asciimoo/hister
|
|
18
|
+
[BLOCKED] human-only policy
|
|
19
|
+
sources: CONTRIBUTING.md, README.md
|
|
20
|
+
confidence: 1.0 score: 19.0
|
|
21
|
+
autonomous contribution: NOT SAFE - require human co-authorship
|
|
22
|
+
[+5.0] ...Issues and PR descriptions must be fully human-written...
|
|
23
|
+
[+5.0] ...AI should never be the main author of the PR...
|
|
24
|
+
|
|
25
|
+
$ aipr apache/maka
|
|
26
|
+
aipr: apache/maka
|
|
27
|
+
[UNKNOWN] no explicit AI policy found
|
|
28
|
+
exit=2
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Why
|
|
32
|
+
|
|
33
|
+
More repositories are publishing explicit AI policies - from "we welcome
|
|
34
|
+
AI-assisted work" to "agents are strictly forbidden". Violating one burns the
|
|
35
|
+
contributor (and, for autonomous agents, the operator): rejected PRs at best,
|
|
36
|
+
blocks at worst. `aipr` makes the check mechanical and cheap, for humans
|
|
37
|
+
deciding where to spend review effort and for agents deciding where to spend
|
|
38
|
+
their quota.
|
|
39
|
+
|
|
40
|
+
## Install
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
# 1. From PyPI
|
|
44
|
+
pip install aipr-py
|
|
45
|
+
|
|
46
|
+
# 2. Standalone from GitHub
|
|
47
|
+
pip install git+https://github.com/yunaremaia/aipr.git
|
|
48
|
+
# requires Python 3.10+; GH_TOKEN recommended (anonymous API calls rate-limit fast)
|
|
49
|
+
export GH_TOKEN=ghp_xxx # classic token with public repo read access
|
|
50
|
+
|
|
51
|
+
# 3. As a GitHub CLI extension (recommended)
|
|
52
|
+
gh extension install yunaremaia/aipr
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The `gh extension install` method is the easiest — after install, `gh aipr OWNER/REPO` works immediately.
|
|
56
|
+
|
|
57
|
+
No dependencies beyond the standard library. `pytest` only to develop.
|
|
58
|
+
|
|
59
|
+
## Usage
|
|
60
|
+
|
|
61
|
+
### Standalone
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
aipr OWNER/REPO # classify a GitHub repository
|
|
65
|
+
aipr --text FILE # classify a local governance file
|
|
66
|
+
aipr --json OWNER/REPO # machine-readable output
|
|
67
|
+
aipr --sarif OWNER/REPO # SARIF 2.1.0 output for GitHub Code Scanning
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### As a GitHub CLI extension
|
|
71
|
+
|
|
72
|
+
After `gh extension install yunaremaia/aipr`, use `gh aipr` identically:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
gh aipr OWNER/REPO
|
|
76
|
+
gh aipr --json OWNER/REPO
|
|
77
|
+
gh aipr --sarif OWNER/REPO
|
|
78
|
+
gh aipr --text FILE
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Exit codes are preserved (0/1/2) for CI conditionals.
|
|
82
|
+
|
|
83
|
+
### `init` — scaffold AI policy files
|
|
84
|
+
|
|
85
|
+
Generate `AI_POLICY.md` and `AI_TOOL_POLICY.md` in your repo:
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
aipr init [--dir .] [--type disclose|permissive|human_only] [--org ORG]
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
Presets:
|
|
92
|
+
- `permissive` — explicitly welcomes AI-assisted contributions (aipr-safe)
|
|
93
|
+
- `disclose_ok` (default) — allowed with `Assisted-by: AI` disclosure trailer
|
|
94
|
+
- `human_only` — AI must not be the main author (NOT autonomous-safe)
|
|
95
|
+
|
|
96
|
+
### Verdicts
|
|
97
|
+
|
|
98
|
+
| Verdict | Meaning | Autonomous-safe? |
|
|
99
|
+
|---|---|---|
|
|
100
|
+
| `human_only` | AI must not be the main author / human-written only / bans agents | no |
|
|
101
|
+
| `restrictive` | heavy process: mandatory disclosure + human-in-the-loop requirements | no |
|
|
102
|
+
| `disclose_ok` | allowed with a disclosure trailer (`Assisted-by: AI`) | yes* |
|
|
103
|
+
| `permissive` | explicitly welcomes AI-assisted contributions | yes |
|
|
104
|
+
| `unknown` | no explicit policy found | ask first |
|
|
105
|
+
|
|
106
|
+
\* still follow the disclosure rules - "safe" means *no human co-authorship
|
|
107
|
+
required by policy*, not *no obligations*.
|
|
108
|
+
|
|
109
|
+
### Exit codes (for CI and agents)
|
|
110
|
+
|
|
111
|
+
| Code | Meaning |
|
|
112
|
+
|---|---|
|
|
113
|
+
| 0 | all inspected repos are autonomous-safe |
|
|
114
|
+
| 1 | at least one repo is restricted or human-only |
|
|
115
|
+
| 2 | at least one repo is unknown / no policy found (ranks worse than 1) |
|
|
116
|
+
| 64 | usage error |
|
|
117
|
+
|
|
118
|
+
Batch mode: `aipr owner/repo1 owner/repo2 ...` prints one block per repo
|
|
119
|
+
(JSON array with `--json`) and the exit code reflects the worst result —
|
|
120
|
+
so an unverified repo can never pass a gate silently.
|
|
121
|
+
|
|
122
|
+
Policy fetches are cached on disk for 24h (`~/.cache/aipr`, configurable via
|
|
123
|
+
`AIPR_CACHE_DIR` / `AIPR_CACHE_TTL`), so repeated scans cost zero API calls.
|
|
124
|
+
Use `--no-cache` to force a fresh fetch.
|
|
125
|
+
|
|
126
|
+
## How classification works
|
|
127
|
+
|
|
128
|
+
Weighted regex matching over concatenated governance text. Restrictive phrases
|
|
129
|
+
score positive ("must be fully human-written" +5), permissive ones negative
|
|
130
|
+
("we warmly welcome AI-assisted" -3.5). The strongest signals force the
|
|
131
|
+
verdict; weak mixed signals lean restrictive on purpose - when in doubt, do
|
|
132
|
+
not send a bot.
|
|
133
|
+
|
|
134
|
+
Known limits: English-only patterns; phrase matching cannot understand nuance;
|
|
135
|
+
a repo can carry policy in unusual files we don't probe. Treat UNKNOWN as
|
|
136
|
+
"read it yourself".
|
|
137
|
+
|
|
138
|
+
## CI/CD Integration
|
|
139
|
+
|
|
140
|
+
### Block PRs Violating AI Policy
|
|
141
|
+
|
|
142
|
+
Drop `.github/workflows/aipr.yml` into your repository to automatically block pull requests targeting repos with human-only or restrictive AI policies:
|
|
143
|
+
|
|
144
|
+
```yaml
|
|
145
|
+
# .github/workflows/aipr.yml – block PRs against repos with human-only AI policies
|
|
146
|
+
name: AI Policy Check
|
|
147
|
+
on:
|
|
148
|
+
pull_request:
|
|
149
|
+
branches: [main, master]
|
|
150
|
+
|
|
151
|
+
jobs:
|
|
152
|
+
aipr:
|
|
153
|
+
runs-on: ubuntu-latest
|
|
154
|
+
steps:
|
|
155
|
+
- uses: actions/checkout@v5
|
|
156
|
+
- uses: actions/setup-python@v5
|
|
157
|
+
with:
|
|
158
|
+
python-version: "3.10"
|
|
159
|
+
- run: pip install git+https://github.com/yunaremaia/aipr.git
|
|
160
|
+
- name: Check AI policy
|
|
161
|
+
run: |
|
|
162
|
+
aipr "$GITHUB_REPOSITORY" --json || true
|
|
163
|
+
# Exit 1 = human_only/restrictive (block)
|
|
164
|
+
# Exit 2 = unknown (warn, not block)
|
|
165
|
+
aipr "$GITHUB_REPOSITORY" --json | jq -e '.verdict == "human_only" or .verdict == "restrictive"' && exit 1 || exit 0
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
### GitHub Code Scanning (SARIF)
|
|
169
|
+
|
|
170
|
+
Use `--sarif` to output SARIF 2.1.0 (Static Analysis Results Interchange Format)
|
|
171
|
+
and upload to GitHub Code Scanning. This surfaces AI policy compliance as
|
|
172
|
+
alerts in the GitHub Security tab.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
# Generate SARIF output
|
|
176
|
+
aipr --sarif OWNER/REPO > aipr-results.sarif
|
|
177
|
+
# Upload to GitHub Code Scanning via GitHub Actions:
|
|
178
|
+
# github/codeql-action/upload-sarif with sarif_file: aipr-results.sarif
|
|
179
|
+
```
|
|
180
|
+
|
|
181
|
+
Verdict mapping to SARIF levels:
|
|
182
|
+
- `human_only` / `restrictive` → `error` (blocks contribution)
|
|
183
|
+
- `unknown` → `warning` (needs manual review)
|
|
184
|
+
- `permissive` / `disclose_ok` → `note` (safe to proceed)
|
|
185
|
+
|
|
186
|
+
Example GitHub Actions workflow snippet:
|
|
187
|
+
|
|
188
|
+
```yaml
|
|
189
|
+
name: AI Policy Check (SARIF)
|
|
190
|
+
on:
|
|
191
|
+
pull_request:
|
|
192
|
+
branches: [main]
|
|
193
|
+
|
|
194
|
+
jobs:
|
|
195
|
+
aipr-check:
|
|
196
|
+
runs-on: ubuntu-latest
|
|
197
|
+
permissions:
|
|
198
|
+
security-events: write
|
|
199
|
+
steps:
|
|
200
|
+
- uses: actions/checkout@v4
|
|
201
|
+
- uses: actions/setup-python@v5
|
|
202
|
+
with:
|
|
203
|
+
python-version: '3.11'
|
|
204
|
+
- run: pip install git+https://github.com/yunaremaia/aipr.git
|
|
205
|
+
- run: aipr --sarif ${{ github.event.pull_request.head.repo.full_name }} > aipr-results.sarif
|
|
206
|
+
- uses: github/codeql-action/upload-sarif@v3
|
|
207
|
+
with:
|
|
208
|
+
sarif_file: aipr-results.sarif
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## Status
|
|
212
|
+
|
|
213
|
+
Early beta - battle-tested against a handful of real policies (hister,
|
|
214
|
+
modular, polars, MDAnalysis, maka). Rule additions welcome: open an issue with
|
|
215
|
+
the policy text and the verdict you expected.
|
|
216
|
+
|
|
217
|
+
## License
|
|
218
|
+
|
|
219
|
+
MIT
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aipr-py"
|
|
7
|
+
version = "0.2.4"
|
|
8
|
+
description = "Read a repository's AI contribution policy before you (or your agent) contribute."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
license = { text = "MIT" }
|
|
12
|
+
authors = [{ name = "Yunare Maia", email = "yunare@gmail.com" }]
|
|
13
|
+
keywords = ["ai-policy", "open-source", "contributing", "cli", "agents"]
|
|
14
|
+
classifiers = [
|
|
15
|
+
"Development Status :: 4 - Beta",
|
|
16
|
+
"Environment :: Console",
|
|
17
|
+
"Intended Audience :: Developers",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Programming Language :: Python :: 3",
|
|
20
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
21
|
+
]
|
|
22
|
+
dependencies = [
|
|
23
|
+
"regex>=2024.1.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.scripts]
|
|
27
|
+
aipr = "aipr.cli:main"
|
|
28
|
+
|
|
29
|
+
[project.urls]
|
|
30
|
+
Homepage = "https://github.com/yunaremaia/aipr"
|
|
31
|
+
|
|
32
|
+
[tool.setuptools.packages.find]
|
|
33
|
+
where = ["src"]
|
aipr_py-0.2.4/setup.cfg
ADDED
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
"""Thread-safe TTL cache for policy detection results."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import copy
|
|
6
|
+
import os
|
|
7
|
+
import threading
|
|
8
|
+
import time
|
|
9
|
+
from dataclasses import dataclass
|
|
10
|
+
from typing import TYPE_CHECKING, Optional
|
|
11
|
+
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from aipr.detector import Policy
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# Default TTL: 24 hours (in seconds)
|
|
17
|
+
DEFAULT_CACHE_TTL = int(os.environ.get("AIPR_CACHE_TTL", "86400"))
|
|
18
|
+
MAX_CACHE_SIZE = int(os.environ.get("AIPR_CACHE_SIZE", "1024"))
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass
|
|
22
|
+
class CacheEntry:
|
|
23
|
+
ts: float
|
|
24
|
+
policy: object # Policy dataclass
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class TTLCache:
|
|
28
|
+
"""Thread-safe TTL cache with LRU eviction and deep copy on read.
|
|
29
|
+
|
|
30
|
+
Replaces lru_cache for Policy objects to ensure:
|
|
31
|
+
1. Thread safety via threading.Lock
|
|
32
|
+
2. TTL-based expiration (configurable via AIPR_CACHE_TTL env var)
|
|
33
|
+
3. Deep copy on read to prevent mutation of cached state
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
def __init__(self, ttl: int = DEFAULT_CACHE_TTL, maxsize: int = MAX_CACHE_SIZE):
|
|
37
|
+
self._ttl = ttl
|
|
38
|
+
self._maxsize = maxsize
|
|
39
|
+
self._cache: dict[str, CacheEntry] = {}
|
|
40
|
+
self._lock = threading.Lock()
|
|
41
|
+
|
|
42
|
+
def get(self, key: str) -> Optional[object]:
|
|
43
|
+
"""Get a cached Policy, returning a deep copy.
|
|
44
|
+
|
|
45
|
+
Returns None if key not found or entry expired.
|
|
46
|
+
"""
|
|
47
|
+
with self._lock:
|
|
48
|
+
entry = self._cache.get(key)
|
|
49
|
+
if entry is None:
|
|
50
|
+
return None
|
|
51
|
+
if time.time() - entry.ts > self._ttl:
|
|
52
|
+
del self._cache[key]
|
|
53
|
+
return None
|
|
54
|
+
return copy.deepcopy(entry.policy)
|
|
55
|
+
|
|
56
|
+
def put(self, key: str, policy: object) -> None:
|
|
57
|
+
"""Store a Policy with current timestamp.
|
|
58
|
+
|
|
59
|
+
Evicts oldest entries if cache exceeds maxsize.
|
|
60
|
+
"""
|
|
61
|
+
with self._lock:
|
|
62
|
+
while len(self._cache) >= self._maxsize:
|
|
63
|
+
oldest_key = min(self._cache, key=lambda k: self._cache[k].ts)
|
|
64
|
+
del self._cache[oldest_key]
|
|
65
|
+
self._cache[key] = CacheEntry(ts=time.time(), policy=copy.deepcopy(policy))
|
|
66
|
+
|
|
67
|
+
def clear(self) -> None:
|
|
68
|
+
"""Clear all cached entries."""
|
|
69
|
+
with self._lock:
|
|
70
|
+
self._cache.clear()
|
|
71
|
+
|
|
72
|
+
def __len__(self) -> int:
|
|
73
|
+
with self._lock:
|
|
74
|
+
return len(self._cache)
|
|
75
|
+
|
|
76
|
+
def __contains__(self, key: str) -> bool:
|
|
77
|
+
with self._lock:
|
|
78
|
+
entry = self._cache.get(key)
|
|
79
|
+
if entry is None:
|
|
80
|
+
return False
|
|
81
|
+
if time.time() - entry.ts > self._ttl:
|
|
82
|
+
del self._cache[key]
|
|
83
|
+
return False
|
|
84
|
+
return True
|