Skip to content

Commit 52aa9cf

Browse files
committed
fix: close Wow Wave sub-issue gaps (#1444 write-path + build drift)
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).
1 parent c1dd7f1 commit 52aa9cf

7 files changed

Lines changed: 152 additions & 17 deletions

File tree

apps/mcp-server/src/rules/rule-insights.service.spec.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -215,7 +215,7 @@ describe('RuleInsightsService', () => {
215215
generatedRule: true,
216216
baselineFailureRate: 0.25,
217217
currentFailureRate: 0.05,
218-
} as unknown as RuleStats,
218+
},
219219
};
220220

221221
const result = service.generateInsights(stats, [], NOW);
@@ -235,7 +235,7 @@ describe('RuleInsightsService', () => {
235235
generatedRule: true,
236236
baselineFailureRate: 0.2,
237237
currentFailureRate: 0.22,
238-
} as unknown as RuleStats,
238+
},
239239
};
240240

241241
const result = service.generateInsights(stats, [], NOW);
@@ -252,7 +252,7 @@ describe('RuleInsightsService', () => {
252252
generatedRule: true,
253253
baselineFailureRate: 0.3,
254254
currentFailureRate: 0.2,
255-
} as unknown as RuleStats,
255+
},
256256
};
257257

258258
const result = service.generateInsights(stats, [], NOW);

apps/mcp-server/src/rules/rule-insights.service.ts

Lines changed: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -139,21 +139,15 @@ export class RuleInsightsService {
139139
const scores: EffectivenessScore[] = [];
140140

141141
for (const [name, stat] of entries) {
142-
const extended = stat as RuleStats & {
143-
generatedRule?: boolean;
144-
baselineFailureRate?: number;
145-
currentFailureRate?: number;
146-
};
147-
148-
if (!extended.generatedRule) continue;
142+
if (!stat.generatedRule) continue;
149143
if (
150-
typeof extended.baselineFailureRate !== 'number' ||
151-
typeof extended.currentFailureRate !== 'number'
144+
typeof stat.baselineFailureRate !== 'number' ||
145+
typeof stat.currentFailureRate !== 'number'
152146
)
153147
continue;
154148

155-
const baseline = extended.baselineFailureRate;
156-
const current = extended.currentFailureRate;
149+
const baseline = stat.baselineFailureRate;
150+
const current = stat.currentFailureRate;
157151
const reductionPercent = baseline > 0 ? ((baseline - current) / baseline) * 100 : 0;
158152

159153
let verdict: EffectivenessScore['verdict'];

apps/mcp-server/src/rules/rule-tracker.spec.ts

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -189,4 +189,94 @@ describe('RuleTracker', () => {
189189
expect(loaded.getStats()).toEqual({});
190190
});
191191
});
192+
193+
describe('markRuleAsGenerated (#1444)', () => {
194+
it('should tag an existing rule as auto-generated', () => {
195+
tracker.trackRuleUsage(['auto-bash-guard']);
196+
tracker.markRuleAsGenerated('auto-bash-guard');
197+
198+
const stats = tracker.getStats();
199+
expect(stats['auto-bash-guard'].generatedRule).toBe(true);
200+
});
201+
202+
it('should create a fresh stat entry when marking an unseen rule', () => {
203+
tracker.markRuleAsGenerated('fresh-rule');
204+
205+
const stats = tracker.getStats();
206+
expect(stats['fresh-rule']).toBeDefined();
207+
expect(stats['fresh-rule'].generatedRule).toBe(true);
208+
expect(stats['fresh-rule'].count).toBe(0);
209+
});
210+
211+
it('should ignore empty rule name', () => {
212+
tracker.markRuleAsGenerated('');
213+
214+
expect(tracker.getStats()).toEqual({});
215+
});
216+
217+
it('should not wipe generatedRule flag on subsequent trackRuleUsage', () => {
218+
tracker.markRuleAsGenerated('auto-rule');
219+
tracker.trackRuleUsage(['auto-rule']);
220+
tracker.trackRuleUsage(['auto-rule']);
221+
222+
const stats = tracker.getStats();
223+
expect(stats['auto-rule'].generatedRule).toBe(true);
224+
expect(stats['auto-rule'].count).toBe(2);
225+
});
226+
});
227+
228+
describe('recordFailureRate (#1444)', () => {
229+
it('should record baseline failure rate for a generated rule', () => {
230+
tracker.markRuleAsGenerated('auto-rule');
231+
tracker.recordFailureRate('auto-rule', 0.25, 'baseline');
232+
233+
const stats = tracker.getStats();
234+
expect(stats['auto-rule'].baselineFailureRate).toBe(0.25);
235+
});
236+
237+
it('should record current failure rate for a generated rule', () => {
238+
tracker.markRuleAsGenerated('auto-rule');
239+
tracker.recordFailureRate('auto-rule', 0.05, 'current');
240+
241+
const stats = tracker.getStats();
242+
expect(stats['auto-rule'].currentFailureRate).toBe(0.05);
243+
});
244+
245+
it('should overwrite current failure rate on repeat calls', () => {
246+
tracker.markRuleAsGenerated('auto-rule');
247+
tracker.recordFailureRate('auto-rule', 0.2, 'current');
248+
tracker.recordFailureRate('auto-rule', 0.1, 'current');
249+
250+
const stats = tracker.getStats();
251+
expect(stats['auto-rule'].currentFailureRate).toBe(0.1);
252+
});
253+
254+
it('should be a no-op for rules not marked as generated', () => {
255+
tracker.trackRuleUsage(['manual-rule']);
256+
tracker.recordFailureRate('manual-rule', 0.5, 'baseline');
257+
258+
const stats = tracker.getStats();
259+
expect(stats['manual-rule'].baselineFailureRate).toBeUndefined();
260+
});
261+
262+
it('should be a no-op for unknown rules', () => {
263+
tracker.recordFailureRate('unknown-rule', 0.5, 'baseline');
264+
265+
expect(tracker.getStats()).toEqual({});
266+
});
267+
268+
it('should persist failure rates through save/load cycle', async () => {
269+
vi.mocked(existsSync).mockReturnValue(true);
270+
tracker.markRuleAsGenerated('auto-rule');
271+
tracker.recordFailureRate('auto-rule', 0.3, 'baseline');
272+
tracker.recordFailureRate('auto-rule', 0.05, 'current');
273+
274+
await tracker.save();
275+
276+
const written = JSON.parse(vi.mocked(fs.writeFile).mock.calls[0][1] as string);
277+
expect(written['auto-rule'].generatedRule).toBe(true);
278+
expect(written['auto-rule'].baselineFailureRate).toBe(0.3);
279+
expect(written['auto-rule'].currentFailureRate).toBe(0.05);
280+
});
281+
});
192282
});

