housebroken-cli 0.6.7__tar.gz → 0.6.9__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.6.7 → housebroken_cli-0.6.9}/PKG-INFO +3 -3
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/README.md +2 -2
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/pyproject.toml +1 -1
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/branch-check.sh +58 -4
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/file-pr.sh +30 -4
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/prior-art.sh +31 -3
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/review.sh +23 -2
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/verify-filed-pr.sh +1 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/skills/housebroken/SKILL.md +18 -5
- housebroken_cli-0.6.9/src/housebroken/__init__.py +1 -0
- housebroken_cli-0.6.7/src/housebroken/__init__.py +0 -1
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/.gitignore +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/LICENSE +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/bin/housebroken +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/hatch_build.py +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/check-ai-policy.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/claim-check.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/comment-census.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/comment-check.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/diff-defaults.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/distinct-outside.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/file-issue.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/fork-hygiene.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/inbox.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/notes.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/pr-sweep.sh +0 -0
- {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/src/housebroken/cli.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: housebroken-cli
|
|
3
|
-
Version: 0.6.
|
|
3
|
+
Version: 0.6.9
|
|
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
|
|
@@ -76,8 +76,8 @@ Before the first pull request to an organization: CLA, DCO, signed-commit requir
|
|
|
76
76
|
A memory-safety, remote-abort, injection or path-escape finding goes by the project's SECURITY.md route, privately, and never becomes a public pull request until the project answers.
|
|
77
77
|
|
|
78
78
|
**9. File through the gate.**
|
|
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,
|
|
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
|
|
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 no build log shows firing, and reflection added to a .NET project that declares AOT or trim support without an AOT publish log; it prints every absolute claim in added prose for falsification, 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 and the gate refuses one that does not. 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
81
|
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old, a policy printout under a day old that does not read BAN, a body that discloses when the policy asks, a branch stamp for the exact head, and a POST AS IS review of that head and that body.
|
|
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
|
|
|
@@ -64,8 +64,8 @@ Before the first pull request to an organization: CLA, DCO, signed-commit requir
|
|
|
64
64
|
A memory-safety, remote-abort, injection or path-escape finding goes by the project's SECURITY.md route, privately, and never becomes a public pull request until the project answers.
|
|
65
65
|
|
|
66
66
|
**9. File through the gate.**
|
|
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,
|
|
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
|
|
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 no build log shows firing, and reflection added to a .NET project that declares AOT or trim support without an AOT publish log; it prints every absolute claim in added prose for falsification, 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 and the gate refuses one that does not. 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
69
|
`file-pr.sh` is the only way a pull request is opened. It refuses without a prior-art printout under a day old, a policy printout under a day old that does not read BAN, a body that discloses when the policy asks, a branch stamp for the exact head, and a POST AS IS review of that head and that body.
|
|
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
|
|
|
@@ -7,20 +7,29 @@
|
|
|
7
7
|
# .gitignore excludes; a working artifact; a commit carrying a tool trailer, a
|
|
8
8
|
# session link or a tool identity; a sign-off naming a username; a branch that
|
|
9
9
|
# is not on top of its base; and a comment added to code, test files included,
|
|
10
|
-
# unless a maintainer asked for it (comment-check.sh)
|
|
11
|
-
#
|
|
10
|
+
# unless a maintainer asked for it (comment-check.sh); a warning suppression the
|
|
11
|
+
# branch adds, until a build log shows the warning it suppresses firing; and
|
|
12
|
+
# reflection added to a .NET project that declares itself AOT compatible or
|
|
13
|
+
# trimmable, until an AOT publish log is given. shouldly #1335 suppressed an
|
|
14
|
+
# IL2075 that never fired, and the reflection under it found nothing under
|
|
15
|
+
# Native AOT. Absolute claims in added prose are printed for you to falsify one
|
|
16
|
+
# by one; some of them are true.
|
|
12
17
|
#
|
|
13
18
|
# A branch that passes gets a stamp at $HOUSEBROKEN_HOME/branch-checks/<head>,
|
|
14
19
|
# which file-pr.sh requires for the head it files.
|
|
15
20
|
#
|
|
16
21
|
# Usage:
|
|
17
22
|
# bash branch-check.sh [CLONE] [--base REF] [--asked URL]
|
|
23
|
+
# [--warning-log FILE] [--aot-log FILE]
|
|
18
24
|
# bash branch-check.sh --help
|
|
19
25
|
#
|
|
20
26
|
# CLONE defaults to the current directory. REF defaults to the first of
|
|
21
27
|
# upstream/main, upstream/master, origin/main, origin/master that resolves.
|
|
22
28
|
# --asked URL is the maintainer's request for documentation, which turns the
|
|
23
|
-
# comment refusal into a check.
|
|
29
|
+
# comment refusal into a check. --warning-log FILE is a build log of the
|
|
30
|
+
# branch without its suppressions; it must name every warning id they suppress.
|
|
31
|
+
# --aot-log FILE is the output of an AOT publish (dotnet publish
|
|
32
|
+
# -p:PublishAot=true) of the branch, run and read.
|
|
24
33
|
#
|
|
25
34
|
# Exits 0 when nothing blocks, 1 on a refusal, 2 on a usage error.
|
|
26
35
|
set -u
|
|
@@ -32,6 +41,8 @@ home="${HOUSEBROKEN_HOME:-$HOME/.housebroken}"
|
|
|
32
41
|
clone="."
|
|
33
42
|
base=""
|
|
34
43
|
asked=""
|
|
44
|
+
warnlog=""
|
|
45
|
+
aotlog=""
|
|
35
46
|
while [ "$#" -gt 0 ]; do
|
|
36
47
|
case "$1" in
|
|
37
48
|
--help|-h) usage; exit 0 ;;
|
|
@@ -47,6 +58,16 @@ while [ "$#" -gt 0 ]; do
|
|
|
47
58
|
[ -n "$asked" ] || { echo "REFUSED: --asked needs the URL of the maintainer's ask" >&2; exit 2; }
|
|
48
59
|
;;
|
|
49
60
|
--asked=*) asked="${1#--asked=}" ;;
|
|
61
|
+
--warning-log)
|
|
62
|
+
shift
|
|
63
|
+
warnlog="${1:-}"
|
|
64
|
+
[ -f "$warnlog" ] || { echo "REFUSED: --warning-log needs a build log file" >&2; exit 2; }
|
|
65
|
+
;;
|
|
66
|
+
--aot-log)
|
|
67
|
+
shift
|
|
68
|
+
aotlog="${1:-}"
|
|
69
|
+
[ -f "$aotlog" ] || { echo "REFUSED: --aot-log needs an AOT publish log file" >&2; exit 2; }
|
|
70
|
+
;;
|
|
50
71
|
-*) echo "REFUSED: unknown option $1" >&2; exit 2 ;;
|
|
51
72
|
*) clone="$1" ;;
|
|
52
73
|
esac
|
|
@@ -161,6 +182,39 @@ case "$comment_rc" in
|
|
|
161
182
|
esac
|
|
162
183
|
printf '%s\n' "$comments" | grep -E '^CHECK: ' | sed 's/^/ /'
|
|
163
184
|
|
|
185
|
+
# A suppression hides a warning from the maintainer too, so it has to be shown
|
|
186
|
+
# that the warning exists. A suppression covering nothing usually covers code
|
|
187
|
+
# that is wrong in a way the analyzer cannot see.
|
|
188
|
+
added=$(git diff -U0 "$base..HEAD" -- . ':(exclude)*.md' ':(exclude)*.rst' ':(exclude)*.txt' 2>/dev/null \
|
|
189
|
+
| grep '^+' | grep -v '^+++' | sed 's/^+//')
|
|
190
|
+
suppressions=$(printf '%s\n' "$added" | grep -E 'UnconditionalSuppressMessage|SuppressMessage\(|#pragma warning disable|@SuppressWarnings|@Suppress\(|#!?\[allow\(|nolint|NOLINT|noqa|eslint-disable|@ts-ignore|@ts-expect-error|type: *ignore|pylint: *disable|rubocop:disable|swiftlint:disable')
|
|
191
|
+
if [ -n "$suppressions" ]; then
|
|
192
|
+
ids=$(printf '%s\n' "$suppressions" | grep -oE '\b[A-Z]{1,4}[0-9]{3,5}\b' | sort -u)
|
|
193
|
+
unshown=""
|
|
194
|
+
if [ -z "$warnlog" ]; then
|
|
195
|
+
unshown="no --warning-log given"
|
|
196
|
+
else
|
|
197
|
+
for id in $ids; do grep -q "$id" "$warnlog" || unshown="$unshown $id"; done
|
|
198
|
+
fi
|
|
199
|
+
if [ -n "$unshown" ]; then
|
|
200
|
+
block "the branch adds a warning suppression, and no build log shows the warning firing ($unshown)"
|
|
201
|
+
printf '%s\n' "$suppressions" | sed 's/^/ /'
|
|
202
|
+
fi
|
|
203
|
+
fi
|
|
204
|
+
|
|
205
|
+
# The trim analyzer does not flag object.GetType().GetProperty(...), so a clean
|
|
206
|
+
# build proves nothing about reflection under Native AOT; only a publish does.
|
|
207
|
+
aotdecl=$(git grep -lE '<(IsAotCompatible|IsTrimmable|PublishAot|EnableTrimAnalyzer|EnableAotAnalyzer)( [^>]*)?>[[:space:]]*true' HEAD -- '*.csproj' '*.props' '*.targets' 2>/dev/null \
|
|
208
|
+
| sed 's/^HEAD://' | tr '\n' ' ')
|
|
209
|
+
if [ -n "$aotdecl" ]; then
|
|
210
|
+
reflection=$(git diff -U0 "$base..HEAD" -- '*.cs' 2>/dev/null | grep '^+' | grep -v '^+++' | sed 's/^+//' \
|
|
211
|
+
| grep -E '\.Get(Property|Properties|Method|Methods|Field|Fields|Member|Members|Interfaces)\(|Activator\.CreateInstance|MakeGenericType|MakeGenericMethod|Type\.GetType\(|\.InvokeMember\(')
|
|
212
|
+
if [ -n "$reflection" ] && ! { [ -n "$aotlog" ] && grep -q "Generating native code" "$aotlog"; }; then
|
|
213
|
+
block "the branch adds reflection to a project that declares AOT or trim support (${aotdecl% }), and no --aot-log shows an AOT publish"
|
|
214
|
+
printf '%s\n' "$reflection" | sed 's/^/ /'
|
|
215
|
+
fi
|
|
216
|
+
fi
|
|
217
|
+
|
|
164
218
|
# Absolutes in added prose are the claims a maintainer can falsify in one grep.
|
|
165
219
|
claims=$(git diff -U0 "$base..HEAD" -- '*.md' '*.mdx' '*.rst' '*.txt' 2>/dev/null \
|
|
166
220
|
| grep '^+' | grep -v '^+++' | sed 's/^+//' \
|
|
@@ -184,6 +238,6 @@ fi
|
|
|
184
238
|
# The stamp file-pr.sh reads: this head passed, against this base, at this time.
|
|
185
239
|
head=$(git rev-parse HEAD)
|
|
186
240
|
mkdir -p "$home/branch-checks"
|
|
187
|
-
printf 'head: %s\nbase: %s\nchecked: %s\n' "$head" "$(git rev-parse "$base")" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$home/branch-checks/$head"
|
|
241
|
+
printf 'head: %s\nbase: %s\nbase-ref: %s\nchecked: %s\n' "$head" "$(git rev-parse "$base")" "$base" "$(date -u +%Y-%m-%dT%H:%M:%SZ)" > "$home/branch-checks/$head"
|
|
188
242
|
echo
|
|
189
243
|
echo "branch-check: nothing mechanical blocks this branch."
|
|
@@ -47,9 +47,11 @@ gates:
|
|
|
47
47
|
be under 24 hours old and not read RESULT: BAN; when it reads
|
|
48
48
|
RESULT: DISCLOSE the body must name the AI use
|
|
49
49
|
prose no tool footer, session trailer, co-author trailer, em dash or
|
|
50
|
-
en dash, in any case, in the --body-file or the --title
|
|
50
|
+
en dash, in any case, in the --body-file or the --title; a
|
|
51
|
+
body of at most 120 words
|
|
51
52
|
branch $HOUSEBROKEN_HOME/branch-checks/<head> must exist, written by
|
|
52
|
-
branch-check.sh when this head passed
|
|
53
|
+
branch-check.sh when this head passed, against the branch this
|
|
54
|
+
files into (--base, or the repository's default branch)
|
|
53
55
|
review run from the clone being filed; the report at
|
|
54
56
|
$HOUSEBROKEN_HOME/reviews/review-<owner>-<repo>-<head12>.md must
|
|
55
57
|
pass review.sh check for this head and the --body-file, and an
|
|
@@ -128,6 +130,7 @@ check_text() {
|
|
|
128
130
|
# as its own argument or after an equals sign.
|
|
129
131
|
bodyfile=""
|
|
130
132
|
title=""
|
|
133
|
+
filebase=""
|
|
131
134
|
pass=()
|
|
132
135
|
while [ "$#" -gt 0 ]; do
|
|
133
136
|
case "$1" in
|
|
@@ -135,8 +138,10 @@ while [ "$#" -gt 0 ]; do
|
|
|
135
138
|
--body-file=*) bodyfile="${1#--body-file=}"; pass+=("$1") ;;
|
|
136
139
|
-t|--title) title="${2:-}"; pass+=("$1" "${2:-}"); shift ;;
|
|
137
140
|
--title=*) title="${1#--title=}"; pass+=("$1") ;;
|
|
138
|
-
-B|--base
|
|
139
|
-
--base
|
|
141
|
+
-B|--base) filebase="${2:-}"; pass+=("$1" "${2:-}"); shift ;;
|
|
142
|
+
--base=*) filebase="${1#--base=}"; pass+=("$1") ;;
|
|
143
|
+
-l|--label|-a|--assignee|-r|--reviewer|-m|--milestone|-p|--project) pass+=("$1" "${2:-}"); shift ;;
|
|
144
|
+
--label=*|--assignee=*|--reviewer=*|--milestone=*|--project=*) pass+=("$1") ;;
|
|
140
145
|
-d|--draft|--no-maintainer-edit) pass+=("$1") ;;
|
|
141
146
|
*)
|
|
142
147
|
echo "REFUSED: file-pr.sh does not pass $1 to gh pr create: the body comes from one --body-file, the head is the branch you are on, the repository is the first argument" >&2
|
|
@@ -152,6 +157,11 @@ if [ "$bodyfile" = "-" ] || [ ! -f "$bodyfile" ]; then
|
|
|
152
157
|
fi
|
|
153
158
|
check_text "body file $bodyfile" "$(cat "$bodyfile")"
|
|
154
159
|
check_text "--title" "$title"
|
|
160
|
+
words=$(wc -w < "$bodyfile" | tr -d ' ')
|
|
161
|
+
if [ "$words" -gt 120 ]; then
|
|
162
|
+
echo "REFUSED: the body is $words words - say what was wrong, what the change does and how it was verified in 120 or fewer" >&2
|
|
163
|
+
exit 2
|
|
164
|
+
fi
|
|
155
165
|
if [ "$policy_result" = "DISCLOSE" ] && ! grep -qiE '\bAI\b|LLM|Claude|Copilot|ChatGPT|language model|assist' "$bodyfile"; then
|
|
156
166
|
echo "REFUSED: $repo requires AI use to be disclosed ($policy) and the body does not name it" >&2
|
|
157
167
|
exit 2
|
|
@@ -162,6 +172,22 @@ if [ ! -f "$home/branch-checks/$head" ]; then
|
|
|
162
172
|
echo "REFUSED: branch-check.sh has not passed head ${head:0:12} - run it from this clone first" >&2
|
|
163
173
|
exit 2
|
|
164
174
|
fi
|
|
175
|
+
# Every diff gate read the diff against the stamp's base; it must be the branch this files into.
|
|
176
|
+
checked=$(sed -n 's/^base-ref: //p' "$home/branch-checks/$head" | tr -d '\r')
|
|
177
|
+
if [ -z "$checked" ]; then
|
|
178
|
+
echo "REFUSED: the branch-check stamp for head ${head:0:12} names no base - rerun branch-check.sh" >&2
|
|
179
|
+
exit 2
|
|
180
|
+
fi
|
|
181
|
+
target="$filebase"
|
|
182
|
+
if [ -z "$target" ]; then
|
|
183
|
+
target=$(gh repo view "$repo" --json defaultBranchRef --jq .defaultBranchRef.name 2>/dev/null | tr -d '\r')
|
|
184
|
+
[ -n "$target" ] || { echo "REFUSED: cannot read the default branch of $repo - pass --base" >&2; exit 2; }
|
|
185
|
+
fi
|
|
186
|
+
case "$checked" in
|
|
187
|
+
"$target"|*/"$target") ;;
|
|
188
|
+
*) echo "REFUSED: branch-check.sh measured this head against $checked, but it is filed into $target - rerun branch-check.sh --base <remote>/$target" >&2
|
|
189
|
+
exit 2 ;;
|
|
190
|
+
esac
|
|
165
191
|
review="$home/reviews/review-${repo%%/*}-${repo##*/}-${head:0:12}.md"
|
|
166
192
|
if [ ! -f "$review" ]; then
|
|
167
193
|
echo "REFUSED: no adversarial review at $review - run review.sh brief and have a different model attack this head" >&2
|
|
@@ -9,7 +9,9 @@
|
|
|
9
9
|
# 2026-09-04) argued against a ruling already made in CLOSED issue #98, which
|
|
10
10
|
# nobody read, so this script prints the last non-author comment on every closed
|
|
11
11
|
# item. A term containing a slash or a dot is treated as a file path and the
|
|
12
|
-
# commit history of that path is scanned for the PRs that touched it
|
|
12
|
+
# commit history of that path is scanned for the PRs that touched it, and
|
|
13
|
+
# every open PR that changes it is listed, which no text search finds
|
|
14
|
+
# (Shopify/ruby-lsp #4103 deleted the file a fix was written against).
|
|
13
15
|
# Written 2026-09-07.
|
|
14
16
|
#
|
|
15
17
|
# Usage:
|
|
@@ -34,7 +36,8 @@ usage: prior-art.sh owner/repo <term>... [--out]
|
|
|
34
36
|
|
|
35
37
|
owner/repo the upstream repository to search
|
|
36
38
|
<term> a symbol, phrase or file path; a term with a slash or a dot is
|
|
37
|
-
also treated as a path
|
|
39
|
+
also treated as a path: its commit history is scanned and
|
|
40
|
+
every open PR that changes it is listed
|
|
38
41
|
--out save the printout under $HOUSEBROKEN_HOME/prior-art/
|
|
39
42
|
|
|
40
43
|
environment:
|
|
@@ -70,7 +73,8 @@ calls=0
|
|
|
70
73
|
items=0
|
|
71
74
|
tmp=$(mktemp) || { echo "prior-art.sh: cannot create temp file" >&2; exit 1; }
|
|
72
75
|
row=$(mktemp) || { echo "prior-art.sh: cannot create temp file" >&2; exit 1; }
|
|
73
|
-
|
|
76
|
+
openfiles=$(mktemp) || { echo "prior-art.sh: cannot create temp file" >&2; exit 1; }
|
|
77
|
+
trap 'rm -f "$tmp" "$row" "$openfiles"' EXIT
|
|
74
78
|
|
|
75
79
|
# last comment on a closed item written by somebody other than its author
|
|
76
80
|
last_word() {
|
|
@@ -132,6 +136,30 @@ for term in "${terms[@]}"; do
|
|
|
132
136
|
printf '| PR | #%s | merged | %s | %s | %s | |\n' "$num" "$date" "$author" "$subject" >>"$tmp"
|
|
133
137
|
items=$((items+1))
|
|
134
138
|
done <"$row"
|
|
139
|
+
|
|
140
|
+
# an open PR that changes the path is prior art even when no title or body names it
|
|
141
|
+
if [ ! -s "$openfiles" ]; then
|
|
142
|
+
# shellcheck disable=SC2016 # $p is a jq variable
|
|
143
|
+
gh pr list -R "$repo" --state open --limit 200 --json number,createdAt,author,title,changedFiles,files \
|
|
144
|
+
--jq '.[] | . as $p | (if .changedFiles > (.files | length) then ["MORE"] else [.files[].path] end)[] |
|
|
145
|
+
[($p.number|tostring), $p.createdAt[0:10], $p.author.login,
|
|
146
|
+
($p.title|gsub("[|]";"/")|gsub("[\n\r\t]";" ")), .] | @tsv' >"$openfiles" ||
|
|
147
|
+
{ echo "prior-art.sh: open PR listing failed for $repo" >&2; exit 1; }
|
|
148
|
+
calls=$((calls+1))
|
|
149
|
+
# GitHub returns at most 100 files per PR here; a larger PR is read in full
|
|
150
|
+
awk -F'\t' '$5 == "MORE"' "$openfiles" | while IFS=$'\t' read -r num date author title _; do
|
|
151
|
+
gh api --paginate "repos/$repo/pulls/$num/files?per_page=100" --jq '.[].filename' |
|
|
152
|
+
while IFS= read -r f; do printf '%s\t%s\t%s\t%s\t%s\n' "$num" "$date" "$author" "$title" "$f"; done
|
|
153
|
+
done >"$row"
|
|
154
|
+
cat "$row" >>"$openfiles"
|
|
155
|
+
[ -s "$openfiles" ] || printf 'none\n' >"$openfiles"
|
|
156
|
+
fi
|
|
157
|
+
while IFS=$'\t' read -r num date author title path; do
|
|
158
|
+
case "$path" in "$term"|*/"$term") ;; *) continue ;; esac
|
|
159
|
+
grep -q "^| PR | #$num | open, changes this file |" "$tmp" && continue
|
|
160
|
+
printf '| PR | #%s | open, changes this file | %s | %s | %s | |\n' "$num" "$date" "$author" "$title" >>"$tmp"
|
|
161
|
+
items=$((items+1))
|
|
162
|
+
done <"$openfiles"
|
|
135
163
|
;;
|
|
136
164
|
esac
|
|
137
165
|
done
|
|
@@ -180,6 +180,11 @@ EOF
|
|
|
180
180
|
printf -- '- context from the author, to attack rather than trust: %s\n' "$t"
|
|
181
181
|
done
|
|
182
182
|
|
|
183
|
+
local blind="$home/reviews/blind-${repo%%/*}-${repo##*/}-$key.diff"
|
|
184
|
+
if [ -n "$clone" ]; then
|
|
185
|
+
printf '\n0. Before you read the diff, fix it yourself.\n'
|
|
186
|
+
printf ' Read only the problem: the issue or the description of the bug, and the code at %s. In a copy of the clone at that commit, write the fix you would send, with its test, and save it as a diff to %s. Only then read the change under review and compare. A design that differs from yours in where it fixes the problem, what it recomputes or what it can break is a finding: SHOULD FIX when yours is better, with both versions side by side. The point is the design you reach without the author'"'"'s anchoring it; a maintainer reads the bug, not our diff, and writes their own.\n' "${mb:-the base}" "$blind"
|
|
187
|
+
fi
|
|
183
188
|
printf '\nAttack at least\n'
|
|
184
189
|
printf '1. Every claim the text makes, re-derived now against the exact revision it names.\n'
|
|
185
190
|
for t in ${texts[@]+"${texts[@]}"}; do
|
|
@@ -194,9 +199,15 @@ EOF
|
|
|
194
199
|
6. Prior art: nothing duplicates an open pull request or argues with a ruling in a closed issue.
|
|
195
200
|
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.
|
|
196
201
|
8. Anything else that would embarrass the author in front of this maintainer.
|
|
197
|
-
|
|
198
|
-
Report
|
|
199
202
|
EOF
|
|
203
|
+
if [ -n "$clone" ]; then
|
|
204
|
+
cat <<'EOF'
|
|
205
|
+
9. Where the information is lost: trace every value the change uses back to the last place its type, comparer or source was known. A change that recomputes what a caller already had (a second lookup, a second enumeration, reflection to recover a type the generic code knew, a default comparer standing in for the caller's) fixes the symptom where it surfaced, not where it began. That is SHOULD FIX, with the version that carries the caller's result forward.
|
|
206
|
+
10. Inputs the project already tests: list the input shapes the test files beside the changed code use, such as custom comparers, culture, single-use or lazy sequences, null, empty, very large, concurrent callers. Every shape the changed code can receive that the new tests leave out is a finding; run the change on it.
|
|
207
|
+
11. What the project promises to run on: read the manifests and CI matrix for AOT, trimming, no_std, wasm, minimum language or runtime versions, and every platform leg. Run the changed path in each mode the project promises, not only the default build; a clean build is not a run. A suppression the change adds has to be shown covering a warning that fires.
|
|
208
|
+
EOF
|
|
209
|
+
fi
|
|
210
|
+
printf '\nReport\n'
|
|
200
211
|
printf 'Write it to %s. It must start with exactly these lines, with the verdict filled in:\n\n' "$report"
|
|
201
212
|
printf 'review-of: %s\n' "$repo"
|
|
202
213
|
[ -n "$head" ] && printf 'head: %s\n' "$head"
|
|
@@ -204,6 +215,7 @@ EOF
|
|
|
204
215
|
printf 'text: sha256:%s %s\n' "$(sha256 "$t")" "$(basename "$t")"
|
|
205
216
|
done
|
|
206
217
|
printf 'author-model: %s\nreviewer-model: %s\n' "$author" "$reviewer"
|
|
218
|
+
[ -n "$head" ] && printf 'blind-fix: %s\n' "$blind"
|
|
207
219
|
printf 'verdict: POST AS IS | POST WITH CHANGES | DO NOT POST\n\n'
|
|
208
220
|
cat <<'EOF'
|
|
209
221
|
POST AS IS means nothing BLOCKING or SHOULD FIX remains. POST WITH CHANGES means the author fixes what you found and the fixed version comes back for another review. DO NOT POST means it should not go at all. After the header, number your findings, each with a severity (BLOCKING, SHOULD FIX or NIT), the evidence, and the exact fix, with replacement text in full for any wording. A report with no findings says what was checked and how.
|
|
@@ -242,6 +254,15 @@ cmd_check() {
|
|
|
242
254
|
[ "$verdict" = "POST AS IS" ] ||
|
|
243
255
|
refuse "the verdict is '${verdict:-missing}' and only POST AS IS clears the gate: fix what the review found and review the new version"
|
|
244
256
|
|
|
257
|
+
# A reviewer that read our diff first critiques our design; shouldly #1335
|
|
258
|
+
# went out with a fix the maintainer replaced, because nobody wrote another.
|
|
259
|
+
if [ -n "$head" ]; then
|
|
260
|
+
local blind
|
|
261
|
+
blind=$(field blind-fix)
|
|
262
|
+
[ -n "$blind" ] || refuse "$report reviews code at ${head:0:12} and names no blind-fix: the reviewer's own fix, written before reading ours"
|
|
263
|
+
[ -f "$blind" ] || refuse "the blind fix $blind named in $report does not exist"
|
|
264
|
+
fi
|
|
265
|
+
|
|
245
266
|
if [ -n "$clone" ]; then
|
|
246
267
|
now=$(git -C "$clone" rev-parse HEAD 2>/dev/null) || bad_usage "$clone is not a git clone"
|
|
247
268
|
[ -n "$head" ] || refuse "$report records no head, so it cannot clear a branch"
|
|
@@ -97,6 +97,7 @@ view comments '.comments[] | " " + .author.login + ": " + (.body | gsub("\n";
|
|
|
97
97
|
|
|
98
98
|
echo ""
|
|
99
99
|
if [ "$fail" -ne 0 ]; then echo "PR-VERIFY: PROBLEMS ABOVE"
|
|
100
|
+
elif [ "${waiting:-0}" -gt 0 ]; then echo "PR-VERIFY: NOT SETTLED ($waiting workflow run(s) wait for a maintainer to approve them; nothing settles until one does, so move on)"
|
|
100
101
|
elif [ "$unsettled" -ne 0 ]; then echo "PR-VERIFY: NOT SETTLED (CI has not run on this head)"
|
|
101
102
|
else echo "PR-VERIFY: OK"; fi
|
|
102
103
|
[ "$fail" -eq 0 ] && [ "$unsettled" -ne 0 ] && exit 3
|
|
@@ -157,8 +157,11 @@ report it in one line and do not proceed to the next step.
|
|
|
157
157
|
whose mode disagrees with its own siblings, a file the branch adds that the
|
|
158
158
|
repository ignores, a commit carrying a tool trailer, a session link or a
|
|
159
159
|
tool identity, a sign-off that names a username, a branch that is not on
|
|
160
|
-
top of its base,
|
|
161
|
-
maintainer asked for it)
|
|
160
|
+
top of its base, any comment added to code (`--asked <URL>` when the
|
|
161
|
+
maintainer asked for it), a warning suppression the branch adds until
|
|
162
|
+
`--warning-log` shows each suppressed warning firing, and reflection added
|
|
163
|
+
to a .NET project that declares AOT or trim support until `--aot-log`
|
|
164
|
+
shows an AOT publish you ran the changed path under; it prints every absolute claim your added prose
|
|
162
165
|
makes so you can falsify each one against the code, and it stamps the
|
|
163
166
|
head it passed, which the filing requires. Then the adversarial review:
|
|
164
167
|
`housebroken review brief --author <your model> --repo owner/repo --clone <clone> --text <body file>`
|
|
@@ -166,7 +169,14 @@ report it in one line and do not proceed to the next step.
|
|
|
166
169
|
that wrote the change: one tier down, Fable to Opus and Opus to Sonnet,
|
|
167
170
|
and never below Sonnet, so Sonnet's work goes to Opus. Hand the brief to a
|
|
168
171
|
fresh agent running that model; anything of yours it reads is for it to
|
|
169
|
-
attack, not to trust.
|
|
172
|
+
attack, not to trust. Before it reads the diff, it reads only the bug and
|
|
173
|
+
the code at the base and writes the fix it would send, saved where the
|
|
174
|
+
brief says; the report names that file, and the gate refuses a code review
|
|
175
|
+
without it. A maintainer reads the bug, not our diff, and writes their own
|
|
176
|
+
fix; the reviewer does the same first. It then traces every value the change
|
|
177
|
+
uses to where its type or comparer was last known, covers the input shapes
|
|
178
|
+
the neighbouring tests use, and runs the changed path in every mode the
|
|
179
|
+
project promises to run on. It attacks the form as well as the facts: it writes
|
|
170
180
|
the changed block the way the strongest engineer in that language would,
|
|
171
181
|
and a smaller or clearer version is a finding you fix before filing. It
|
|
172
182
|
writes its report where the brief says, with its own evidence behind
|
|
@@ -179,8 +189,11 @@ report it in one line and do not proceed to the next step.
|
|
|
179
189
|
is missing or older than a day, when the policy printout is missing, older
|
|
180
190
|
than a day or reads BAN, when the policy reads DISCLOSE and the body does
|
|
181
191
|
not disclose, when the body carries a footer, a trailer or a dash
|
|
182
|
-
character, when `housebroken branch` has not
|
|
183
|
-
|
|
192
|
+
character or runs past 120 words, when `housebroken branch` has not
|
|
193
|
+
stamped the exact head against the branch it is filed into (`--base`
|
|
194
|
+
to both when the project takes pull requests on a branch other than its
|
|
195
|
+
default), or when there is no POST AS IS review of the exact head and
|
|
196
|
+
body being filed.
|
|
184
197
|
A refusal is a result; fix the cause.
|
|
185
198
|
|
|
186
199
|
10. **Watch it land.**
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.6.9"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.6.7"
|
|
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
|