aisignoff 0.4.0__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.
- aisignoff-0.4.0/LICENSE +21 -0
- aisignoff-0.4.0/PKG-INFO +250 -0
- aisignoff-0.4.0/README.md +228 -0
- aisignoff-0.4.0/pyproject.toml +38 -0
- aisignoff-0.4.0/setup.cfg +4 -0
- aisignoff-0.4.0/src/aisignoff/__init__.py +2 -0
- aisignoff-0.4.0/src/aisignoff/__main__.py +5 -0
- aisignoff-0.4.0/src/aisignoff/cli.py +771 -0
- aisignoff-0.4.0/src/aisignoff/config.py +175 -0
- aisignoff-0.4.0/src/aisignoff/globbing.py +50 -0
- aisignoff-0.4.0/src/aisignoff/inline.py +275 -0
- aisignoff-0.4.0/src/aisignoff/model.py +210 -0
- aisignoff-0.4.0/src/aisignoff/project.py +116 -0
- aisignoff-0.4.0/src/aisignoff.egg-info/PKG-INFO +250 -0
- aisignoff-0.4.0/src/aisignoff.egg-info/SOURCES.txt +17 -0
- aisignoff-0.4.0/src/aisignoff.egg-info/dependency_links.txt +1 -0
- aisignoff-0.4.0/src/aisignoff.egg-info/entry_points.txt +2 -0
- aisignoff-0.4.0/src/aisignoff.egg-info/top_level.txt +1 -0
- aisignoff-0.4.0/tests/test_aisignoff.py +967 -0
aisignoff-0.4.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Pascal Jung
|
|
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.
|
aisignoff-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,250 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: aisignoff
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Sign off on AI use per file: declare and check AI generation (G0-G3), human review (H0-H3) and AI review (A0-A3), REUSE-style.
|
|
5
|
+
Author: Industrial Mathematics Group
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/industrial-mathematics-group/aisignoff
|
|
8
|
+
Project-URL: Issues, https://github.com/industrial-mathematics-group/aisignoff/issues
|
|
9
|
+
Keywords: ai,declaration,provenance,code review,research software,reuse
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Science/Research
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
18
|
+
Requires-Python: >=3.11
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Dynamic: license-file
|
|
22
|
+
|
|
23
|
+
# aisignoff
|
|
24
|
+
|
|
25
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#g-generation)
|
|
26
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#h-human-review)
|
|
27
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#a-ai-review)
|
|
28
|
+
|
|
29
|
+

|
|
30
|
+

|
|
31
|
+

