codingbuddy-rules 4.3.0 → 4.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,292 @@
1
+ ---
2
+ name: legacy-modernization
3
+ description: Use when modernizing legacy code or migrating outdated patterns to current best practices. Covers assessment, strangler fig pattern, incremental migration, and risk management.
4
+ ---
5
+
6
+ # Legacy Modernization
7
+
8
+ ## Overview
9
+
10
+ Legacy code isn't bad code — it's code that solved a real problem when it was written. Modernization is the process of adapting it to current requirements without breaking what works.
11
+
12
+ **Core principle:** Never rewrite from scratch. Strangle it incrementally. Each step must leave the system in a working state.
13
+
14
+ **Iron Law:**
15
+ ```
16
+ THE SYSTEM MUST WORK AFTER EVERY CHANGE
17
+ There is no "temporarily broken while we modernize"
18
+ ```
19
+
20
+ ## When to Use
21
+
22
+ - Migrating from CommonJS to ESM
23
+ - Upgrading major framework versions (NestJS 9 → 10)
24
+ - Replacing callback patterns with async/await
25
+ - Moving from JavaScript to TypeScript
26
+ - Replacing deprecated APIs
27
+ - Architectural migration (monolith → modules)
28
+
29
+ ## Assessment Phase
30
+
31
+ ### Inventory
32
+
33
+ Before any changes, understand what you have:
34
+
35
+ ```bash
36
+ # Size and complexity
37
+ find src/ -name "*.ts" -o -name "*.js" | \
38
+ xargs wc -l | sort -rn | head -20
39
+
40
+ # Dependency graph
41
+ npx madge --circular src/
42
+
43
+ # Test coverage (what's protected)
44
+ npx jest --coverage --coverageReporters text-summary
45
+
46
+ # Outdated packages
47
+ npm outdated
48
+
49
+ # Known patterns to modernize
50
+ grep -rn "require\(" src/ --include="*.ts" | wc -l # CommonJS in TypeScript
51
+ grep -rn "callback\|\.then\|\.catch" src/ --include="*.ts" | wc -l # Promises vs async
52
+ grep -rn ": any" src/ --include="*.ts" | wc -l # Untyped code
53
+ ```
54
+
55
+ ### Risk Assessment
56
+
57
+ Rate each area for modernization risk:
58
+
59
+ ```markdown
60
+ ## Modernization Risk Register
61
+
62
+ | Module | Lines | Test Coverage | External Deps | Criticality | Risk |
63
+ |--------|-------|---------------|---------------|-------------|------|
64
+ | rules.service.ts | 120 | 85% | None | High | LOW |
65
+ | mcp.service.ts | 450 | 45% | MCP SDK | High | HIGH |
66
+ | main.ts | 60 | 0% | NestJS | Medium | MEDIUM |
67
+
68
+ Risk = (Criticality × (100 - Coverage%)) / 100
69
+ ```
70
+
71
+ ### Modernization Backlog
72
+
73
+ ```markdown
74
+ ## Modernization Items
75
+
76
+ | ID | Pattern | Count | Files | Effort |
77
+ |----|---------|-------|-------|--------|
78
+ | M-001 | `any` type → explicit types | 23 | 8 files | 2h |
79
+ | M-002 | Callbacks → async/await | 12 | 4 files | 4h |
80
+ | M-003 | CommonJS → ESM imports | 45 | 15 files | 1h |
81
+ | M-004 | NestJS 9 → NestJS 10 | 1 | package.json | 8h |
82
+ ```
83
+
84
+ ## Migration Strategies
85
+
86
+ ### Strategy 1: Strangler Fig (Recommended)
87
+
88
+ Build new behavior alongside old behavior, gradually replacing old with new.
89
+
90
+ ```typescript
91
+ // Phase 1: New implementation behind feature flag
92
+ async getRules(): Promise<Rule[]> {
93
+ if (process.env.USE_NEW_RULES_ENGINE === 'true') {
94
+ return this.newRulesEngine.getRules(); // New implementation
95
+ }
96
+ return this.legacyGetRules(); // Old implementation still works
97
+ }
98
+
99
+ // Phase 2: Enable in staging, verify
100
+ // Phase 3: Enable in production (10% → 50% → 100%)
101
+ // Phase 4: Remove old implementation
102
+ async getRules(): Promise<Rule[]> {
103
+ return this.newRulesEngine.getRules(); // Old code removed
104
+ }
105
+ ```
106
+
107
+ ### Strategy 2: Branch by Abstraction
108
+
109
+ 1. Create abstraction over legacy code
110
+ 2. Implement new code behind abstraction
111
+ 3. Switch abstraction to new code
112
+ 4. Remove old code
113
+
114
+ ```typescript
115
+ // Step 1: Introduce interface
116
+ interface RulesEngine {
117
+ getRules(): Promise<Rule[]>;
118
+ searchRules(query: string): Promise<Rule[]>;
119
+ }
120
+
121
+ // Step 2: Wrap legacy code
122
+ class LegacyRulesEngine implements RulesEngine {
123
+ // Existing code, now behind interface
124
+ }
125
+
126
+ // Step 3: New implementation
127
+ class NewRulesEngine implements RulesEngine {
128
+ // Modern implementation
129
+ }
130
+
131
+ // Step 4: Switch (one line change)
132
+ const rulesEngine: RulesEngine = new NewRulesEngine();
133
+ // Was: new LegacyRulesEngine()
134
+ ```
135
+
136
+ ## Common Modernization Patterns
137
+
138
+ ### JavaScript → TypeScript
139
+
140
+ ```javascript
141
+ // Before (JavaScript)
142
+ function getUser(id) {
143
+ return fetch('/api/users/' + id)
144
+ .then(function(response) {
145
+ return response.json();
146
+ })
147
+ .then(function(data) {
148
+ return data.user;
149
+ });
150
+ }
151
+ ```
152
+
153
+ ```typescript
154
+ // After (TypeScript + async/await)
155
+ async function getUser(id: string): Promise<User> {
156
+ const response = await fetch(`/api/users/${id}`);
157
+ const data: { user: User } = await response.json();
158
+ return data.user;
159
+ }
160
+ ```
161
+
162
+ ### Callbacks → Async/Await
163
+
164
+ ```javascript
165
+ // Before (callbacks)
166
+ function readRule(path, callback) {
167
+ fs.readFile(path, 'utf8', function(err, content) {
168
+ if (err) return callback(err);
169
+ callback(null, parseRule(content));
170
+ });
171
+ }
172
+ ```
173
+
174
+ ```typescript
175
+ // After (async/await)
176
+ async function readRule(path: string): Promise<Rule> {
177
+ const content = await fs.promises.readFile(path, 'utf-8');
178
+ return parseRule(content);
179
+ }
180
+ ```
181
+
182
+ ### Removing `any` Types
183
+
184
+ ```typescript
185
+ // Before
186
+ function processRule(rule: any): any {
187
+ return { name: rule.name, content: rule.content };
188
+ }
189
+
190
+ // After (step 1: define interface)
191
+ interface RuleInput {
192
+ name: string;
193
+ content: string;
194
+ [key: string]: unknown; // allow extra properties
195
+ }
196
+
197
+ interface ProcessedRule {
198
+ name: string;
199
+ content: string;
200
+ }
201
+
202
+ // After (step 2: type the function)
203
+ function processRule(rule: RuleInput): ProcessedRule {
204
+ return { name: rule.name, content: rule.content };
205
+ }
206
+ ```
207
+
208
+ ## Safe Migration Process
209
+
210
+ ```
211
+ For EACH modernization item:
212
+
213
+ 1. WRITE TESTS (if coverage < 80%)
214
+ - Tests must cover current behavior
215
+ - These tests protect against regression
216
+
217
+ 2. MODERNIZE one file at a time
218
+ - Apply pattern change
219
+ - Keep behavior identical
220
+
221
+ 3. RUN ALL TESTS
222
+ - All existing tests must still pass
223
+ - No "we'll fix the tests later"
224
+
225
+ 4. COMMIT
226
+ - One commit per file or per pattern type
227
+ - Message: "refactor: migrate X to Y in Z"
228
+
229
+ 5. VERIFY in staging before next item
230
+ ```
231
+
232
+ ## Version Upgrade Process (Major Versions)
233
+
234
+ ```markdown
235
+ ## NestJS 9 → 10 Migration Plan
236
+
237
+ ### Preparation
238
+ - [ ] Read migration guide: https://docs.nestjs.com/migration-guide
239
+ - [ ] Test suite at 80%+ coverage
240
+ - [ ] Feature branch created
241
+
242
+ ### Steps
243
+ 1. [ ] Update peer dependencies first
244
+ 2. [ ] Update NestJS packages
245
+ 3. [ ] Fix breaking API changes (see migration guide)
246
+ 4. [ ] Run test suite
247
+ 5. [ ] Fix type errors
248
+ 6. [ ] Test in staging
249
+ 7. [ ] Deploy to production
250
+
251
+ ### Rollback
252
+ - git revert to previous package.json
253
+ - npm install
254
+ - Deploy previous version
255
+ ```
256
+
257
+ ## Risk Mitigation
258
+
259
+ | Risk | Mitigation |
260
+ |------|-----------|
261
+ | Breaking working functionality | Write tests before modernizing |
262
+ | Long migration blocks features | Use strangler fig pattern |
263
+ | Merge conflicts | Small, frequent commits |
264
+ | Regression in edge cases | Test coverage for edge cases first |
265
+ | Team unfamiliar with new patterns | Document new patterns + pair programming |
266
+
267
+ ## Red Flags — STOP
268
+
269
+ | Thought | Reality |
270
+ |---------|---------|
271
+ | "Let's rewrite it from scratch" | Rewrites take 3× longer and miss edge cases |
272
+ | "We'll fix tests later" | Tests are the safety net — fix them now |
273
+ | "One big refactoring PR" | Small PRs are safer and reviewable |
274
+ | "The new code is obviously correct" | Verify with tests, not confidence |
275
+ | "We can modernize while adding features" | Keep modernization commits separate |
276
+
277
+ ## Quick Reference
278
+
279
+ ```
280
+ Migration Patterns:
281
+ ──────────────────────────────
282
+ Strangler Fig → New code wraps old, gradual replacement
283
+ Branch by Abstraction → Interface first, then implementations
284
+ Parallel Run → Old and new run simultaneously, compare results
285
+ Expand-Contract → Database: add new → migrate → remove old
286
+
287
+ Priority Order:
288
+ ──────────────────────────────
289
+ 1. Highest risk (low coverage, high criticality) → test first
290
+ 2. Quick wins (high impact, low effort)
291
+ 3. Architectural changes (last, requires stability)
292
+ ```
@@ -0,0 +1,356 @@
1
+ ---
2
+ name: mcp-builder
3
+ description: Use when building or extending MCP (Model Context Protocol) servers. Covers NestJS-based server design, Tools/Resources/Prompts capability design, transport implementation (stdio/SSE), and testing strategies.
4
+ ---
5
+
6
+ # MCP Builder
7
+
8
+ ## Overview
9
+
10
+ The Model Context Protocol (MCP) is the standard for connecting AI assistants to external tools, data, and prompts. Building a quality MCP server requires understanding the protocol's three capability types and implementing them with proper error handling and transport support.
11
+
12
+ **Core principle:** MCP servers are AI interfaces, not REST APIs. Design for machine consumption and LLM context efficiency.
13
+
14
+ **Iron Law:**
15
+ ```
16
+ SCHEMA FIRST. Every Tool, Resource, and Prompt must have complete JSON Schema before implementation.
17
+ ```
18
+
19
+ ## When to Use
20
+
21
+ - Creating a new MCP server from scratch
22
+ - Adding new Tools, Resources, or Prompts to an existing server
23
+ - Implementing new transport modes (stdio ↔ SSE)
24
+ - Testing MCP server capabilities
25
+ - Debugging MCP communication issues
26
+
27
+ ## MCP Architecture Overview
28
+
29
+ ```
30
+ MCP Client (Claude, Cursor, etc.)
31
+ ↕ stdio / SSE
32
+ MCP Server
33
+ ├── Tools → Functions the AI can call (side effects allowed)
34
+ ├── Resources → Data the AI can read (read-only, URI-addressed)
35
+ └── Prompts → Reusable prompt templates
36
+ ```
37
+
38
+ ## NestJS MCP Server Structure
39
+
40
+ ```
41
+ apps/mcp-server/src/
42
+ ├── main.ts # Transport setup (stdio vs SSE)
43
+ ├── app.module.ts # Root module
44
+ ├── mcp/
45
+ │ ├── mcp.module.ts # MCP module
46
+ │ ├── mcp.service.ts # MCP server registration
47
+ │ ├── tools/ # Tool handlers
48
+ │ ├── resources/ # Resource handlers
49
+ │ └── prompts/ # Prompt handlers
50
+ └── rules/
51
+ ├── rules.module.ts
52
+ └── rules.service.ts # Business logic (pure)
53
+ ```
54
+
55
+ ## Designing MCP Capabilities
56
+
57
+ ### Tools (AI can call these)
58
+
59
+ Tools perform actions and return results. They CAN have side effects.
60
+
61
+ ```typescript
62
+ // Tool Schema (define first)
63
+ const searchRulesTool = {
64
+ name: 'search_rules',
65
+ description: 'Search AI coding rules by keyword or topic. Returns matching rules with their content.',
66
+ inputSchema: {
67
+ type: 'object',
68
+ properties: {
69
+ query: {
70
+ type: 'string',
71
+ description: 'Search term to find relevant rules',
72
+ minLength: 1,
73
+ maxLength: 200,
74
+ },
75
+ limit: {
76
+ type: 'number',
77
+ description: 'Maximum results to return (default: 10)',
78
+ minimum: 1,
79
+ maximum: 50,
80
+ default: 10,
81
+ },
82
+ },
83
+ required: ['query'],
84
+ },
85
+ };
86
+ ```
87
+
88
+ ```typescript
89
+ // Tool Handler
90
+ @Injectable()
91
+ export class SearchRulesHandler {
92
+ constructor(private readonly rulesService: RulesService) {}
93
+
94
+ async handle(params: { query: string; limit?: number }): Promise<ToolResult> {
95
+ // Validate input
96
+ if (!params.query?.trim()) {
97
+ return {
98
+ content: [{ type: 'text', text: 'Error: query is required' }],
99
+ isError: true,
100
+ };
101
+ }
102
+
103
+ try {
104
+ const results = await this.rulesService.search(params.query, {
105
+ limit: params.limit ?? 10,
106
+ });
107
+
108
+ return {
109
+ content: [
110
+ {
111
+ type: 'text',
112
+ text: results.length === 0
113
+ ? 'No rules found for: ' + params.query
114
+ : results.map(r => `## ${r.name}\n${r.content}`).join('\n\n'),
115
+ },
116
+ ],
117
+ };
118
+ } catch (error) {
119
+ return {
120
+ content: [{ type: 'text', text: `Error: ${error.message}` }],
121
+ isError: true,
122
+ };
123
+ }
124
+ }
125
+ }
126
+ ```
127
+
128
+ ### Resources (AI can read these)
129
+
130
+ Resources are read-only data exposed via URI. Use for static/cacheable content.
131
+
132
+ ```typescript
133
+ // Resource definition
134
+ const ruleResource = {
135
+ uri: 'rules://core',
136
+ name: 'Core Rules',
137
+ description: 'Core workflow rules (PLAN/ACT/EVAL modes)',
138
+ mimeType: 'text/markdown',
139
+ };
140
+
141
+ // Resource handler
142
+ async readResource(uri: string): Promise<ResourceContent> {
143
+ const ruleName = uri.replace('rules://', '');
144
+ const content = await this.rulesService.getRule(ruleName);
145
+
146
+ if (!content) {
147
+ throw new Error(`Resource not found: ${uri}`);
148
+ }
149
+
150
+ return {
151
+ uri,
152
+ mimeType: 'text/markdown',
153
+ text: content,
154
+ };
155
+ }
156
+ ```
157
+
158
+ **Resource URI design:**
159
+ ```
160
+ rules://core → Core rules file
161
+ rules://agents/list → List of agents
162
+ agents://planner → Specific agent definition
163
+ checklists://security → Security checklist
164
+ ```
165
+
166
+ ### Prompts (Reusable templates)
167
+
168
+ ```typescript
169
+ const activateAgentPrompt = {
170
+ name: 'activate_agent',
171
+ description: 'Generate activation prompt for a specialist agent',
172
+ arguments: [
173
+ {
174
+ name: 'agentName',
175
+ description: 'Name of the agent to activate (e.g., "solution-architect")',
176
+ required: true,
177
+ },
178
+ {
179
+ name: 'task',
180
+ description: 'Task description for the agent',
181
+ required: false,
182
+ },
183
+ ],
184
+ };
185
+
186
+ async getPrompt(name: string, args: Record<string, string>): Promise<PromptResult> {
187
+ if (name === 'activate_agent') {
188
+ const agent = await this.agentsService.getAgent(args.agentName);
189
+ return {
190
+ messages: [
191
+ {
192
+ role: 'user',
193
+ content: {
194
+ type: 'text',
195
+ text: `Activate the ${agent.displayName} specialist.\n\n${agent.systemPrompt}\n\n${args.task ?? ''}`,
196
+ },
197
+ },
198
+ ],
199
+ };
200
+ }
201
+ throw new Error(`Unknown prompt: ${name}`);
202
+ }
203
+ ```
204
+
205
+ ## Transport Implementation
206
+
207
+ ### Stdio Transport (Default)
208
+
209
+ ```typescript
210
+ // main.ts
211
+ async function bootstrap() {
212
+ const transport = process.env.MCP_TRANSPORT ?? 'stdio';
213
+
214
+ if (transport === 'stdio') {
215
+ const app = await NestFactory.createApplicationContext(AppModule);
216
+ const mcpService = app.get(McpService);
217
+ await mcpService.startStdio();
218
+ } else {
219
+ const app = await NestFactory.create(AppModule);
220
+ await app.listen(process.env.PORT ?? 3000);
221
+ }
222
+ }
223
+ ```
224
+
225
+ ### SSE Transport (HTTP)
226
+
227
+ ```typescript
228
+ // SSE endpoint with optional auth
229
+ @Get('/sse')
230
+ @UseGuards(SseAuthGuard)
231
+ async sse(@Req() req: Request, @Res() res: Response) {
232
+ const transport = new SSEServerTransport('/messages', res);
233
+ await this.mcpService.connect(transport);
234
+ }
235
+
236
+ @Post('/messages')
237
+ @UseGuards(SseAuthGuard)
238
+ async messages(@Req() req: Request, @Res() res: Response) {
239
+ await this.mcpService.handleMessage(req, res);
240
+ }
241
+ ```
242
+
243
+ ```typescript
244
+ // Auth guard
245
+ @Injectable()
246
+ export class SseAuthGuard implements CanActivate {
247
+ canActivate(context: ExecutionContext): boolean {
248
+ const token = process.env.MCP_SSE_TOKEN;
249
+ if (!token) return true; // Auth disabled if token not set
250
+
251
+ const request = context.switchToHttp().getRequest();
252
+ const provided = request.headers.authorization?.replace('Bearer ', '');
253
+ return provided === token;
254
+ }
255
+ }
256
+ ```
257
+
258
+ ## Testing Strategy
259
+
260
+ ### Unit Tests (Tools/Resources)
261
+
262
+ ```typescript
263
+ describe('SearchRulesHandler', () => {
264
+ let handler: SearchRulesHandler;
265
+ let rulesService: RulesService;
266
+
267
+ beforeEach(() => {
268
+ rulesService = new RulesService('./test-fixtures/.ai-rules');
269
+ handler = new SearchRulesHandler(rulesService);
270
+ });
271
+
272
+ it('returns results for valid query', async () => {
273
+ const result = await handler.handle({ query: 'TDD' });
274
+ expect(result.isError).toBeUndefined();
275
+ expect(result.content[0].text).toContain('TDD');
276
+ });
277
+
278
+ it('returns error for empty query', async () => {
279
+ const result = await handler.handle({ query: '' });
280
+ expect(result.isError).toBe(true);
281
+ });
282
+ });
283
+ ```
284
+
285
+ ### Integration Tests (Protocol Level)
286
+
287
+ ```typescript
288
+ import { Client } from '@modelcontextprotocol/sdk/client/index.js';
289
+ import { StdioClientTransport } from '@modelcontextprotocol/sdk/client/stdio.js';
290
+
291
+ describe('MCP Server Integration', () => {
292
+ let client: Client;
293
+
294
+ beforeAll(async () => {
295
+ const transport = new StdioClientTransport({
296
+ command: 'node',
297
+ args: ['dist/main.js'],
298
+ });
299
+ client = new Client({ name: 'test', version: '1.0.0' }, {});
300
+ await client.connect(transport);
301
+ });
302
+
303
+ it('lists available tools', async () => {
304
+ const { tools } = await client.listTools();
305
+ expect(tools.map(t => t.name)).toContain('search_rules');
306
+ });
307
+
308
+ it('calls search_rules tool', async () => {
309
+ const result = await client.callTool({
310
+ name: 'search_rules',
311
+ arguments: { query: 'TDD' },
312
+ });
313
+ expect(result.isError).toBeFalsy();
314
+ });
315
+ });
316
+ ```
317
+
318
+ ## Design Checklist
319
+
320
+ ```
321
+ Tool Design:
322
+ - [ ] Name is a verb or verb_noun (search_rules, get_agent)
323
+ - [ ] Description explains WHAT and WHEN (for LLM to understand)
324
+ - [ ] All parameters have descriptions and types
325
+ - [ ] Required vs optional params clearly marked
326
+ - [ ] Error responses use isError: true with descriptive messages
327
+ - [ ] Input validated before processing
328
+
329
+ Resource Design:
330
+ - [ ] URI follows scheme://path pattern
331
+ - [ ] mimeType set correctly (text/markdown, application/json)
332
+ - [ ] Read-only (no side effects)
333
+ - [ ] Returns 404-equivalent for missing resources
334
+
335
+ Prompt Design:
336
+ - [ ] Arguments have clear descriptions
337
+ - [ ] Output is ready-to-use for the LLM
338
+ - [ ] Does not duplicate Tool functionality
339
+
340
+ Server:
341
+ - [ ] Stdio and SSE transports both tested
342
+ - [ ] Auth guard works when MCP_SSE_TOKEN is set
343
+ - [ ] Auth disabled when MCP_SSE_TOKEN is unset
344
+ - [ ] Error handling prevents server crashes
345
+ ```
346
+
347
+ ## Common Mistakes
348
+
349
+ | Mistake | Fix |
350
+ |---------|-----|
351
+ | Tool does too many things | One Tool = one action |
352
+ | Resource has side effects | Move to Tool if it changes state |
353
+ | Schema has no descriptions | LLM uses descriptions to decide when/how to call |
354
+ | No error handling in tools | Always catch and return isError: true |
355
+ | Breaking stdio with console.log | Use stderr for logs: `process.stderr.write(...)` |
356
+ | Hardcoded paths | Use env vars or config injection |