Skip to content

Commit b2bf689

Browse files
mbelhadiclaude
andcommitted
Carry the shared tooling fixes: tag filter, encodings, python3 shim, workflow permissions
Byte-identical with the sibling repos: gen-llms-txt.py strips sloppy comment closes (`--!>`) and unterminated openers instead of only well-formed `<!-- -->` (CodeQL bad-tag-filter; --check proves the generated output is byte-identical); every text-mode open()/read_text()/write_text() names encoding="utf-8" so the remediation commands the guards print survive a Windows console; doc-lint.sh resolves python3 to python where only the latter exists; doc-lint.yml declares contents: read. CHANGELOG rolls [Unreleased] into [3.0.0]. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
1 parent 432f997 commit b2bf689

9 files changed

Lines changed: 38 additions & 15 deletions

File tree

‎.github/workflows/doc-lint.yml‎

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ on:
7171
# can invalidate hundreds of citations. Without this the guard never fires on it.
7272
- 'src/**'
7373

74+
75+
# Least privilege. Nothing in this workflow writes to the repository, so nothing
76+
# in it needs a token that can.
77+
permissions:
78+
contents: read
79+
7480
concurrency:
7581
group: doc-lint-${{ github.ref }}
7682
cancel-in-progress: true

‎CHANGELOG.md‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,11 @@ All notable changes to the qbm-http module are documented here. The format is ba
77

88
## [Unreleased]
99

10+
Nothing yet. Entries land here as they are merged, and move under a version heading when that
11+
version is tagged.
12+
13+
## [3.0.0] - 2026-08-20
14+
1015
Tracks changes not yet part of a tagged release. Since 2026-08-11 that is **both** branches:
1116
`main` was fast-forwarded to `develop` for the release, so the module version is **3.0.0** on either,
1217
in lockstep with the qb framework; see the qb CHANGELOG for what makes that release major.
@@ -229,5 +234,6 @@ Aligns qbm-http with the qb 2.0 framework and hardens the HTTP/2, HTTP/3, WebSoc
229234
pending-request queue.
230235
- Reject control characters in quoted header-attribute values.
231236
232-
[Unreleased]: https://github.com/isndev/qbm-http/compare/v2.6.0...HEAD
237+
[Unreleased]: https://github.com/isndev/qbm-http/compare/v3.0.0...HEAD
238+
[3.0.0]: https://github.com/isndev/qbm-http/compare/v2.6.0...v3.0.0
233239
[2.6.0]: https://github.com/isndev/qbm-http/releases/tag/v2.6.0
24.1 KB
Binary file not shown.
26.3 KB
Binary file not shown.
58.7 KB
Binary file not shown.

‎scripts/cite-check.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,13 +92,13 @@
9292
def project_prefix(root):
9393
"""This project's prefix in repo-root-form paths, or None if it is none of the four."""
9494
try:
95-
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore") as fh:
95+
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore", encoding="utf-8") as fh:
9696
if re.search(r'^\s*set\(QB_FRAMEWORK_NAME\s+"qb"\)', fh.read(), re.M):
9797
return "qb/"
9898
except OSError:
9999
pass
100100
try:
101-
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore") as fh:
101+
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore", encoding="utf-8") as fh:
102102
m = re.search(r"^\s*project\(\s*qbm-([A-Za-z0-9_]+)\b", fh.read(), re.M)
103103
except OSError:
104104
m = None

‎scripts/doc-lint.sh‎

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,11 @@
1515
# Usage: ./scripts/doc-lint.sh (from the qbm-http root)
1616
#
1717
set -uo pipefail
18+
# `python3` is not a given on Windows hosts (the launcher is `py`, the binary `python`);
19+
# resolve it once so a direct invocation of this script works everywhere verify.sh's shim
20+
# is not wrapping it. A no-op wherever python3 exists.
21+
command -v python3 >/dev/null 2>&1 || python3() { python "$@"; }
22+
1823
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
1924
ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)"
2025
cd "${ROOT}" || exit 2

‎scripts/gen-llms-txt.py‎

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -94,13 +94,13 @@
9494
def project_prefix(root):
9595
"""This project's prefix in repo-root-form paths, or None if it is none of the four."""
9696
try:
97-
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore") as fh:
97+
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore", encoding="utf-8") as fh:
9898
if re.search(r'^\s*set\(QB_FRAMEWORK_NAME\s+"qb"\)', fh.read(), re.M):
9999
return "qb/"
100100
except OSError:
101101
pass
102102
try:
103-
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore") as fh:
103+
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore", encoding="utf-8") as fh:
104104
m = re.search(r"^\s*project\(\s*qbm-([A-Za-z0-9_]+)\b", fh.read(), re.M)
105105
except OSError:
106106
m = None
@@ -199,7 +199,13 @@ def note_of(rel: str) -> str:
199199
if para:
200200
break
201201
continue
202-
para.append(re.sub(r"<!--.*?-->", "", s).strip())
202+
# Strip HTML comments defensively, not just the well-formed shape. `<!--.*?-->` alone
203+
# leaves two things behind that CodeQL's bad-tag-filter rightly flags: the sloppy
204+
# close `--!>` that browsers accept, and an opener with no close on the line, which
205+
# would pass the whole comment body through into the published lead verbatim.
206+
cleaned = re.sub(r"<!--.*?(?:-->|--!>)", "", s)
207+
cleaned = re.sub(r"<!--.*$", "", cleaned)
208+
para.append(cleaned.strip())
203209
# Join the WHOLE paragraph before looking for a sentence end. Reading only the first
204210
# physical line truncated every hard-wrapped lead ("... libraries of the"), which is how
205211
# a generated index ends up quoting half-sentences at an agent.

