@@ -242,12 +242,14 @@ def _sub(match: re.Match[str]) -> str:
242242# or, inside a require ( ... ) block:
243243# \tgithub.com/ai-agent-assembly/go-sdk v0.0.1-rc.3
244244# The regex captures the prefix (indent + optional "require ") so we preserve
245- # whichever form the surrounding file uses. The tail requires whitespace before a
246- # ``// comment`` so the version token ``\S+`` has an unambiguous, non-whitespace
247- # boundary — otherwise the engine could backtrack ``\S+`` into the ``//``,
248- # non-linear behavior static analysis flags as a ReDoS risk.
245+ # whichever form the surrounding file uses. The tail is a prefix-free alternation
246+ # — either whitespace before a ``// comment`` (``\s+//[^\n]*``) or bare trailing
247+ # whitespace (``\s*``). Written as ``(?:\s+//.*)?\s*`` instead, the comment's
248+ # ``.*`` and the trailing ``\s*`` both compete for trailing whitespace, a
249+ # super-linear ambiguity static analysis flags as a ReDoS risk (S8786); the
250+ # disjoint alternation removes that overlap while matching the same set.
249251_GO_PIN_RE = re .compile (
250- r'''^(?P<prefix>\s*(?:require\s+)?)github\.com/ai-agent-assembly/go-sdk\s+\S+(?P<tail>(?: \s+//.*)? \s*)$''' ,
252+ r'''^(?P<prefix>\s*(?:require\s+)?)github\.com/ai-agent-assembly/go-sdk\s+\S+(?P<tail>\s+//[^\n]*| \s*)$''' ,
251253 re .MULTILINE ,
252254)
253255
@@ -451,8 +453,13 @@ def rewrite_go_readme(path: Path, sdk: GoSdk) -> bool:
451453# alpha/beta/rc pre-release suffix. Only the version token is rewritten, so an
452454# operator prefix (``>= ``) and any trailing note (``(with the LlamaIndex
453455# adapter)``) are preserved verbatim.
456+ # The pre-release alternation is prefix-free: ``b(?:eta)?`` / ``a(?:lpha)?``
457+ # fold the ``beta``/``b`` and ``alpha``/``a`` pairs together so no branch is a
458+ # prefix of another. The flat ``(?:rc|beta|alpha|b|a)`` form has ``b``/``a``
459+ # shadowing ``beta``/``alpha``, the kind of alternation overlap static analysis
460+ # flags as super-linear (S8786); this folds it out while matching the same set.
454461_VERSION_TOKEN_RE = re .compile (
455- r"v?\d+\.\d+\.\d+(?:[-.]?(?:rc|beta|alpha|b|a )\.?\d+)?"
462+ r"v?\d+\.\d+\.\d+(?:[-.]?(?:rc|b(?:eta)?|a(?:lpha)? )\.?\d+)?"
456463)
457464
458465
@@ -862,6 +869,25 @@ def _prereq_readmes(repo_root: Path) -> list[Path]:
862869 return out
863870
864871
872+ def _align_prereq_readme (
873+ readme : Path ,
874+ labels : tuple [tuple [str , str ], ...],
875+ backtick_packages : tuple [tuple [str , str ], ...],
876+ ) -> bool :
877+ """Align every prereq row/bullet in one README; return True if any changed."""
878+
879+ touched = False
880+ for label , version in labels :
881+ if rewrite_prereq_row (readme , label , version ):
882+ touched = True
883+ if rewrite_prereq_bullet (readme , label , version ):
884+ touched = True
885+ for package , version in backtick_packages :
886+ if rewrite_prereq_backtick_row (readme , package , version ):
887+ touched = True
888+ return touched
889+
890+
865891def process_prereq_rows (repo_root : Path , versions : SdkVersions ) -> list [Path ]:
866892 """Align every hand-written ``Agent Assembly <Lang> SDK`` prereq row.
867893
@@ -884,16 +910,7 @@ def process_prereq_rows(repo_root: Path, versions: SdkVersions) -> list[Path]:
884910 )
885911 changed : list [Path ] = []
886912 for readme in _prereq_readmes (repo_root ):
887- touched = False
888- for label , version in labels :
889- if rewrite_prereq_row (readme , label , version ):
890- touched = True
891- if rewrite_prereq_bullet (readme , label , version ):
892- touched = True
893- for package , version in backtick_packages :
894- if rewrite_prereq_backtick_row (readme , package , version ):
895- touched = True
896- if touched :
913+ if _align_prereq_readme (readme , labels , backtick_packages ):
897914 changed .append (readme )
898915 return changed
899916
@@ -1017,7 +1034,7 @@ def _audit_pins(repo_root: Path, versions: SdkVersions) -> list[str]:
10171034# pins are a bare ``"<version>"`` string and go pins are a bare ``go.mod``
10181035# version. The workflow rewriter already forces ``==`` on a bump, but this pass
10191036# also fails CI on a hand-authored bad operator before a regen runs.
1020- def _audit_py_operator (repo_root : Path , versions : SdkVersions ) -> list [str ]:
1037+ def _audit_py_operator (repo_root : Path ) -> list [str ]:
10211038 """Report every operator-bearing core-SDK pin that is not exact (``==``).
10221039
10231040 Covers both the python manifests and the CI-workflow pip-install pins.
@@ -1038,6 +1055,102 @@ def _audit_py_operator(repo_root: Path, versions: SdkVersions) -> list[str]:
10381055 return problems
10391056
10401057
1058+ def _audit_prose_label (line : str , expected_by_label : dict [str , str ]) -> str | None :
1059+ """Return a drift message for an ``Agent Assembly <Lang> SDK`` prose line."""
1060+
1061+ label_match = _PROSE_LABEL_RE .search (line )
1062+ if label_match is None :
1063+ return None
1064+ token_match = _VERSION_TOKEN_RE .search (line )
1065+ if token_match is None :
1066+ return None
1067+ expected = expected_by_label [label_match .group (1 )]
1068+ if token_match .group (0 ) == expected :
1069+ return None
1070+ return f"states { token_match .group (0 )!r} , expected { expected !r} "
1071+
1072+
1073+ def _audit_prose_backtick (
1074+ line : str , backtick_checks : tuple [tuple [re .Pattern [str ], str ], ...]
1075+ ) -> list [str ]:
1076+ """Return drift messages for backtick-``package``-labelled prereq rows."""
1077+
1078+ problems : list [str ] = []
1079+ for row_re , expected in backtick_checks :
1080+ row_match = row_re .search (line )
1081+ if row_match is None :
1082+ continue
1083+ token_match = _VERSION_TOKEN_RE .search (row_match .group ("val" ))
1084+ if token_match is not None and token_match .group (0 ) != expected :
1085+ problems .append (f"states { token_match .group (0 )!r} , expected { expected !r} " )
1086+ return problems
1087+
1088+
1089+ def _audit_prose_install (
1090+ line : str , install_checks : tuple [tuple [re .Pattern [str ], str ], ...]
1091+ ) -> list [str ]:
1092+ """Return drift messages for raw install-hint literals on a prose line."""
1093+
1094+ problems : list [str ] = []
1095+ for hint_re , expected in install_checks :
1096+ hint_match = hint_re .search (line )
1097+ if hint_match and hint_match .group ("ver" ) != expected :
1098+ problems .append (
1099+ f"install hint pins { hint_match .group ('ver' )!r} , "
1100+ f"expected { expected !r} "
1101+ )
1102+ return problems
1103+
1104+
1105+ def _audit_prose_line (
1106+ line : str ,
1107+ in_block : bool ,
1108+ expected_by_label : dict [str , str ],
1109+ backtick_checks : tuple [tuple [re .Pattern [str ], str ], ...],
1110+ install_checks : tuple [tuple [re .Pattern [str ], str ], ...],
1111+ ) -> list [str ]:
1112+ """Return every drift message for one prose line, in stable emit order.
1113+
1114+ Install hints are skipped inside the generated sdk-install block — that
1115+ block is generator-owned and the prose install-hint pass excludes it.
1116+ """
1117+
1118+ problems : list [str ] = []
1119+ label_problem = _audit_prose_label (line , expected_by_label )
1120+ if label_problem is not None :
1121+ problems .append (label_problem )
1122+ problems .extend (_audit_prose_backtick (line , backtick_checks ))
1123+ if not in_block :
1124+ problems .extend (_audit_prose_install (line , install_checks ))
1125+ return problems
1126+
1127+
1128+ def _audit_prose_file (
1129+ path : Path ,
1130+ rel : Path ,
1131+ expected_by_label : dict [str , str ],
1132+ backtick_checks : tuple [tuple [re .Pattern [str ], str ], ...],
1133+ install_checks : tuple [tuple [re .Pattern [str ], str ], ...],
1134+ ) -> list [str ]:
1135+ """Report drift for one README/doc, tracking the generated-block region."""
1136+
1137+ problems : list [str ] = []
1138+ in_block = False
1139+ for lineno , line in _audit_lines (path ):
1140+ if SDK_BLOCK_BEGIN in line :
1141+ in_block = True
1142+ elif SDK_BLOCK_END in line :
1143+ in_block = False
1144+ continue
1145+ if EXEMPT_MARKER in line :
1146+ continue
1147+ for suffix in _audit_prose_line (
1148+ line , in_block , expected_by_label , backtick_checks , install_checks
1149+ ):
1150+ problems .append (f"{ rel } :{ lineno } : { suffix } " )
1151+ return problems
1152+
1153+
10411154def _audit_prose (repo_root : Path , versions : SdkVersions ) -> list [str ]:
10421155 """Report every README/doc prose line whose SDK version drifts from the SoT.
10431156
@@ -1072,47 +1185,11 @@ def _audit_prose(repo_root: Path, versions: SdkVersions) -> list[str]:
10721185 problems : list [str ] = []
10731186 for path in _globbed (repo_root , _README_GLOBS + ("docs/*.md" ,)):
10741187 rel = path .relative_to (repo_root )
1075- in_block = False
1076- for lineno , line in _audit_lines (path ):
1077- if SDK_BLOCK_BEGIN in line :
1078- in_block = True
1079- elif SDK_BLOCK_END in line :
1080- in_block = False
1081- continue
1082- if EXEMPT_MARKER in line :
1083- continue
1084-
1085- label_match = _PROSE_LABEL_RE .search (line )
1086- if label_match :
1087- token_match = _VERSION_TOKEN_RE .search (line )
1088- if token_match is not None :
1089- expected = expected_by_label [label_match .group (1 )]
1090- if token_match .group (0 ) != expected :
1091- problems .append (
1092- f"{ rel } :{ lineno } : states { token_match .group (0 )!r} , "
1093- f"expected { expected !r} "
1094- )
1095-
1096- for row_re , expected in backtick_checks :
1097- row_match = row_re .search (line )
1098- if row_match is None :
1099- continue
1100- token_match = _VERSION_TOKEN_RE .search (row_match .group ("val" ))
1101- if token_match is not None and token_match .group (0 ) != expected :
1102- problems .append (
1103- f"{ rel } :{ lineno } : states { token_match .group (0 )!r} , "
1104- f"expected { expected !r} "
1105- )
1106-
1107- if in_block :
1108- continue
1109- for hint_re , expected in install_checks :
1110- hint_match = hint_re .search (line )
1111- if hint_match and hint_match .group ("ver" ) != expected :
1112- problems .append (
1113- f"{ rel } :{ lineno } : install hint pins "
1114- f"{ hint_match .group ('ver' )!r} , expected { expected !r} "
1115- )
1188+ problems .extend (
1189+ _audit_prose_file (
1190+ path , rel , expected_by_label , backtick_checks , install_checks
1191+ )
1192+ )
11161193 return problems
11171194
11181195
@@ -1121,7 +1198,7 @@ def audit(repo_root: Path, versions: SdkVersions) -> list[str]:
11211198
11221199 return sorted (
11231200 _audit_pins (repo_root , versions )
1124- + _audit_py_operator (repo_root , versions )
1201+ + _audit_py_operator (repo_root )
11251202 + _audit_prose (repo_root , versions )
11261203 )
11271204
0 commit comments