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.
Files changed (27) hide show
  1. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/PKG-INFO +3 -3
  2. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/README.md +2 -2
  3. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/pyproject.toml +1 -1
  4. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/branch-check.sh +58 -4
  5. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/file-pr.sh +30 -4
  6. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/prior-art.sh +31 -3
  7. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/review.sh +23 -2
  8. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/verify-filed-pr.sh +1 -0
  9. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/skills/housebroken/SKILL.md +18 -5
  10. housebroken_cli-0.6.9/src/housebroken/__init__.py +1 -0
  11. housebroken_cli-0.6.7/src/housebroken/__init__.py +0 -1
  12. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/.gitignore +0 -0
  13. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/LICENSE +0 -0
  14. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/bin/housebroken +0 -0
  15. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/hatch_build.py +0 -0
  16. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/check-ai-policy.sh +0 -0
  17. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/claim-check.sh +0 -0
  18. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/comment-census.sh +0 -0
  19. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/comment-check.sh +0 -0
  20. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/diff-defaults.sh +0 -0
  21. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/distinct-outside.sh +0 -0
  22. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/file-issue.sh +0 -0
  23. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/fork-hygiene.sh +0 -0
  24. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/inbox.sh +0 -0
  25. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/notes.sh +0 -0
  26. {housebroken_cli-0.6.7 → housebroken_cli-0.6.9}/scripts/pr-sweep.sh +0 -0
  27. {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.7
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, and any comment added to code; 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 an attack list that 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.
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, and any comment added to code; 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 an attack list that 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.
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
 
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "housebroken-cli"
7
- version = "0.6.7"
7
+ version = "0.6.9"
8
8
  description = "Agent-written pull requests that do not make a mess in someone else's house."
9
9
  dynamic = ["readme"]
10
10
  license = "MIT"
@@ -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). Absolute claims in added
11
- # prose are printed for you to falsify one by one; some of them are true.
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|-l|--label|-a|--assignee|-r|--reviewer|-m|--milestone|-p|--project) pass+=("$1" "${2:-}"); shift ;;
139
- --base=*|--label=*|--assignee=*|--reviewer=*|--milestone=*|--project=*) pass+=("$1") ;;
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 and its commit history is scanned
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
- trap 'rm -f "$tmp" "$row"' EXIT
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, and any comment added to code (`--asked <URL>` when the
161
- maintainer asked for it); it prints every absolute claim your added prose
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. It attacks the form as well as the facts: it writes
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 stamped the exact head, or
183
- when there is no POST AS IS review of the exact head and body being filed.
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