diff --git a/.github/workflows/devlens-live-integration.yml b/.github/workflows/devlens-live-integration.yml index ea52b28..070409b 100644 --- a/.github/workflows/devlens-live-integration.yml +++ b/.github/workflows/devlens-live-integration.yml @@ -66,7 +66,7 @@ jobs: assert set(report["scores"]) == dimensions assert all(0 <= int(v) <= 100 for v in report["scores"].values()) assert os.environ["BADGE"].startswith("https://img.shields.io/badge/") - assert report["action_version"] == "2.0.8" + assert report["action_version"] == "2.0.9" print(f"Live DevLens integration passed: {score}/100") diff --git a/CHANGELOG.md b/CHANGELOG.md index c3fe757..3a199b6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,10 @@ All notable changes to DevLens, newest first. ## Unreleased — 2026-09-30 +- Corrected Issue Response scoring to exclude pull requests from issue counts. +- Added deterministic regression fixtures for issue-response scoring. +- Updated live integration expectations for the `2.0.9` candidate. + ### Documentation and distribution status - Verified by the Human Owner that the live GitHub Marketplace listing displays **DevLens Repo Health** as **Latest v2.0.8** with the current Action-only product content. - Closed the Marketplace publication/verification blocker and advanced the project to Phase 2 — Adoption and onboarding. diff --git a/docs/AI_PROJECT_CONTEXT.md b/docs/AI_PROJECT_CONTEXT.md index dfa7192..3cc1158 100644 --- a/docs/AI_PROJECT_CONTEXT.md +++ b/docs/AI_PROJECT_CONTEXT.md @@ -310,7 +310,7 @@ As of 2026-09-30: - `v2.0.0` exists as the original Marketplace release tag. - `v2.0.1` was previously published successfully. -- `v2.0.8` is the latest verified release and was created and published by the automatic critical-change release workflow. +- `v2.0.8` is the latest currently published release; `v2.0.9` is the pending candidate for the issue-response scoring correction. - The release preflight executed the real Action successfully and validated the reported Action version against the release version. - The floating `v2` tag was updated successfully to `v2.0.8`. - The automatic release workflow detects critical production paths on pushes to `main`, derives the release version from `scripts/devlens.py`, runs static validation and live Action preflight, updates `v2`, and publishes the GitHub Release. @@ -331,10 +331,10 @@ As of 2026-09-30: - Vercel configuration is removed from the repository. - Legacy scanner code is removed. - Legacy Groq scoring dependencies/inputs are removed. -- README, contributor, security, documentation, and changelog materials are aligned with v2.0.8. +- README, contributor, security, documentation, and changelog materials are aligned with the current v2.0.9 candidate where applicable. - Live GitHub integration has executed the actual Action successfully. - README persistence has been tested on a disposable branch and verified through GitHub API. -- Current release line is v2; `v2.0.8` is the latest verified version. +- Current release line is v2; `v2.0.8` is the latest verified published version, with `v2.0.9` pending validation. - Floating `v2` points to the latest verified v2 release (`v2.0.8`). - Repository release automation has been hardened so manual releases complete in one workflow run and do not depend on a token-generated tag push triggering a second workflow. - Repository metadata may still contain stale external/homepage information and should be verified before treating metadata cleanup as complete. @@ -389,7 +389,7 @@ Remaining Phase 1 operational check: ### Phase 3 — Scoring quality and observability — NEXT -- Expand automated edge-case tests. +- **IN PROGRESS:** Expand automated edge-case tests, beginning with Issue Response. - Improve evidence shown for individual dimension scores. - Reduce unnecessary API calls and startup cost. - Improve sparse-history behavior. @@ -498,7 +498,7 @@ The repository adopts the AI CEO/CIO → COO execution model and treats document ### D-012 — Action report version must match implementation **Status:** DECISION / ACTIVE. -The machine-readable report action version must identify the actual DevLens implementation version. This is enforced by release preflight and live integration validation. The current verified implementation reports `2.0.8`. +The machine-readable report action version must identify the actual DevLens implementation version. This is enforced by release preflight and live integration validation. The current verified implementation candidate reports `2.0.9`; the production release remains `v2.0.8` until release preflight completes. ### D-010 — Automated semantic v2 release flow **Status:** DECISION / ACTIVE. diff --git a/scripts/devlens.py b/scripts/devlens.py index c7713a9..9dcffc3 100644 --- a/scripts/devlens.py +++ b/scripts/devlens.py @@ -81,15 +81,25 @@ def score_ci(): return 0 except: return 0 +def score_issue_response(open_count, closed_count): + # Keep issue response deterministic and independent of pull-request closure activity. + """Score actual GitHub issues only; pull requests are excluded from both counts.""" + if open_count < 0 or closed_count < 0: + raise ValueError("Issue counts cannot be negative") + total = open_count + closed_count + if total == 0: + return 100 + return int(closed_count / total * 100) + def score_issues(): try: - open_i = repo.open_issues_count - closed_i = list(repo.get_issues(state="closed"))[:50] - if not closed_i and open_i == 0: return 100 - total = open_i + len(closed_i) - if total == 0: return 100 - return int(len(closed_i) / total * 100) - except: return 50 + # GitHub's /issues collection includes pull requests. Search with is:issue + # so the dimension measures issue response rather than PR closure activity. + open_i = g.search_issues(query=f"repo:{REPO_NAME} is:issue is:open").totalCount + closed_i = g.search_issues(query=f"repo:{REPO_NAME} is:issue is:closed").totalCount + return score_issue_response(open_i, closed_i) + except Exception: + return 50 def score_community(): return min(int(math.log1p(repo.stargazers_count)*15)+int(math.log1p(repo.forks_count)*10), 100) @@ -162,7 +172,7 @@ def dim_bar(score): badge_url = (f"https://img.shields.io/badge/DevLens%20Health-{health}%2F100" f"-{badge_color(health)}?style={BADGE_STYLE}&logo=github") -report = {"repo":REPO_NAME,"score_model":"action-v2-9d","action_version":"2.0.8","health_score":health,"scores":scores, +report = {"repo":REPO_NAME,"score_model":"action-v2-9d","action_version":"2.0.9","health_score":health,"scores":scores, "badge_url":badge_url,"generated_at":now.isoformat()} print(json.dumps(report, indent=2)) diff --git a/tests/test_issue_response_scoring.py b/tests/test_issue_response_scoring.py new file mode 100644 index 0000000..858f14f --- /dev/null +++ b/tests/test_issue_response_scoring.py @@ -0,0 +1,31 @@ +import ast +from pathlib import Path + +source = Path("scripts/devlens.py").read_text() +tree = ast.parse(source) +fn = next(node for node in tree.body if isinstance(node, ast.FunctionDef) and node.name == "score_issue_response") +module = ast.Module(body=[fn], type_ignores=[]) +namespace = {} +exec(compile(module, "scripts/devlens.py", "exec"), namespace) + +score = namespace["score_issue_response"] +cases = [ + ((0, 0), 100), + ((0, 10), 100), + ((10, 0), 0), + ((5, 5), 50), + ((1, 9), 90), +] +for args, expected in cases: + actual = score(*args) + assert actual == expected, (args, actual, expected) + +for args in [(-1, 0), (0, -1)]: + try: + score(*args) + except ValueError: + pass + else: + raise AssertionError(f"negative counts accepted: {args}") + +print("Issue-response scoring fixtures passed.")