From 52aa9cfd8a016e34dcd6a77d4e70ea188ad4329f Mon Sep 17 00:00:00 2001 From: JeremyDev87 Date: Fri, 10 Apr 2026 18:04:20 +0900 Subject: [PATCH] fix: close Wow Wave sub-issue gaps (#1444 write-path + build drift) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Wire RuleTracker write-path for self-evolving rule effectiveness (#1444): - Extend RuleStats with generatedRule / baselineFailureRate / currentFailureRate - Add markRuleAsGenerated(name) and recordFailureRate(name, rate, kind) APIs - Existing trackRuleUsage preserves the generatedRule flag across updates - Drop cast hack in RuleInsightsService now that fields are first-class - 7 new rule-tracker tests cover tagging, failure rate recording, and persistence Sync auto-generated plugin README with manual surface additions (#1218): - scripts/build.ts feature table now includes Staged planning, Clarification gate, Permission forecast, and Council scene rows - Footnote matches post-v5.4.0 wording ("features that read agent definitions") - Regenerated README.md bumps version 5.4.0 → 5.4.1 (aligning with package.json) Also closes already-implemented sub-issues found by the verification pass: #1442 (pipeline wiring), #1443 (suggest_rules handler), #1445 (prompt suggestions), #1446 (onboarding tour integration), #1447 (PreToolUse rule checker), #1448 (violation renderer). --- .../src/rules/rule-insights.service.spec.ts | 6 +- .../src/rules/rule-insights.service.ts | 16 ++-- .../mcp-server/src/rules/rule-tracker.spec.ts | 90 +++++++++++++++++++ apps/mcp-server/src/rules/rule-tracker.ts | 47 ++++++++++ packages/claude-code-plugin/README.md | 2 +- .../namespace-manifest.json | 2 +- packages/claude-code-plugin/scripts/build.ts | 6 +- 7 files changed, 152 insertions(+), 17 deletions(-) diff --git a/apps/mcp-server/src/rules/rule-insights.service.spec.ts b/apps/mcp-server/src/rules/rule-insights.service.spec.ts index 0c7be2dd..8b3e24d4 100644 --- a/apps/mcp-server/src/rules/rule-insights.service.spec.ts +++ b/apps/mcp-server/src/rules/rule-insights.service.spec.ts @@ -215,7 +215,7 @@ describe('RuleInsightsService', () => { generatedRule: true, baselineFailureRate: 0.25, currentFailureRate: 0.05, - } as unknown as RuleStats, + }, }; const result = service.generateInsights(stats, [], NOW); @@ -235,7 +235,7 @@ describe('RuleInsightsService', () => { generatedRule: true, baselineFailureRate: 0.2, currentFailureRate: 0.22, - } as unknown as RuleStats, + }, }; const result = service.generateInsights(stats, [], NOW); @@ -252,7 +252,7 @@ describe('RuleInsightsService', () => { generatedRule: true, baselineFailureRate: 0.3, currentFailureRate: 0.2, - } as unknown as RuleStats, + }, }; const result = service.generateInsights(stats, [], NOW); diff --git a/apps/mcp-server/src/rules/rule-insights.service.ts b/apps/mcp-server/src/rules/rule-insights.service.ts index aac4cf91..265cafea 100644 --- a/apps/mcp-server/src/rules/rule-insights.service.ts +++ b/apps/mcp-server/src/rules/rule-insights.service.ts @@ -139,21 +139,15 @@ export class RuleInsightsService { const scores: EffectivenessScore[] = []; for (const [name, stat] of entries) { - const extended = stat as RuleStats & { - generatedRule?: boolean; - baselineFailureRate?: number; - currentFailureRate?: number; - }; - - if (!extended.generatedRule) continue; + if (!stat.generatedRule) continue; if ( - typeof extended.baselineFailureRate !== 'number' || - typeof extended.currentFailureRate !== 'number' + typeof stat.baselineFailureRate !== 'number' || + typeof stat.currentFailureRate !== 'number' ) continue; - const baseline = extended.baselineFailureRate; - const current = extended.currentFailureRate; + const baseline = stat.baselineFailureRate; + const current = stat.currentFailureRate; const reductionPercent = baseline > 0 ? ((baseline - current) / baseline) * 100 : 0; let verdict: EffectivenessScore['verdict']; diff --git a/apps/mcp-server/src/rules/rule-tracker.spec.ts b/apps/mcp-server/src/rules/rule-tracker.spec.ts index 9b33742d..af98b76b 100644 --- a/apps/mcp-server/src/rules/rule-tracker.spec.ts +++ b/apps/mcp-server/src/rules/rule-tracker.spec.ts @@ -189,4 +189,94 @@ describe('RuleTracker', () => { expect(loaded.getStats()).toEqual({}); }); }); + + describe('markRuleAsGenerated (#1444)', () => { + it('should tag an existing rule as auto-generated', () => { + tracker.trackRuleUsage(['auto-bash-guard']); + tracker.markRuleAsGenerated('auto-bash-guard'); + + const stats = tracker.getStats(); + expect(stats['auto-bash-guard'].generatedRule).toBe(true); + }); + + it('should create a fresh stat entry when marking an unseen rule', () => { + tracker.markRuleAsGenerated('fresh-rule'); + + const stats = tracker.getStats(); + expect(stats['fresh-rule']).toBeDefined(); + expect(stats['fresh-rule'].generatedRule).toBe(true); + expect(stats['fresh-rule'].count).toBe(0); + }); + + it('should ignore empty rule name', () => { + tracker.markRuleAsGenerated(''); + + expect(tracker.getStats()).toEqual({}); + }); + + it('should not wipe generatedRule flag on subsequent trackRuleUsage', () => { + tracker.markRuleAsGenerated('auto-rule'); + tracker.trackRuleUsage(['auto-rule']); + tracker.trackRuleUsage(['auto-rule']); + + const stats = tracker.getStats(); + expect(stats['auto-rule'].generatedRule).toBe(true); + expect(stats['auto-rule'].count).toBe(2); + }); + }); + + describe('recordFailureRate (#1444)', () => { + it('should record baseline failure rate for a generated rule', () => { + tracker.markRuleAsGenerated('auto-rule'); + tracker.recordFailureRate('auto-rule', 0.25, 'baseline'); + + const stats = tracker.getStats(); + expect(stats['auto-rule'].baselineFailureRate).toBe(0.25); + }); + + it('should record current failure rate for a generated rule', () => { + tracker.markRuleAsGenerated('auto-rule'); + tracker.recordFailureRate('auto-rule', 0.05, 'current'); + + const stats = tracker.getStats(); + expect(stats['auto-rule'].currentFailureRate).toBe(0.05); + }); + + it('should overwrite current failure rate on repeat calls', () => { + tracker.markRuleAsGenerated('auto-rule'); + tracker.recordFailureRate('auto-rule', 0.2, 'current'); + tracker.recordFailureRate('auto-rule', 0.1, 'current'); + + const stats = tracker.getStats(); + expect(stats['auto-rule'].currentFailureRate).toBe(0.1); + }); + + it('should be a no-op for rules not marked as generated', () => { + tracker.trackRuleUsage(['manual-rule']); + tracker.recordFailureRate('manual-rule', 0.5, 'baseline'); + + const stats = tracker.getStats(); + expect(stats['manual-rule'].baselineFailureRate).toBeUndefined(); + }); + + it('should be a no-op for unknown rules', () => { + tracker.recordFailureRate('unknown-rule', 0.5, 'baseline'); + + expect(tracker.getStats()).toEqual({}); + }); + + it('should persist failure rates through save/load cycle', async () => { + vi.mocked(existsSync).mockReturnValue(true); + tracker.markRuleAsGenerated('auto-rule'); + tracker.recordFailureRate('auto-rule', 0.3, 'baseline'); + tracker.recordFailureRate('auto-rule', 0.05, 'current'); + + await tracker.save(); + + const written = JSON.parse(vi.mocked(fs.writeFile).mock.calls[0][1] as string); + expect(written['auto-rule'].generatedRule).toBe(true); + expect(written['auto-rule'].baselineFailureRate).toBe(0.3); + expect(written['auto-rule'].currentFailureRate).toBe(0.05); + }); + }); }); diff --git a/apps/mcp-server/src/rules/rule-tracker.ts b/apps/mcp-server/src/rules/rule-tracker.ts index e4db9134..5c3785e3 100644 --- a/apps/mcp-server/src/rules/rule-tracker.ts +++ b/apps/mcp-server/src/rules/rule-tracker.ts @@ -5,6 +5,22 @@ import { dirname } from 'path'; export interface RuleStats { count: number; lastUsed: number; + /** + * True when the rule was generated by the self-evolving pipeline + * (#1444). Used to separate auto-generated rules from manual rules in + * effectiveness reports. + */ + generatedRule?: boolean; + /** + * Baseline failure rate captured when a generated rule was first + * introduced. Combined with {@link currentFailureRate} to compute + * effectiveness reduction percent. + */ + baselineFailureRate?: number; + /** + * Latest observed failure rate for a generated rule. + */ + currentFailureRate?: number; } export interface RuleEffectivenessReport { @@ -46,6 +62,37 @@ export class RuleTracker { } } + /** + * Mark a rule as auto-generated by the self-evolving pipeline (#1444). + * Creates the stat entry if the rule has never been tracked. + */ + markRuleAsGenerated(ruleName: string): void { + if (!ruleName) return; + if (!this.stats[ruleName]) { + this.stats[ruleName] = { count: 0, lastUsed: Date.now() }; + } + this.stats[ruleName].generatedRule = true; + } + + /** + * Record a baseline or current failure rate for a generated rule + * (#1444). Baseline is captured once when the rule is introduced; + * current is updated as new execution data arrives. + * + * A rule must be marked as generated (see {@link markRuleAsGenerated}) + * before failure rates are recorded — otherwise the call is a no-op. + */ + recordFailureRate(ruleName: string, rate: number, kind: 'baseline' | 'current'): void { + if (!ruleName) return; + const stat = this.stats[ruleName]; + if (!stat || !stat.generatedRule) return; + if (kind === 'baseline') { + stat.baselineFailureRate = rate; + } else { + stat.currentFailureRate = rate; + } + } + getStats(): Record { return JSON.parse(JSON.stringify(this.stats)); } diff --git a/packages/claude-code-plugin/README.md b/packages/claude-code-plugin/README.md index 5fa84b45..d04e7f0c 100644 --- a/packages/claude-code-plugin/README.md +++ b/packages/claude-code-plugin/README.md @@ -2,7 +2,7 @@ # CodingBuddy Claude Code Plugin -> Version 5.4.0 +> Version 5.4.1 Multi-AI Rules for consistent coding practices - PLAN/ACT/EVAL workflow, specialist agents, and reusable skills for systematic development. diff --git a/packages/claude-code-plugin/namespace-manifest.json b/packages/claude-code-plugin/namespace-manifest.json index 94f9a63d..0d63da5c 100644 --- a/packages/claude-code-plugin/namespace-manifest.json +++ b/packages/claude-code-plugin/namespace-manifest.json @@ -34,5 +34,5 @@ "namespaced": "codingbuddy:plan" } ], - "generatedAt": "2026-04-06T09:54:05.647Z" + "generatedAt": "2026-04-10T09:03:53.981Z" } diff --git a/packages/claude-code-plugin/scripts/build.ts b/packages/claude-code-plugin/scripts/build.ts index 66dce259..2651d281 100644 --- a/packages/claude-code-plugin/scripts/build.ts +++ b/packages/claude-code-plugin/scripts/build.ts @@ -119,6 +119,10 @@ The plugin works standalone with core features. MCP integration unlocks the full | Feature | Standalone | With MCP | | ------------------------------------ | :--------: | :------: | | PLAN/ACT/EVAL/AUTO keyword triggers | ✅ | ✅ | +| Staged planning (Discover→Design→Plan) | ✅ | ✅ | +| Clarification gate (ambiguous prompts) | ✅ | ✅ | +| Permission forecast (prompt-aware) | ✅ | ✅ | +| Council scene (agent eye glyphs) | ✅ ¹ | ✅ | | Specialist agent prompts | ⚠️ ¹ | ✅ | | Slash commands (codingbuddy:\\*) | ✅ | ✅ | | Dynamic checklists | — | ✅ | @@ -126,7 +130,7 @@ The plugin works standalone with core features. MCP integration unlocks the full | Rule search & impact reports | — | ✅ | | Session briefings & recovery | — | ✅ | -> ¹ Standalone specialist prompts require a local \`.ai-rules/\` directory. Run \`npx codingbuddy-rules init\` to scaffold it. See [#1216](https://github.com/JeremyDev87/codingbuddy/issues/1216) for the standalone enhancement roadmap. +> ¹ Standalone features that read agent definitions require a local \`.ai-rules/\` directory. Run \`npx codingbuddy-rules init\` to scaffold it. See [#1216](https://github.com/JeremyDev87/codingbuddy/issues/1216) for the standalone enhancement roadmap. ## MCP Integration (Recommended)