|
|
32
|
+
|
|
33
|
+
**Declare and check how each file was created and reviewed**, using the Industrial Mathematics Group's AI levels:
|
|
34
|
+
- **G0–G3:** how much an AI generated;
|
|
35
|
+
- **H0–H3:** how thoroughly a human reviewed it;
|
|
36
|
+
- **A0–A3:** how thoroughly an AI reviewed it.
|
|
37
|
+
|
|
38
|
+
The name is meant both ways: the AI signs off on what it generated or reviewed, and so does the human ("ai", said in German, sounds like "I"). It's a declaration you sign off on, not a cryptographic signature.
|
|
39
|
+
|
|
40
|
+
It works like [REUSE](https://reuse.software) does for licences: a path-based `SIGNOFF.toml`, optional tags in file headers, and a `lint` command for CI.
|
|
41
|
+
|
|
42
|
+
It complements the declaration in pull requests, where the levels are ticked in the PR template:
|
|
43
|
+
|
|
44
|
+
| | PR declaration | `SIGNOFF.toml` |
|
|
45
|
+
|---|---|---|
|
|
46
|
+
| Describes | each **change** merged into `main` | the **current state** of every file |
|
|
47
|
+
| Good for | code developed through pull requests | results, data, notebooks, papers, single-commit uploads |
|
|
48
|
+
| Checked by | the *AI declaration* PR check | `aisignoff lint` |
|
|
49
|
+
|
|
50
|
+
The keys of `SIGNOFF.toml` and the inline tags are the same ones the PR check shows in its job summary.
|
|
51
|
+
|
|
52
|
+
The levels are defined in the [AI policy](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#ai-assisted-contributions):
|
|
53
|
+
|
|
54
|
+
| Generation | | Human review | | AI review | |
|
|
55
|
+
|---|---|---|---|---|---|
|
|
56
|
+
| **G0** | Human | **H0** | Plausibility check | **A0** | None |
|
|
57
|
+
| **G1** | AI-assisted | **H1** | Tested | **A1** | Self-check by the generating session |
|
|
58
|
+
| **G2** | AI-generated | **H2** | Reviewed line by line | **A2** | Review by a new session |
|
|
59
|
+
| **G3** | AI-autonomous | **H3** | Independently validated | **A3** | Extensive review or benchmark by a different model |
|
|
60
|
+
|
|
61
|
+
For human-written content (G0) there is no generating session, so it can't have A1: any AI review of it is A2 or A3.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install aisignoff
|
|
67
|
+
# or, without touching your environment:
|
|
68
|
+
pipx install aisignoff # or: uv tool install aisignoff
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
The latest development version: `pip install "aisignoff @ git+https://github.com/industrial-mathematics-group/aisignoff"`.
|
|
72
|
+
|
|
73
|
+
Requires Python 3.11 or newer; no other dependencies.
|
|
74
|
+
|
|
75
|
+
## Quick start
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
# 1. A default for everything (here: written by a human, reviewed line by line, no AI review)
|
|
79
|
+
aisignoff init -g G0 -H H2 -a A0 --reviewed-by "Jane Doe"
|
|
80
|
+
|
|
81
|
+
# 2. Declare what is different, by file, folder or glob
|
|
82
|
+
aisignoff annotate results/ -g G2 -H H1 -a A1 \
|
|
83
|
+
--assisted-by "Claude Opus 5.5 (Anthropic)" \
|
|
84
|
+
--ai-reviewed-by "Claude Opus 5.5 (Anthropic), same session" --reviewed-by "Jane Doe"
|
|
85
|
+
aisignoff annotate "scripts/*.jl" -g G1 -H H3 -a A2 --assisted-by "GitHub Copilot" \
|
|
86
|
+
--ai-reviewed-by "Claude Opus 5.5 (Anthropic), new session" --reviewed-by "Jane Doe"
|
|
87
|
+
|
|
88
|
+
# 3. Check that every file is declared correctly
|
|
89
|
+
aisignoff lint
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
`annotate` refuses incomplete declarations, and anything below the repository's policy (e.g. G2 without `--assisted-by`, or G2 with `-a A0`). It also checks that the resulting `SIGNOFF.toml` is still valid, and leaves it unchanged if not. Paths are handled like this:
|
|
93
|
+
- **Inside the project root:** files outside `--root` are refused, also with `--inline`.
|
|
94
|
+
- **Files and folders:** existing files are matched exactly, even with `*` or `?` in their name, and a folder becomes `folder/**`.
|
|
95
|
+
- **Globs:** quote them, e.g. `"scripts/*.jl"`. Inside the project they're read relative to the current directory, as the shell would; with `--root` from outside, relative to the root. Globs with `..` or absolute paths are refused.
|
|
96
|
+
|
|
97
|
+
## SIGNOFF.toml
|
|
98
|
+
|
|
99
|
+
```toml
|
|
100
|
+
version = 3
|
|
101
|
+
exclude = ["reviews/**", "docs/build/**"] # optional: files that need no declaration
|
|
102
|
+
|
|
103
|
+
[policy] # optional: minimum levels
|
|
104
|
+
"*" = { human = "H1" }
|
|
105
|
+
G3 = { human = "H2", ai = "A2" }
|
|
106
|
+
|
|
107
|
+
[[annotations]]
|
|
108
|
+
path = "**"
|
|
109
|
+
AI-Generation = "G0"
|
|
110
|
+
Human-Review = "H2"
|
|
111
|
+
AI-Review = "A0"
|
|
112
|
+
Reviewed-by = "Jane Doe"
|
|
113
|
+
|
|
114
|
+
[[annotations]]
|
|
115
|
+
path = ["results/**", "figures/*.pdf"]
|
|
116
|
+
AI-Generation = "G2"
|
|
117
|
+
Human-Review = "H3"
|
|
118
|
+
AI-Review = "A3"
|
|
119
|
+
Assisted-by = ["Claude Opus 5.5 (Anthropic)"]
|
|
120
|
+
AI-Reviewed-by = "GPT-5 (OpenAI), full benchmark against reference solutions"
|
|
121
|
+
Reviewed-by = "Jane Doe"
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
- **`path`**: one glob or a list, relative to the repository root.
|
|
125
|
+
- `*` stays within a folder, `**` crosses folders (`**/` also matches no folder), and `?` is one character.
|
|
126
|
+
- `\*`, `\?` and `\\` match those characters literally.
|
|
127
|
+
- This is modelled on `REUSE.toml`, plus `?`.
|
|
128
|
+
- **The last matching annotation wins**, so put the general default first and the exceptions after it.
|
|
129
|
+
- **Keys:** the levels and the names below. `Assisted-by`, `AI-Reviewed-by` and `Reviewed-by` take a string or a list.
|
|
130
|
+
- **Every file needs a valid declaration:**
|
|
131
|
+
|
|
132
|
+
| Key | Required for |
|
|
133
|
+
|---|---|
|
|
134
|
+
| `AI-Generation`, `Human-Review`, `AI-Review` | every file |
|
|
135
|
+
| `Reviewed-by` (the responsible human) | every file |
|
|
136
|
+
| `Assisted-by` | G1–G3; not allowed for G0 |
|
|
137
|
+
| `AI-Reviewed-by` | A1–A3; not allowed for A0 |
|
|
138
|
+
|
|
139
|
+
- **Placeholders:** any value may be `"TODO"`, e.g. `Human-Review = "TODO"` when an agent declares its part and a person adds the human review later. `annotate` accepts it too: `-H TODO` without `--reviewed-by` also writes `Reviewed-by` as TODO, so the person fills in both together. It still checks the axes that are filled in. `lint` reports every TODO until it's filled in.
|
|
140
|
+
- **Minimum levels:** G1–G3 always need at least **A1**: AI-written content must at least be self-checked. The `[policy]` table can require more (for G0, a minimum of A1 means A2):
|
|
141
|
+
- `"*"` sets the default for every generation level;
|
|
142
|
+
- `G0`–`G3` refine it per generation level, overriding the `"*"` values key by key.
|
|
143
|
+
- **Which files are checked:**
|
|
144
|
+
- **In a git repository:** all tracked files plus untracked files that aren't ignored, so `.gitignore` is respected. If git can't be run there, aisignoff stops with an error rather than ignoring `.gitignore`.
|
|
145
|
+
- **Outside git:** every file below the root.
|
|
146
|
+
- **Always skipped:** `SIGNOFF.toml` itself (and `AIDECL.toml`, its old name) and anything under `exclude`. Exclude the folder of committed AI reviews (`reviews/` by default, see CONTRIBUTING.md), since the reviews themselves need no declaration.
|
|
147
|
+
|
|
148
|
+
## Inline tags
|
|
149
|
+
|
|
150
|
+
Source files and documents can also carry the declaration in a comment within their first 30 lines:
|
|
151
|
+
|
|
152
|
+
```julia
|
|
153
|
+
# AI-Generation: G2
|
|
154
|
+
# Human-Review: H2
|
|
155
|
+
# AI-Review: A2
|
|
156
|
+
# Assisted-by: Claude Opus 5.5 (Anthropic)
|
|
157
|
+
# AI-Reviewed-by: Claude Opus 5.5 (Anthropic), fresh review session
|
|
158
|
+
# Reviewed-by: Jane Doe
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
Three rules keep documentation from being mistaken for a declaration:
|
|
162
|
+
|
|
163
|
+
- **The file type's own comment syntax:**
|
|
164
|
+
- `#` in Julia, Python or YAML;
|
|
165
|
+
- `//` or `/* */` in C-like languages;
|
|
166
|
+
- `%` in LaTeX;
|
|
167
|
+
- `<!-- -->` in Markdown and HTML.
|
|
168
|
+
|
|
169
|
+
Fenced code blocks in Markdown are skipped. File types without a known comment syntax, such as `.txt`, `.csv`, `.json`, notebooks or `LICENSE`, are only declared through `SIGNOFF.toml`.
|
|
170
|
+
- **Start of the line:** the comment must start the line. Only `*` continuation lines of a `/* */` comment may be indented, so indented examples in docstrings are ignored.
|
|
171
|
+
- **A contiguous block with a valid level:** tag lines count only as a block of adjacent lines that contains a valid level line, such as `AI-Generation: G2` or `AI-Review: A0`. A comment that merely explains the keys, like `# AI-Generation: G0-G3`, is ignored. `TODO` (a placeholder, e.g. left by `aisignoff migrate` or an agent) also counts as a level here, so a block with an unfilled TODO is found and fails `lint`.
|
|
172
|
+
|
|
173
|
+
`aisignoff annotate FILE … --inline` writes or updates these lines for you:
|
|
174
|
+
- **Placement:** after a shebang, an encoding line, an XML declaration or (in Markdown) YAML front matter.
|
|
175
|
+
- **The rest of the file stays untouched,** including line endings and a byte-order mark.
|
|
176
|
+
- **Safety:** it refuses if the block wouldn't fit into the first 30 lines. It reads the result back and restores the file if the tags couldn't be read.
|
|
177
|
+
|
|
178
|
+
**Precedence:** a file's inline tags win over `SIGNOFF.toml` ("closest"), unless the matching annotation sets `precedence = "override"`. This works per axis:
|
|
179
|
+
- **The axes** are generation (`AI-Generation` with `Assisted-by`), human review (`Human-Review` with `Reviewed-by`) and AI review (`AI-Review` with `AI-Reviewed-by`).
|
|
180
|
+
- **An axis counts as declared inline** when its level line is present. Any axis the inline block leaves out comes from the matching annotation.
|
|
181
|
+
- **A name line on its own,** such as `Assisted-by` without `AI-Generation`, is an error.
|
|
182
|
+
|
|
183
|
+
## Commands
|
|
184
|
+
|
|
185
|
+
| Command | What it does |
|
|
186
|
+
|---|---|
|
|
187
|
+
| `aisignoff init [-g G -H H -a A …]` | Create `SIGNOFF.toml`, optionally with a default for all files |
|
|
188
|
+
| `aisignoff annotate PATHS -g G -H H -a A [--assisted-by …] [--ai-reviewed-by …] --reviewed-by …` | Append an annotation (`--inline`: tag the files themselves) |
|
|
189
|
+
| `aisignoff lint [--strict]` | Fail if any file lacks a valid declaration, falls below the policy, or if no files were found (`--strict`: also on annotations that match nothing) |
|
|
190
|
+
| `aisignoff report [--files] [--json]` | Summary per axis, one line per file (with the problems), or JSON; invalid declarations count as "undeclared", as in the badges |
|
|
191
|
+
| `aisignoff badge [--out DIR] [--weight files\|lines]` | Write shields.io badges `aisignoff-generation.json`, `aisignoff-human-review.json` and `aisignoff-ai-review.json` |
|
|
192
|
+
| `aisignoff migrate [--dry-run] [--from 2]` | Convert declarations of earlier versions, see below |
|
|
193
|
+
|
|
194
|
+
Use `--root DIR` before the command to run on another directory, e.g. `aisignoff --root ../paper lint`.
|
|
195
|
+
|
|
196
|
+
Exit codes: `0` success, `1` problems found by `lint`, `2` usage or configuration errors.
|
|
197
|
+
|
|
198
|
+
## Migrating from earlier versions
|
|
199
|
+
|
|
200
|
+
**From aidecl** (up to version 0.3.0). The tool, its command and its file were renamed: `aidecl` is now `aisignoff`, and `AIDECL.toml` is now `SIGNOFF.toml`. The keys, levels and inline tags stay the same.
|
|
201
|
+
- **Reinstall:** `pipx uninstall aidecl && pipx install aisignoff` (or the same with pip).
|
|
202
|
+
- **Rename the file:** `aisignoff migrate` renames `AIDECL.toml` to `SIGNOFF.toml` and keeps its content. If the file still uses version 1 or 2, it's converted in the same step, as described below. Commit the rename (`git add -A SIGNOFF.toml AIDECL.toml`).
|
|
203
|
+
- **Until then,** aisignoff still reads `AIDECL.toml` and prints a warning. Having both files is an error.
|
|
204
|
+
- **Badges** are now called `aisignoff-generation.json`, `aisignoff-human-review.json` and `aisignoff-ai-review.json`; update the URLs in your README (see "In CI" below).
|
|
205
|
+
|
|
206
|
+
**Between versions of the file format,** `aisignoff migrate` converts `SIGNOFF.toml` and the inline tags to version 3, all or nothing. Without `SIGNOFF.toml`, inline tags with the old AI review scale need `--from 2`; `migrate` points this out when it finds `AI-Review: A-`. `--from 2` then creates a version-3 `SIGNOFF.toml` without annotations, which records that the tags are converted, so they're never shifted twice. Once `SIGNOFF.toml` is version 3, `--from 2` is refused.
|
|
207
|
+
|
|
208
|
+
**From version 2** (AI review A-, A0–A3). The scale moved up by one, so only `AI-Review` (and `ai` in `[policy]`) changes. Comments and layout of `SIGNOFF.toml` are kept.
|
|
209
|
+
|
|
210
|
+
| Old | New |
|
|
211
|
+
|---|---|
|
|
212
|
+
| A- (none) | A0 |
|
|
213
|
+
| A0 (self-check) | A1; for G0 a TODO, since human-written content has no self-check |
|
|
214
|
+
| A1 (independent session) | A2 |
|
|
215
|
+
| A2 (different model) | A2, with a note: raise it to A3 if the review was extensive or benchmarked |
|
|
216
|
+
| A3 (different model with benchmark) | A3 |
|
|
217
|
+
|
|
218
|
+
**From version 1** (one verification axis, V0–V3):
|
|
219
|
+
|
|
220
|
+
| Old | New |
|
|
221
|
+
|---|---|
|
|
222
|
+
| V0 (human, line by line) | H2, and A0 for G0 |
|
|
223
|
+
| V1 / V2 (independent agent, same / different model) | A2 |
|
|
224
|
+
| V3 (human + independent agent) | H2 + A2 |
|
|
225
|
+
| `Tested-by` | `Reviewed-by` |
|
|
226
|
+
| `Verified-by` | `AI-Reviewed-by` |
|
|
227
|
+
|
|
228
|
+
What the old levels don't say must be added by a person. Examples are the human review of a V1 file, or the AI review of an AI-written V0 file. `migrate` marks these with a `TODO` comment in `SIGNOFF.toml` and with a `TODO` value in inline tags (e.g. `# Human-Review: TODO`), and `lint` reports them until they're filled in. When converting version 1, comments in `SIGNOFF.toml` are not carried over.
|
|
229
|
+
|
|
230
|
+
## In CI
|
|
231
|
+
|
|
232
|
+
The organisation provides a ready-made workflow (*Actions → New workflow → AI file declarations*). It runs `aisignoff lint` on every push and pull request and publishes the badges to the `badges` branch:
|
|
233
|
+
|
|
234
|
+
```markdown
|
|
235
|
+

|
|
236
|
+

|
|
237
|
+

|
|
238
|
+
```
|
|
239
|
+
|
|
240
|
+
The badges turn red and show an "undeclared" share while files are missing a valid declaration.
|
|
241
|
+
|
|
242
|
+
## Development
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
pip install -e . pytest
|
|
246
|
+
pytest
|
|
247
|
+
aisignoff lint # the tool declares its own files, too (see SIGNOFF.toml)
|
|
248
|
+
```
|
|
249
|
+
|
|
250
|
+
**Releasing:** bump `version` in `pyproject.toml`, `src/aisignoff/__init__.py` and `uv.lock`, then publish a GitHub release with the tag `vX.Y.Z`. The *Publish to PyPI* workflow builds the package and uploads it through PyPI's trusted publishing, so no token is stored in the repository.
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
# aisignoff
|
|
2
|
+
|
|
3
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#g-generation)
|
|
4
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#h-human-review)
|
|
5
|
+
[](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#a-ai-review)
|
|
6
|
+
|
|
7
|
+

|
|
8
|
+

|
|
9
|
+

|
|
10
|
+
|
|
11
|
+
**Declare and check how each file was created and reviewed**, using the Industrial Mathematics Group's AI levels:
|
|
12
|
+
- **G0–G3:** how much an AI generated;
|
|
13
|
+
- **H0–H3:** how thoroughly a human reviewed it;
|
|
14
|
+
- **A0–A3:** how thoroughly an AI reviewed it.
|
|
15
|
+
|
|
16
|
+
The name is meant both ways: the AI signs off on what it generated or reviewed, and so does the human ("ai", said in German, sounds like "I"). It's a declaration you sign off on, not a cryptographic signature.
|
|
17
|
+
|
|
18
|
+
It works like [REUSE](https://reuse.software) does for licences: a path-based `SIGNOFF.toml`, optional tags in file headers, and a `lint` command for CI.
|
|
19
|
+
|
|
20
|
+
It complements the declaration in pull requests, where the levels are ticked in the PR template:
|
|
21
|
+
|
|
22
|
+
| | PR declaration | `SIGNOFF.toml` |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| Describes | each **change** merged into `main` | the **current state** of every file |
|
|
25
|
+
| Good for | code developed through pull requests | results, data, notebooks, papers, single-commit uploads |
|
|
26
|
+
| Checked by | the *AI declaration* PR check | `aisignoff lint` |
|
|
27
|
+
|
|
28
|
+
The keys of `SIGNOFF.toml` and the inline tags are the same ones the PR check shows in its job summary.
|
|
29
|
+
|
|
30
|
+
The levels are defined in the [AI policy](https://github.com/industrial-mathematics-group/.github/blob/main/.github/CONTRIBUTING.md#ai-assisted-contributions):
|
|
31
|
+
|
|
32
|
+
| Generation | | Human review | | AI review | |
|
|
33
|
+
|---|---|---|---|---|---|
|
|
34
|
+
| **G0** | Human | **H0** | Plausibility check | **A0** | None |
|
|
35
|
+
| **G1** | AI-assisted | **H1** | Tested | **A1** | Self-check by the generating session |
|
|
36
|
+
| **G2** | AI-generated | **H2** | Reviewed line by line | **A2** | Review by a new session |
|
|
37
|
+
| **G3** | AI-autonomous | **H3** | Independently validated | **A3** | Extensive review or benchmark by a different model |
|
|
38
|
+
|
|
39
|
+
For human-written content (G0) there is no generating session, so it can't have A1: any AI review of it is A2 or A3.
|
|
40
|
+
|
|
41
|
+
## Install
|
|
42
|
+
|
|
43
|
+
```bash
|
|
44
|
+
pip install aisignoff
|
|
45
|
+
# or, without touching your environment:
|
|
46
|
+
pipx install aisignoff # or: uv tool install aisignoff
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
The latest development version: `pip install "aisignoff @ git+https://github.com/industrial-mathematics-group/aisignoff"`.
|
|
50
|
+
|
|
51
|
+
Requires Python 3.11 or newer; no other dependencies.
|
|
52
|
+
|
|
53
|
+
## Quick start
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
# 1. A default for everything (here: written by a human, reviewed line by line, no AI review)
|
|
57
|
+
aisignoff init -g G0 -H H2 -a A0 --reviewed-by "Jane Doe"
|
|
58
|
+
|
|
59
|
+
# 2. Declare what is different, by file, folder or glob
|
|
60
|
+
aisignoff annotate results/ -g G2 -H H1 -a A1 \
|
|
61
|
+
--assisted-by "Claude Opus 5.5 (Anthropic)" \
|
|
62
|
+
--ai-reviewed-by "Claude Opus 5.5 (Anthropic), same session" --reviewed-by "Jane Doe"
|
|
63
|
+
aisignoff annotate "scripts/*.jl" -g G1 -H H3 -a A2 --assisted-by "GitHub Copilot" \
|
|
64
|
+
--ai-reviewed-by "Claude Opus 5.5 (Anthropic), new session" --reviewed-by "Jane Doe"
|
|
65
|
+
|
|
66
|
+
# 3. Check that every file is declared correctly
|
|
67
|
+
aisignoff lint
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
`annotate` refuses incomplete declarations, and anything below the repository's policy (e.g. G2 without `--assisted-by`, or G2 with `-a A0`). It also checks that the resulting `SIGNOFF.toml` is still valid, and leaves it unchanged if not. Paths are handled like this:
|
|
71
|
+
- **Inside the project root:** files outside `--root` are refused, also with `--inline`.
|
|
72
|
+
- **Files and folders:** existing files are matched exactly, even with `*` or `?` in their name, and a folder becomes `folder/**`.
|
|
73
|
+
- **Globs:** quote them, e.g. `"scripts/*.jl"`. Inside the project they're read relative to the current directory, as the shell would; with `--root` from outside, relative to the root. Globs with `..` or absolute paths are refused.
|
|
74
|
+
|
|
75
|
+
## SIGNOFF.toml
|
|
76
|
+
|
|
77
|
+
```toml
|
|
78
|
+
version = 3
|
|
79
|
+
exclude = ["reviews/**", "docs/build/**"] # optional: files that need no declaration
|
|
80
|
+
|
|
81
|
+
[policy] # optional: minimum levels
|
|
82
|
+
"*" = { human = "H1" }
|
|
83
|
+
G3 = { human = "H2", ai = "A2" }
|
|
84
|
+
|
|
85
|
+
[[annotations]]
|
|
86
|
+
path = "**"
|
|
87
|
+
AI-Generation = "G0"
|
|
88
|
+
Human-Review = "H2"
|
|
89
|
+
AI-Review = "A0"
|
|
90
|
+
Reviewed-by = "Jane Doe"
|
|
91
|
+
|
|
92
|
+
[[annotations]]
|
|
93
|
+
path = ["results/**", "figures/*.pdf"]
|
|
94
|
+
AI-Generation = "G2"
|
|
95
|
+
Human-Review = "H3"
|
|
96
|
+
AI-Review = "A3"
|
|
97
|
+
Assisted-by = ["Claude Opus 5.5 (Anthropic)"]
|
|
98
|
+
AI-Reviewed-by = "GPT-5 (OpenAI), full benchmark against reference solutions"
|
|
99
|
+
Reviewed-by = "Jane Doe"
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
- **`path`**: one glob or a list, relative to the repository root.
|
|
103
|
+
- `*` stays within a folder, `**` crosses folders (`**/` also matches no folder), and `?` is one character.
|
|
104
|
+
- `\*`, `\?` and `\\` match those characters literally.
|
|
105
|
+
- This is modelled on `REUSE.toml`, plus `?`.
|
|
106
|
+
- **The last matching annotation wins**, so put the general default first and the exceptions after it.
|
|
107
|
+
- **Keys:** the levels and the names below. `Assisted-by`, `AI-Reviewed-by` and `Reviewed-by` take a string or a list.
|
|
108
|
+
- **Every file needs a valid declaration:**
|
|
109
|
+
|
|
110
|
+
| Key | Required for |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `AI-Generation`, `Human-Review`, `AI-Review` | every file |
|
|
113
|
+
| `Reviewed-by` (the responsible human) | every file |
|
|
114
|
+
| `Assisted-by` | G1–G3; not allowed for G0 |
|
|
115
|
+
| `AI-Reviewed-by` | A1–A3; not allowed for A0 |
|
|
116
|
+
|
|
117
|
+
- **Placeholders:** any value may be `"TODO"`, e.g. `Human-Review = "TODO"` when an agent declares its part and a person adds the human review later. `annotate` accepts it too: `-H TODO` without `--reviewed-by` also writes `Reviewed-by` as TODO, so the person fills in both together. It still checks the axes that are filled in. `lint` reports every TODO until it's filled in.
|
|
118
|
+
- **Minimum levels:** G1–G3 always need at least **A1**: AI-written content must at least be self-checked. The `[policy]` table can require more (for G0, a minimum of A1 means A2):
|
|
119
|
+
- `"*"` sets the default for every generation level;
|
|
120
|
+
- `G0`–`G3` refine it per generation level, overriding the `"*"` values key by key.
|
|
121
|
+
- **Which files are checked:**
|
|
122
|
+
- **In a git repository:** all tracked files plus untracked files that aren't ignored, so `.gitignore` is respected. If git can't be run there, aisignoff stops with an error rather than ignoring `.gitignore`.
|
|
123
|
+
- **Outside git:** every file below the root.
|
|
124
|
+
- **Always skipped:** `SIGNOFF.toml` itself (and `AIDECL.toml`, its old name) and anything under `exclude`. Exclude the folder of committed AI reviews (`reviews/` by default, see CONTRIBUTING.md), since the reviews themselves need no declaration.
|
|
125
|
+
|
|
126
|
+
## Inline tags
|
|
127
|
+
|
|
128
|
+
Source files and documents can also carry the declaration in a comment within their first 30 lines:
|
|
129
|
+
|
|
130
|
+
```julia
|
|
131
|
+
# AI-Generation: G2
|
|
132
|
+
# Human-Review: H2
|
|
133
|
+
# AI-Review: A2
|
|
134
|
+
# Assisted-by: Claude Opus 5.5 (Anthropic)
|
|
135
|
+
# AI-Reviewed-by: Claude Opus 5.5 (Anthropic), fresh review session
|
|
136
|
+
# Reviewed-by: Jane Doe
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Three rules keep documentation from being mistaken for a declaration:
|
|
140
|
+
|
|
141
|
+
- **The file type's own comment syntax:**
|
|
142
|
+
- `#` in Julia, Python or YAML;
|
|
143
|
+
- `//` or `/* */` in C-like languages;
|
|
144
|
+
- `%` in LaTeX;
|
|
145
|
+
- `<!-- -->` in Markdown and HTML.
|
|
146
|
+
|
|
147
|
+
Fenced code blocks in Markdown are skipped. File types without a known comment syntax, such as `.txt`, `.csv`, `.json`, notebooks or `LICENSE`, are only declared through `SIGNOFF.toml`.
|
|
148
|
+
- **Start of the line:** the comment must start the line. Only `*` continuation lines of a `/* */` comment may be indented, so indented examples in docstrings are ignored.
|
|
149
|
+
- **A contiguous block with a valid level:** tag lines count only as a block of adjacent lines that contains a valid level line, such as `AI-Generation: G2` or `AI-Review: A0`. A comment that merely explains the keys, like `# AI-Generation: G0-G3`, is ignored. `TODO` (a placeholder, e.g. left by `aisignoff migrate` or an agent) also counts as a level here, so a block with an unfilled TODO is found and fails `lint`.
|
|
150
|
+
|
|
151
|
+
`aisignoff annotate FILE … --inline` writes or updates these lines for you:
|
|
152
|
+
- **Placement:** after a shebang, an encoding line, an XML declaration or (in Markdown) YAML front matter.
|
|
153
|
+
- **The rest of the file stays untouched,** including line endings and a byte-order mark.
|
|
154
|
+
- **Safety:** it refuses if the block wouldn't fit into the first 30 lines. It reads the result back and restores the file if the tags couldn't be read.
|
|
155
|
+
|
|
156
|
+
**Precedence:** a file's inline tags win over `SIGNOFF.toml` ("closest"), unless the matching annotation sets `precedence = "override"`. This works per axis:
|
|
157
|
+
- **The axes** are generation (`AI-Generation` with `Assisted-by`), human review (`Human-Review` with `Reviewed-by`) and AI review (`AI-Review` with `AI-Reviewed-by`).
|
|
158
|
+
- **An axis counts as declared inline** when its level line is present. Any axis the inline block leaves out comes from the matching annotation.
|
|
159
|
+
- **A name line on its own,** such as `Assisted-by` without `AI-Generation`, is an error.
|
|
160
|
+
|
|
161
|
+
## Commands
|
|
162
|
+
|
|
163
|
+
| Command | What it does |
|
|
164
|
+
|---|---|
|
|
165
|
+
| `aisignoff init [-g G -H H -a A …]` | Create `SIGNOFF.toml`, optionally with a default for all files |
|
|
166
|
+
| `aisignoff annotate PATHS -g G -H H -a A [--assisted-by …] [--ai-reviewed-by …] --reviewed-by …` | Append an annotation (`--inline`: tag the files themselves) |
|
|
167
|
+
| `aisignoff lint [--strict]` | Fail if any file lacks a valid declaration, falls below the policy, or if no files were found (`--strict`: also on annotations that match nothing) |
|
|
168
|
+
| `aisignoff report [--files] [--json]` | Summary per axis, one line per file (with the problems), or JSON; invalid declarations count as "undeclared", as in the badges |
|
|
169
|
+
| `aisignoff badge [--out DIR] [--weight files\|lines]` | Write shields.io badges `aisignoff-generation.json`, `aisignoff-human-review.json` and `aisignoff-ai-review.json` |
|
|
170
|
+
| `aisignoff migrate [--dry-run] [--from 2]` | Convert declarations of earlier versions, see below |
|
|
171
|
+
|
|
172
|
+
Use `--root DIR` before the command to run on another directory, e.g. `aisignoff --root ../paper lint`.
|
|
173
|
+
|
|
174
|
+
Exit codes: `0` success, `1` problems found by `lint`, `2` usage or configuration errors.
|
|
175
|
+
|
|
176
|
+
## Migrating from earlier versions
|
|
177
|
+
|
|
178
|
+
**From aidecl** (up to version 0.3.0). The tool, its command and its file were renamed: `aidecl` is now `aisignoff`, and `AIDECL.toml` is now `SIGNOFF.toml`. The keys, levels and inline tags stay the same.
|
|
179
|
+
- **Reinstall:** `pipx uninstall aidecl && pipx install aisignoff` (or the same with pip).
|
|
180
|
+
- **Rename the file:** `aisignoff migrate` renames `AIDECL.toml` to `SIGNOFF.toml` and keeps its content. If the file still uses version 1 or 2, it's converted in the same step, as described below. Commit the rename (`git add -A SIGNOFF.toml AIDECL.toml`).
|
|
181
|
+
- **Until then,** aisignoff still reads `AIDECL.toml` and prints a warning. Having both files is an error.
|
|
182
|
+
- **Badges** are now called `aisignoff-generation.json`, `aisignoff-human-review.json` and `aisignoff-ai-review.json`; update the URLs in your README (see "In CI" below).
|
|
183
|
+
|
|
184
|
+
**Between versions of the file format,** `aisignoff migrate` converts `SIGNOFF.toml` and the inline tags to version 3, all or nothing. Without `SIGNOFF.toml`, inline tags with the old AI review scale need `--from 2`; `migrate` points this out when it finds `AI-Review: A-`. `--from 2` then creates a version-3 `SIGNOFF.toml` without annotations, which records that the tags are converted, so they're never shifted twice. Once `SIGNOFF.toml` is version 3, `--from 2` is refused.
|
|
185
|
+
|
|
186
|
+
**From version 2** (AI review A-, A0–A3). The scale moved up by one, so only `AI-Review` (and `ai` in `[policy]`) changes. Comments and layout of `SIGNOFF.toml` are kept.
|
|
187
|
+
|
|
188
|
+
| Old | New |
|
|
189
|
+
|---|---|
|
|
190
|
+
| A- (none) | A0 |
|
|
191
|
+
| A0 (self-check) | A1; for G0 a TODO, since human-written content has no self-check |
|
|
192
|
+
| A1 (independent session) | A2 |
|
|
193
|
+
| A2 (different model) | A2, with a note: raise it to A3 if the review was extensive or benchmarked |
|
|
194
|
+
| A3 (different model with benchmark) | A3 |
|
|
195
|
+
|
|
196
|
+
**From version 1** (one verification axis, V0–V3):
|
|
197
|
+
|
|
198
|
+
| Old | New |
|
|
199
|
+
|---|---|
|
|
200
|
+
| V0 (human, line by line) | H2, and A0 for G0 |
|
|
201
|
+
| V1 / V2 (independent agent, same / different model) | A2 |
|
|
202
|
+
| V3 (human + independent agent) | H2 + A2 |
|
|
203
|
+
| `Tested-by` | `Reviewed-by` |
|
|
204
|
+
| `Verified-by` | `AI-Reviewed-by` |
|
|
205
|
+
|
|
206
|
+
What the old levels don't say must be added by a person. Examples are the human review of a V1 file, or the AI review of an AI-written V0 file. `migrate` marks these with a `TODO` comment in `SIGNOFF.toml` and with a `TODO` value in inline tags (e.g. `# Human-Review: TODO`), and `lint` reports them until they're filled in. When converting version 1, comments in `SIGNOFF.toml` are not carried over.
|
|
207
|
+
|
|
208
|
+
## In CI
|
|
209
|
+
|
|
210
|
+
The organisation provides a ready-made workflow (*Actions → New workflow → AI file declarations*). It runs `aisignoff lint` on every push and pull request and publishes the badges to the `badges` branch:
|
|
211
|
+
|
|
212
|
+
```markdown
|
|
213
|
+

|
|
214
|
+

|
|
215
|
+

|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
The badges turn red and show an "undeclared" share while files are missing a valid declaration.
|
|
219
|
+
|
|
220
|
+
## Development
|
|
221
|
+
|
|
222
|
+
```bash
|
|
223
|
+
pip install -e . pytest
|
|
224
|
+
pytest
|
|
225
|
+
aisignoff lint # the tool declares its own files, too (see SIGNOFF.toml)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Releasing:** bump `version` in `pyproject.toml`, `src/aisignoff/__init__.py` and `uv.lock`, then publish a GitHub release with the tag `vX.Y.Z`. The *Publish to PyPI* workflow builds the package and uploads it through PyPI's trusted publishing, so no token is stored in the repository.
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=77"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "aisignoff"
|
|
7
|
+
version = "0.4.0"
|
|
8
|
+
description = "Sign off on AI use per file: declare and check AI generation (G0-G3), human review (H0-H3) and AI review (A0-A3), REUSE-style."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
license-files = ["LICENSE"]
|
|
13
|
+
authors = [{ name = "Industrial Mathematics Group" }]
|
|
14
|
+
keywords = ["ai", "declaration", "provenance", "code review", "research software", "reuse"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Development Status :: 4 - Beta",
|
|
17
|
+
"Environment :: Console",
|
|
18
|
+
"Intended Audience :: Developers",
|
|
19
|
+
"Intended Audience :: Science/Research",
|
|
20
|
+
"Operating System :: OS Independent",
|
|
21
|
+
"Programming Language :: Python :: 3",
|
|
22
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
23
|
+
"Topic :: Software Development :: Quality Assurance",
|
|
24
|
+
]
|
|
25
|
+
dependencies = []
|
|
26
|
+
|
|
27
|
+
[project.scripts]
|
|
28
|
+
aisignoff = "aisignoff.cli:main"
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://github.com/industrial-mathematics-group/aisignoff"
|
|
32
|
+
Issues = "https://github.com/industrial-mathematics-group/aisignoff/issues"
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.packages.find]
|
|
35
|
+
where = ["src"]
|
|
36
|
+
|
|
37
|
+
[tool.pytest.ini_options]
|
|
38
|
+
testpaths = ["tests"]
|