runboth 0.1.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.
- runboth-0.1.0/LICENSE.md +93 -0
- runboth-0.1.0/PKG-INFO +163 -0
- runboth-0.1.0/README.md +146 -0
- runboth-0.1.0/pyproject.toml +42 -0
- runboth-0.1.0/runboth/__init__.py +30 -0
- runboth-0.1.0/runboth/adjudicate.py +841 -0
- runboth-0.1.0/runboth/agent_hook.py +180 -0
- runboth-0.1.0/runboth/blast.py +241 -0
- runboth-0.1.0/runboth/cli.py +337 -0
- runboth-0.1.0/runboth/control_constructors.py +49 -0
- runboth-0.1.0/runboth/control_dotted_imports.py +43 -0
- runboth-0.1.0/runboth/control_overblock.py +69 -0
- runboth-0.1.0/runboth/determinism.py +302 -0
- runboth-0.1.0/runboth/engine.py +593 -0
- runboth-0.1.0/runboth/fixtures.py +326 -0
- runboth-0.1.0/runboth/hook.py +153 -0
- runboth-0.1.0/runboth/install_agent.py +97 -0
- runboth-0.1.0/runboth/install_hook.py +217 -0
- runboth-0.1.0/runboth/interagent.py +229 -0
- runboth-0.1.0/runboth/isolate.py +181 -0
- runboth-0.1.0/runboth/mcp_server.py +181 -0
- runboth-0.1.0/runboth/mergecheck.py +224 -0
- runboth-0.1.0/runboth/methods.py +445 -0
- runboth-0.1.0/runboth/pr_report.py +262 -0
- runboth-0.1.0/runboth/precommit.py +591 -0
- runboth-0.1.0/runboth/sandbox.py +1246 -0
- runboth-0.1.0/runboth/versions.py +220 -0
- runboth-0.1.0/runboth.egg-info/PKG-INFO +163 -0
- runboth-0.1.0/runboth.egg-info/SOURCES.txt +38 -0
- runboth-0.1.0/runboth.egg-info/dependency_links.txt +1 -0
- runboth-0.1.0/runboth.egg-info/entry_points.txt +2 -0
- runboth-0.1.0/runboth.egg-info/top_level.txt +1 -0
- runboth-0.1.0/setup.cfg +4 -0
- runboth-0.1.0/tests/test_change_coverage.py +103 -0
- runboth-0.1.0/tests/test_controls.py +91 -0
- runboth-0.1.0/tests/test_gate_end_to_end.py +178 -0
- runboth-0.1.0/tests/test_measurement_artifacts.py +205 -0
- runboth-0.1.0/tests/test_report_shaping.py +136 -0
- runboth-0.1.0/tests/test_versions.py +103 -0
- runboth-0.1.0/tests/test_witness_quality.py +113 -0
runboth-0.1.0/LICENSE.md
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Functional Source License, Version 1.1, Apache 2.0 Future License
|
|
2
|
+
|
|
3
|
+
## Abbreviation
|
|
4
|
+
|
|
5
|
+
FSL-1.1-Apache-2.0
|
|
6
|
+
|
|
7
|
+
## Notice
|
|
8
|
+
|
|
9
|
+
Copyright 2026 Kyle Clouthier, Clouthier Simulation Labs
|
|
10
|
+
|
|
11
|
+
## Terms and Conditions
|
|
12
|
+
|
|
13
|
+
### Licensor ("We")
|
|
14
|
+
|
|
15
|
+
The party offering the Software under these Terms and Conditions.
|
|
16
|
+
|
|
17
|
+
### The Software
|
|
18
|
+
|
|
19
|
+
The "Software" is each version of the software that we make available under these Terms and
|
|
20
|
+
Conditions, as indicated by our inclusion of these Terms and Conditions with the Software.
|
|
21
|
+
|
|
22
|
+
### License Grant
|
|
23
|
+
|
|
24
|
+
Subject to your compliance with this License Grant and the Patents, Redistribution and Trademark
|
|
25
|
+
clauses below, we hereby grant you the right to use, copy, modify, create derivative works,
|
|
26
|
+
publicly perform, publicly display and redistribute the Software for any Permitted Purpose
|
|
27
|
+
identified below.
|
|
28
|
+
|
|
29
|
+
### Permitted Purpose
|
|
30
|
+
|
|
31
|
+
A Permitted Purpose is any purpose other than a Competing Use. A Competing Use means making the
|
|
32
|
+
Software available to others in a commercial product or service that:
|
|
33
|
+
|
|
34
|
+
1. substitutes for the Software;
|
|
35
|
+
2. substitutes for any other product or service we offer using the Software that exists as of the
|
|
36
|
+
date we make the Software available; or
|
|
37
|
+
3. offers the same or substantially similar functionality as the Software.
|
|
38
|
+
|
|
39
|
+
Permitted Purposes specifically include using the Software:
|
|
40
|
+
|
|
41
|
+
1. for your internal use and access;
|
|
42
|
+
2. for non-commercial education;
|
|
43
|
+
3. for non-commercial research; and
|
|
44
|
+
4. in connection with professional services that you provide to a licensee using the Software in
|
|
45
|
+
accordance with these Terms and Conditions.
|
|
46
|
+
|
|
47
|
+
### Patents
|
|
48
|
+
|
|
49
|
+
To the extent your use for a Permitted Purpose would necessarily infringe our patents, the license
|
|
50
|
+
grant above includes a license under our patents. If you make a claim against any party that the
|
|
51
|
+
Software infringes or contributes to the infringement of any patent, then your patent license to
|
|
52
|
+
the Software ends immediately.
|
|
53
|
+
|
|
54
|
+
### Redistribution
|
|
55
|
+
|
|
56
|
+
The Terms and Conditions apply to all copies, modifications and derivatives of the Software.
|
|
57
|
+
|
|
58
|
+
If you redistribute any copies, modifications or derivatives of the Software, you must include a
|
|
59
|
+
copy of or a link to these Terms and Conditions and not remove any copyright notices provided in
|
|
60
|
+
or with the Software.
|
|
61
|
+
|
|
62
|
+
### Disclaimer
|
|
63
|
+
|
|
64
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT
|
|
65
|
+
NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
|
66
|
+
NONINFRINGEMENT.
|
|
67
|
+
|
|
68
|
+
IN NO EVENT WILL WE HAVE ANY LIABILITY TO YOU ARISING OUT OF OR RELATED TO THE SOFTWARE,
|
|
69
|
+
INCLUDING INDIRECT, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES, EVEN IF WE HAVE BEEN INFORMED OF
|
|
70
|
+
THEIR POSSIBILITY IN ADVANCE.
|
|
71
|
+
|
|
72
|
+
### Trademarks
|
|
73
|
+
|
|
74
|
+
Except for displaying the License Details and identifying us as the origin of the Software, you
|
|
75
|
+
have no right under these Terms and Conditions to use our trademarks, trade names, service marks
|
|
76
|
+
or product names.
|
|
77
|
+
|
|
78
|
+
## Grant of Future License
|
|
79
|
+
|
|
80
|
+
We hereby irrevocably grant you an additional license to use the Software under the Apache License,
|
|
81
|
+
Version 2.0 that is effective on the second anniversary of the date we make the Software available.
|
|
82
|
+
On or after that date, you may use the Software under the Apache License, Version 2.0, in which
|
|
83
|
+
case the following will apply:
|
|
84
|
+
|
|
85
|
+
Licensed under the Apache License, Version 2.0 (the "License"); you may not use this Software
|
|
86
|
+
except in compliance with the License.
|
|
87
|
+
|
|
88
|
+
You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0
|
|
89
|
+
|
|
90
|
+
Unless required by applicable law or agreed to in writing, software distributed under the License
|
|
91
|
+
is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express
|
|
92
|
+
or implied. See the License for the specific language governing permissions and limitations under
|
|
93
|
+
the License.
|
runboth-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: runboth
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: An AI changed your code. RunBoth runs both versions and tells you what actually behaves differently, including the functions nobody touched.
|
|
5
|
+
Author-email: Kyle Clouthier <kyle@simgen.dev>
|
|
6
|
+
License: FSL-1.1-Apache-2.0
|
|
7
|
+
Project-URL: Homepage, https://runboth.dev
|
|
8
|
+
Keywords: differential testing,behaviour,refactoring,AI agents,code review
|
|
9
|
+
Classifier: Development Status :: 3 - Alpha
|
|
10
|
+
Classifier: Intended Audience :: Developers
|
|
11
|
+
Classifier: Programming Language :: Python :: 3
|
|
12
|
+
Classifier: Topic :: Software Development :: Testing
|
|
13
|
+
Requires-Python: >=3.10
|
|
14
|
+
Description-Content-Type: text/markdown
|
|
15
|
+
License-File: LICENSE.md
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
<p align="center">
|
|
19
|
+
<img src="brand/mark-128.png" width="72" alt="">
|
|
20
|
+
</p>
|
|
21
|
+
|
|
22
|
+
<h1 align="center">RunBoth</h1>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<b>An AI changed your code. RunBoth runs both versions and tells you what actually behaves
|
|
26
|
+
differently, including the functions nobody touched.</b>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<a href="https://runboth.dev">runboth.dev</a> ·
|
|
31
|
+
<a href="HOW_IT_WORKS.md">How it works</a> ·
|
|
32
|
+
<a href="RED_TEAM_2026-09-12.md">Red team results</a>
|
|
33
|
+
</p>
|
|
34
|
+
|
|
35
|
+
---
|
|
36
|
+
|
|
37
|
+
```
|
|
38
|
+
$ git commit -m "refactor: tidy up the rates module"
|
|
39
|
+
|
|
40
|
+
BLOCKED: the behaviour changed and your message does not say so.
|
|
41
|
+
|
|
42
|
+
rate(100)
|
|
43
|
+
used to: return 0.1
|
|
44
|
+
now: return 0.0
|
|
45
|
+
|
|
46
|
+
AND 1 function you did NOT touch now behaves differently,
|
|
47
|
+
because it calls what you changed:
|
|
48
|
+
|
|
49
|
+
total(2.5, 100) pkg/invoice.py
|
|
50
|
+
used to: return 225.0
|
|
51
|
+
now: return 250.0
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## What it does
|
|
55
|
+
|
|
56
|
+
It checks out both versions of your code, generates inputs for every changed function from its
|
|
57
|
+
signature and from the constants mined out of its own bytecode, runs both versions in separate
|
|
58
|
+
sandboxed subprocesses, and compares seven observation channels. When they disagree it hands you
|
|
59
|
+
the exact input that separates them.
|
|
60
|
+
|
|
61
|
+
No test suite required. No network calls. No AI model. No dependencies.
|
|
62
|
+
|
|
63
|
+
## Install
|
|
64
|
+
|
|
65
|
+
```bash
|
|
66
|
+
pip install runboth # once the first release is on PyPI
|
|
67
|
+
pip install git+https://github.com/runboth/runboth # works today
|
|
68
|
+
runboth install-hook # a commit-msg gate, silent unless behaviour moved
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
As a GitHub Action, running on your own runners:
|
|
72
|
+
|
|
73
|
+
```yaml
|
|
74
|
+
- uses: runboth/runboth@v0.1.0
|
|
75
|
+
with:
|
|
76
|
+
budget: 60
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
## Three verdicts, never two
|
|
80
|
+
|
|
81
|
+
| verdict | meaning |
|
|
82
|
+
|---|---|
|
|
83
|
+
| `changed` | with a witness: the arguments, the old result, the new result |
|
|
84
|
+
| `no_change at budget N` | N generated inputs found no difference across seven channels |
|
|
85
|
+
| `abstained` | it could not be checked, and here is the reason |
|
|
86
|
+
|
|
87
|
+
"Cannot tell" and "no difference" are different claims, and collapsing them into a green check is
|
|
88
|
+
how tools end up lying. **RunBoth never says safe.**
|
|
89
|
+
|
|
90
|
+
## The seven channels
|
|
91
|
+
|
|
92
|
+
Return value · exception raised · warnings · stdout · stderr · argument mutation · object state.
|
|
93
|
+
|
|
94
|
+
A narrow definition of behaviour does not under-report, it lies, because whatever sits outside the
|
|
95
|
+
definition comes back as `no_change`.
|
|
96
|
+
|
|
97
|
+
## What it is not
|
|
98
|
+
|
|
99
|
+
**Not a model checker.** Kani and CBMC translate code into logic, let inputs be unconstrained
|
|
100
|
+
symbols, and ask a solver whether a bad state is reachable within a bound. They return a proof.
|
|
101
|
+
RunBoth executes real code on concrete values. It finds differences and reproduces them; it
|
|
102
|
+
cannot prove absence, and never claims to.
|
|
103
|
+
|
|
104
|
+
**Not mutation testing.** Mutation testing damages your code to score your test suite. RunBoth
|
|
105
|
+
damages nothing; both versions come from your git history, and no test suite is needed.
|
|
106
|
+
|
|
107
|
+
## Measured
|
|
108
|
+
|
|
109
|
+
Red-teamed against eight public repositories it had never been tuned on, with an automated oracle
|
|
110
|
+
built to catch the tool lying. **2,548 functions, zero false positives.** Full method and numbers
|
|
111
|
+
in [RED_TEAM_2026-09-12.md](RED_TEAM_2026-09-12.md).
|
|
112
|
+
|
|
113
|
+
| repo | layout | functions | abstained | median/commit |
|
|
114
|
+
|---|---|---|---|---|
|
|
115
|
+
| boltons | flat | 268 | 0.0% | 7.9s |
|
|
116
|
+
| sqlparse | flat | 230 | 0.9% | 14.3s |
|
|
117
|
+
| arrow | flat | 283 | 1.4% | 118s |
|
|
118
|
+
| cachetools | src/ | 325 | 1.8% | 60.9s |
|
|
119
|
+
| more-itertools | flat | 843 | 4.4% | 102.5s |
|
|
120
|
+
| packaging | src/ | 78 | 5.1% | 0.2s |
|
|
121
|
+
| pluggy | src/ | 157 | 6.4% | 33.5s |
|
|
122
|
+
| tenacity | async | 364 | 14.3% | 192.8s |
|
|
123
|
+
|
|
124
|
+
An adversarial corpus of 22 functions written specifically to induce false positives (object
|
|
125
|
+
addresses in default `repr`, `datetime.now`, unseeded `random`, `uuid4`, `os.getpid`, set
|
|
126
|
+
iteration order, mutable defaults, generators, `__file__` paths) produced none.
|
|
127
|
+
|
|
128
|
+
## Environment variables
|
|
129
|
+
|
|
130
|
+
| variable | effect |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `RUNBOTH_SKIP=1` | let a commit through without checking it |
|
|
133
|
+
| `RUNBOTH_BUDGET` | generated inputs per function (gate default 80) |
|
|
134
|
+
| `RUNBOTH_WORKERS` | parallel adjudications, default 4 |
|
|
135
|
+
| `RUNBOTH_ALL_PATHS=1` | also check tests, benchmarks, docs and task runners |
|
|
136
|
+
| `RUNBOTH_ENGINE` | engine directory, if the hook cannot resolve it |
|
|
137
|
+
|
|
138
|
+
`git commit --no-verify` also bypasses the gate, and the gate says so itself when it blocks.
|
|
139
|
+
|
|
140
|
+
## Honest limits
|
|
141
|
+
|
|
142
|
+
- Function-level checking is **Python only**. Changed files in other languages are named
|
|
143
|
+
explicitly rather than passed over quietly.
|
|
144
|
+
- Sampling finds differences; it cannot prove their absence.
|
|
145
|
+
- Nondeterministic, too-slow, or unconstructible functions abstain **with a reason**, and are
|
|
146
|
+
never counted as passing.
|
|
147
|
+
- The sandbox contains accidents: resource limits, network blocked, filesystem writes blocked. It
|
|
148
|
+
is **not** a security boundary against hostile code, and no pure-Python sandbox is.
|
|
149
|
+
|
|
150
|
+
## Development
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
pip install -e .
|
|
154
|
+
pytest tests/ -q
|
|
155
|
+
runboth selftest # the control suites, half of which must fail
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
## Licence
|
|
159
|
+
|
|
160
|
+
[FSL-1.1-Apache-2.0](LICENSE.md). Free for every use except building a competing product, and it
|
|
161
|
+
converts to plain Apache 2.0 two years after each release.
|
|
162
|
+
|
|
163
|
+
Built by Kyle Clouthier at Clouthier Simulation Labs.
|
runboth-0.1.0/README.md
ADDED
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="brand/mark-128.png" width="72" alt="">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">RunBoth</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center">
|
|
8
|
+
<b>An AI changed your code. RunBoth runs both versions and tells you what actually behaves
|
|
9
|
+
differently, including the functions nobody touched.</b>
|
|
10
|
+
</p>
|
|
11
|
+
|
|
12
|
+
<p align="center">
|
|
13
|
+
<a href="https://runboth.dev">runboth.dev</a> ·
|
|
14
|
+
<a href="HOW_IT_WORKS.md">How it works</a> ·
|
|
15
|
+
<a href="RED_TEAM_2026-09-12.md">Red team results</a>
|
|
16
|
+
</p>
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
$ git commit -m "refactor: tidy up the rates module"
|
|
22
|
+
|
|
23
|
+
BLOCKED: the behaviour changed and your message does not say so.
|
|
24
|
+
|
|
25
|
+
rate(100)
|
|
26
|
+
used to: return 0.1
|
|
27
|
+
now: return 0.0
|
|
28
|
+
|
|
29
|
+
AND 1 function you did NOT touch now behaves differently,
|
|
30
|
+
because it calls what you changed:
|
|
31
|
+
|
|
32
|
+
total(2.5, 100) pkg/invoice.py
|
|
33
|
+
used to: return 225.0
|
|
34
|
+
now: return 250.0
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## What it does
|
|
38
|
+
|
|
39
|
+
It checks out both versions of your code, generates inputs for every changed function from its
|
|
40
|
+
signature and from the constants mined out of its own bytecode, runs both versions in separate
|
|
41
|
+
sandboxed subprocesses, and compares seven observation channels. When they disagree it hands you
|
|
42
|
+
the exact input that separates them.
|
|
43
|
+
|
|
44
|
+
No test suite required. No network calls. No AI model. No dependencies.
|
|
45
|
+
|
|
46
|
+
## Install
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
pip install runboth # once the first release is on PyPI
|
|
50
|
+
pip install git+https://github.com/runboth/runboth # works today
|
|
51
|
+
runboth install-hook # a commit-msg gate, silent unless behaviour moved
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
As a GitHub Action, running on your own runners:
|
|
55
|
+
|
|
56
|
+
```yaml
|
|
57
|
+
- uses: runboth/runboth@v0.1.0
|
|
58
|
+
with:
|
|
59
|
+
budget: 60
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Three verdicts, never two
|
|
63
|
+
|
|
64
|
+
| verdict | meaning |
|
|
65
|
+
|---|---|
|
|
66
|
+
| `changed` | with a witness: the arguments, the old result, the new result |
|
|
67
|
+
| `no_change at budget N` | N generated inputs found no difference across seven channels |
|
|
68
|
+
| `abstained` | it could not be checked, and here is the reason |
|
|
69
|
+
|
|
70
|
+
"Cannot tell" and "no difference" are different claims, and collapsing them into a green check is
|
|
71
|
+
how tools end up lying. **RunBoth never says safe.**
|
|
72
|
+
|
|
73
|
+
## The seven channels
|
|
74
|
+
|
|
75
|
+
Return value · exception raised · warnings · stdout · stderr · argument mutation · object state.
|
|
76
|
+
|
|
77
|
+
A narrow definition of behaviour does not under-report, it lies, because whatever sits outside the
|
|
78
|
+
definition comes back as `no_change`.
|
|
79
|
+
|
|
80
|
+
## What it is not
|
|
81
|
+
|
|
82
|
+
**Not a model checker.** Kani and CBMC translate code into logic, let inputs be unconstrained
|
|
83
|
+
symbols, and ask a solver whether a bad state is reachable within a bound. They return a proof.
|
|
84
|
+
RunBoth executes real code on concrete values. It finds differences and reproduces them; it
|
|
85
|
+
cannot prove absence, and never claims to.
|
|
86
|
+
|
|
87
|
+
**Not mutation testing.** Mutation testing damages your code to score your test suite. RunBoth
|
|
88
|
+
damages nothing; both versions come from your git history, and no test suite is needed.
|
|
89
|
+
|
|
90
|
+
## Measured
|
|
91
|
+
|
|
92
|
+
Red-teamed against eight public repositories it had never been tuned on, with an automated oracle
|
|
93
|
+
built to catch the tool lying. **2,548 functions, zero false positives.** Full method and numbers
|
|
94
|
+
in [RED_TEAM_2026-09-12.md](RED_TEAM_2026-09-12.md).
|
|
95
|
+
|
|
96
|
+
| repo | layout | functions | abstained | median/commit |
|
|
97
|
+
|---|---|---|---|---|
|
|
98
|
+
| boltons | flat | 268 | 0.0% | 7.9s |
|
|
99
|
+
| sqlparse | flat | 230 | 0.9% | 14.3s |
|
|
100
|
+
| arrow | flat | 283 | 1.4% | 118s |
|
|
101
|
+
| cachetools | src/ | 325 | 1.8% | 60.9s |
|
|
102
|
+
| more-itertools | flat | 843 | 4.4% | 102.5s |
|
|
103
|
+
| packaging | src/ | 78 | 5.1% | 0.2s |
|
|
104
|
+
| pluggy | src/ | 157 | 6.4% | 33.5s |
|
|
105
|
+
| tenacity | async | 364 | 14.3% | 192.8s |
|
|
106
|
+
|
|
107
|
+
An adversarial corpus of 22 functions written specifically to induce false positives (object
|
|
108
|
+
addresses in default `repr`, `datetime.now`, unseeded `random`, `uuid4`, `os.getpid`, set
|
|
109
|
+
iteration order, mutable defaults, generators, `__file__` paths) produced none.
|
|
110
|
+
|
|
111
|
+
## Environment variables
|
|
112
|
+
|
|
113
|
+
| variable | effect |
|
|
114
|
+
|---|---|
|
|
115
|
+
| `RUNBOTH_SKIP=1` | let a commit through without checking it |
|
|
116
|
+
| `RUNBOTH_BUDGET` | generated inputs per function (gate default 80) |
|
|
117
|
+
| `RUNBOTH_WORKERS` | parallel adjudications, default 4 |
|
|
118
|
+
| `RUNBOTH_ALL_PATHS=1` | also check tests, benchmarks, docs and task runners |
|
|
119
|
+
| `RUNBOTH_ENGINE` | engine directory, if the hook cannot resolve it |
|
|
120
|
+
|
|
121
|
+
`git commit --no-verify` also bypasses the gate, and the gate says so itself when it blocks.
|
|
122
|
+
|
|
123
|
+
## Honest limits
|
|
124
|
+
|
|
125
|
+
- Function-level checking is **Python only**. Changed files in other languages are named
|
|
126
|
+
explicitly rather than passed over quietly.
|
|
127
|
+
- Sampling finds differences; it cannot prove their absence.
|
|
128
|
+
- Nondeterministic, too-slow, or unconstructible functions abstain **with a reason**, and are
|
|
129
|
+
never counted as passing.
|
|
130
|
+
- The sandbox contains accidents: resource limits, network blocked, filesystem writes blocked. It
|
|
131
|
+
is **not** a security boundary against hostile code, and no pure-Python sandbox is.
|
|
132
|
+
|
|
133
|
+
## Development
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
pip install -e .
|
|
137
|
+
pytest tests/ -q
|
|
138
|
+
runboth selftest # the control suites, half of which must fail
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
## Licence
|
|
142
|
+
|
|
143
|
+
[FSL-1.1-Apache-2.0](LICENSE.md). Free for every use except building a competing product, and it
|
|
144
|
+
converts to plain Apache 2.0 two years after each release.
|
|
145
|
+
|
|
146
|
+
Built by Kyle Clouthier at Clouthier Simulation Labs.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=68"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "runboth"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "An AI changed your code. RunBoth runs both versions and tells you what actually behaves differently, including the functions nobody touched."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.10"
|
|
11
|
+
# FSL-1.1-Apache-2.0: source-available, free for every use except building a competing
|
|
12
|
+
# product, and it converts to plain Apache 2.0 automatically two years after each release.
|
|
13
|
+
# Chosen 2026-09-12 over the MIT OR Apache-2.0 that was declared here before, because nothing
|
|
14
|
+
# is published yet so the change costs nothing, and because a permissive licence on a product
|
|
15
|
+
# whose whole value is one narrow mechanism invites exactly one outcome. Reversible: relicense
|
|
16
|
+
# is the author's to make right up until the first public release.
|
|
17
|
+
license = { text = "FSL-1.1-Apache-2.0" }
|
|
18
|
+
authors = [{ name = "Kyle Clouthier", email = "kyle@simgen.dev" }]
|
|
19
|
+
keywords = ["differential testing", "behaviour", "refactoring", "AI agents", "code review"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 3 - Alpha",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"Programming Language :: Python :: 3",
|
|
24
|
+
"Topic :: Software Development :: Testing",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.urls]
|
|
28
|
+
Homepage = "https://runboth.dev"
|
|
29
|
+
|
|
30
|
+
[project.scripts]
|
|
31
|
+
runboth = "runboth.cli:main"
|
|
32
|
+
|
|
33
|
+
# The engine is a flat directory of modules that import each other by bare name
|
|
34
|
+
# (`import sandbox`), and every entry point already puts its own directory on sys.path.
|
|
35
|
+
# Installing that directory AS the `runboth` package, with an __init__ that does the same
|
|
36
|
+
# sys.path insert, keeps those imports working unchanged whether the tree is run from a
|
|
37
|
+
# checkout or from site-packages.
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
packages = ["runboth"]
|
|
40
|
+
|
|
41
|
+
[tool.setuptools.package-dir]
|
|
42
|
+
runboth = "runboth"
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""runboth: behaviour adjudication for AI-written code.
|
|
2
|
+
|
|
3
|
+
This directory is a flat set of modules that import one another by bare name. When it is
|
|
4
|
+
installed as the `runboth` package, this file puts the package directory itself on sys.path so
|
|
5
|
+
those imports resolve to the installed copies. It is the same thing `cli.py` already does when
|
|
6
|
+
run from a checkout, made unconditional so `from runboth.cli import main` works too.
|
|
7
|
+
"""
|
|
8
|
+
import sys as _sys
|
|
9
|
+
from pathlib import Path as _Path
|
|
10
|
+
|
|
11
|
+
_here = str(_Path(__file__).resolve().parent)
|
|
12
|
+
if _here not in _sys.path:
|
|
13
|
+
_sys.path.insert(0, _here)
|
|
14
|
+
|
|
15
|
+
__all__ = ["main", "engine_dir"]
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def engine_dir():
|
|
19
|
+
"""Where the engine modules live, wherever this package was installed or checked out.
|
|
20
|
+
|
|
21
|
+
The git hook uses this to locate `precommit.py` at RUN time instead of having an absolute
|
|
22
|
+
path baked into it at INSTALL time. Moving the project used to silently break every hook
|
|
23
|
+
already installed, which is exactly the class of failure this tool exists to catch.
|
|
24
|
+
"""
|
|
25
|
+
return _here
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def main():
|
|
29
|
+
from cli import main as _main
|
|
30
|
+
return _main()
|