housebroken-cli 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.
- housebroken_cli-0.1.0/.gitignore +13 -0
- housebroken_cli-0.1.0/LICENSE +21 -0
- housebroken_cli-0.1.0/PKG-INFO +175 -0
- housebroken_cli-0.1.0/README.md +163 -0
- housebroken_cli-0.1.0/bin/housebroken +110 -0
- housebroken_cli-0.1.0/pyproject.toml +40 -0
- housebroken_cli-0.1.0/scripts/check-ai-policy.sh +109 -0
- housebroken_cli-0.1.0/scripts/comment-census.sh +83 -0
- housebroken_cli-0.1.0/scripts/distinct-outside.sh +85 -0
- housebroken_cli-0.1.0/scripts/file-pr.sh +118 -0
- housebroken_cli-0.1.0/scripts/fork-hygiene.sh +82 -0
- housebroken_cli-0.1.0/scripts/pr-sweep.sh +75 -0
- housebroken_cli-0.1.0/scripts/prior-art.sh +149 -0
- housebroken_cli-0.1.0/scripts/verify-filed-pr.sh +90 -0
- housebroken_cli-0.1.0/skills/housebroken/SKILL.md +144 -0
- housebroken_cli-0.1.0/src/housebroken/__init__.py +1 -0
- housebroken_cli-0.1.0/src/housebroken/cli.py +92 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026-present Jeff Lenamon and the housebroken contributors
|
|
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.
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: housebroken-cli
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Agent-written pull requests that do not make a mess in someone else's house.
|
|
5
|
+
Project-URL: Repository, https://github.com/lenamonj/housebroken
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
License-File: LICENSE
|
|
8
|
+
Classifier: Operating System :: OS Independent
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Requires-Python: >=3.10
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
|
|
13
|
+
<p align="center">
|
|
14
|
+
<img src="assets/door.jpg" width="560" alt="A white ceramic robot dog sits at an open, lit door, a scroll in its mouth, its paws stopped at a glowing amber line on the threshold">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<h1 align="center">housebroken</h1>
|
|
18
|
+
|
|
19
|
+
<p align="center"><em>Agent-written pull requests that do not make a mess in someone else's house.</em></p>
|
|
20
|
+
|
|
21
|
+
<p align="center">
|
|
22
|
+
<img src="https://img.shields.io/github/stars/lenamonj/housebroken?style=flat-square&color=111111&label=stars" alt="Stars">
|
|
23
|
+
<img src="https://img.shields.io/github/v/release/lenamonj/housebroken?style=flat-square&color=111111&label=release" alt="Release">
|
|
24
|
+
<img src="https://img.shields.io/pypi/v/housebroken-cli?style=flat-square&color=111111&label=pypi" alt="PyPI">
|
|
25
|
+
<img src="https://img.shields.io/badge/works%20with-Claude%20Code-111111?style=flat-square" alt="Works with Claude Code">
|
|
26
|
+
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
<p align="center">
|
|
30
|
+
<img src="https://img.shields.io/badge/gates-12-E8A23B?style=flat-square" alt="12 gates">
|
|
31
|
+
<img src="https://img.shields.io/badge/merged%20upstream-24%20PRs%20in%2021%20projects-E8A23B?style=flat-square" alt="24 merged pull requests across 21 projects">
|
|
32
|
+
<img src="https://img.shields.io/badge/closures%20turned%20into%20gates-5-E8A23B?style=flat-square" alt="5 closures turned into gates">
|
|
33
|
+
<img src="https://img.shields.io/badge/shellcheck-clean-111111?style=flat-square" alt="shellcheck clean">
|
|
34
|
+
</p>
|
|
35
|
+
|
|
36
|
+
<p align="center">
|
|
37
|
+
<strong>24 merged upstream · 21 projects · 5 closures, each one now a gate</strong><br>
|
|
38
|
+
<sub>Derived from GitHub on 7 September 2026: one account, every patch written by an agent, every filing governed by these rules as they were learned. Apple, Microsoft, Google, Apache and JetBrains are among the mergers. <a href="#numbers">The numbers</a> · <a href="#how-it-works">the twelve gates</a>.</sub>
|
|
39
|
+
</p>
|
|
40
|
+
|
|
41
|
+
You know the maintainer. She runs the project on evenings and weekends, has for nine years, and opened GitHub this morning to four pull requests from accounts created last month. Each one rewrites a function she wrote in 2019. Each carries a paragraph of comments in code that had none, a six-hundred-word body, and a footer naming the tool that wrote it. One of them re-argues a decision she closed in April. She closes all four without reading them.
|
|
42
|
+
|
|
43
|
+
She is right to.
|
|
44
|
+
|
|
45
|
+
housebroken is the set of rules that get a pull request past her, written as scripts that refuse to file until every rule is met. The rules are not opinions. Each one was paid for with a closed pull request, and the closure is named next to the rule.
|
|
46
|
+
|
|
47
|
+
## Before / after
|
|
48
|
+
|
|
49
|
+
Before: one branch, fourteen files, three unrelated fixes, a planning document and an agent config directory in the diff, a comment on every added line, a body that explains the tool, filed against a ruling already made in a closed issue, the project's own test target never run.
|
|
50
|
+
|
|
51
|
+
After:
|
|
52
|
+
|
|
53
|
+
```
|
|
54
|
+
one finding, one pull request
|
|
55
|
+
9 lines changed in the project's own style, no comment where the file has none
|
|
56
|
+
1 test that fails on their main and passes with the patch
|
|
57
|
+
body under 120 words, in their template, no footer
|
|
58
|
+
prior art read and quoted before the branch existed
|
|
59
|
+
their CI target green on a fresh clone of their main
|
|
60
|
+
the CLA known before filing, the SECURITY.md route taken when it applies
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The second one gets merged. Sometimes in twelve minutes.
|
|
64
|
+
|
|
65
|
+
## Numbers
|
|
66
|
+
|
|
67
|
+
Every rule here was learned on real repositories with real maintainers. Between late July and 7 September 2026, one account filed pull requests on projects it had never touched before, every patch written by an autonomous agent, every filing governed by these gates as they were learned. Derived from GitHub on 7 September 2026:
|
|
68
|
+
|
|
69
|
+
| | count |
|
|
70
|
+
|---|--:|
|
|
71
|
+
| pull requests filed | 102 |
|
|
72
|
+
| merged | 24, across 21 projects |
|
|
73
|
+
| open, waiting on a maintainer | 73 |
|
|
74
|
+
| closed without merging | 5 |
|
|
75
|
+
|
|
76
|
+
The merged patches include ones accepted by Apple, Microsoft, Google, Apache, JetBrains, and the URL parser that Node.js ships. The fastest merge came twelve minutes after filing. Several came the same day.
|
|
77
|
+
|
|
78
|
+
The five closures matter more than the merges. Each became a gate. Two of the five were the same class, three days apart, because the first lesson was written as prose and prose is followed when remembered. That is why the rules here are scripts that refuse, not a checklist that suggests.
|
|
79
|
+
|
|
80
|
+
## How it works
|
|
81
|
+
|
|
82
|
+
<img align="right" width="230" src="assets/treatise.jpg" alt="An obsidian book with chrome corners titled The Housebroken Agent, a treatise on manners for machines calling at the homes of maintainers">
|
|
83
|
+
|
|
84
|
+
A pull request passes through the door in order. Each step is a script or a rule, and each names the closure that put it there.
|
|
85
|
+
|
|
86
|
+
**1. Read the house rules before knocking.**
|
|
87
|
+
`check-ai-policy.sh` reads the repository's contribution policy on its development branch and in the organization's `.github` repository, and prints the sentence, not a verdict. Some projects ask contributors not to use AI for pull request text; those are never filed. Some accept pull requests only for issues they have labelled; those get an issue with the fix offered.
|
|
88
|
+
`distinct-outside.sh` counts outside contributors merged in the last 120 days. A project that has merged none is closed to outsiders whatever its README says.
|
|
89
|
+
|
|
90
|
+
**2. Is it already on the table?**
|
|
91
|
+
`prior-art.sh` lists every issue and pull request, open, closed and merged, that touches the file or symbol, with the type taken from the API field and the closing ruling quoted for every closed item. Two closures built it: a pull request that duplicated an open pull request because a search mixed issues and pull requests and nobody checked the type, and a pull request that argued against a ruling in a closed issue nobody had read.
|
|
92
|
+
|
|
93
|
+
**3. Is it a fix or an opinion?**
|
|
94
|
+
A change that rejects an input the project tolerated, or changes a default, is a breaking change on a stable major. It becomes an issue, never a pull request. Two closures, one class, two projects, before this was a rule.
|
|
95
|
+
|
|
96
|
+
**4. Prove it red first.**
|
|
97
|
+
On a fresh clone of the upstream default branch, the new test fails. With the patch, it passes. The proof lives in the pull request as the test, not in the body as a claim.
|
|
98
|
+
|
|
99
|
+
**5. Run their CI, not yours.**
|
|
100
|
+
The project's own test target, on the fresh clone, including the lint, format and mutation gates its workflow runs. Two pull requests went red on gates the author had never run, and the maintainer saw it before the author did.
|
|
101
|
+
|
|
102
|
+
**6. Match the house style.**
|
|
103
|
+
`comment-census.sh` counts added code lines against added comment lines and compares them with the file. No comment in code that has none. Body under 120 words, in the project's template if it has one. No tool footer, no session link, no co-author trailer. When a template asks whether AI was used, the answer is one truthful sentence. Three maintainers said the same thing about comment density before it became a script.
|
|
104
|
+
|
|
105
|
+
**7. Know the paperwork.**
|
|
106
|
+
Every organization gets a card before the first filing: CLA, DCO, signed-commit requirement, template. Some CLA bots post nothing on the pull request and put the instructions in a failed job's log; the card is where that is written down.
|
|
107
|
+
|
|
108
|
+
**8. Security goes through the side door.**
|
|
109
|
+
A memory-safety or remote-abort finding in a library goes by the project's SECURITY.md route, privately, and never becomes a public pull request until the project answers.
|
|
110
|
+
|
|
111
|
+
**9. File through the gate.**
|
|
112
|
+
`file-pr.sh` wraps the pull request creation and refuses when the prior-art printout for that repository is missing or older than a day, or when the body carries a footer, a trailer, or a typographic dash.
|
|
113
|
+
|
|
114
|
+
**10. Watch it land.**
|
|
115
|
+
`verify-filed-pr.sh` re-derives from GitHub that the head is the intended commit, the diff is exactly the intended files, and CI settled green. `pr-sweep.sh` lists every open pull request where the ball is in your court: a maintainer's comment unanswered, a review requesting changes, a red check, a conflict. Every maintainer comment gets a same-day answer. A ruling in a closed issue is never argued with. When the maintainer is right, concede and let them close it.
|
|
116
|
+
|
|
117
|
+
**11. Clean up.**
|
|
118
|
+
`fork-hygiene.sh` deletes the fork branch of every merged or closed pull request and lists forks with no pull request left, which are deleted when the work is over. No planning file, agent directory or build output ever enters a diff.
|
|
119
|
+
|
|
120
|
+
**12. Three per repository, one finding each.**
|
|
121
|
+
A repository gets at most three pull requests, each one finding, each meeting every rule above on its own.
|
|
122
|
+
|
|
123
|
+
## Install
|
|
124
|
+
|
|
125
|
+
You need `bash`, `gh` (signed in) and `jq`. Then:
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
uv tool install housebroken-cli
|
|
129
|
+
housebroken install-skill
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
The first line puts the `housebroken` command on your path. The second puts the skill where Claude Code loads it, so the agent runs the door itself before it opens anything upstream. Until the package is on PyPI, install from the repository instead:
|
|
133
|
+
|
|
134
|
+
```
|
|
135
|
+
uv tool install git+https://github.com/lenamonj/housebroken
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
`housebroken help` prints the door in order. Every gate is also a plain bash script under `scripts/`, runnable on its own.
|
|
139
|
+
|
|
140
|
+
## What is in the repository
|
|
141
|
+
|
|
142
|
+
| script | gate |
|
|
143
|
+
|---|---|
|
|
144
|
+
| `scripts/check-ai-policy.sh` | reads the AI-contribution policy where it actually lives |
|
|
145
|
+
| `scripts/distinct-outside.sh` | outside contributors merged in 120 days |
|
|
146
|
+
| `scripts/prior-art.sh` | issues and pull requests on the touched files, type stated, rulings quoted |
|
|
147
|
+
| `scripts/comment-census.sh` | added code versus added comments, per branch |
|
|
148
|
+
| `scripts/file-pr.sh` | the only way a pull request gets filed |
|
|
149
|
+
| `scripts/verify-filed-pr.sh` | the filed pull request is what was meant, and CI settled |
|
|
150
|
+
| `scripts/pr-sweep.sh` | every open pull request where the ball is in your court |
|
|
151
|
+
| `scripts/fork-hygiene.sh` | branches deleted after merge or close, orphan forks listed |
|
|
152
|
+
|
|
153
|
+
The scripts are bash and need `gh` and `jq`. They came out of one operator's workshop and some still carry that operator's assumptions; each script's header says what it assumes. Generalizing them is the current work.
|
|
154
|
+
|
|
155
|
+
## Not in scope
|
|
156
|
+
|
|
157
|
+
housebroken does not find bugs and does not write patches. Any agent that produces a change can use it. It governs what leaves your machine and how it behaves once it arrives.
|
|
158
|
+
|
|
159
|
+
## FAQ
|
|
160
|
+
|
|
161
|
+
**Is this only for AI-written pull requests?**
|
|
162
|
+
No. Every rule here predates agents. Agents made it cheap to break all of them at once, on a hundred repositories, before breakfast.
|
|
163
|
+
|
|
164
|
+
**Does it make maintainers like agent pull requests?**
|
|
165
|
+
No. It makes the pull request indistinguishable from a careful human's, and answers truthfully when they ask.
|
|
166
|
+
|
|
167
|
+
**What if the maintainer closes it anyway?**
|
|
168
|
+
Then the maintainer is the judge and the ruling stands. Read why, write it down, and if it is a class of mistake, make it a gate.
|
|
169
|
+
|
|
170
|
+
**Why "housebroken"?**
|
|
171
|
+
Because the alternative is what maintainers are calling it.
|
|
172
|
+
|
|
173
|
+
## License
|
|
174
|
+
|
|
175
|
+
MIT.
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
<p align="center">
|
|
2
|
+
<img src="assets/door.jpg" width="560" alt="A white ceramic robot dog sits at an open, lit door, a scroll in its mouth, its paws stopped at a glowing amber line on the threshold">
|
|
3
|
+
</p>
|
|
4
|
+
|
|
5
|
+
<h1 align="center">housebroken</h1>
|
|
6
|
+
|
|
7
|
+
<p align="center"><em>Agent-written pull requests that do not make a mess in someone else's house.</em></p>
|
|
8
|
+
|
|
9
|
+
<p align="center">
|
|
10
|
+
<img src="https://img.shields.io/github/stars/lenamonj/housebroken?style=flat-square&color=111111&label=stars" alt="Stars">
|
|
11
|
+
<img src="https://img.shields.io/github/v/release/lenamonj/housebroken?style=flat-square&color=111111&label=release" alt="Release">
|
|
12
|
+
<img src="https://img.shields.io/pypi/v/housebroken-cli?style=flat-square&color=111111&label=pypi" alt="PyPI">
|
|
13
|
+
<img src="https://img.shields.io/badge/works%20with-Claude%20Code-111111?style=flat-square" alt="Works with Claude Code">
|
|
14
|
+
<img src="https://img.shields.io/badge/license-MIT-111111?style=flat-square" alt="MIT license">
|
|
15
|
+
</p>
|
|
16
|
+
|
|
17
|
+
<p align="center">
|
|
18
|
+
<img src="https://img.shields.io/badge/gates-12-E8A23B?style=flat-square" alt="12 gates">
|
|
19
|
+
<img src="https://img.shields.io/badge/merged%20upstream-24%20PRs%20in%2021%20projects-E8A23B?style=flat-square" alt="24 merged pull requests across 21 projects">
|
|
20
|
+
<img src="https://img.shields.io/badge/closures%20turned%20into%20gates-5-E8A23B?style=flat-square" alt="5 closures turned into gates">
|
|
21
|
+
<img src="https://img.shields.io/badge/shellcheck-clean-111111?style=flat-square" alt="shellcheck clean">
|
|
22
|
+
</p>
|
|
23
|
+
|
|
24
|
+
<p align="center">
|
|
25
|
+
<strong>24 merged upstream · 21 projects · 5 closures, each one now a gate</strong><br>
|
|
26
|
+
<sub>Derived from GitHub on 7 September 2026: one account, every patch written by an agent, every filing governed by these rules as they were learned. Apple, Microsoft, Google, Apache and JetBrains are among the mergers. <a href="#numbers">The numbers</a> · <a href="#how-it-works">the twelve gates</a>.</sub>
|
|
27
|
+
</p>
|
|
28
|
+
|
|
29
|
+
You know the maintainer. She runs the project on evenings and weekends, has for nine years, and opened GitHub this morning to four pull requests from accounts created last month. Each one rewrites a function she wrote in 2019. Each carries a paragraph of comments in code that had none, a six-hundred-word body, and a footer naming the tool that wrote it. One of them re-argues a decision she closed in April. She closes all four without reading them.
|
|
30
|
+
|
|
31
|
+
She is right to.
|
|
32
|
+
|
|
33
|
+
housebroken is the set of rules that get a pull request past her, written as scripts that refuse to file until every rule is met. The rules are not opinions. Each one was paid for with a closed pull request, and the closure is named next to the rule.
|
|
34
|
+
|
|
35
|
+
## Before / after
|
|
36
|
+
|
|
37
|
+
Before: one branch, fourteen files, three unrelated fixes, a planning document and an agent config directory in the diff, a comment on every added line, a body that explains the tool, filed against a ruling already made in a closed issue, the project's own test target never run.
|
|
38
|
+
|
|
39
|
+
After:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
one finding, one pull request
|
|
43
|
+
9 lines changed in the project's own style, no comment where the file has none
|
|
44
|
+
1 test that fails on their main and passes with the patch
|
|
45
|
+
body under 120 words, in their template, no footer
|
|
46
|
+
prior art read and quoted before the branch existed
|
|
47
|
+
their CI target green on a fresh clone of their main
|
|
48
|
+
the CLA known before filing, the SECURITY.md route taken when it applies
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
The second one gets merged. Sometimes in twelve minutes.
|
|
52
|
+
|
|
53
|
+
## Numbers
|
|
54
|
+
|
|
55
|
+
Every rule here was learned on real repositories with real maintainers. Between late July and 7 September 2026, one account filed pull requests on projects it had never touched before, every patch written by an autonomous agent, every filing governed by these gates as they were learned. Derived from GitHub on 7 September 2026:
|
|
56
|
+
|
|
57
|
+
| | count |
|
|
58
|
+
|---|--:|
|
|
59
|
+
| pull requests filed | 102 |
|
|
60
|
+
| merged | 24, across 21 projects |
|
|
61
|
+
| open, waiting on a maintainer | 73 |
|
|
62
|
+
| closed without merging | 5 |
|
|
63
|
+
|
|
64
|
+
The merged patches include ones accepted by Apple, Microsoft, Google, Apache, JetBrains, and the URL parser that Node.js ships. The fastest merge came twelve minutes after filing. Several came the same day.
|
|
65
|
+
|
|
66
|
+
The five closures matter more than the merges. Each became a gate. Two of the five were the same class, three days apart, because the first lesson was written as prose and prose is followed when remembered. That is why the rules here are scripts that refuse, not a checklist that suggests.
|
|
67
|
+
|
|
68
|
+
## How it works
|
|
69
|
+
|
|
70
|
+
<img align="right" width="230" src="assets/treatise.jpg" alt="An obsidian book with chrome corners titled The Housebroken Agent, a treatise on manners for machines calling at the homes of maintainers">
|
|
71
|
+
|
|
72
|
+
A pull request passes through the door in order. Each step is a script or a rule, and each names the closure that put it there.
|
|
73
|
+
|
|
74
|
+
**1. Read the house rules before knocking.**
|
|
75
|
+
`check-ai-policy.sh` reads the repository's contribution policy on its development branch and in the organization's `.github` repository, and prints the sentence, not a verdict. Some projects ask contributors not to use AI for pull request text; those are never filed. Some accept pull requests only for issues they have labelled; those get an issue with the fix offered.
|
|
76
|
+
`distinct-outside.sh` counts outside contributors merged in the last 120 days. A project that has merged none is closed to outsiders whatever its README says.
|
|
77
|
+
|
|
78
|
+
**2. Is it already on the table?**
|
|
79
|
+
`prior-art.sh` lists every issue and pull request, open, closed and merged, that touches the file or symbol, with the type taken from the API field and the closing ruling quoted for every closed item. Two closures built it: a pull request that duplicated an open pull request because a search mixed issues and pull requests and nobody checked the type, and a pull request that argued against a ruling in a closed issue nobody had read.
|
|
80
|
+
|
|
81
|
+
**3. Is it a fix or an opinion?**
|
|
82
|
+
A change that rejects an input the project tolerated, or changes a default, is a breaking change on a stable major. It becomes an issue, never a pull request. Two closures, one class, two projects, before this was a rule.
|
|
83
|
+
|
|
84
|
+
**4. Prove it red first.**
|
|
85
|
+
On a fresh clone of the upstream default branch, the new test fails. With the patch, it passes. The proof lives in the pull request as the test, not in the body as a claim.
|
|
86
|
+
|
|
87
|
+
**5. Run their CI, not yours.**
|
|
88
|
+
The project's own test target, on the fresh clone, including the lint, format and mutation gates its workflow runs. Two pull requests went red on gates the author had never run, and the maintainer saw it before the author did.
|
|
89
|
+
|
|
90
|
+
**6. Match the house style.**
|
|
91
|
+
`comment-census.sh` counts added code lines against added comment lines and compares them with the file. No comment in code that has none. Body under 120 words, in the project's template if it has one. No tool footer, no session link, no co-author trailer. When a template asks whether AI was used, the answer is one truthful sentence. Three maintainers said the same thing about comment density before it became a script.
|
|
92
|
+
|
|
93
|
+
**7. Know the paperwork.**
|
|
94
|
+
Every organization gets a card before the first filing: CLA, DCO, signed-commit requirement, template. Some CLA bots post nothing on the pull request and put the instructions in a failed job's log; the card is where that is written down.
|
|
95
|
+
|
|
96
|
+
**8. Security goes through the side door.**
|
|
97
|
+
A memory-safety or remote-abort finding in a library goes by the project's SECURITY.md route, privately, and never becomes a public pull request until the project answers.
|
|
98
|
+
|
|
99
|
+
**9. File through the gate.**
|
|
100
|
+
`file-pr.sh` wraps the pull request creation and refuses when the prior-art printout for that repository is missing or older than a day, or when the body carries a footer, a trailer, or a typographic dash.
|
|
101
|
+
|
|
102
|
+
**10. Watch it land.**
|
|
103
|
+
`verify-filed-pr.sh` re-derives from GitHub that the head is the intended commit, the diff is exactly the intended files, and CI settled green. `pr-sweep.sh` lists every open pull request where the ball is in your court: a maintainer's comment unanswered, a review requesting changes, a red check, a conflict. Every maintainer comment gets a same-day answer. A ruling in a closed issue is never argued with. When the maintainer is right, concede and let them close it.
|
|
104
|
+
|
|
105
|
+
**11. Clean up.**
|
|
106
|
+
`fork-hygiene.sh` deletes the fork branch of every merged or closed pull request and lists forks with no pull request left, which are deleted when the work is over. No planning file, agent directory or build output ever enters a diff.
|
|
107
|
+
|
|
108
|
+
**12. Three per repository, one finding each.**
|
|
109
|
+
A repository gets at most three pull requests, each one finding, each meeting every rule above on its own.
|
|
110
|
+
|
|
111
|
+
## Install
|
|
112
|
+
|
|
113
|
+
You need `bash`, `gh` (signed in) and `jq`. Then:
|
|
114
|
+
|
|
115
|
+
```
|
|
116
|
+
uv tool install housebroken-cli
|
|
117
|
+
housebroken install-skill
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
The first line puts the `housebroken` command on your path. The second puts the skill where Claude Code loads it, so the agent runs the door itself before it opens anything upstream. Until the package is on PyPI, install from the repository instead:
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
uv tool install git+https://github.com/lenamonj/housebroken
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
`housebroken help` prints the door in order. Every gate is also a plain bash script under `scripts/`, runnable on its own.
|
|
127
|
+
|
|
128
|
+
## What is in the repository
|
|
129
|
+
|
|
130
|
+
| script | gate |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `scripts/check-ai-policy.sh` | reads the AI-contribution policy where it actually lives |
|
|
133
|
+
| `scripts/distinct-outside.sh` | outside contributors merged in 120 days |
|
|
134
|
+
| `scripts/prior-art.sh` | issues and pull requests on the touched files, type stated, rulings quoted |
|
|
135
|
+
| `scripts/comment-census.sh` | added code versus added comments, per branch |
|
|
136
|
+
| `scripts/file-pr.sh` | the only way a pull request gets filed |
|
|
137
|
+
| `scripts/verify-filed-pr.sh` | the filed pull request is what was meant, and CI settled |
|
|
138
|
+
| `scripts/pr-sweep.sh` | every open pull request where the ball is in your court |
|
|
139
|
+
| `scripts/fork-hygiene.sh` | branches deleted after merge or close, orphan forks listed |
|
|
140
|
+
|
|
141
|
+
The scripts are bash and need `gh` and `jq`. They came out of one operator's workshop and some still carry that operator's assumptions; each script's header says what it assumes. Generalizing them is the current work.
|
|
142
|
+
|
|
143
|
+
## Not in scope
|
|
144
|
+
|
|
145
|
+
housebroken does not find bugs and does not write patches. Any agent that produces a change can use it. It governs what leaves your machine and how it behaves once it arrives.
|
|
146
|
+
|
|
147
|
+
## FAQ
|
|
148
|
+
|
|
149
|
+
**Is this only for AI-written pull requests?**
|
|
150
|
+
No. Every rule here predates agents. Agents made it cheap to break all of them at once, on a hundred repositories, before breakfast.
|
|
151
|
+
|
|
152
|
+
**Does it make maintainers like agent pull requests?**
|
|
153
|
+
No. It makes the pull request indistinguishable from a careful human's, and answers truthfully when they ask.
|
|
154
|
+
|
|
155
|
+
**What if the maintainer closes it anyway?**
|
|
156
|
+
Then the maintainer is the judge and the ruling stands. Read why, write it down, and if it is a class of mistake, make it a gate.
|
|
157
|
+
|
|
158
|
+
**Why "housebroken"?**
|
|
159
|
+
Because the alternative is what maintainers are calling it.
|
|
160
|
+
|
|
161
|
+
## License
|
|
162
|
+
|
|
163
|
+
MIT.
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# housebroken - one command name for the eight gate scripts.
|
|
3
|
+
#
|
|
4
|
+
# The skill file, and any future adapter for another agent host, must be able
|
|
5
|
+
# to say `housebroken prior-art owner/repo term` without knowing where the
|
|
6
|
+
# scripts live. This dispatcher is that stable name: it finds the scripts
|
|
7
|
+
# directory, maps a short subcommand to a script, and hands over every
|
|
8
|
+
# remaining argument unchanged. Nothing else. No gate logic lives here.
|
|
9
|
+
#
|
|
10
|
+
# Usage:
|
|
11
|
+
# housebroken <subcommand> [args...]
|
|
12
|
+
# housebroken help
|
|
13
|
+
# housebroken version
|
|
14
|
+
#
|
|
15
|
+
# Environment:
|
|
16
|
+
# HOUSEBROKEN_SCRIPTS directory holding the eight *.sh gate scripts. When
|
|
17
|
+
# unset, the scripts directory next to this file
|
|
18
|
+
# (../scripts), then this file's own directory, then
|
|
19
|
+
# $HOUSEBROKEN_HOME/scripts are tried in that order.
|
|
20
|
+
# HOUSEBROKEN_HOME workshop root, default $HOME/.housebroken. The gate
|
|
21
|
+
# scripts read it too; it is passed through untouched.
|
|
22
|
+
#
|
|
23
|
+
# Assumes bash. The scripts themselves assume gh and jq.
|
|
24
|
+
set -u
|
|
25
|
+
|
|
26
|
+
self_dir=$(cd "$(dirname "$0")" && pwd)
|
|
27
|
+
HOUSEBROKEN_HOME="${HOUSEBROKEN_HOME:-$HOME/.housebroken}"
|
|
28
|
+
|
|
29
|
+
candidates=()
|
|
30
|
+
if [ -n "${HOUSEBROKEN_SCRIPTS:-}" ]; then
|
|
31
|
+
candidates+=("$HOUSEBROKEN_SCRIPTS")
|
|
32
|
+
fi
|
|
33
|
+
candidates+=("$self_dir/../scripts" "$self_dir" "$HOUSEBROKEN_HOME/scripts")
|
|
34
|
+
|
|
35
|
+
scripts_dir=""
|
|
36
|
+
for d in "${candidates[@]}"; do
|
|
37
|
+
if [ -f "$d/prior-art.sh" ]; then
|
|
38
|
+
scripts_dir=$(cd "$d" && pwd)
|
|
39
|
+
break
|
|
40
|
+
fi
|
|
41
|
+
done
|
|
42
|
+
|
|
43
|
+
door() {
|
|
44
|
+
cat <<'EOF'
|
|
45
|
+
usage: housebroken <subcommand> [args...]
|
|
46
|
+
|
|
47
|
+
1 policy read the repository's AI-contribution policy where it lives
|
|
48
|
+
1 outside count outside contributors merged in the last 120 days
|
|
49
|
+
2 prior-art issues and pull requests on the touched files, rulings quoted
|
|
50
|
+
6 census added code lines against added comment lines, per branch
|
|
51
|
+
9 file the only way a pull request gets filed
|
|
52
|
+
10 verify the filed pull request is what was meant, and CI settled
|
|
53
|
+
10 sweep every open pull request where the ball is in your court
|
|
54
|
+
11 hygiene delete fork branches after merge or close, list orphan forks
|
|
55
|
+
|
|
56
|
+
help this list
|
|
57
|
+
version the installed housebroken version
|
|
58
|
+
|
|
59
|
+
Step numbers are the steps of "How it works" in the README. Every argument
|
|
60
|
+
after the subcommand is passed to the script unchanged.
|
|
61
|
+
EOF
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
version() {
|
|
65
|
+
vpaths=("$self_dir/../VERSION" "$self_dir/VERSION")
|
|
66
|
+
if [ -n "$scripts_dir" ]; then
|
|
67
|
+
vpaths=("$scripts_dir/../VERSION" "$scripts_dir/VERSION" "${vpaths[@]}")
|
|
68
|
+
fi
|
|
69
|
+
for v in "${vpaths[@]}"; do
|
|
70
|
+
if [ -f "$v" ]; then
|
|
71
|
+
head -1 "$v"
|
|
72
|
+
return 0
|
|
73
|
+
fi
|
|
74
|
+
done
|
|
75
|
+
echo "housebroken (unversioned)"
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
cmd="${1:-help}"
|
|
79
|
+
case "$cmd" in
|
|
80
|
+
help|--help|-h) door; exit 0 ;;
|
|
81
|
+
version|--version) version; exit 0 ;;
|
|
82
|
+
esac
|
|
83
|
+
shift
|
|
84
|
+
|
|
85
|
+
case "$cmd" in
|
|
86
|
+
policy) script=check-ai-policy.sh ;;
|
|
87
|
+
outside) script=distinct-outside.sh ;;
|
|
88
|
+
prior-art) script=prior-art.sh ;;
|
|
89
|
+
census) script=comment-census.sh ;;
|
|
90
|
+
file) script=file-pr.sh ;;
|
|
91
|
+
verify) script=verify-filed-pr.sh ;;
|
|
92
|
+
sweep) script=pr-sweep.sh ;;
|
|
93
|
+
hygiene) script=fork-hygiene.sh ;;
|
|
94
|
+
*)
|
|
95
|
+
echo "housebroken: unknown subcommand: $cmd (try: housebroken help)" >&2
|
|
96
|
+
exit 2
|
|
97
|
+
;;
|
|
98
|
+
esac
|
|
99
|
+
|
|
100
|
+
if [ -z "$scripts_dir" ] || [ ! -f "$scripts_dir/$script" ]; then
|
|
101
|
+
{
|
|
102
|
+
echo "housebroken: cannot find $script. Looked in:"
|
|
103
|
+
for d in "${candidates[@]}"; do echo " $d"; done
|
|
104
|
+
echo "Set HOUSEBROKEN_SCRIPTS to the directory holding the gate scripts."
|
|
105
|
+
} >&2
|
|
106
|
+
exit 2
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
export HOUSEBROKEN_HOME
|
|
110
|
+
exec bash "$scripts_dir/$script" "$@"
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "housebroken-cli"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Agent-written pull requests that do not make a mess in someone else's house."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
dependencies = []
|
|
13
|
+
classifiers = [
|
|
14
|
+
"Programming Language :: Python :: 3",
|
|
15
|
+
"Operating System :: OS Independent",
|
|
16
|
+
]
|
|
17
|
+
|
|
18
|
+
[project.scripts]
|
|
19
|
+
housebroken = "housebroken.cli:main"
|
|
20
|
+
|
|
21
|
+
[project.urls]
|
|
22
|
+
Repository = "https://github.com/lenamonj/housebroken"
|
|
23
|
+
|
|
24
|
+
[tool.hatch.build.targets.wheel]
|
|
25
|
+
packages = ["src/housebroken"]
|
|
26
|
+
|
|
27
|
+
[tool.hatch.build.targets.wheel.force-include]
|
|
28
|
+
"scripts" = "housebroken/scripts"
|
|
29
|
+
"bin/housebroken" = "housebroken/scripts/housebroken"
|
|
30
|
+
"skills/housebroken/SKILL.md" = "housebroken/skill/SKILL.md"
|
|
31
|
+
|
|
32
|
+
[tool.hatch.build.targets.sdist]
|
|
33
|
+
include = [
|
|
34
|
+
"src/housebroken",
|
|
35
|
+
"scripts",
|
|
36
|
+
"bin/housebroken",
|
|
37
|
+
"skills/housebroken/SKILL.md",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE",
|
|
40
|
+
]
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# check-ai-policy.sh - independently verify a repo's stance on AI-assisted
|
|
3
|
+
# contributions. Scouts report a policy; this reads it. Never adopt a target on
|
|
4
|
+
# a scout's description of its policy (cold-cohort-2026-08-09 lesson, applied to
|
|
5
|
+
# a new criterion).
|
|
6
|
+
#
|
|
7
|
+
# Checks a branch of the real repo, not a memory of it, and then the
|
|
8
|
+
# organization's .github repository, where an org-wide policy often lives.
|
|
9
|
+
# Prints every hit with its file so the operator reads the sentence, not a
|
|
10
|
+
# verdict. The branch is overridable because some projects keep the policy on a
|
|
11
|
+
# development branch and not on the default branch.
|
|
12
|
+
#
|
|
13
|
+
# Usage:
|
|
14
|
+
# ./check-ai-policy.sh [--branch <name>] owner/repo [owner/repo ...]
|
|
15
|
+
# ./check-ai-policy.sh --help
|
|
16
|
+
#
|
|
17
|
+
# Environment:
|
|
18
|
+
# HOUSEBROKEN_HOME work directory, default $HOME/.housebroken. Not written
|
|
19
|
+
# by this script; it prints to stdout only.
|
|
20
|
+
#
|
|
21
|
+
# Assumes gh is installed and authenticated. Read-only: GET calls only.
|
|
22
|
+
set -u
|
|
23
|
+
|
|
24
|
+
usage() {
|
|
25
|
+
cat <<'EOF'
|
|
26
|
+
usage: check-ai-policy.sh [--branch <name>] owner/repo [owner/repo ...]
|
|
27
|
+
check-ai-policy.sh --help
|
|
28
|
+
|
|
29
|
+
--branch <name> read the policy files from this branch instead of the
|
|
30
|
+
repository default branch; some projects keep the policy
|
|
31
|
+
on a development branch
|
|
32
|
+
|
|
33
|
+
environment:
|
|
34
|
+
HOUSEBROKEN_HOME work directory, default $HOME/.housebroken (unused here)
|
|
35
|
+
EOF
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
FILES="CONTRIBUTING.md CONTRIBUTING.rst CONTRIBUTING .github/CONTRIBUTING.md
|
|
39
|
+
docs/CONTRIBUTING.md docs/contributing.md CODE_OF_CONDUCT.md
|
|
40
|
+
.github/CODE_OF_CONDUCT.md README.md AI.md AI_POLICY.md POLICY.md
|
|
41
|
+
.github/AI_POLICY.md .github/AI.md .github/POLICY.md .github/PULL_REQUEST_TEMPLATE.md .github/pull_request_template.md
|
|
42
|
+
.github/ISSUE_TEMPLATE/config.yml CLAUDE.md AGENTS.md .cursorrules"
|
|
43
|
+
|
|
44
|
+
PATTERN='[Aa][Ii]-generated|[Aa]rtificial [Ii]ntelligence|\bLLM|[Ll]arge [Ll]anguage [Mm]odel|Copilot|ChatGPT|[Cc]laude|[Gg]enerative|AI assistance|AI-assisted|AI tool|AI slop|machine-generated'
|
|
45
|
+
|
|
46
|
+
branch_opt=""
|
|
47
|
+
repos=()
|
|
48
|
+
while [ "$#" -gt 0 ]; do
|
|
49
|
+
case "$1" in
|
|
50
|
+
--help|-h) usage; exit 0 ;;
|
|
51
|
+
--branch) shift; [ "$#" -gt 0 ] || { echo "check-ai-policy.sh: --branch needs a name" >&2; exit 1; }
|
|
52
|
+
branch_opt="$1" ;;
|
|
53
|
+
--branch=*) branch_opt="${1#--branch=}" ;;
|
|
54
|
+
--*) usage >&2; echo "check-ai-policy.sh: unknown option $1" >&2; exit 1 ;;
|
|
55
|
+
*/*) repos+=("$1") ;;
|
|
56
|
+
*) usage >&2; echo "check-ai-policy.sh: '$1' is not owner/repo" >&2; exit 1 ;;
|
|
57
|
+
esac
|
|
58
|
+
shift
|
|
59
|
+
done
|
|
60
|
+
[ "${#repos[@]}" -gt 0 ] || { usage >&2; exit 1; }
|
|
61
|
+
|
|
62
|
+
command -v gh >/dev/null 2>&1 || { echo "check-ai-policy.sh: gh is not on PATH" >&2; exit 1; }
|
|
63
|
+
|
|
64
|
+
# print every policy hit in one repo at one ref; returns 1 when nothing matched
|
|
65
|
+
scan() {
|
|
66
|
+
local target="$1" ref="$2" label="$3" found=0 f body hits
|
|
67
|
+
for f in $FILES; do
|
|
68
|
+
body=$(gh api "repos/$target/contents/$f?ref=$ref" --jq '.content' 2>/dev/null | base64 -d 2>/dev/null)
|
|
69
|
+
[ -z "$body" ] && continue
|
|
70
|
+
hits=$(printf '%s' "$body" | grep -nEi "$PATTERN" | head -12)
|
|
71
|
+
if [ -n "$hits" ]; then
|
|
72
|
+
found=1
|
|
73
|
+
echo " --- $label$f ---"
|
|
74
|
+
printf '%s\n' "$hits" | cut -c1-300 | sed 's/^/ /'
|
|
75
|
+
else
|
|
76
|
+
echo " (clean) $label$f"
|
|
77
|
+
fi
|
|
78
|
+
done
|
|
79
|
+
[ "$found" -eq 1 ]
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
status=0
|
|
83
|
+
for repo in "${repos[@]}"; do
|
|
84
|
+
echo "=================================================================="
|
|
85
|
+
echo "$repo"
|
|
86
|
+
branch="$branch_opt"
|
|
87
|
+
if [ -z "$branch" ]; then
|
|
88
|
+
branch=$(gh api "repos/$repo" --jq .default_branch 2>/dev/null) || branch=""
|
|
89
|
+
if [ -z "$branch" ]; then
|
|
90
|
+
echo "check-ai-policy.sh: cannot read $repo from GitHub" >&2
|
|
91
|
+
status=1
|
|
92
|
+
continue
|
|
93
|
+
fi
|
|
94
|
+
fi
|
|
95
|
+
echo " branch: $branch"
|
|
96
|
+
found_any=0
|
|
97
|
+
scan "$repo" "$branch" "" && found_any=1
|
|
98
|
+
|
|
99
|
+
# an org-wide policy often lives in the organization's .github repository
|
|
100
|
+
org="${repo%%/*}"
|
|
101
|
+
org_branch=$(gh api "repos/$org/.github" --jq .default_branch 2>/dev/null) || org_branch=""
|
|
102
|
+
if [ -n "$org_branch" ]; then
|
|
103
|
+
echo " org repo: $org/.github ($org_branch)"
|
|
104
|
+
scan "$org/.github" "$org_branch" "$org/.github: " && found_any=1
|
|
105
|
+
fi
|
|
106
|
+
|
|
107
|
+
[ "$found_any" -eq 0 ] && echo " RESULT: no AI/LLM language in any policy file checked"
|
|
108
|
+
done
|
|
109
|
+
exit "$status"
|