Skip to content

Commit 7b3c6e3

Browse files
committed
feat(mcp): add PatternAnalysisService and wire to dev_inspect
- Created PatternAnalysisService in @lytics/dev-agent-core - 5 core patterns: file size, testing, imports, error handling, types - Pattern comparison against similar files - 31/31 tests passing with comprehensive fixtures - Integrated PatternAnalysisService into InspectAdapter - Removed action parameter (single-purpose tool) - Added patternsAnalyzed field to output schema - Real pattern analysis in compact & verbose formats - 18/18 tests passing with dedicated fixtures - Clean architecture - Barrel exports for types - Fixtures excluded from compilation, linting, and type-checking - Separate fixture sets for unit (core) and integration (adapter) tests Test coverage: - PatternAnalysisService: 31 tests ✅ - InspectAdapter: 18 tests ✅ - Build: successful ✅
1 parent 16c93d3 commit 7b3c6e3

24 files changed

Lines changed: 2248 additions & 378 deletions

biome.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,12 @@
2626
}
2727
}
2828
}
29+
},
30+
{
31+
"includes": ["**/__fixtures__/**"],
32+
"linter": {
33+
"enabled": false
34+
}
2935
}
3036
],
3137
"formatter": {
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
# Pattern Analysis Fixtures
2+
3+
This directory contains realistic code examples for testing pattern detection.
4+
5+
## Files
6+
7+
### `modern-typescript.ts`
8+
**Pattern characteristics:**
9+
- ✅ ESM imports (`import` statements)
10+
- ✅ Result<T> error handling
11+
- ✅ Full type annotations
12+
- ✅ Explicit return types
13+
- ✅ Has test file (`modern-typescript.test.ts`)
14+
15+
**Use for testing:**
16+
- Import style detection (ESM)
17+
- Error handling detection (Result<T>)
18+
- Type annotation coverage (full)
19+
- Test coverage (present)
20+
21+
---
22+
23+
### `react-component.tsx`
24+
**Pattern characteristics:**
25+
- ✅ ESM imports
26+
- ✅ TypeScript interfaces for props
27+
- ✅ React hooks in proper order
28+
- ✅ Full type annotations
29+
- ✅ Throws exceptions (try/catch pattern)
30+
- ❌ No test file
31+
32+
**Use for testing:**
33+
- React hook detection
34+
- Error handling (throw in async functions)
35+
- Type annotation (full)
36+
- Test coverage (missing)
37+
38+
---
39+
40+
### `legacy-javascript.js`
41+
**Pattern characteristics:**
42+
- ❌ CommonJS (`require`, `module.exports`)
43+
- ❌ Throw-based error handling
44+
- ❌ No type annotations
45+
- ❌ No test file
46+
47+
**Use for testing:**
48+
- Import style detection (CJS)
49+
- Error handling (throw)
50+
- Type annotation coverage (none)
51+
- Older codebase patterns
52+
53+
---
54+
55+
### `mixed-patterns.ts`
56+
**Pattern characteristics:**
57+
- ⚠️ Mixed ESM and CJS (`import` + `require`)
58+
- ⚠️ Mixed error handling (throw + Result<T>)
59+
- ⚠️ Partial type annotations
60+
- ❌ No test file
61+
62+
**Use for testing:**
63+
- Inconsistency detection
64+
- Mixed pattern analysis
65+
- What to flag for review
66+
67+
---
68+
69+
### `go-service.go`
70+
**Pattern characteristics:**
71+
- ✅ Go error returns (`(T, error)`)
72+
- ✅ Exported naming (PascalCase)
73+
- ✅ Standard library imports
74+
- ✅ Error wrapping with `fmt.Errorf`
75+
76+
**Use for testing:**
77+
- Go-specific patterns
78+
- Error return detection
79+
- Multi-language support
80+
81+
---
82+
83+
## Usage in Tests
84+
85+
```typescript
86+
import { PatternAnalysisService } from '../pattern-analysis-service';
87+
88+
const service = new PatternAnalysisService({
89+
repositoryPath: path.join(__dirname, '../__fixtures__')
90+
});
91+
92+
// Analyze modern TypeScript file
93+
const patterns = await service.analyzeFile('modern-typescript.ts');
94+
expect(patterns.importStyle.style).toBe('esm');
95+
expect(patterns.errorHandling.style).toBe('result');
96+
expect(patterns.typeAnnotations.coverage).toBe('full');
97+
expect(patterns.testing.hasTest).toBe(true);
98+
99+
// Compare against legacy code
100+
const comparison = await service.comparePatterns(
101+
'modern-typescript.ts',
102+
['legacy-javascript.js']
103+
);
104+
expect(comparison.importStyle.common).toBe('cjs');
105+
```
106+
107+
## Adding New Fixtures
108+
109+
When adding new fixtures:
110+
1. Use realistic code examples
111+
2. Document pattern characteristics
112+
3. Add test file if demonstrating test coverage
113+
4. Update this README
114+
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* Go Service Example
3+
*
4+
* Demonstrates Go best practices:
5+
* - Error returns
6+
* - Exported naming (PascalCase)
7+
* - Standard library imports
8+
* - Table-driven tests pattern (in corresponding test file)
9+
*/
10+
11+
package service
12+
13+
import (
14+
"errors"
15+
"fmt"
16+
"strings"
17+
)
18+
19+
var (
20+
ErrInvalidEmail = errors.New("invalid email address")
21+
ErrEmptyName = errors.New("name cannot be empty")
22+
ErrShortPassword = errors.New("password must be at least 8 characters")
23+
)
24+
25+
// User represents a user in the system
26+
type User struct {
27+
ID string
28+
Email string
29+
Name string
30+
Password string
31+
}
32+
33+
// ValidateEmail checks if an email address is valid
34+
func ValidateEmail(email string) error {
35+
if email == "" {
36+
return ErrInvalidEmail
37+
}
38+
39+
if !strings.Contains(email, "@") {
40+
return ErrInvalidEmail
41+
}
42+
43+
return nil
44+
}
45+
46+
// ValidatePassword checks if a password meets requirements
47+
func ValidatePassword(password string) error {
48+
if len(password) < 8 {
49+
return ErrShortPassword
50+
}
51+
52+
return nil
53+
}
54+
55+
// CreateUser creates a new user with validation
56+
func CreateUser(email, name, password string) (*User, error) {
57+
if name == "" {
58+
return nil, ErrEmptyName
59+
}
60+
61+
if err := ValidateEmail(email); err != nil {
62+
return nil, fmt.Errorf("email validation failed: %w", err)
63+
}
64+
65+
if err := ValidatePassword(password); err != nil {
66+
return nil, fmt.Errorf("password validation failed: %w", err)
67+
}
68+
69+
user := &User{
70+
ID: generateID(),
71+
Email: email,
72+
Name: name,
73+
Password: hashPassword(password),
74+
}
75+
76+
return user, nil
77+
}
78+
79+
// Private helpers
80+
func generateID() string {
81+
return "user-" + randomString(16)
82+
}
83+
84+
func hashPassword(password string) string {
85+
// Simplified for example
86+
return "hashed:" + password
87+
}
88+
89+
func randomString(length int) string {
90+
// Simplified for example
91+
return strings.Repeat("x", length)
92+
}
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
/**
2+
* Legacy JavaScript Example
3+
*
4+
* Demonstrates older patterns:
5+
* - CommonJS requires
6+
* - throw-based error handling
7+
* - No type annotations
8+
* - module.exports
9+
*/
10+
11+
const crypto = require('node:crypto');
12+
const fs = require('node:fs');
13+
14+
/**
15+
* Validate email address
16+
*/
17+
function validateEmail(email) {
18+
if (!email) {
19+
throw new Error('Email is required');
20+
}
21+
22+
if (!email.includes('@')) {
23+
throw new Error('Invalid email format');
24+
}
25+
26+
return true;
27+
}
28+
29+
/**
30+
* Hash password using crypto
31+
*/
32+
function hashPassword(password) {
33+
if (!password || password.length < 8) {
34+
throw new Error('Password must be at least 8 characters');
35+
}
36+
37+
const salt = crypto.randomBytes(16).toString('hex');
38+
const hash = crypto.pbkdf2Sync(password, salt, 1000, 64, 'sha512').toString('hex');
39+
40+
return { hash, salt };
41+
}
42+
43+
/**
44+
* Read user data from file
45+
*/
46+
function readUserData(filePath) {
47+
if (!filePath) {
48+
throw new Error('File path is required');
49+
}
50+
51+
if (!fs.existsSync(filePath)) {
52+
throw new Error('File not found');
53+
}
54+
55+
const content = fs.readFileSync(filePath, 'utf-8');
56+
return JSON.parse(content);
57+
}
58+
59+
/**
60+
* Create user with validation
61+
*/
62+
function createUser(email, password, name) {
63+
validateEmail(email);
64+
65+
if (!name) {
66+
throw new Error('Name is required');
67+
}
68+
69+
const { hash, salt } = hashPassword(password);
70+
71+
return {
72+
id: crypto.randomUUID(),
73+
email,
74+
name,
75+
passwordHash: hash,
76+
salt,
77+
createdAt: new Date().toISOString(),
78+
};
79+
}
80+
81+
module.exports = {
82+
validateEmail,
83+
hashPassword,
84+
readUserData,
85+
createUser,
86+
};
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* Mixed Patterns Example
3+
*
4+
* Demonstrates inconsistent patterns (what we want to detect):
5+
* - Mixed ESM and CJS (ESM imports but still some requires)
6+
* - Mixed error handling (both throw and Result<T>)
7+
* - Partial type annotations
8+
*/
9+
10+
import type { User } from './types';
11+
12+
const fs = require('node:fs'); // CJS require mixed with ESM!
13+
14+
export type Result<T, E = Error> = { ok: true; value: T } | { ok: false; error: E };
15+
16+
/**
17+
* Load config from file - uses throw
18+
*/
19+
export function loadConfig(filePath) {
20+
// Missing types!
21+
if (!filePath) {
22+
throw new Error('File path required'); // Uses throw
23+
}
24+
25+
const content = fs.readFileSync(filePath, 'utf-8');
26+
return JSON.parse(content);
27+
}
28+
29+
/**
30+
* Validate user - uses Result<T>
31+
*/
32+
export function validateUser(data: unknown): Result<User> {
33+
// Has types
34+
if (!data) {
35+
return { ok: false, error: new Error('Invalid data') }; // Uses Result
36+
}
37+
38+
return { ok: true, value: data as User };
39+
}
40+
41+
/**
42+
* Process data - missing return type
43+
*/
44+
export async function processData(input: string) {
45+
// Missing return type!
46+
if (!input) {
47+
throw new Error('Input required'); // Back to throw
48+
}
49+
50+
return input.toUpperCase();
51+
}
52+
53+
/**
54+
* Helper with full types
55+
*/
56+
export function formatName(firstName: string, lastName: string): string {
57+
return `${firstName} ${lastName}`;
58+
}

0 commit comments

Comments
 (0)