From 32bbddab1d21276abd4bcc5d3e30ec6674718bee Mon Sep 17 00:00:00 2001 From: Eneko Ruiz <196690101+eneekoruiz@users.noreply.github.com> Date: Sun, 11 Oct 2026 11:31:21 +0200 Subject: [PATCH] fix: keep unchanged skill rates stable at zero threshold --- scripts/lib/skill-evolution/health.js | 3 +++ tests/lib/skill-evolution.test.js | 30 +++++++++++++++++++++++++++ 2 files changed, 33 insertions(+) diff --git a/scripts/lib/skill-evolution/health.js b/scripts/lib/skill-evolution/health.js index c57e23354..34caaf80b 100644 --- a/scripts/lib/skill-evolution/health.js +++ b/scripts/lib/skill-evolution/health.js @@ -121,6 +121,9 @@ function getFailureTrend(successRate7d, successRate30d, warnThreshold) { } const delta = roundRate(successRate7d - successRate30d); + if (delta === 0) { + return 'stable'; + } if (delta <= (-1 * warnThreshold)) { return 'worsening'; } diff --git a/tests/lib/skill-evolution.test.js b/tests/lib/skill-evolution.test.js index f2dd4e6ec..db2452023 100644 --- a/tests/lib/skill-evolution.test.js +++ b/tests/lib/skill-evolution.test.js @@ -580,6 +580,36 @@ function runTests() { ); })) passed++; else failed++; + if (test('zero warning threshold distinguishes unchanged rates from actual trends', () => { + const recent = '2026-03-14T10:00:00.000Z'; + const older = '2026-02-24T10:00:00.000Z'; + const records = [ + { skill_id: 'unchanged', outcome: 'success', recorded_at: recent }, + { skill_id: 'improving', outcome: 'failure', recorded_at: older }, + { skill_id: 'improving', outcome: 'success', recorded_at: recent }, + { skill_id: 'declining', outcome: 'success', recorded_at: older }, + { skill_id: 'declining', outcome: 'failure', recorded_at: recent }, + ]; + const report = health.collectSkillHealth({ + repoRoot, + homeDir, + now, + warnThreshold: 0, + stateStore: { listSkillExecutionRecords: () => records }, + }); + + assert.strictEqual(report.warn_threshold, 0); + for (const [skillId, expectedTrend] of [ + ['unchanged', 'stable'], + ['improving', 'improving'], + ['declining', 'worsening'], + ]) { + const skill = report.skills.find(entry => entry.skill_id === skillId); + assert.strictEqual(skill.failure_trend, expectedTrend); + assert.strictEqual(skill.declining, expectedTrend === 'worsening'); + } + })) passed++; else failed++; + if (test('does not report unmeasured skills as healthy (#2463)', () => { // A runs file that does not exist: skills are still discovered, so this // is exactly the "discovered but never executed" state.