housebroken-cli 0.7.0__tar.gz → 0.8.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.7.0 → housebroken_cli-0.8.0}/PKG-INFO +6 -4
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/README.md +5 -3
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/bin/housebroken +4 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/pyproject.toml +1 -1
- housebroken_cli-0.8.0/scripts/declare.sh +90 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/file-pr.sh +31 -2
- housebroken_cli-0.8.0/scripts/proof.sh +204 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/review.sh +11 -1
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/skills/housebroken/SKILL.md +20 -3
- housebroken_cli-0.8.0/src/housebroken/__init__.py +1 -0
- housebroken_cli-0.7.0/src/housebroken/__init__.py +0 -1
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/.gitignore +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/LICENSE +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/hatch_build.py +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/branch-check.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/check-ai-policy.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/claim-check.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/comment-census.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/comment-check.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/diff-defaults.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/distinct-outside.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/file-issue.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/fork-hygiene.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/inbox.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/notes.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/pr-sweep.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/prior-art.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/ref-check.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/scripts/verify-filed-pr.sh +0 -0
- {housebroken_cli-0.7.0 → housebroken_cli-0.8.0}/src/housebroken/cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: housebroken-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.8.0
|
|
4
4
|
Summary: Agent-written pull requests that do not make a mess in someone else's house.
|
|
5
5
|
Project-URL: Repository, https://github.com/lenamonj/housebroken
|
|
6
6
|
License-Expression: MIT
|
|
@@ -61,7 +61,7 @@ A pull request passes through the door in order, and stops at the first rule it
|
|
|
61
61
|
A change that rejects an input the project tolerated, changes a default, or narrows what the project publishes is the maintainer's call. It is sent with the call stated in the body: what breaks for existing users, and an offer to narrow or close it. A finding whose only evidence is a measurement is a hold, not a pull request.
|
|
62
62
|
|
|
63
63
|
**4. Prove it red first, and prove the proof.**
|
|
64
|
-
On a fresh clone of the upstream default branch, the new test fails; with the patch, it passes. Every arm of the experiment prints evidence that its precondition held, files are swapped by content and checked by hash, and two arms that agree exactly have not tested the variable.
|
|
64
|
+
On a fresh clone of the upstream default branch, the new test fails; with the patch, it passes. Every arm of the experiment prints evidence that its precondition held, files are swapped by content and checked by hash, and two arms that agree exactly have not tested the variable. `proof.sh` runs the experiment itself: the test on the head, then on the head with every changed non-test file swapped back to its content at the merge base, then the head restored and checked by hash, and it stamps the head only when the base arm fails and the head arm passes. A change with nothing to show red says so, with the reason, and the filing prints it.
|
|
65
65
|
|
|
66
66
|
**5. Run their CI, not yours.**
|
|
67
67
|
The project's own test target on the fresh clone, and every gate its pull request workflow runs: lint, format, mutation, API check. A gate not run is a red check the maintainer sees first.
|
|
@@ -77,8 +77,8 @@ A memory-safety, remote-abort, injection or path-escape finding goes by the proj
|
|
|
77
77
|
|
|
78
78
|
**9. File through the gate.**
|
|
79
79
|
`branch-check.sh` refuses a dirty working tree, a file whose mode disagrees with its siblings, a file the repository's own `.gitignore` excludes, a working artifact, a commit carrying a tool trailer, a session link or a tool identity, a sign-off naming a username, a branch not on top of its base, any comment added to code, a warning suppression that does not name what it hides or that no build log shows firing, reflection added to a .NET project that declares AOT or trim support without an AOT publish log of it, a credential or a path under your own home, a lockfile, generated file or binary nobody named, an agent's working file, and a file rewritten for its line endings; it notes a base the upstream has moved past, a user path that may be a fixture and every absolute claim in added prose, and stamps the head it passed.
|
|
80
|
-
`review.sh` names the adversarial reviewer: a different model from the one that wrote the change, one tier down, never below Sonnet. The reviewer gets a fresh context, hard read-only limits, and before it reads the diff it writes its own fix from the bug alone, so it is not anchored on ours; the report names that fix as a diff and the gate refuses one that does not, a POST AS IS over an open BLOCKING or SHOULD FIX finding, and a POST AS IS over items the reviewer skipped. Its attack list starts with every claim the prose makes and includes the form of the code: the reviewer writes the changed block the way the strongest engineer in that language would, in the repository's own idioms, and a smaller or clearer version is a finding; every finding carries evidence it produced itself, and its report is bound to the head and the text it read. A fix is a new head and gets a new review.
|
|
81
|
-
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old that searched every file the branch changes, a policy printout under a day old that reads neither BAN nor UNREAD, a body that discloses when the policy asks and runs under 120 words of its own beside the project's template, a branch stamp for the exact head whose base the upstream has not since changed under the branch's files, a POST AS IS review of that head and that
|
|
80
|
+
`review.sh` names the adversarial reviewer: a different model from the one that wrote the change, one tier down, never below Sonnet. Before it runs, the author writes a declaration (`declare.sh`): what the change was not tested against, and every assumption with what breaks if it is wrong and how it was checked. "Nothing" is refused as an answer. The review reads the declaration as one of its texts and attacks each line. The reviewer gets a fresh context, hard read-only limits, and before it reads the diff it writes its own fix from the bug alone, so it is not anchored on ours; the report names that fix as a diff and the gate refuses one that does not, a POST AS IS over an open BLOCKING or SHOULD FIX finding, and a POST AS IS over items the reviewer skipped. Its attack list starts with every claim the prose makes and includes the form of the code: the reviewer writes the changed block the way the strongest engineer in that language would, in the repository's own idioms, and a smaller or clearer version is a finding; every finding carries evidence it produced itself, and its report is bound to the head and the text it read. A fix is a new head and gets a new review.
|
|
81
|
+
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old that searched every file the branch changes, a policy printout under a day old that reads neither BAN nor UNREAD, a body that discloses when the policy asks and runs under 120 words of its own beside the project's template, a branch stamp for the exact head whose base the upstream has not since changed under the branch's files, a proof stamp for that head, a declaration that passes, a POST AS IS review of that head, that body and that declaration, fewer than three open pull requests of yours on the repository, and ten minutes since the last.
|
|
82
82
|
`file-issue.sh` is the only way an issue is opened. It refuses without the same prior-art and policy printouts, refuses the same prose, and refuses a body that mentions a fix, a patch or a pull request without a GitHub link to the branch or commit, so work that exists is attributable to whoever did it.
|
|
83
83
|
|
|
84
84
|
**10. Watch it land.**
|
|
@@ -121,6 +121,8 @@ uv tool install git+https://github.com/lenamonj/housebroken
|
|
|
121
121
|
| `scripts/comment-census.sh` | added code versus added comments, per branch |
|
|
122
122
|
| `scripts/diff-defaults.sh` | default arguments and one-line wrappers added to keep call sites untouched |
|
|
123
123
|
| `scripts/claim-check.sh` | every falsifiable claim in a body or a reply, listed to re-derive |
|
|
124
|
+
| `scripts/proof.sh` | the red-first experiment, run and stamped: red on the base, green on the head |
|
|
125
|
+
| `scripts/declare.sh` | what was not tested and what was assumed, for the review to attack |
|
|
124
126
|
| `scripts/ref-check.sh` | every issue, link, test, file and commit a body names, looked up |
|
|
125
127
|
| `scripts/branch-check.sh` | the branch's mechanical facts: clean tree, live base, modes, artifacts, secrets, generated files, trailers, sign-offs, comments, suppressions, absolute claims |
|
|
126
128
|
| `scripts/review.sh` | the adversarial review: a different model attacks the change, and the filing is held to exactly what it read |
|
|
@@ -49,7 +49,7 @@ A pull request passes through the door in order, and stops at the first rule it
|
|
|
49
49
|
A change that rejects an input the project tolerated, changes a default, or narrows what the project publishes is the maintainer's call. It is sent with the call stated in the body: what breaks for existing users, and an offer to narrow or close it. A finding whose only evidence is a measurement is a hold, not a pull request.
|
|
50
50
|
|
|
51
51
|
**4. Prove it red first, and prove the proof.**
|
|
52
|
-
On a fresh clone of the upstream default branch, the new test fails; with the patch, it passes. Every arm of the experiment prints evidence that its precondition held, files are swapped by content and checked by hash, and two arms that agree exactly have not tested the variable.
|
|
52
|
+
On a fresh clone of the upstream default branch, the new test fails; with the patch, it passes. Every arm of the experiment prints evidence that its precondition held, files are swapped by content and checked by hash, and two arms that agree exactly have not tested the variable. `proof.sh` runs the experiment itself: the test on the head, then on the head with every changed non-test file swapped back to its content at the merge base, then the head restored and checked by hash, and it stamps the head only when the base arm fails and the head arm passes. A change with nothing to show red says so, with the reason, and the filing prints it.
|
|
53
53
|
|
|
54
54
|
**5. Run their CI, not yours.**
|
|
55
55
|
The project's own test target on the fresh clone, and every gate its pull request workflow runs: lint, format, mutation, API check. A gate not run is a red check the maintainer sees first.
|
|
@@ -65,8 +65,8 @@ A memory-safety, remote-abort, injection or path-escape finding goes by the proj
|
|
|
65
65
|
|
|
66
66
|
**9. File through the gate.**
|
|
67
67
|
`branch-check.sh` refuses a dirty working tree, a file whose mode disagrees with its siblings, a file the repository's own `.gitignore` excludes, a working artifact, a commit carrying a tool trailer, a session link or a tool identity, a sign-off naming a username, a branch not on top of its base, any comment added to code, a warning suppression that does not name what it hides or that no build log shows firing, reflection added to a .NET project that declares AOT or trim support without an AOT publish log of it, a credential or a path under your own home, a lockfile, generated file or binary nobody named, an agent's working file, and a file rewritten for its line endings; it notes a base the upstream has moved past, a user path that may be a fixture and every absolute claim in added prose, and stamps the head it passed.
|
|
68
|
-
`review.sh` names the adversarial reviewer: a different model from the one that wrote the change, one tier down, never below Sonnet. The reviewer gets a fresh context, hard read-only limits, and before it reads the diff it writes its own fix from the bug alone, so it is not anchored on ours; the report names that fix as a diff and the gate refuses one that does not, a POST AS IS over an open BLOCKING or SHOULD FIX finding, and a POST AS IS over items the reviewer skipped. Its attack list starts with every claim the prose makes and includes the form of the code: the reviewer writes the changed block the way the strongest engineer in that language would, in the repository's own idioms, and a smaller or clearer version is a finding; every finding carries evidence it produced itself, and its report is bound to the head and the text it read. A fix is a new head and gets a new review.
|
|
69
|
-
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old that searched every file the branch changes, a policy printout under a day old that reads neither BAN nor UNREAD, a body that discloses when the policy asks and runs under 120 words of its own beside the project's template, a branch stamp for the exact head whose base the upstream has not since changed under the branch's files, a POST AS IS review of that head and that
|
|
68
|
+
`review.sh` names the adversarial reviewer: a different model from the one that wrote the change, one tier down, never below Sonnet. Before it runs, the author writes a declaration (`declare.sh`): what the change was not tested against, and every assumption with what breaks if it is wrong and how it was checked. "Nothing" is refused as an answer. The review reads the declaration as one of its texts and attacks each line. The reviewer gets a fresh context, hard read-only limits, and before it reads the diff it writes its own fix from the bug alone, so it is not anchored on ours; the report names that fix as a diff and the gate refuses one that does not, a POST AS IS over an open BLOCKING or SHOULD FIX finding, and a POST AS IS over items the reviewer skipped. Its attack list starts with every claim the prose makes and includes the form of the code: the reviewer writes the changed block the way the strongest engineer in that language would, in the repository's own idioms, and a smaller or clearer version is a finding; every finding carries evidence it produced itself, and its report is bound to the head and the text it read. A fix is a new head and gets a new review.
|
|
69
|
+
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old that searched every file the branch changes, a policy printout under a day old that reads neither BAN nor UNREAD, a body that discloses when the policy asks and runs under 120 words of its own beside the project's template, a branch stamp for the exact head whose base the upstream has not since changed under the branch's files, a proof stamp for that head, a declaration that passes, a POST AS IS review of that head, that body and that declaration, fewer than three open pull requests of yours on the repository, and ten minutes since the last.
|
|
70
70
|
`file-issue.sh` is the only way an issue is opened. It refuses without the same prior-art and policy printouts, refuses the same prose, and refuses a body that mentions a fix, a patch or a pull request without a GitHub link to the branch or commit, so work that exists is attributable to whoever did it.
|
|
71
71
|
|
|
72
72
|
**10. Watch it land.**
|
|
@@ -109,6 +109,8 @@ uv tool install git+https://github.com/lenamonj/housebroken
|
|
|
109
109
|
| `scripts/comment-census.sh` | added code versus added comments, per branch |
|
|
110
110
|
| `scripts/diff-defaults.sh` | default arguments and one-line wrappers added to keep call sites untouched |
|
|
111
111
|
| `scripts/claim-check.sh` | every falsifiable claim in a body or a reply, listed to re-derive |
|
|
112
|
+
| `scripts/proof.sh` | the red-first experiment, run and stamped: red on the base, green on the head |
|
|
113
|
+
| `scripts/declare.sh` | what was not tested and what was assumed, for the review to attack |
|
|
112
114
|
| `scripts/ref-check.sh` | every issue, link, test, file and commit a body names, looked up |
|
|
113
115
|
| `scripts/branch-check.sh` | the branch's mechanical facts: clean tree, live base, modes, artifacts, secrets, generated files, trailers, sign-offs, comments, suppressions, absolute claims |
|
|
114
116
|
| `scripts/review.sh` | the adversarial review: a different model attacks the change, and the filing is held to exactly what it read |
|
|
@@ -47,11 +47,13 @@ usage: housebroken <subcommand> [args...]
|
|
|
47
47
|
1 policy read the repository's AI-contribution policy where it lives
|
|
48
48
|
1 outside count outside contributors merged in the last 120 days
|
|
49
49
|
2 prior-art issues and pull requests on the touched files, rulings quoted
|
|
50
|
+
4 proof run the test on the base and the head, and stamp red then green
|
|
50
51
|
6 census added code lines against added comment lines, per branch
|
|
51
52
|
6 comments refuse a comment added to code nobody asked for
|
|
52
53
|
6 claims every falsifiable claim in outbound text, listed to re-derive
|
|
53
54
|
6 refs every issue, link, test, file and commit the text names, looked up
|
|
54
55
|
9 branch the mechanical facts about the branch: tree, modes, artifacts, claims
|
|
56
|
+
9 declare what was not tested and what was assumed, for the review to attack
|
|
55
57
|
9 review a different model attacks the change before it is filed
|
|
56
58
|
9 file the only way a pull request gets filed
|
|
57
59
|
9 issue the only way an issue gets filed
|
|
@@ -98,6 +100,8 @@ case "$cmd" in
|
|
|
98
100
|
comments) script=comment-check.sh ;;
|
|
99
101
|
claims) script=claim-check.sh ;;
|
|
100
102
|
refs) script=ref-check.sh ;;
|
|
103
|
+
proof) script=proof.sh ;;
|
|
104
|
+
declare) script=declare.sh ;;
|
|
101
105
|
branch) script=branch-check.sh ;;
|
|
102
106
|
review) script=review.sh ;;
|
|
103
107
|
file) script=file-pr.sh ;;
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# declare.sh - the author says what was not tested and what was assumed.
|
|
3
|
+
#
|
|
4
|
+
# A review attacks what it is shown. The gaps an author knows about and does
|
|
5
|
+
# not mention are the ones a maintainer finds: the mode that was never run,
|
|
6
|
+
# the comparer that was assumed to be the default, the caller that was
|
|
7
|
+
# assumed not to exist. The declaration names them before the review, so
|
|
8
|
+
# the reviewer can attack each one and a gap left out becomes a statement
|
|
9
|
+
# that was false, not an oversight.
|
|
10
|
+
#
|
|
11
|
+
# The first run writes a declaration for the clone's head and prints its
|
|
12
|
+
# path; fill it in. Later runs check it:
|
|
13
|
+
# not-tested: what this change was not run against, at least one line;
|
|
14
|
+
# "none" is refused, because something always was not
|
|
15
|
+
# assumptions: one line each, "- <assumption> | if wrong: <what breaks,
|
|
16
|
+
# for whom> | checked by: <the command and its result, or
|
|
17
|
+
# unchecked>"
|
|
18
|
+
#
|
|
19
|
+
# Pass the declaration to the review as a --text, so the reviewer attacks it
|
|
20
|
+
# and the review is bound to it. file-pr.sh requires a passing declaration
|
|
21
|
+
# for the head, and that the review read it.
|
|
22
|
+
#
|
|
23
|
+
# Usage:
|
|
24
|
+
# bash declare.sh [CLONE]
|
|
25
|
+
# bash declare.sh --help
|
|
26
|
+
#
|
|
27
|
+
# Writes or reads $HOUSEBROKEN_HOME/declarations/<head>.md. Exits 0 when the
|
|
28
|
+
# declaration passes, 1 when it was just written or does not pass, 2 on a
|
|
29
|
+
# usage error.
|
|
30
|
+
set -u
|
|
31
|
+
|
|
32
|
+
usage() { awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; }
|
|
33
|
+
|
|
34
|
+
case "${1:-}" in
|
|
35
|
+
--help|-h) usage; exit 0 ;;
|
|
36
|
+
-*) echo "REFUSED: unknown option $1" >&2; exit 2 ;;
|
|
37
|
+
esac
|
|
38
|
+
clone="${1:-.}"
|
|
39
|
+
head=$(git -C "$clone" rev-parse HEAD 2>/dev/null) || { echo "REFUSED: $clone is not a git clone" >&2; exit 2; }
|
|
40
|
+
home="${HOUSEBROKEN_HOME:-$HOME/.housebroken}"
|
|
41
|
+
decl="$home/declarations/$head.md"
|
|
42
|
+
|
|
43
|
+
if [ ! -f "$decl" ]; then
|
|
44
|
+
mkdir -p "$home/declarations"
|
|
45
|
+
cat > "$decl" <<EOF
|
|
46
|
+
head: $head
|
|
47
|
+
|
|
48
|
+
not-tested:
|
|
49
|
+
-
|
|
50
|
+
|
|
51
|
+
assumptions:
|
|
52
|
+
- <assumption> | if wrong: <what breaks, for whom> | checked by: <command and result, or unchecked>
|
|
53
|
+
EOF
|
|
54
|
+
echo "declare: fill in $decl, then run this again"
|
|
55
|
+
exit 1
|
|
56
|
+
fi
|
|
57
|
+
|
|
58
|
+
bad=""
|
|
59
|
+
tr -d '\r' < "$decl" | grep -q "^head: $head\$" || bad="$bad
|
|
60
|
+
the head line is not $head"
|
|
61
|
+
section() { tr -d '\r' < "$decl" | awk -v s="$1:" '$0 == s {on = 1; next} /^[a-z-]+:/ {on = 0} on && /^- */ {sub(/^- */, ""); print}' | sed 's/[[:space:]]*$//' | grep -v '^$'; }
|
|
62
|
+
nt=$(section not-tested)
|
|
63
|
+
if [ -z "$nt" ]; then
|
|
64
|
+
bad="$bad
|
|
65
|
+
not-tested: names nothing; one line per item, each starting with '- '"
|
|
66
|
+
elif printf '%s\n' "$nt" | grep -qixE 'none\.?|n/a|nothing\.?'; then
|
|
67
|
+
bad="$bad
|
|
68
|
+
not-tested: says none; something always was not tested"
|
|
69
|
+
fi
|
|
70
|
+
as=$(section assumptions)
|
|
71
|
+
[ -n "$as" ] || bad="$bad
|
|
72
|
+
assumptions: lists none"
|
|
73
|
+
while IFS= read -r a; do
|
|
74
|
+
[ -n "$a" ] || continue
|
|
75
|
+
case "$a" in
|
|
76
|
+
*"<assumption>"*|*"<what breaks"*|*"<command and result"*) bad="$bad
|
|
77
|
+
an assumption is still the template line" ;;
|
|
78
|
+
*"| if wrong: "*"| checked by: "*) ;;
|
|
79
|
+
*) bad="$bad
|
|
80
|
+
an assumption lacks '| if wrong: ... | checked by: ...': $a" ;;
|
|
81
|
+
esac
|
|
82
|
+
done <<EOF
|
|
83
|
+
$as
|
|
84
|
+
EOF
|
|
85
|
+
|
|
86
|
+
if [ -n "$bad" ]; then
|
|
87
|
+
echo "REFUSED: $decl does not pass:$bad"
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
echo "declare: $decl passes ($(printf '%s\n' "$nt" | grep -c .) not tested, $(printf '%s\n' "$as" | grep -c .) assumptions)"
|
|
@@ -263,12 +263,41 @@ if [ "$since" -lt 600 ]; then
|
|
|
263
263
|
echo "REFUSED: your last pull request on $repo was filed ${since}s ago - wait until ten minutes have passed" >&2
|
|
264
264
|
exit 2
|
|
265
265
|
fi
|
|
266
|
+
# The experiment the body describes was run by proof.sh, not recounted, and
|
|
267
|
+
# against the base branch-check measured; the author's declaration of what
|
|
268
|
+
# was not tested passes and names a proof that was waived; and the review
|
|
269
|
+
# read the declaration. Proof and declaration come before the review because
|
|
270
|
+
# the review is bound to the declaration.
|
|
271
|
+
proof="$home/proofs/$head"
|
|
272
|
+
if [ ! -f "$proof" ]; then
|
|
273
|
+
echo "REFUSED: no proof for head ${head:0:12} - run proof.sh --test 'COMMAND', or --none 'REASON' when nothing can fail first" >&2
|
|
274
|
+
exit 2
|
|
275
|
+
fi
|
|
276
|
+
decl="$home/declarations/$head.md"
|
|
277
|
+
if ! declared=$(bash "$(dirname "$0")/declare.sh" . 2>&1); then
|
|
278
|
+
echo "$declared" >&2
|
|
279
|
+
exit 2
|
|
280
|
+
fi
|
|
281
|
+
if grep -q '^proof: none' "$proof"; then
|
|
282
|
+
reason=$(sed -n 's/^reason: //p' "$proof" | tr -d '\r')
|
|
283
|
+
if ! grep -qF -- "$reason" "$decl"; then
|
|
284
|
+
echo "REFUSED: the proof for ${head:0:12} is none ($reason) and the declaration does not say so - add it under not-tested, so the reviewer attacks it" >&2
|
|
285
|
+
exit 2
|
|
286
|
+
fi
|
|
287
|
+
echo "note: no red-green proof for ${head:0:12}: $reason" >&2
|
|
288
|
+
else
|
|
289
|
+
proved=$(sed -n 's/^base: //p' "$proof" | tr -d '\r')
|
|
290
|
+
if [ "$proved" != "$stamped" ]; then
|
|
291
|
+
echo "REFUSED: the proof for ${head:0:12} ran against base ${proved:0:12}, branch-check measured ${stamped:0:12} - rerun proof.sh --base $checked" >&2
|
|
292
|
+
exit 2
|
|
293
|
+
fi
|
|
294
|
+
fi
|
|
266
295
|
review="$home/reviews/review-${repo%%/*}-${repo##*/}-${head:0:12}.md"
|
|
267
296
|
if [ ! -f "$review" ]; then
|
|
268
|
-
echo "REFUSED: no adversarial review at $review - run review.sh brief and have a different model attack this head" >&2
|
|
297
|
+
echo "REFUSED: no adversarial review at $review - run review.sh brief with --text $decl and have a different model attack this head" >&2
|
|
269
298
|
exit 2
|
|
270
299
|
fi
|
|
271
|
-
bash "$(dirname "$0")/review.sh" check "$review" --clone . --text "$bodyfile" >/dev/null || exit 2
|
|
300
|
+
bash "$(dirname "$0")/review.sh" check "$review" --clone . --text "$bodyfile" --text "$decl" >/dev/null || exit 2
|
|
272
301
|
|
|
273
302
|
if [ "${FILE_PR_DRY:-0}" = "1" ]; then
|
|
274
303
|
printf 'DRY RUN, not filed:\ngh pr create -R %s' "$repo"
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# proof.sh - run the red-first experiment and stamp what it showed.
|
|
3
|
+
#
|
|
4
|
+
# "The new test fails on main and passes with the patch" is the claim every
|
|
5
|
+
# pull request rests on, and the ledger has five runs that reported it while
|
|
6
|
+
# seeing nothing: a worktree whose checkout did nothing, a refused checkout
|
|
7
|
+
# that left the patch in place, a soft reset, a filtered count, a test that
|
|
8
|
+
# never compiled. This runs the experiment itself. On the clone's head it
|
|
9
|
+
# runs the test command; then it swaps every changed file that is not a test
|
|
10
|
+
# back to its content at the merge base, by content and checked by hash, so
|
|
11
|
+
# the tests stay and the fix goes; runs the command again; and restores the
|
|
12
|
+
# head from the commit, checked by hash. The stamp is written only when the
|
|
13
|
+
# base arm fails, the head arm passes, and the two arms ran different code.
|
|
14
|
+
#
|
|
15
|
+
# A change with nothing to prove red, a documentation fix or a refactor
|
|
16
|
+
# whose behaviour the existing suite already pins, says so with --none and a
|
|
17
|
+
# reason; the stamp records the reason and the filing prints it.
|
|
18
|
+
#
|
|
19
|
+
# Usage:
|
|
20
|
+
# bash proof.sh [CLONE] --test 'COMMAND' [--base REF] [--keep PATH]...
|
|
21
|
+
# [--fix PATH]... [--base-file PATH=FILE]...
|
|
22
|
+
# bash proof.sh [CLONE] --none 'REASON'
|
|
23
|
+
# bash proof.sh --help
|
|
24
|
+
#
|
|
25
|
+
# CLONE defaults to the current directory. REF defaults to the base the
|
|
26
|
+
# branch-check stamp for the head names, then to the first of upstream/main,
|
|
27
|
+
# upstream/master, origin/main and origin/master that resolves. COMMAND runs
|
|
28
|
+
# with bash -c from the clone's top; name the new test in it so the run is
|
|
29
|
+
# that test, not the whole suite. A changed file is taken for a test when its
|
|
30
|
+
# path says test or spec; --keep PATH keeps another changed file at the head
|
|
31
|
+
# in the base arm, such as a fixture the test reads; --fix PATH takes a file
|
|
32
|
+
# away although its path says test, such as src/testing/util.go. When one
|
|
33
|
+
# file carries both the test and the fix, a Rust module with its #[cfg(test)]
|
|
34
|
+
# block, --base-file PATH=FILE puts FILE's content, the base plus the test,
|
|
35
|
+
# at PATH in the base arm.
|
|
36
|
+
#
|
|
37
|
+
# Writes $HOUSEBROKEN_HOME/proofs/<head>, which file-pr.sh requires.
|
|
38
|
+
# Exits 0 when the stamp is written, 1 when the experiment does not show the
|
|
39
|
+
# fix, 2 on a usage error or when the clone cannot be swapped safely.
|
|
40
|
+
set -u
|
|
41
|
+
|
|
42
|
+
usage() { awk 'NR>1 && /^#/ {sub(/^# ?/, ""); print; next} NR>1 {exit}' "$0"; }
|
|
43
|
+
|
|
44
|
+
home="${HOUSEBROKEN_HOME:-$HOME/.housebroken}"
|
|
45
|
+
clone="."; cmd=""; none=""; base=""
|
|
46
|
+
keeps=(); forces=(); basefiles=()
|
|
47
|
+
while [ "$#" -gt 0 ]; do
|
|
48
|
+
case "$1" in
|
|
49
|
+
--help|-h) usage; exit 0 ;;
|
|
50
|
+
--test) cmd="${2:-}"; [ -n "$cmd" ] || { echo "REFUSED: --test needs the command" >&2; exit 2; }; shift ;;
|
|
51
|
+
--none) none="${2:-}"; [ -n "$none" ] || { echo "REFUSED: --none needs the reason" >&2; exit 2; }; shift ;;
|
|
52
|
+
--base) base="${2:-}"; [ -n "$base" ] || { echo "REFUSED: --base needs a ref" >&2; exit 2; }; shift ;;
|
|
53
|
+
--keep) [ -n "${2:-}" ] || { echo "REFUSED: --keep needs a path" >&2; exit 2; }; keeps+=("$2"); shift ;;
|
|
54
|
+
--fix) [ -n "${2:-}" ] || { echo "REFUSED: --fix needs a path" >&2; exit 2; }; forces+=("$2"); shift ;;
|
|
55
|
+
--base-file)
|
|
56
|
+
case "${2:-}" in ?*=?*) ;; *) echo "REFUSED: --base-file needs PATH=FILE" >&2; exit 2 ;; esac
|
|
57
|
+
[ -f "${2#*=}" ] || { echo "REFUSED: --base-file: ${2#*=} does not exist" >&2; exit 2; }
|
|
58
|
+
basefiles+=("$2"); shift ;;
|
|
59
|
+
-*) echo "REFUSED: unknown option $1" >&2; exit 2 ;;
|
|
60
|
+
*) clone="$1" ;;
|
|
61
|
+
esac
|
|
62
|
+
shift
|
|
63
|
+
done
|
|
64
|
+
[ -n "$cmd" ] || [ -n "$none" ] || { echo "REFUSED: pass --test 'COMMAND', or --none 'REASON'" >&2; exit 2; }
|
|
65
|
+
[ -z "$cmd" ] || [ -z "$none" ] || { echo "REFUSED: --test and --none are exclusive" >&2; exit 2; }
|
|
66
|
+
if ! top=$(git -C "$clone" rev-parse --show-toplevel 2>/dev/null) || ! cd "$top"; then
|
|
67
|
+
echo "REFUSED: $clone is not a git clone" >&2
|
|
68
|
+
exit 2
|
|
69
|
+
fi
|
|
70
|
+
head=$(git rev-parse HEAD)
|
|
71
|
+
mkdir -p "$home/proofs"
|
|
72
|
+
stamp="$home/proofs/$head"
|
|
73
|
+
when=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
74
|
+
|
|
75
|
+
if [ -n "$none" ]; then
|
|
76
|
+
printf 'head: %s\nproof: none\nreason: %s\nchecked: %s\n' "$head" "$none" "$when" > "$stamp"
|
|
77
|
+
echo "proof: none recorded for ${head:0:12}: $none"
|
|
78
|
+
exit 0
|
|
79
|
+
fi
|
|
80
|
+
|
|
81
|
+
[ -z "$(git status --porcelain)" ] || { echo "REFUSED: the working tree is dirty, so the arms would not be the commits; after a proof run that was killed, git checkout -- . puts the head back" >&2; exit 2; }
|
|
82
|
+
[ -n "$base" ] || base=$(sed -n 's/^base-ref: //p' "$home/branch-checks/$head" 2>/dev/null | tr -d '\r')
|
|
83
|
+
if [ -z "$base" ]; then
|
|
84
|
+
for candidate in upstream/main upstream/master origin/main origin/master; do
|
|
85
|
+
if git rev-parse --verify -q "$candidate" >/dev/null 2>&1; then base="$candidate"; break; fi
|
|
86
|
+
done
|
|
87
|
+
fi
|
|
88
|
+
[ -n "$base" ] || { echo "REFUSED: no --base, no branch-check stamp for ${head:0:12} to read it from, and no upstream or origin main or master" >&2; exit 2; }
|
|
89
|
+
mb=$(git merge-base "$base" HEAD 2>/dev/null) || { echo "REFUSED: $base and HEAD share no history" >&2; exit 2; }
|
|
90
|
+
|
|
91
|
+
listed() { local n="$1" k; shift; for k in "$@"; do [ "$n" = "$k" ] && return 0; done; return 1; }
|
|
92
|
+
base_file_for() { local e; for e in ${basefiles[@]+"${basefiles[@]}"}; do case "$e" in "$1="*) printf '%s' "${e#*=}"; return 0 ;; esac; done; return 1; }
|
|
93
|
+
mode_in() { git ls-tree "$1" -- "$2" | cut -c1-6; }
|
|
94
|
+
# Renames are read as the delete and the add they are, so the base arm gets
|
|
95
|
+
# the old path back; quotePath off, so a non-ASCII path is the path.
|
|
96
|
+
changed=$(git -c core.quotePath=false diff --name-only --no-renames "$mb" HEAD)
|
|
97
|
+
for p in ${keeps[@]+"${keeps[@]}"} ${forces[@]+"${forces[@]}"} ${basefiles[@]+"${basefiles[@]%%=*}"}; do
|
|
98
|
+
printf '%s\n' "$changed" | grep -qxF -- "$p" || { echo "REFUSED: $p is not a file this branch changes (paths are as git diff --name-only prints them)" >&2; exit 2; }
|
|
99
|
+
done
|
|
100
|
+
fixes=(); kept=()
|
|
101
|
+
while IFS= read -r f; do
|
|
102
|
+
[ -n "$f" ] || continue
|
|
103
|
+
if listed "$f" ${keeps[@]+"${keeps[@]}"}; then kept+=("$f"); continue; fi
|
|
104
|
+
if ! listed "$f" ${forces[@]+"${forces[@]}"}; then
|
|
105
|
+
case "$f" in *test*|*Test*|*spec*) kept+=("$f"); continue ;; esac
|
|
106
|
+
fi
|
|
107
|
+
for t in HEAD "$mb"; do
|
|
108
|
+
case "$(mode_in "$t" "$f")" in
|
|
109
|
+
100644|100755|"") ;;
|
|
110
|
+
*) echo "REFUSED: $f is not a regular file in $t (a symlink, a submodule or a directory); the experiment swaps regular files only" >&2; exit 2 ;;
|
|
111
|
+
esac
|
|
112
|
+
done
|
|
113
|
+
fixes+=("$f")
|
|
114
|
+
done <<EOF
|
|
115
|
+
$changed
|
|
116
|
+
EOF
|
|
117
|
+
if [ "${#fixes[@]}" -eq 0 ]; then
|
|
118
|
+
echo "REFUSED: every changed file is a test or kept (${kept[*]}); there is no fix to take away - --fix PATH takes one away although its path says test" >&2
|
|
119
|
+
exit 2
|
|
120
|
+
fi
|
|
121
|
+
echo "proof: taking away ${#fixes[@]} file(s): ${fixes[*]}${kept[0]:+; keeping ${#kept[@]} at the head: ${kept[*]}}"
|
|
122
|
+
|
|
123
|
+
hash_of() { if [ -f "$1" ]; then git hash-object -- "$1"; else echo absent; fi; }
|
|
124
|
+
hashes() { local f; for f in "${fixes[@]}"; do printf '%s %s\n' "$(hash_of "$f")" "$f"; done; }
|
|
125
|
+
run_arm() { # label -> rc in arm_rc, output tail in arm_out
|
|
126
|
+
arm_out=$(bash -c "$cmd" 2>&1); arm_rc=$?
|
|
127
|
+
arm_out=$(printf '%s\n' "$arm_out" | tail -15)
|
|
128
|
+
echo "== $1 arm: exit $arm_rc"
|
|
129
|
+
printf '%s\n' "$arm_out" | sed 's/^/ /'
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
head_hashes=$(hashes)
|
|
133
|
+
run_arm head
|
|
134
|
+
head_rc=$arm_rc; head_out=$arm_out
|
|
135
|
+
|
|
136
|
+
# The base arm: each fix file by its content at the merge base, through the
|
|
137
|
+
# clone's own filters so line endings are what a checkout gives, or gone
|
|
138
|
+
# when the branch added it. The restore below runs whatever happens in
|
|
139
|
+
# between. It restores from the commit, not the index: the tree was clean,
|
|
140
|
+
# so the two agree, and the commit brings the mode and the line endings back
|
|
141
|
+
# with the content, which a plain write does not.
|
|
142
|
+
restore() {
|
|
143
|
+
local f
|
|
144
|
+
for f in "${fixes[@]}"; do
|
|
145
|
+
if git cat-file -e "HEAD:./$f" 2>/dev/null; then
|
|
146
|
+
git checkout -q HEAD -- "$f"
|
|
147
|
+
else
|
|
148
|
+
rm -f -- "$f"
|
|
149
|
+
fi
|
|
150
|
+
done
|
|
151
|
+
}
|
|
152
|
+
trap restore EXIT
|
|
153
|
+
for f in "${fixes[@]}"; do
|
|
154
|
+
if bf=$(base_file_for "$f"); then
|
|
155
|
+
cat "$bf" > "$f"
|
|
156
|
+
elif git cat-file -e "$mb:./$f" 2>/dev/null; then
|
|
157
|
+
mkdir -p "$(dirname "$f")" && git cat-file --filters "$mb:./$f" > "$f"
|
|
158
|
+
[ "$(mode_in "$mb" "$f")" != 100755 ] || chmod +x "$f"
|
|
159
|
+
else
|
|
160
|
+
rm -f -- "$f"
|
|
161
|
+
fi
|
|
162
|
+
done
|
|
163
|
+
base_hashes=$(hashes)
|
|
164
|
+
run_arm base
|
|
165
|
+
base_rc=$arm_rc; base_out=$arm_out
|
|
166
|
+
restore
|
|
167
|
+
trap - EXIT
|
|
168
|
+
|
|
169
|
+
if [ "$(hashes)" != "$head_hashes" ]; then
|
|
170
|
+
echo "REFUSED: the head did not come back byte for byte; check the clone before anything else:" >&2
|
|
171
|
+
diff <(printf '%s\n' "$head_hashes") <(hashes) | sed 's/^/ /' >&2
|
|
172
|
+
exit 2
|
|
173
|
+
fi
|
|
174
|
+
dirt=$(git status --porcelain --untracked-files=no)
|
|
175
|
+
if [ -n "$dirt" ]; then
|
|
176
|
+
echo "REFUSED: the test command changed tracked files; restore them with git checkout -- <path>, then make the command leave the tree alone or commit what it generates:" >&2
|
|
177
|
+
printf '%s\n' "$dirt" | sed 's/^/ /' >&2
|
|
178
|
+
exit 2
|
|
179
|
+
fi
|
|
180
|
+
left=$(git status --porcelain | sed -n 's/^?? //p')
|
|
181
|
+
[ -z "$left" ] || echo "note: the test command left untracked files, which branch-check refuses: $(printf '%s' "$left" | tr '\n' ' ')"
|
|
182
|
+
|
|
183
|
+
failed=""
|
|
184
|
+
[ "$base_hashes" != "$head_hashes" ] || failed="the two arms ran the same files, so they did not test the fix"
|
|
185
|
+
case "$base_rc" in
|
|
186
|
+
126|127) failed="${failed:+$failed; }the base arm's command did not run (exit $base_rc: $(printf '%s' "$base_out" | head -1)); a test named in the command has to exist in both arms" ;;
|
|
187
|
+
0) failed="${failed:+$failed; }the test passes without the fix, so it does not show the bug${kept[0]:+ (or the fix is in a kept file: ${kept[*]})}" ;;
|
|
188
|
+
esac
|
|
189
|
+
[ "$head_rc" -eq 0 ] || failed="${failed:+$failed; }the test fails with the fix"
|
|
190
|
+
if [ -n "$failed" ]; then
|
|
191
|
+
echo "proof: not shown - $failed"
|
|
192
|
+
exit 1
|
|
193
|
+
fi
|
|
194
|
+
|
|
195
|
+
{
|
|
196
|
+
printf 'head: %s\nbase: %s\nproof: red-green\ncommand: %s\nchecked: %s\n' "$head" "$mb" "$cmd" "$when"
|
|
197
|
+
printf 'base-exit: %s\nhead-exit: %s\n' "$base_rc" "$head_rc"
|
|
198
|
+
printf '%s\n' "$head_hashes" | sed 's/^/head-file: /'
|
|
199
|
+
printf '%s\n' "$base_hashes" | sed 's/^/base-file: /'
|
|
200
|
+
for f in ${kept[@]+"${kept[@]}"}; do printf 'kept-file: %s\n' "$f"; done
|
|
201
|
+
for e in ${basefiles[@]+"${basefiles[@]}"}; do printf 'base-from: %s\n' "$e"; done
|
|
202
|
+
printf 'base-output:\n%s\nhead-output:\n%s\n' "$(printf '%s\n' "$base_out" | sed 's/^/ /')" "$(printf '%s\n' "$head_out" | sed 's/^/ /')"
|
|
203
|
+
} > "$stamp"
|
|
204
|
+
echo "proof: red on the base, green on the head, for ${head:0:12}; stamped at $stamp"
|
|
@@ -193,12 +193,22 @@ EOF
|
|
|
193
193
|
done
|
|
194
194
|
cat <<'EOF'
|
|
195
195
|
2. The proof: the new test fails on the base and passes with the change. Swap file content with git show <base>:<path> rather than switching branches, compare hashes, and make sure the two arms differ. Ask what each step would print if it did nothing.
|
|
196
|
+
EOF
|
|
197
|
+
if [ -n "$clone" ]; then
|
|
198
|
+
if [ -f "$home/proofs/$head" ]; then
|
|
199
|
+
printf ' The author'"'"'s run, stamped by proof.sh; rerun it. A base arm that never ran the test (nothing collected, a file missing, a build that did not happen), or a swapped file the test needed for a reason other than the fix, is a finding:\n'
|
|
200
|
+
sed 's/^/ /' "$home/proofs/$head"
|
|
201
|
+
else
|
|
202
|
+
printf ' No proof stamp exists for this head: the author has not run proof.sh. Say so.\n'
|
|
203
|
+
fi
|
|
204
|
+
fi
|
|
205
|
+
cat <<'EOF'
|
|
196
206
|
3. The project's own CI, read from its workflow files: tests, lint, format, API and mutation checks, run on the change.
|
|
197
207
|
4. The form: write the changed block the way the best engineer in this language would, in this repository's own idioms and at the language version its manifest names, then measure the diff against it. Fewer moving parts, no state the loop already carries, no extra pass, no extra allocation, no counter beside a cursor that could be bounded itself. When yours is smaller or clearer, the finding is SHOULD FIX with your version in full; when the diff is already that form, say which alternatives you tried and why each is worse. A maintainer who asks for a rework is a review round you could have spent on the merge.
|
|
198
208
|
5. Scope: every changed line is needed for what was asked, nothing is unrequested, no comment appears where the surrounding code has none, and documentation, README and CHANGELOG that describe the changed behaviour change with it.
|
|
199
209
|
6. Prior art: nothing duplicates an open pull request or argues with a ruling in a closed issue.
|
|
200
210
|
7. The text: it leads with the fact; no verdict on the reviewer, no thanks, apology or offer of help; a reply runs one to three sentences and a body stays under 120 words; no footer, trailer or typographic dash; every sentence is true as literally worded.
|
|
201
|
-
8. Anything else that would embarrass the author in front of this maintainer.
|
|
211
|
+
8. Anything else that would embarrass the author in front of this maintainer. When a text is the author's declaration (not-tested and assumptions), rerun every "checked by", and test the assumptions marked unchecked yourself: a false assumption is a finding, and so is one the change rests on that the declaration leaves out. A "checked by" that cannot be rerun as written, because it names no command or no result, is a finding too.
|
|
202
212
|
EOF
|
|
203
213
|
if [ -n "$clone" ]; then
|
|
204
214
|
cat <<'EOF'
|
|
@@ -110,6 +110,17 @@ report it in one line and do not proceed to the next step.
|
|
|
110
110
|
invariant instead of resetting it, and its grid has a row for every
|
|
111
111
|
order the two are called in, the same value twice and null included. A
|
|
112
112
|
reviewer lists the empty cells otherwise.
|
|
113
|
+
Then run it through the gate: `housebroken proof <clone> --test '<the
|
|
114
|
+
command that runs the new test>'` runs the test on the head and again with
|
|
115
|
+
every changed file that is not a test swapped back to the base, restores
|
|
116
|
+
the head by hash, and stamps it only when the base arm fails and the head
|
|
117
|
+
arm passes. `--keep <path>` keeps a fixture the test reads; `--fix <path>`
|
|
118
|
+
takes a file away although its path says test; `--base-file
|
|
119
|
+
<path>=<file>` gives the base arm a file that carries both the test and
|
|
120
|
+
the fix, a Rust module with its `#[cfg(test)]` block, as the base plus
|
|
121
|
+
the test. A change with
|
|
122
|
+
nothing that can fail first, documentation or a pinned refactor, records
|
|
123
|
+
`--none '<reason>'`. The filing requires the stamp.
|
|
113
124
|
|
|
114
125
|
5. **Run their CI, not yours.**
|
|
115
126
|
Run the project's own test target on the fresh clone, and every gate its
|
|
@@ -182,7 +193,12 @@ report it in one line and do not proceed to the next step.
|
|
|
182
193
|
makes so you can falsify each one against the code, and it stamps the
|
|
183
194
|
head it passed, which the filing requires. Then the adversarial review:
|
|
184
195
|
`housebroken review brief --author <your model> --repo owner/repo --clone <clone> --text <body file>`
|
|
185
|
-
names the reviewer and prints its brief
|
|
196
|
+
names the reviewer and prints its brief; first run `housebroken declare
|
|
197
|
+
<clone>`, fill in what the change was not tested against and every
|
|
198
|
+
assumption it rests on ("- <assumption> | if wrong: <what breaks> |
|
|
199
|
+
checked by: <command and result, or unchecked>"), and pass that file to
|
|
200
|
+
the brief as a second `--text`, so the reviewer attacks it and the review
|
|
201
|
+
is bound to it. The reviewer is never the model
|
|
186
202
|
that wrote the change: one tier down, Fable to Opus and Opus to Sonnet,
|
|
187
203
|
and never below Sonnet, so Sonnet's work goes to Opus. Hand the brief to a
|
|
188
204
|
fresh agent running that model; anything of yours it reads is for it to
|
|
@@ -216,8 +232,9 @@ report it in one line and do not proceed to the next step.
|
|
|
216
232
|
has not stamped the exact head against the branch it is filed into
|
|
217
233
|
(`--base` to both when the project takes pull requests on a branch other
|
|
218
234
|
than its default), when the upstream has since changed a file the branch
|
|
219
|
-
changes, when there is no
|
|
220
|
-
|
|
235
|
+
changes, when there is no proof stamp for the head, when the declaration
|
|
236
|
+
does not pass, when there is no POST AS IS review of the exact head, body
|
|
237
|
+
and declaration being filed, when you already have three open pull requests on the
|
|
221
238
|
repository, and when your last one there is under ten minutes old.
|
|
222
239
|
A refusal is a result; fix the cause.
|
|
223
240
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.8.0"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.7.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|