apps/mcp-server/src/rules/rule-tracker.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,22 @@ import { dirname } from 'path';
55
export interface RuleStats {
66
count: number;
77
lastUsed: number;
8+
/**
9+
* True when the rule was generated by the self-evolving pipeline
10+
* (#1444). Used to separate auto-generated rules from manual rules in
11+
* effectiveness reports.
12+
*/
13+
generatedRule?: boolean;
14+
/**
15+
* Baseline failure rate captured when a generated rule was first
16+
* introduced. Combined with {@link currentFailureRate} to compute
17+
* effectiveness reduction percent.
18+
*/
19+
baselineFailureRate?: number;
20+
/**
21+
* Latest observed failure rate for a generated rule.
22+
*/
23+
currentFailureRate?: number;
824
}
925

1026
export interface RuleEffectivenessReport {
@@ -46,6 +62,37 @@ export class RuleTracker {
4662
}
4763
}
4864

65+
/**
66+
* Mark a rule as auto-generated by the self-evolving pipeline (#1444).
67+
* Creates the stat entry if the rule has never been tracked.
68+
*/
69+
markRuleAsGenerated(ruleName: string): void {
70+
if (!ruleName) return;
71+
if (!this.stats[ruleName]) {
72+
this.stats[ruleName] = { count: 0, lastUsed: Date.now() };
73+
}
74+
this.stats[ruleName].generatedRule = true;
75+
}
76+
77+
/**
78+
* Record a baseline or current failure rate for a generated rule
79+
* (#1444). Baseline is captured once when the rule is introduced;
80+
* current is updated as new execution data arrives.
81+
*
82+
* A rule must be marked as generated (see {@link markRuleAsGenerated})
83+
* before failure rates are recorded — otherwise the call is a no-op.
84+
*/
85+
recordFailureRate(ruleName: string, rate: number, kind: 'baseline' | 'current'): void {
86+
if (!ruleName) return;
87+
const stat = this.stats[ruleName];
88+
if (!stat || !stat.generatedRule) return;
89+
if (kind === 'baseline') {
90+
stat.baselineFailureRate = rate;
91+
} else {
92+
stat.currentFailureRate = rate;
93+
}
94+
}
95+
4996
getStats(): Record<string, RuleStats> {
5097
return JSON.parse(JSON.stringify(this.stats));
5198
}

packages/claude-code-plugin/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
# CodingBuddy Claude Code Plugin
44

5-
> Version 5.4.0
5+
> Version 5.4.1
66
77
Multi-AI Rules for consistent coding practices - PLAN/ACT/EVAL workflow, specialist agents, and reusable skills for systematic development.
88

packages/claude-code-plugin/namespace-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,5 +34,5 @@
3434
"namespaced": "codingbuddy:plan"
3535
}
3636
],
37-
"generatedAt": "2026-04-06T09:54:05.647Z"
37+
"generatedAt": "2026-04-10T09:03:53.981Z"
3838
}

packages/claude-code-plugin/scripts/build.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,14 +119,18 @@ The plugin works standalone with core features. MCP integration unlocks the full
119119
| Feature | Standalone | With MCP |
120120
| ------------------------------------ | :--------: | :------: |
121121
| PLAN/ACT/EVAL/AUTO keyword triggers | ✅ | ✅ |
122+
| Staged planning (Discover→Design→Plan) | ✅ | ✅ |
123+
| Clarification gate (ambiguous prompts) | ✅ | ✅ |
124+
| Permission forecast (prompt-aware) | ✅ | ✅ |
125+
| Council scene (agent eye glyphs) | ✅ ¹ | ✅ |
122126
| Specialist agent prompts | ⚠️ ¹ | ✅ |
123127
| Slash commands (codingbuddy:\\*) | ✅ | ✅ |
124128
| Dynamic checklists | — | ✅ |
125129
| Context persistence across modes | — | ✅ |
126130
| Rule search & impact reports | — | ✅ |
127131
| Session briefings & recovery | — | ✅ |
128132
129-
> ¹ 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.
133+
> ¹ 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.
130134
131135
## MCP Integration (Recommended)
132136

0 commit comments

Comments
 (0)