Skip to content

Commit 1d45fc7

Browse files
committed
PR feedback
1 parent 2a1e38f commit 1d45fc7

11 files changed

Lines changed: 341 additions & 53 deletions

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,7 +179,7 @@ repos:
179179
name: check hook output format
180180
entry: python scripts/hooks/check-hook-output-format.py
181181
language: python
182-
files: '^scripts/hooks/check-.*\.py$'
182+
files: '^scripts/(hooks/)?[a-z][^/]*\.py$'
183183
pass_filenames: true
184184

185185
# ══════════════════════════════════════════════════════════════════════

scripts/check-kani-coverage.py

Lines changed: 11 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -222,13 +222,15 @@ def check_unwind_attributes(
222222
has_errors = True
223223
print(
224224
f"\nERROR: {len(errors)} Tier 2/3 proof(s) missing required "
225-
f"#[kani::unwind(N)]:"
225+
f"#[kani::unwind(N)]:",
226+
file=sys.stderr,
226227
)
227228
for fn_name, file_path, tier in sorted(errors):
228229
print(
229230
f" ERROR: Tier {tier} proof '{fn_name}' in file '{file_path}' "
230231
f"has no #[kani::unwind(N)]. Tier 2/3 proofs MUST have explicit "
231-
f"unwind bounds to prevent CI timeouts."
232+
f"unwind bounds to prevent CI timeouts.",
233+
file=sys.stderr,
232234
)
233235

234236
if advisories:
@@ -241,7 +243,8 @@ def check_unwind_attributes(
241243
print(
242244
f" WARNING: proof '{fn_name}' in file '{file_path}' has no explicit "
243245
f"#[kani::unwind(N)]. CI uses --default-unwind 8; larger data "
244-
f"structures may cause timeouts."
246+
f"structures may cause timeouts.",
247+
file=sys.stderr,
245248
)
246249
else:
247250
print(
@@ -273,16 +276,16 @@ def main() -> int:
273276

274277
if missing_proofs:
275278
has_errors = True
276-
print("ERROR: The following Kani proofs are NOT in verify-kani.sh:")
279+
print("ERROR: The following Kani proofs are NOT in verify-kani.sh:", file=sys.stderr)
277280
for proof in sorted(missing_proofs):
278-
print(f" - {proof}")
279-
print("\nAdd them to one of the TIER*_PROOFS arrays in scripts/verify-kani.sh")
281+
print(f" - {proof}", file=sys.stderr)
282+
print("\nAdd them to one of the TIER*_PROOFS arrays in scripts/verify-kani.sh", file=sys.stderr)
280283

281284
if extra_proofs:
282285
# This is a warning, not an error (could be commented out proofs)
283-
print("\nWARNING: The following proofs are in verify-kani.sh but not in source:")
286+
print("\nWARNING: The following proofs are in verify-kani.sh but not in source:", file=sys.stderr)
284287
for proof in sorted(extra_proofs):
285-
print(f" - {proof}")
288+
print(f" - {proof}", file=sys.stderr)
286289

287290
if not has_errors:
288291
print(f"[OK] All {len(source_proofs)} Kani proofs are covered in verify-kani.sh")

scripts/check-links.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -297,7 +297,7 @@ def check_markdown_file(
297297
rel_path = file_path.relative_to(project_root)
298298
except ValueError:
299299
rel_path = file_path
300-
print(f"ERROR: Could not read {rel_path}: {e}")
300+
print(f"ERROR: Could not read {rel_path}: {e}", file=sys.stderr)
301301
return LinkCheckResult(errors=1, warnings=0, checked=0)
302302

303303
# Find code fence ranges to skip
@@ -331,7 +331,7 @@ def check_markdown_file(
331331
if not is_valid:
332332
errors += 1
333333
rel_path = _rel(file_path, project_root)
334-
print(f"ERROR: {rel_path}: {error_msg}")
334+
print(f"ERROR: {rel_path}: {error_msg}", file=sys.stderr)
335335

336336
return LinkCheckResult(errors=errors, warnings=warnings, checked=checked)
337337

scripts/check-rustdoc-links.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -54,20 +54,20 @@ def main() -> int:
5454

5555
# If cargo doc failed (non-zero exit code), print stderr and fail
5656
if result.returncode != 0:
57-
print("ERROR: cargo doc failed with rustdoc warnings/errors:")
57+
print("ERROR: cargo doc failed with rustdoc warnings/errors:", file=sys.stderr)
5858
if result.stderr:
59-
print(result.stderr)
59+
print(result.stderr, file=sys.stderr)
6060
if result.stdout:
61-
print(result.stdout)
61+
print(result.stdout, file=sys.stderr)
6262
return 1
6363

6464
return 0
6565

6666
except FileNotFoundError:
67-
print("ERROR: cargo not found. Is Rust installed?")
67+
print("ERROR: cargo not found. Is Rust installed?", file=sys.stderr)
6868
return 1
6969
except Exception as e:
70-
print(f"ERROR: Failed to run cargo doc: {e}")
70+
print(f"ERROR: Failed to run cargo doc: {e}", file=sys.stderr)
7171
return 1
7272

7373

scripts/check-wiki-consistency.py

Lines changed: 33 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ def parse_wiki_structure_from_sync_script(sync_script_path: Path) -> dict[str, s
116116
result[str(key.value)] = str(value.value)
117117
return result
118118
except (OSError, SyntaxError) as e:
119-
print(red(f"ERROR: Could not parse {sync_script_path}: {e}"))
119+
print(red(f"ERROR: Could not parse {sync_script_path}: {e}"), file=sys.stderr)
120120

121121
return {}
122122

@@ -156,7 +156,7 @@ def parse_hardcoded_sidebar_from_sync_script(sync_script_path: Path) -> str | No
156156
if isinstance(stmt.value, ast.Constant):
157157
return str(stmt.value.value)
158158
except (OSError, SyntaxError) as e:
159-
print(red(f"ERROR: Could not parse {sync_script_path}: {e}"))
159+
print(red(f"ERROR: Could not parse {sync_script_path}: {e}"), file=sys.stderr)
160160

161161
return None
162162

@@ -272,7 +272,8 @@ def validate_sync_script_sidebar_template(
272272
errors += 1
273273
print(
274274
red("ERROR:")
275-
+ f" Could not extract hardcoded sidebar template from {sync_script_path}"
275+
+ f" Could not extract hardcoded sidebar template from {sync_script_path}",
276+
file=sys.stderr,
276277
)
277278
return ValidationResult(errors=errors, warnings=warnings)
278279

@@ -302,7 +303,8 @@ def validate_sync_script_sidebar_template(
302303
+ f" {sync_script_path.name}:generate_sidebar(): "
303304
+ f"Wiki-link [[{page_name}|{display_text}]] "
304305
+ f"contains '{char}' in display text ({reason}). "
305-
+ f"Use standard markdown [Display](Page) syntax instead."
306+
+ f"Use standard markdown [Display](Page) syntax instead.",
307+
file=sys.stderr,
306308
)
307309
break # Only report first problematic character per link
308310

@@ -367,7 +369,7 @@ def parse_sidebar_wiki_links(
367369
content = sidebar_path.read_text(encoding="utf-8")
368370
return parse_wiki_links_from_string(content)
369371
except (OSError, UnicodeDecodeError) as e:
370-
print(red(f"ERROR: Could not read {sidebar_path}: {e}"))
372+
print(red(f"ERROR: Could not read {sidebar_path}: {e}"), file=sys.stderr)
371373
return []
372374

373375

@@ -433,7 +435,8 @@ def validate_wiki_link_display_text(
433435
red("ERROR:")
434436
+ f" _Sidebar.md:{line_num}: Wiki-link [[{page_name}|{display_text}]] "
435437
+ f"contains '{char}' in display text ({reason}). "
436-
+ f"Use standard markdown [Display](Page) syntax instead."
438+
+ f"Use standard markdown [Display](Page) syntax instead.",
439+
file=sys.stderr,
437440
)
438441
break # Only report first problematic character per link
439442

@@ -463,7 +466,8 @@ def validate_sidebar_links(
463466
print(
464467
red("ERROR:")
465468
+ f" _Sidebar.md:{line_num}: Link to '{page_name}' "
466-
+ f"points to non-existent page '{page_name}.md'"
469+
+ f"points to non-existent page '{page_name}.md'",
470+
file=sys.stderr,
467471
)
468472
elif verbose:
469473
syntax_label = "wiki" if syntax_type == "wiki" else "md"
@@ -495,7 +499,8 @@ def validate_wiki_structure_completeness(
495499
print(
496500
yellow("WARNING:")
497501
+ f" docs/{unmapped} has no mapping in WIKI_STRUCTURE "
498-
+ "(scripts/sync-wiki.py)"
502+
+ "(scripts/sync-wiki.py)",
503+
file=sys.stderr,
499504
)
500505

501506
# Also check for stale mappings (mapped files that no longer exist)
@@ -506,7 +511,8 @@ def validate_wiki_structure_completeness(
506511
print(
507512
yellow("WARNING:")
508513
+ f" WIKI_STRUCTURE contains mapping for '{stale}' "
509-
+ "which does not exist in docs/"
514+
+ "which does not exist in docs/",
515+
file=sys.stderr,
510516
)
511517

512518
return ValidationResult(errors=errors, warnings=warnings)
@@ -546,7 +552,8 @@ def validate_sidebar_completeness(
546552
warnings += 1
547553
print(
548554
yellow("WARNING:")
549-
+ f" Wiki page '{missing}.md' has no entry in _Sidebar.md"
555+
+ f" Wiki page '{missing}.md' has no entry in _Sidebar.md",
556+
file=sys.stderr,
550557
)
551558

552559
return ValidationResult(errors=errors, warnings=warnings)
@@ -632,17 +639,19 @@ def validate_table_pipe_escaping(
632639
print(
633640
red("ERROR:")
634641
+ f" {md_file.name}:{line_num}: Table cell contains "
635-
+ f"unescaped pipe in code span: `{code_content}`"
642+
+ f"unescaped pipe in code span: `{code_content}`",
643+
file=sys.stderr,
636644
)
637645
print(
638646
" "
639647
+ yellow("Tip:")
640648
+ " Escape the pipe with backslash: "
641-
+ f"`{code_content.replace('|', chr(92) + '|')}`"
649+
+ f"`{code_content.replace('|', chr(92) + '|')}`",
650+
file=sys.stderr,
642651
)
643652

644653
except (OSError, UnicodeDecodeError) as e:
645-
print(red(f"ERROR: Could not read {md_file}: {e}"))
654+
print(red(f"ERROR: Could not read {md_file}: {e}"), file=sys.stderr)
646655
errors += 1
647656

648657
return ValidationResult(errors=errors, warnings=warnings)
@@ -682,7 +691,8 @@ def validate_markdown_link_syntax(wiki_dir: Path, verbose: bool = False) -> Vali
682691
print(
683692
red("ERROR:")
684693
+ f" {md_file.name}:{line_num}: Malformed link with space "
685-
+ f"after '[': {match.group()}"
694+
+ f"after '[': {match.group()}",
695+
file=sys.stderr,
686696
)
687697

688698
# Check for space before closing bracket
@@ -691,7 +701,8 @@ def validate_markdown_link_syntax(wiki_dir: Path, verbose: bool = False) -> Vali
691701
print(
692702
yellow("WARNING:")
693703
+ f" {md_file.name}:{line_num}: Link has trailing space "
694-
+ f"before ']': {match.group()}"
704+
+ f"before ']': {match.group()}",
705+
file=sys.stderr,
695706
)
696707

697708
# Check for empty link text
@@ -700,11 +711,12 @@ def validate_markdown_link_syntax(wiki_dir: Path, verbose: bool = False) -> Vali
700711
print(
701712
yellow("WARNING:")
702713
+ f" {md_file.name}:{line_num}: Empty link text: "
703-
+ f"{match.group()}"
714+
+ f"{match.group()}",
715+
file=sys.stderr,
704716
)
705717

706718
except (OSError, UnicodeDecodeError) as e:
707-
print(red(f"ERROR: Could not read {md_file}: {e}"))
719+
print(red(f"ERROR: Could not read {md_file}: {e}"), file=sys.stderr)
708720
errors += 1
709721

710722
return ValidationResult(errors=errors, warnings=warnings)
@@ -730,15 +742,15 @@ def main() -> int:
730742

731743
# Check required files/directories exist
732744
if not wiki_dir.exists():
733-
print(red("ERROR:") + f" Wiki directory not found: {wiki_dir}")
745+
print(red("ERROR:") + f" Wiki directory not found: {wiki_dir}", file=sys.stderr)
734746
return 1
735747

736748
if not sidebar_path.exists():
737-
print(red("ERROR:") + f" Sidebar not found: {sidebar_path}")
749+
print(red("ERROR:") + f" Sidebar not found: {sidebar_path}", file=sys.stderr)
738750
return 1
739751

740752
if not sync_script_path.exists():
741-
print(red("ERROR:") + f" Sync script not found: {sync_script_path}")
753+
print(red("ERROR:") + f" Sync script not found: {sync_script_path}", file=sys.stderr)
742754
return 1
743755

744756
# Get wiki pages and parse WIKI_STRUCTURE
@@ -747,7 +759,7 @@ def main() -> int:
747759
docs_files = get_docs_source_files(docs_dir)
748760

749761
if not wiki_structure:
750-
print(red("ERROR:") + " Could not parse WIKI_STRUCTURE from sync-wiki.py")
762+
print(red("ERROR:") + " Could not parse WIKI_STRUCTURE from sync-wiki.py", file=sys.stderr)
751763
return 1
752764

753765
if verbose:

scripts/hooks/check-hook-output-format.py

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
- No raw path variables in error output when file uses glob/rglob/iterdir
1111
(absolute paths break {path}:{line}: parsing on Windows due to drive letter
1212
colons -- use relative_to() or a display_path variable instead)
13+
- No ERROR:/WARNING: diagnostic prints going to stdout (must use file=sys.stderr)
1314
1415
Cross-platform: Works on Linux, macOS, and Windows.
1516
"""
@@ -152,6 +153,65 @@ def check_file(filepath: Path, repo_root: Path | None = None) -> list[str]:
152153
# logic -- stop looking
153154
break
154155

156+
# Check 7: ERROR/WARNING prints going to stdout instead of stderr
157+
# Detect print() calls containing ERROR: or WARNING: diagnostic
158+
# prefixes that do not include file=sys.stderr. These messages
159+
# must go to stderr per project conventions.
160+
#
161+
# Handles both single-line and multi-line cases:
162+
# Single-line: print("ERROR: something")
163+
# Multi-line: print(
164+
# f" WARNING: proof ..."
165+
# )
166+
# For multi-line, we check if we're inside an open print() call
167+
# (tracked by in_print_call) and look for ERROR:/WARNING: on
168+
# continuation lines.
169+
if re.search(
170+
r"""print\(.*(?:ERROR|WARNING)\s*:""", stripped
171+
) and "file=sys.stderr" not in stripped:
172+
issues.append(
173+
f"{display_path}:{line_num}: print() with ERROR:/WARNING: diagnostic goes to stdout -- add file=sys.stderr"
174+
)
175+
176+
# Check 7b: Multi-line print() calls with ERROR:/WARNING: on
177+
# continuation lines. Walk the file a second time, tracking open
178+
# print( calls and scanning their bodies for diagnostic prefixes.
179+
in_print = False
180+
print_start_line = 0
181+
print_lines: list[str] = []
182+
paren_depth = 0
183+
for line_num, line in enumerate(lines, start=1):
184+
stripped = line.strip()
185+
if stripped.startswith("#") or not stripped:
186+
if in_print:
187+
# accumulate even blank/comment lines inside a print call
188+
print_lines.append(stripped)
189+
continue
190+
191+
if not in_print:
192+
# Detect start of a print( call
193+
if re.match(r"print\(", stripped):
194+
in_print = True
195+
print_start_line = line_num
196+
print_lines = [stripped]
197+
paren_depth = stripped.count("(") - stripped.count(")")
198+
if paren_depth <= 0:
199+
# Single-line — already handled by Check 7 above
200+
in_print = False
201+
else:
202+
print_lines.append(stripped)
203+
paren_depth += stripped.count("(") - stripped.count(")")
204+
if paren_depth <= 0:
205+
# End of multi-line print call — analyse the joined body
206+
joined = " ".join(print_lines)
207+
has_diag = re.search(r"(?:ERROR|WARNING)\s*:", joined)
208+
has_stderr = "file=sys.stderr" in joined
209+
if has_diag and not has_stderr:
210+
issues.append(
211+
f"{display_path}:{print_start_line}: multi-line print() with ERROR:/WARNING: diagnostic goes to stdout -- add file=sys.stderr"
212+
)
213+
in_print = False
214+
155215
# Check 6: Raw path variables in error output when file uses glob/rglob
156216
# When a script discovers files via glob(), rglob(), or iterdir(), the
157217
# resulting Path objects are absolute. Using them directly in error

scripts/run-cargo-clippy.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,16 +34,16 @@ def main() -> int:
3434
)
3535

3636
if result.returncode != 0:
37-
print("\nERROR: Clippy found issues. Fix the warnings above.")
37+
print("\nERROR: Clippy found issues. Fix the warnings above.", file=sys.stderr)
3838

3939
return result.returncode
4040

4141
except FileNotFoundError:
42-
print("ERROR: cargo not found. Is Rust installed?")
43-
print(" Install from: https://rustup.rs/")
42+
print("ERROR: cargo not found. Is Rust installed?", file=sys.stderr)
43+
print(" Install from: https://rustup.rs/", file=sys.stderr)
4444
return 1
4545
except Exception as e:
46-
print(f"ERROR: Failed to run cargo clippy: {e}")
46+
print(f"ERROR: Failed to run cargo clippy: {e}", file=sys.stderr)
4747
return 1
4848

4949

0 commit comments

Comments
 (0)