From 8f71bc0c406486c6e61bbf820ac29a1e3f17d24a Mon Sep 17 00:00:00 2001 From: stranske Date: Fri, 13 Feb 2026 18:04:01 +0000 Subject: [PATCH 1/4] fix(keepalive): avoid false STOP progress reviews MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Extract Acceptance criteria from any heading level (##..######) - Improve heuristic tokenization for snake_case identifiers (e.g., render_cprs_ch_png → cprs/png) - Add regression test - Sync ruff pin to 0.15.1 across tool pins --- .github/workflows/agents-keepalive-loop.yml | 16 ++++++++++------ .github/workflows/autofix-versions.env | 2 +- pyproject.toml | 2 +- scripts/langchain/progress_reviewer.py | 16 ++++++++++++---- .../workflows/agents-keepalive-loop.yml | 16 ++++++++++------ .../.github/workflows/autofix-versions.env | 2 +- tests/scripts/test_progress_reviewer.py | 19 +++++++++++++++++++ 7 files changed, 54 insertions(+), 19 deletions(-) diff --git a/.github/workflows/agents-keepalive-loop.yml b/.github/workflows/agents-keepalive-loop.yml index 38a8b5b6a..dcedf9880 100644 --- a/.github/workflows/agents-keepalive-loop.yml +++ b/.github/workflows/agents-keepalive-loop.yml @@ -635,12 +635,16 @@ jobs: // Extract acceptance criteria from PR body const body = pr.body || ''; const criteria = []; - // Look for acceptance criteria section - const acMatch = body.match(/## Acceptance Criteria[\s\S]*?(?=##|$)/i); - if (acMatch) { - const lines = acMatch[0].split('\n'); - for (const line of lines) { - const match = line.match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/i); + const lines = body.split('\n'); + const startIndex = lines.findIndex((line) => + /^#{2,6}\s*Acceptance\s+criteria\b/i.test(line.trim()) + ); + if (startIndex !== -1) { + for (let i = startIndex + 1; i < lines.length; i += 1) { + if (/^#{1,6}\s+\S/.test(lines[i])) { + break; + } + const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/); if (match) { criteria.push(match[1].trim()); } diff --git a/.github/workflows/autofix-versions.env b/.github/workflows/autofix-versions.env index 3a10b0f18..97d9faeeb 100644 --- a/.github/workflows/autofix-versions.env +++ b/.github/workflows/autofix-versions.env @@ -5,7 +5,7 @@ # Runtime dependencies (PyYAML, Pydantic, Hypothesis) should be managed via Dependabot # in each consumer repo's pyproject.toml directly, NOT synced from this file. BLACK_VERSION=26.1.0 -RUFF_VERSION=0.15.0 +RUFF_VERSION=0.15.1 ISORT_VERSION=7.0.0 DOCFORMATTER_VERSION=1.7.7 MYPY_VERSION=1.19.1 diff --git a/pyproject.toml b/pyproject.toml index c9358d879..b36d86a9e 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -31,7 +31,7 @@ app = [] dev = [ "pre-commit==4.5.1", "black==26.1.0", - "ruff==0.15.0", + "ruff==0.15.1", "isort==7.0.0", "docformatter==1.7.7", "mypy==1.19.1", diff --git a/scripts/langchain/progress_reviewer.py b/scripts/langchain/progress_reviewer.py index 5310bad89..7f1373464 100755 --- a/scripts/langchain/progress_reviewer.py +++ b/scripts/langchain/progress_reviewer.py @@ -152,9 +152,17 @@ def heuristic_alignment_check( """ criteria_keywords = set() for criterion in acceptance_criteria: - # Extract meaningful words from criteria (longer words are more specific) - words = re.findall(r"\b[a-z_]{4,}\b", criterion.lower()) - criteria_keywords.update(words) + # Extract meaningful words from criteria. + # Note: acceptance criteria often include snake_case identifiers (e.g. + # render_cprs_ch_png). Split those into tokens so commits like + # "CPRS-CH PNG" can be recognized as aligned. + words = re.findall(r"\b[a-z0-9_]{3,}\b", criterion.lower()) + for word in words: + criteria_keywords.add(word) + if "_" in word: + for token in word.split("_"): + if len(token) >= 3: + criteria_keywords.add(token) # Infrastructure words that indicate supporting work # These alone don't count as alignment, but combined with criteria keywords they help @@ -201,7 +209,7 @@ def heuristic_alignment_check( for commit in recent_commits: commit_lower = commit.lower() - commit_words = set(re.findall(r"\b[a-z_]{3,}\b", commit_lower)) + commit_words = set(re.findall(r"\b[a-z0-9_]{3,}\b", commit_lower)) # Check for direct criteria match (strong signal) criteria_match = criteria_keywords & commit_words diff --git a/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml b/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml index c05a535bb..42e2caea0 100644 --- a/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml +++ b/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml @@ -633,12 +633,16 @@ jobs: // Extract acceptance criteria from PR body const body = pr.body || ''; const criteria = []; - // Look for acceptance criteria section - const acMatch = body.match(/## Acceptance Criteria[\s\S]*?(?=##|$)/i); - if (acMatch) { - const lines = acMatch[0].split('\n'); - for (const line of lines) { - const match = line.match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/i); + const lines = body.split('\n'); + const startIndex = lines.findIndex((line) => + /^#{2,6}\s*Acceptance\s+criteria\b/i.test(line.trim()) + ); + if (startIndex !== -1) { + for (let i = startIndex + 1; i < lines.length; i += 1) { + if (/^#{1,6}\s+\S/.test(lines[i])) { + break; + } + const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/); if (match) { criteria.push(match[1].trim()); } diff --git a/templates/consumer-repo/.github/workflows/autofix-versions.env b/templates/consumer-repo/.github/workflows/autofix-versions.env index 3a10b0f18..97d9faeeb 100644 --- a/templates/consumer-repo/.github/workflows/autofix-versions.env +++ b/templates/consumer-repo/.github/workflows/autofix-versions.env @@ -5,7 +5,7 @@ # Runtime dependencies (PyYAML, Pydantic, Hypothesis) should be managed via Dependabot # in each consumer repo's pyproject.toml directly, NOT synced from this file. BLACK_VERSION=26.1.0 -RUFF_VERSION=0.15.0 +RUFF_VERSION=0.15.1 ISORT_VERSION=7.0.0 DOCFORMATTER_VERSION=1.7.7 MYPY_VERSION=1.19.1 diff --git a/tests/scripts/test_progress_reviewer.py b/tests/scripts/test_progress_reviewer.py index 7f4f49f20..e044b8a84 100644 --- a/tests/scripts/test_progress_reviewer.py +++ b/tests/scripts/test_progress_reviewer.py @@ -38,3 +38,22 @@ def test_json_output_contains_review_fields(): assert "review" in decoded assert set(decoded["review"].keys()) == {"score", "feedback", "suggestions"} + + +def test_heuristic_alignment_handles_snake_case_tokens(): + result = progress_reviewer.review_progress( + acceptance_criteria=[ + "Running `render_cprs_ch_png(...)` generates PNGs without errors.", + ], + recent_commits=[ + "Define explicit CPRS-CH PNG column layout", + ], + files_changed=[ + "src/counter_risk/renderers/table_png.py", + ], + rounds_without_completion=22, + use_llm=False, + ) + + assert result.alignment_score > 0 + assert result.recommendation != "STOP" From 1446ca01783ad256f1aca097dc6521d3ba3fe03e Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:10:04 +0000 Subject: [PATCH 2/4] chore(codex-autofix): apply updates (PR #1495) --- .github/scripts/install-ci-deps.sh | 45 +++++++++++++++++++++++++++++- 1 file changed, 44 insertions(+), 1 deletion(-) diff --git a/.github/scripts/install-ci-deps.sh b/.github/scripts/install-ci-deps.sh index 8d13a333c..d0a2275c5 100755 --- a/.github/scripts/install-ci-deps.sh +++ b/.github/scripts/install-ci-deps.sh @@ -69,6 +69,7 @@ install_ci_deps() { # Initialize arrays local specs=() + local strict_specs=() local tools_installed=() local tools_skipped=() @@ -198,11 +199,53 @@ install_ci_deps() { skip_tool "coverage (coverage disabled)" fi + strict_specs=("${specs[@]}") + # Install dependencies if [ ${#specs[@]} -eq 0 ]; then echo "No install targets found; skipping dependency installation." else - uv pip install --system "${specs[@]}" + local install_ok="true" + if ! uv pip install --system "${strict_specs[@]}"; then + install_ok="false" + echo "Strict dependency install failed; retrying with relaxed compatibility constraints." >&2 + + local relaxed_specs=() + + # Prefer broader top-level dependencies on retry to avoid lockfile pin drift. + if [ -f requirements.txt ]; then + relaxed_specs+=('-r' 'requirements.txt') + elif [ -f pyproject.toml ] || [ -f setup.cfg ] || [ -f setup.py ]; then + relaxed_specs+=('-e' '.') + fi + + # Add unpinned tool/runtime deps so CI jobs still run when exact pins are unavailable. + if [ "$format_enabled" = "true" ]; then + relaxed_specs+=('black' 'docformatter' 'isort') + fi + if [ "$lint_enabled" = "true" ]; then + relaxed_specs+=('ruff') + fi + if [ "$mypy_enabled" = "true" ]; then + relaxed_specs+=('mypy') + fi + relaxed_specs+=('pytest' 'pytest-xdist') + relaxed_specs+=('hypothesis' 'pandas' 'numpy' 'pydantic' 'pydantic-core' 'requests' 'jsonschema' 'PyYAML' 'tomlkit') + if [ "$coverage_enabled" = "true" ]; then + relaxed_specs+=('pytest-cov' 'coverage') + fi + + if [ ${#relaxed_specs[@]} -eq 0 ]; then + echo "No relaxed install targets available after strict install failure." >&2 + elif uv pip install --system "${relaxed_specs[@]}"; then + install_ok="true" + fi + fi + + if [ "$install_ok" != "true" ]; then + echo "Dependency installation failed for both strict and relaxed specs." >&2 + return 1 + fi fi # Generate summary From 5f9b2f248110a6f8e343f01b1226138e002c4733 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Fri, 13 Feb 2026 18:15:34 +0000 Subject: [PATCH 3/4] chore(codex-autofix): apply updates (PR #1495) --- requirements.lock | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.lock b/requirements.lock index 2e8d5181b..2a2657268 100644 --- a/requirements.lock +++ b/requirements.lock @@ -267,7 +267,7 @@ rpds-py==0.30.0 # via # jsonschema # referencing -ruff==0.15.0 +ruff==0.15.1 # via workflows (pyproject.toml) six==1.17.0 # via python-dateutil From 8f34781ee670213d796766476b4b213bc6388fca Mon Sep 17 00:00:00 2001 From: stranske Date: Fri, 13 Feb 2026 18:35:44 +0000 Subject: [PATCH 4/4] fix(keepalive): address progress review feedback - Avoid generic 3-letter heuristic tokens; allowlisted acronyms like png/cprs - Trim lines when detecting end of Acceptance criteria section --- .github/workflows/agents-keepalive-loop.yml | 3 ++- scripts/langchain/progress_reviewer.py | 20 +++++++++++++++++-- .../workflows/agents-keepalive-loop.yml | 3 ++- 3 files changed, 22 insertions(+), 4 deletions(-) diff --git a/.github/workflows/agents-keepalive-loop.yml b/.github/workflows/agents-keepalive-loop.yml index dcedf9880..6d5f937d4 100644 --- a/.github/workflows/agents-keepalive-loop.yml +++ b/.github/workflows/agents-keepalive-loop.yml @@ -641,7 +641,8 @@ jobs: ); if (startIndex !== -1) { for (let i = startIndex + 1; i < lines.length; i += 1) { - if (/^#{1,6}\s+\S/.test(lines[i])) { + const trimmed = lines[i].trim(); + if (/^#{1,6}\s*(\S|$)/.test(trimmed)) { break; } const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/); diff --git a/scripts/langchain/progress_reviewer.py b/scripts/langchain/progress_reviewer.py index 7f1373464..7fa525d2e 100755 --- a/scripts/langchain/progress_reviewer.py +++ b/scripts/langchain/progress_reviewer.py @@ -150,18 +150,34 @@ def heuristic_alignment_check( Returns: (alignment_score, aligned_commits, unaligned_commits) """ + # Allowlist for common, meaningful short tokens that frequently appear in + # acceptance criteria as snake_case parts, and in commits as acronyms. + # Keep this small to avoid inflating alignment via generic 3-letter words. + short_token_allowlist = { + "png", + "pdf", + "csv", + "ppt", + "pptx", + "cprs", + "fcm", + "json", + "yaml", + "yml", + } + criteria_keywords = set() for criterion in acceptance_criteria: # Extract meaningful words from criteria. # Note: acceptance criteria often include snake_case identifiers (e.g. # render_cprs_ch_png). Split those into tokens so commits like # "CPRS-CH PNG" can be recognized as aligned. - words = re.findall(r"\b[a-z0-9_]{3,}\b", criterion.lower()) + words = re.findall(r"\b[a-z0-9_]{4,}\b", criterion.lower()) for word in words: criteria_keywords.add(word) if "_" in word: for token in word.split("_"): - if len(token) >= 3: + if len(token) >= 4 or token in short_token_allowlist: criteria_keywords.add(token) # Infrastructure words that indicate supporting work diff --git a/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml b/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml index 42e2caea0..4573db0f8 100644 --- a/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml +++ b/templates/consumer-repo/.github/workflows/agents-keepalive-loop.yml @@ -639,7 +639,8 @@ jobs: ); if (startIndex !== -1) { for (let i = startIndex + 1; i < lines.length; i += 1) { - if (/^#{1,6}\s+\S/.test(lines[i])) { + const trimmed = lines[i].trim(); + if (/^#{1,6}\s*(\S|$)/.test(trimmed)) { break; } const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/);