Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 44 additions & 1 deletion .github/scripts/install-ci-deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@ install_ci_deps() {

# Initialize arrays
local specs=()
local strict_specs=()
local tools_installed=()
local tools_skipped=()

Expand Down Expand Up @@ -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
Expand Down
17 changes: 11 additions & 6 deletions .github/workflows/agents-keepalive-loop.yml
Original file line number Diff line number Diff line change
Expand Up @@ -635,12 +635,17 @@ 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) {
const trimmed = lines[i].trim();
if (/^#{1,6}\s*(\S|$)/.test(trimmed)) {
break;
}
const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/);
if (match) {
criteria.push(match[1].trim());
}
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/autofix-versions.env
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
2 changes: 1 addition & 1 deletion requirements.lock
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
32 changes: 28 additions & 4 deletions scripts/langchain/progress_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -150,11 +150,35 @@ 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 (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_]{4,}\b", criterion.lower())
for word in words:
criteria_keywords.add(word)
if "_" in word:
for token in word.split("_"):
if len(token) >= 4 or token in short_token_allowlist:
criteria_keywords.add(token)

# Infrastructure words that indicate supporting work
# These alone don't count as alignment, but combined with criteria keywords they help
Expand Down Expand Up @@ -201,7 +225,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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -633,12 +633,17 @@ 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) {
const trimmed = lines[i].trim();
if (/^#{1,6}\s*(\S|$)/.test(trimmed)) {
break;
}
const match = lines[i].match(/^\s*[-*+]\s*(?:\[[ xX]\]\s*)?(.+)/);
if (match) {
criteria.push(match[1].trim());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
19 changes: 19 additions & 0 deletions tests/scripts/test_progress_reviewer.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Loading