‎scripts/llm-guard.py‎

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -187,13 +187,13 @@ def relpath(path: str) -> str:
187187
def project_prefix(root):
188188
"""This project's prefix in repo-root-form paths, or None if it is none of the four."""
189189
try:
190-
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore") as fh:
190+
with open(os.path.join(root, "cmake", "qbConfig.cmake"), errors="ignore", encoding="utf-8") as fh:
191191
if re.search(r'^\s*set\(QB_FRAMEWORK_NAME\s+"qb"\)', fh.read(), re.M):
192192
return "qb/"
193193
except OSError:
194194
pass
195195
try:
196-
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore") as fh:
196+
with open(os.path.join(root, "CMakeLists.txt"), errors="ignore", encoding="utf-8") as fh:
197197
m = re.search(r"^\s*project\(\s*qbm-([A-Za-z0-9_]+)\b", fh.read(), re.M)
198198
except OSError:
199199
m = None
@@ -343,7 +343,7 @@ def expected_version():
343343
p, rx = os.path.join(ROOT, "CMakeLists.txt"), \
344344
r'^\s*project\(' + re.escape(PROJECT) + r'\s+VERSION\s+([0-9][0-9.]*)\)'
345345
try:
346-
with open(p, errors="ignore") as fh:
346+
with open(p, errors="ignore", encoding="utf-8") as fh:
347347
m = re.search(rx, fh.read(), re.M)
348348
except OSError:
349349
m = None
@@ -415,7 +415,7 @@ def __init__(self):
415415
if not idents_ok or fn in SKIP_IDENT_FILES:
416416
continue
417417
try:
418-
with open(p, errors="ignore") as fh:
418+
with open(p, errors="ignore", encoding="utf-8") as fh:
419419
self.idents.update(re.findall(r"[A-Za-z_][A-Za-z0-9_]*", fh.read()))
420420
except OSError:
421421
pass
@@ -463,7 +463,7 @@ def resolve_dir(self, spec: str):
463463
def lines(self, path):
464464
if path not in self._lines:
465465
try:
466-
with open(path, errors="ignore") as fh:
466+
with open(path, errors="ignore", encoding="utf-8") as fh:
467467
self._lines[path] = fh.read().split("\n")
468468
except OSError:
469469
self._lines[path] = None
@@ -484,7 +484,7 @@ def iter_docs():
484484
def check_doc(path, idx, tol, max_occ, want_ver, ver_src,
485485
findings, stats, suppressions, digests):
486486
rel = relpath(path)
487-
with open(path) as fh:
487+
with open(path, encoding="utf-8") as fh:
488488
lines = fh.read().split("\n")
489489

490490
# ---- 5. Verified-against marker, BY VALUE --------------------------------
@@ -814,7 +814,7 @@ def main() -> int:
814814

815815
accepted = {}
816816
if not a.no_baseline and os.path.isfile(a.baseline):
817-
with open(a.baseline) as fh:
817+
with open(a.baseline, encoding="utf-8") as fh:
818818
for raw in fh:
819819
raw = raw.rstrip("\n")
820820
if not raw.strip() or raw.lstrip().startswith("#"):
@@ -851,7 +851,7 @@ def main() -> int:
851851
f"(expected >= {min_dig}); the parser has stopped matching the corpus and "
852852
f"recording now would erase the baseline")
853853
return 1
854-
with open(a.digest_baseline, "w") as fh:
854+
with open(a.digest_baseline, "w", encoding="utf-8") as fh:
855855
fh.write(
856856
"# llm-cite-digest.baseline -- what every line-cited range in llm/ SAID when\n"
857857
"# it was last verified. Regenerate with:\n"
@@ -876,7 +876,7 @@ def main() -> int:
876876
# -- rule 2b: the cited lines must still say what they said -------------------
877877
recorded = {}
878878
if os.path.isfile(a.digest_baseline):
879-
with open(a.digest_baseline) as fh:
879+
with open(a.digest_baseline, encoding="utf-8") as fh:
880880
for raw in fh:
881881
if not raw.strip() or raw.lstrip().startswith("#"):
882882
continue

0 commit comments

Comments
 (0)