ai-sprint-kit 1.2.1 → 1.3.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.
- package/package.json +1 -1
- package/templates/.claude/agents/implementer.md +53 -0
- package/templates/.claude/agents/planner.md +30 -0
- package/templates/.claude/agents/reviewer.md +34 -1
- package/templates/.claude/agents/tester.md +42 -0
- package/templates/.claude/commands/ai-sprint-review.md +17 -0
- package/templates/.claude/commands/ai-sprint-validate.md +16 -1
- package/templates/.claude/skills/quality-assurance/scripts/check-size.py +333 -0
- package/templates/.claude/workflows/development-rules.md +37 -1
package/package.json
CHANGED
|
@@ -20,9 +20,62 @@ You are an **expert software developer** specializing in production-grade code i
|
|
|
20
20
|
- **YAGNI** - Don't build what you don't need
|
|
21
21
|
- **KISS** - Simple solutions are better
|
|
22
22
|
- **DRY** - Eliminate duplication
|
|
23
|
+
- **SRP** - One function = one operation
|
|
23
24
|
- **Security-First** - Validate all inputs, no secrets in code
|
|
24
25
|
- **Test-Driven** - Write tests alongside code
|
|
25
26
|
|
|
27
|
+
## Code Generation Rules
|
|
28
|
+
|
|
29
|
+
### Size Limits (Warning thresholds)
|
|
30
|
+
- **500 lines max per file** - Split if exceeded
|
|
31
|
+
- **50 lines max per function** - Extract if exceeded
|
|
32
|
+
- **4 parameters max per function** - Use object if exceeded
|
|
33
|
+
- **3 levels max nesting** - Flatten with early returns
|
|
34
|
+
|
|
35
|
+
### YAGNI Rules
|
|
36
|
+
```typescript
|
|
37
|
+
// ❌ Unused parameter "for future use"
|
|
38
|
+
function createUser(name: string, options?: UserOptions) {}
|
|
39
|
+
|
|
40
|
+
// ✅ Only what's needed now
|
|
41
|
+
function createUser(name: string) {}
|
|
42
|
+
|
|
43
|
+
// ❌ Abstract class with single implementation
|
|
44
|
+
abstract class BaseRepository<T> { ... }
|
|
45
|
+
class UserRepository extends BaseRepository<User> { ... }
|
|
46
|
+
|
|
47
|
+
// ✅ Concrete implementation
|
|
48
|
+
class UserRepository { ... }
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### KISS Rules
|
|
52
|
+
```typescript
|
|
53
|
+
// ❌ Clever code
|
|
54
|
+
const result = arr.reduce((a, b) => ({...a, [b.id]: b}), {});
|
|
55
|
+
|
|
56
|
+
// ✅ Readable code
|
|
57
|
+
const result = {};
|
|
58
|
+
for (const item of arr) {
|
|
59
|
+
result[item.id] = item;
|
|
60
|
+
}
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
### SRP Rules
|
|
64
|
+
```typescript
|
|
65
|
+
// ❌ Function does multiple things
|
|
66
|
+
function processUserAndSendEmail(user: User) {
|
|
67
|
+
validateUser(user);
|
|
68
|
+
saveToDatabase(user);
|
|
69
|
+
sendWelcomeEmail(user);
|
|
70
|
+
logAnalytics(user);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// ✅ Single responsibility
|
|
74
|
+
function validateUser(user: User): ValidationResult {}
|
|
75
|
+
function saveUser(user: User): Promise<User> {}
|
|
76
|
+
function sendWelcomeEmail(user: User): Promise<void> {}
|
|
77
|
+
```
|
|
78
|
+
|
|
26
79
|
## Tool Usage
|
|
27
80
|
|
|
28
81
|
### Allowed Tools
|
|
@@ -20,9 +20,39 @@ You are an **expert software architect** with deep expertise in system design, t
|
|
|
20
20
|
- **YAGNI** - You Aren't Gonna Need It
|
|
21
21
|
- **KISS** - Keep It Simple, Stupid
|
|
22
22
|
- **DRY** - Don't Repeat Yourself
|
|
23
|
+
- **SRP** - Single Responsibility Principle
|
|
23
24
|
- **Security-First** - Security in every decision
|
|
24
25
|
- **Token Efficiency** - Concise, actionable output
|
|
25
26
|
|
|
27
|
+
## Design Principles Anti-Patterns
|
|
28
|
+
|
|
29
|
+
**Reject these patterns during planning:**
|
|
30
|
+
|
|
31
|
+
### YAGNI Violations
|
|
32
|
+
- "We might need X later" → Don't plan for hypothetical requirements
|
|
33
|
+
- "Let's add a config option" → Only if currently needed
|
|
34
|
+
- "This should be extensible" → Extensibility isn't free
|
|
35
|
+
- Abstract base classes for single implementation → Premature abstraction
|
|
36
|
+
|
|
37
|
+
### KISS Violations
|
|
38
|
+
- Abstractions before concrete use cases
|
|
39
|
+
- Generic solutions for specific problems
|
|
40
|
+
- Multiple inheritance hierarchies upfront
|
|
41
|
+
- Over-engineered patterns (Factory of Factories)
|
|
42
|
+
|
|
43
|
+
### SRP Violations
|
|
44
|
+
- God modules handling multiple domains
|
|
45
|
+
- Utility files with unrelated functions
|
|
46
|
+
- Components mixing UI/logic/data access
|
|
47
|
+
- Files planned to exceed 500 lines
|
|
48
|
+
|
|
49
|
+
### Planning Checklist
|
|
50
|
+
Before finalizing any plan, ask:
|
|
51
|
+
1. Is this feature explicitly requested? (YAGNI)
|
|
52
|
+
2. Does a simpler alternative exist? (KISS)
|
|
53
|
+
3. Does each module have one clear purpose? (SRP)
|
|
54
|
+
4. Will any file exceed 500 lines? (Split it)
|
|
55
|
+
|
|
26
56
|
## Tool Usage
|
|
27
57
|
|
|
28
58
|
### Allowed Tools
|
|
@@ -18,10 +18,43 @@ You are an **expert code reviewer** specializing in code quality, security analy
|
|
|
18
18
|
## Core Principles
|
|
19
19
|
|
|
20
20
|
- **Security-First** - Every review includes security analysis
|
|
21
|
-
- **YAGNI, KISS, DRY** - Simplicity over complexity
|
|
21
|
+
- **YAGNI, KISS, DRY, SRP** - Simplicity over complexity
|
|
22
22
|
- **Constructive** - Specific, actionable suggestions
|
|
23
23
|
- **No Nitpicking** - Focus on meaningful improvements
|
|
24
24
|
|
|
25
|
+
## Design Principles Check
|
|
26
|
+
|
|
27
|
+
### Size Limits (Warning level)
|
|
28
|
+
- [ ] Files < 500 lines
|
|
29
|
+
- [ ] Functions < 50 lines
|
|
30
|
+
- [ ] Parameters < 5 per function
|
|
31
|
+
- [ ] Nesting < 4 levels
|
|
32
|
+
|
|
33
|
+
### YAGNI Violations to Flag
|
|
34
|
+
- [ ] Unused function parameters
|
|
35
|
+
- [ ] Abstract classes with single implementation
|
|
36
|
+
- [ ] Commented-out code "for reference"
|
|
37
|
+
- [ ] Configuration options without current use
|
|
38
|
+
- [ ] Generic solutions without concrete requirements
|
|
39
|
+
|
|
40
|
+
### KISS Violations to Flag
|
|
41
|
+
- [ ] Deep inheritance hierarchies (>2 levels)
|
|
42
|
+
- [ ] Overly abstract patterns (Factory of Factories)
|
|
43
|
+
- [ ] Complex conditionals (>3 conditions)
|
|
44
|
+
- [ ] Clever code over readable code
|
|
45
|
+
|
|
46
|
+
### SRP Violations to Flag
|
|
47
|
+
- [ ] Classes with >7 public methods
|
|
48
|
+
- [ ] Functions with "and" in name/purpose
|
|
49
|
+
- [ ] Mixed concerns (UI+logic, data+formatting)
|
|
50
|
+
- [ ] Utility files with unrelated functions
|
|
51
|
+
|
|
52
|
+
### Remediation Guidance
|
|
53
|
+
When flagging violations, suggest:
|
|
54
|
+
1. **YAGNI**: What to remove/simplify
|
|
55
|
+
2. **KISS**: How to make it simpler
|
|
56
|
+
3. **SRP**: How to split responsibilities
|
|
57
|
+
|
|
25
58
|
## Tool Usage
|
|
26
59
|
|
|
27
60
|
### Allowed Tools
|
|
@@ -21,6 +21,48 @@ You are an **expert QA engineer** specializing in test generation, coverage anal
|
|
|
21
21
|
- **Test Pyramid** - 70% unit, 20% integration, 10% E2E
|
|
22
22
|
- **Security-Focused** - Test auth, input validation, XSS, SQL injection
|
|
23
23
|
- **Fast Feedback** - Tests run quickly
|
|
24
|
+
- **Test Simplicity** - Tests should be obvious, not clever
|
|
25
|
+
|
|
26
|
+
## Test Simplicity Rules
|
|
27
|
+
|
|
28
|
+
### Size Limits
|
|
29
|
+
- **20 lines max per test function** - Split if exceeded
|
|
30
|
+
- **One assertion concept per test** - Focus on single behavior
|
|
31
|
+
- **Max 3 mocks per test** - Too many = testing wrong abstraction
|
|
32
|
+
|
|
33
|
+
### YAGNI in Tests
|
|
34
|
+
```typescript
|
|
35
|
+
// ❌ Over-engineered test helper
|
|
36
|
+
class TestUserFactory {
|
|
37
|
+
static create(overrides?: Partial<User>) { ... }
|
|
38
|
+
static createMany(count: number) { ... }
|
|
39
|
+
static createWithPermissions(...) { ... }
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// ✅ Simple inline data
|
|
43
|
+
const user = { id: 1, name: 'Test' };
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
### KISS in Tests
|
|
47
|
+
```typescript
|
|
48
|
+
// ❌ Clever shared setup
|
|
49
|
+
beforeEach(() => {
|
|
50
|
+
jest.spyOn(module, 'method').mockImplementation(...)
|
|
51
|
+
// 20 lines of setup
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// ✅ Explicit per-test setup
|
|
55
|
+
it('does X', () => {
|
|
56
|
+
const mock = jest.fn().mockReturnValue('result');
|
|
57
|
+
expect(someFunction(mock)).toBe('expected');
|
|
58
|
+
});
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Test Smells to Avoid
|
|
62
|
+
- [ ] Tests longer than code they test
|
|
63
|
+
- [ ] Complex test helpers that need tests
|
|
64
|
+
- [ ] Shared mutable state between tests
|
|
65
|
+
- [ ] Testing implementation details
|
|
24
66
|
|
|
25
67
|
## Tool Usage
|
|
26
68
|
|
|
@@ -27,6 +27,17 @@ Perform comprehensive code quality review focusing on security, maintainability,
|
|
|
27
27
|
- Identify security issues
|
|
28
28
|
- Analyze performance
|
|
29
29
|
|
|
30
|
+
### 1.5. Design Principles Check (Warning)
|
|
31
|
+
Run size checker:
|
|
32
|
+
```bash
|
|
33
|
+
python3 .claude/skills/quality-assurance/scripts/check-size.py --path $SCOPE
|
|
34
|
+
```
|
|
35
|
+
Flag (warning only):
|
|
36
|
+
- Files >500 lines
|
|
37
|
+
- Functions >50 lines
|
|
38
|
+
- YAGNI violations (unused abstractions)
|
|
39
|
+
- SRP violations (mixed concerns)
|
|
40
|
+
|
|
30
41
|
### 2. Security Review (Critical)
|
|
31
42
|
- OWASP Top 10 compliance
|
|
32
43
|
- SQL injection vulnerabilities
|
|
@@ -73,6 +84,12 @@ Perform comprehensive code quality review focusing on security, maintainability,
|
|
|
73
84
|
- Minor optimizations
|
|
74
85
|
- Naming suggestions
|
|
75
86
|
|
|
87
|
+
### 🟡 Design Principle Warnings
|
|
88
|
+
- Files exceeding 500 lines
|
|
89
|
+
- Functions exceeding 50 lines
|
|
90
|
+
- Over-engineered abstractions
|
|
91
|
+
- Mixed responsibilities (SRP)
|
|
92
|
+
|
|
76
93
|
## Example Review Report
|
|
77
94
|
|
|
78
95
|
```markdown
|
|
@@ -36,6 +36,15 @@ Check `ai_context/memory/learning.md` for known validation issues.
|
|
|
36
36
|
- Analyze complexity
|
|
37
37
|
- Identify code smells
|
|
38
38
|
|
|
39
|
+
### 2.5. Design Principles Check (Warning)
|
|
40
|
+
Run size checker:
|
|
41
|
+
```bash
|
|
42
|
+
python3 .claude/skills/quality-assurance/scripts/check-size.py
|
|
43
|
+
```
|
|
44
|
+
Flag as warnings (non-blocking):
|
|
45
|
+
- Files >500 lines
|
|
46
|
+
- Functions >50 lines
|
|
47
|
+
|
|
39
48
|
### 3. Security Scan
|
|
40
49
|
- SAST scanning
|
|
41
50
|
- Secret detection
|
|
@@ -57,10 +66,15 @@ Save to: `ai_context/reports/ai-sprint-validate-{timestamp}.md`
|
|
|
57
66
|
### Code Quality (Reviewer Agent)
|
|
58
67
|
- [ ] No linting errors
|
|
59
68
|
- [ ] Types complete
|
|
60
|
-
- [ ] Functions < 50 lines
|
|
61
69
|
- [ ] No code smells
|
|
62
70
|
- [ ] Documentation present
|
|
63
71
|
|
|
72
|
+
### Design Principles (Warning only)
|
|
73
|
+
- [ ] Files < 500 lines
|
|
74
|
+
- [ ] Functions < 50 lines
|
|
75
|
+
- [ ] No YAGNI violations
|
|
76
|
+
- [ ] No SRP violations
|
|
77
|
+
|
|
64
78
|
### Security (Security Agent)
|
|
65
79
|
- [ ] No hardcoded secrets
|
|
66
80
|
- [ ] Input validation present
|
|
@@ -85,6 +99,7 @@ Save to: `ai_context/reports/ai-sprint-validate-{timestamp}.md`
|
|
|
85
99
|
| Tests | ✅/❌ | X |
|
|
86
100
|
| Coverage | ✅/❌ | X% |
|
|
87
101
|
| Quality | ✅/❌ | X |
|
|
102
|
+
| Design | ⚠️/✅ | X |
|
|
88
103
|
| Security | ✅/❌ | X |
|
|
89
104
|
|
|
90
105
|
## Test Results
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
Design principles size checker.
|
|
4
|
+
Detects files >500 lines and functions >50 lines.
|
|
5
|
+
Supports: Python, JavaScript, TypeScript, Go, Java, Rust
|
|
6
|
+
Exit code: 0 = no violations (pass), 1 = violations found (warn only)
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import argparse
|
|
10
|
+
import ast
|
|
11
|
+
import os
|
|
12
|
+
import re
|
|
13
|
+
import sys
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
from dataclasses import dataclass
|
|
16
|
+
from typing import List
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class Violation:
|
|
20
|
+
file: str
|
|
21
|
+
line: int
|
|
22
|
+
type: str # 'file' or 'function'
|
|
23
|
+
name: str
|
|
24
|
+
actual: int
|
|
25
|
+
limit: int
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def count_file_lines(path: Path) -> int:
|
|
29
|
+
"""Count non-empty, non-comment lines."""
|
|
30
|
+
try:
|
|
31
|
+
with open(path, 'r', encoding='utf-8', errors='ignore') as f:
|
|
32
|
+
lines = [l for l in f.readlines() if l.strip()
|
|
33
|
+
and not l.strip().startswith(('#', '//', '--', '/*', '*'))]
|
|
34
|
+
return len(lines)
|
|
35
|
+
except Exception:
|
|
36
|
+
return 0
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
def check_python_functions(path: Path, max_lines: int) -> List[Violation]:
|
|
40
|
+
"""Check Python function lengths using AST."""
|
|
41
|
+
violations = []
|
|
42
|
+
try:
|
|
43
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
44
|
+
tree = ast.parse(f.read())
|
|
45
|
+
for node in ast.walk(tree):
|
|
46
|
+
if isinstance(node, (ast.FunctionDef, ast.AsyncFunctionDef)):
|
|
47
|
+
length = (node.end_lineno or node.lineno) - node.lineno + 1
|
|
48
|
+
if length > max_lines:
|
|
49
|
+
violations.append(Violation(
|
|
50
|
+
file=str(path), line=node.lineno, type='function',
|
|
51
|
+
name=node.name, actual=length, limit=max_lines
|
|
52
|
+
))
|
|
53
|
+
except (SyntaxError, Exception):
|
|
54
|
+
pass
|
|
55
|
+
return violations
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def check_js_functions(path: Path, max_lines: int) -> List[Violation]:
|
|
59
|
+
"""Check JS/TS function lengths using brace counting."""
|
|
60
|
+
violations = []
|
|
61
|
+
try:
|
|
62
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
63
|
+
lines = f.readlines()
|
|
64
|
+
|
|
65
|
+
func_pattern = re.compile(
|
|
66
|
+
r'(function\s+(\w+)|(\w+)\s*=\s*(async\s+)?\([^)]*\)\s*=>|'
|
|
67
|
+
r'(async\s+)?(\w+)\s*\([^)]*\)\s*\{)'
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
in_func = False
|
|
71
|
+
func_start = 0
|
|
72
|
+
func_name = 'anonymous'
|
|
73
|
+
brace_count = 0
|
|
74
|
+
|
|
75
|
+
for i, line in enumerate(lines, 1):
|
|
76
|
+
if not in_func:
|
|
77
|
+
match = func_pattern.search(line)
|
|
78
|
+
if match and '{' in line:
|
|
79
|
+
in_func = True
|
|
80
|
+
func_start = i
|
|
81
|
+
func_name = match.group(2) or match.group(3) or match.group(6) or 'anonymous'
|
|
82
|
+
brace_count = line.count('{') - line.count('}')
|
|
83
|
+
else:
|
|
84
|
+
brace_count += line.count('{') - line.count('}')
|
|
85
|
+
if brace_count <= 0:
|
|
86
|
+
length = i - func_start + 1
|
|
87
|
+
if length > max_lines:
|
|
88
|
+
violations.append(Violation(
|
|
89
|
+
file=str(path), line=func_start, type='function',
|
|
90
|
+
name=func_name, actual=length, limit=max_lines
|
|
91
|
+
))
|
|
92
|
+
in_func = False
|
|
93
|
+
brace_count = 0
|
|
94
|
+
except Exception:
|
|
95
|
+
pass
|
|
96
|
+
return violations
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
def check_go_functions(path: Path, max_lines: int) -> List[Violation]:
|
|
100
|
+
"""Check Go function lengths."""
|
|
101
|
+
violations = []
|
|
102
|
+
try:
|
|
103
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
104
|
+
lines = f.readlines()
|
|
105
|
+
|
|
106
|
+
func_pattern = re.compile(r'^func\s+(\w+|\([^)]+\)\s+\w+)\s*\(')
|
|
107
|
+
in_func = False
|
|
108
|
+
func_start = 0
|
|
109
|
+
func_name = ''
|
|
110
|
+
brace_count = 0
|
|
111
|
+
|
|
112
|
+
for i, line in enumerate(lines, 1):
|
|
113
|
+
if not in_func:
|
|
114
|
+
match = func_pattern.match(line)
|
|
115
|
+
if match:
|
|
116
|
+
in_func = True
|
|
117
|
+
func_start = i
|
|
118
|
+
func_name = match.group(1).split()[-1]
|
|
119
|
+
brace_count = line.count('{') - line.count('}')
|
|
120
|
+
else:
|
|
121
|
+
brace_count += line.count('{') - line.count('}')
|
|
122
|
+
if brace_count <= 0:
|
|
123
|
+
length = i - func_start + 1
|
|
124
|
+
if length > max_lines:
|
|
125
|
+
violations.append(Violation(
|
|
126
|
+
file=str(path), line=func_start, type='function',
|
|
127
|
+
name=func_name, actual=length, limit=max_lines
|
|
128
|
+
))
|
|
129
|
+
in_func = False
|
|
130
|
+
except Exception:
|
|
131
|
+
pass
|
|
132
|
+
return violations
|
|
133
|
+
|
|
134
|
+
|
|
135
|
+
def check_java_functions(path: Path, max_lines: int) -> List[Violation]:
|
|
136
|
+
"""Check Java method lengths."""
|
|
137
|
+
violations = []
|
|
138
|
+
try:
|
|
139
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
140
|
+
lines = f.readlines()
|
|
141
|
+
|
|
142
|
+
method_pattern = re.compile(
|
|
143
|
+
r'(public|private|protected|static|\s)+[\w<>\[\]]+\s+(\w+)\s*\([^)]*\)\s*(\{|throws)'
|
|
144
|
+
)
|
|
145
|
+
in_method = False
|
|
146
|
+
method_start = 0
|
|
147
|
+
method_name = ''
|
|
148
|
+
brace_count = 0
|
|
149
|
+
|
|
150
|
+
for i, line in enumerate(lines, 1):
|
|
151
|
+
if not in_method:
|
|
152
|
+
match = method_pattern.search(line)
|
|
153
|
+
if match and '{' in line:
|
|
154
|
+
in_method = True
|
|
155
|
+
method_start = i
|
|
156
|
+
method_name = match.group(2)
|
|
157
|
+
brace_count = line.count('{') - line.count('}')
|
|
158
|
+
else:
|
|
159
|
+
brace_count += line.count('{') - line.count('}')
|
|
160
|
+
if brace_count <= 0:
|
|
161
|
+
length = i - method_start + 1
|
|
162
|
+
if length > max_lines:
|
|
163
|
+
violations.append(Violation(
|
|
164
|
+
file=str(path), line=method_start, type='function',
|
|
165
|
+
name=method_name, actual=length, limit=max_lines
|
|
166
|
+
))
|
|
167
|
+
in_method = False
|
|
168
|
+
except Exception:
|
|
169
|
+
pass
|
|
170
|
+
return violations
|
|
171
|
+
|
|
172
|
+
|
|
173
|
+
def check_rust_functions(path: Path, max_lines: int) -> List[Violation]:
|
|
174
|
+
"""Check Rust function lengths."""
|
|
175
|
+
violations = []
|
|
176
|
+
try:
|
|
177
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
178
|
+
lines = f.readlines()
|
|
179
|
+
|
|
180
|
+
func_pattern = re.compile(r'^\s*(pub\s+)?(async\s+)?fn\s+(\w+)')
|
|
181
|
+
in_func = False
|
|
182
|
+
func_start = 0
|
|
183
|
+
func_name = ''
|
|
184
|
+
brace_count = 0
|
|
185
|
+
|
|
186
|
+
for i, line in enumerate(lines, 1):
|
|
187
|
+
if not in_func:
|
|
188
|
+
match = func_pattern.match(line)
|
|
189
|
+
if match and '{' in line:
|
|
190
|
+
in_func = True
|
|
191
|
+
func_start = i
|
|
192
|
+
func_name = match.group(3)
|
|
193
|
+
brace_count = line.count('{') - line.count('}')
|
|
194
|
+
else:
|
|
195
|
+
brace_count += line.count('{') - line.count('}')
|
|
196
|
+
if brace_count <= 0:
|
|
197
|
+
length = i - func_start + 1
|
|
198
|
+
if length > max_lines:
|
|
199
|
+
violations.append(Violation(
|
|
200
|
+
file=str(path), line=func_start, type='function',
|
|
201
|
+
name=func_name, actual=length, limit=max_lines
|
|
202
|
+
))
|
|
203
|
+
in_func = False
|
|
204
|
+
except Exception:
|
|
205
|
+
pass
|
|
206
|
+
return violations
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
LANG_CHECKERS = {
|
|
210
|
+
'.py': check_python_functions,
|
|
211
|
+
'.js': check_js_functions,
|
|
212
|
+
'.ts': check_js_functions,
|
|
213
|
+
'.tsx': check_js_functions,
|
|
214
|
+
'.jsx': check_js_functions,
|
|
215
|
+
'.go': check_go_functions,
|
|
216
|
+
'.java': check_java_functions,
|
|
217
|
+
'.rs': check_rust_functions,
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
SKIP_DIRS = {'node_modules', '.git', 'dist', 'build', '__pycache__', '.venv',
|
|
221
|
+
'venv', 'vendor', 'target', '.next', 'coverage'}
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def scan_directory(path: Path, max_file: int, max_func: int) -> List[Violation]:
|
|
225
|
+
"""Scan directory for size violations."""
|
|
226
|
+
violations = []
|
|
227
|
+
|
|
228
|
+
for root, dirs, files in os.walk(path):
|
|
229
|
+
dirs[:] = [d for d in dirs if d not in SKIP_DIRS]
|
|
230
|
+
|
|
231
|
+
for file in files:
|
|
232
|
+
filepath = Path(root) / file
|
|
233
|
+
ext = filepath.suffix.lower()
|
|
234
|
+
|
|
235
|
+
if ext not in LANG_CHECKERS:
|
|
236
|
+
continue
|
|
237
|
+
|
|
238
|
+
# Check file length
|
|
239
|
+
file_lines = count_file_lines(filepath)
|
|
240
|
+
if file_lines > max_file:
|
|
241
|
+
violations.append(Violation(
|
|
242
|
+
file=str(filepath), line=1, type='file',
|
|
243
|
+
name=file, actual=file_lines, limit=max_file
|
|
244
|
+
))
|
|
245
|
+
|
|
246
|
+
# Check function lengths
|
|
247
|
+
checker = LANG_CHECKERS.get(ext)
|
|
248
|
+
if checker:
|
|
249
|
+
violations.extend(checker(filepath, max_func))
|
|
250
|
+
|
|
251
|
+
return violations
|
|
252
|
+
|
|
253
|
+
|
|
254
|
+
def generate_report(violations: List[Violation]) -> str:
|
|
255
|
+
"""Generate markdown report."""
|
|
256
|
+
if not violations:
|
|
257
|
+
return "# Size Check Report\n\n**Status:** ✅ PASS\n\nNo design principle violations found."
|
|
258
|
+
|
|
259
|
+
file_v = [v for v in violations if v.type == 'file']
|
|
260
|
+
func_v = [v for v in violations if v.type == 'function']
|
|
261
|
+
|
|
262
|
+
lines = [
|
|
263
|
+
"# Size Check Report",
|
|
264
|
+
"",
|
|
265
|
+
"**Status:** ⚠️ WARNING",
|
|
266
|
+
"",
|
|
267
|
+
f"**Total Violations:** {len(violations)}",
|
|
268
|
+
f"- Files exceeding limit: {len(file_v)}",
|
|
269
|
+
f"- Functions exceeding limit: {len(func_v)}",
|
|
270
|
+
""
|
|
271
|
+
]
|
|
272
|
+
|
|
273
|
+
if file_v:
|
|
274
|
+
lines.extend([
|
|
275
|
+
"## Files Exceeding 500 Lines",
|
|
276
|
+
"",
|
|
277
|
+
"| File | Lines | Limit |",
|
|
278
|
+
"|------|-------|-------|"
|
|
279
|
+
])
|
|
280
|
+
for v in sorted(file_v, key=lambda x: -x.actual):
|
|
281
|
+
lines.append(f"| `{v.file}` | {v.actual} | {v.limit} |")
|
|
282
|
+
lines.append("")
|
|
283
|
+
|
|
284
|
+
if func_v:
|
|
285
|
+
lines.extend([
|
|
286
|
+
"## Functions Exceeding 50 Lines",
|
|
287
|
+
"",
|
|
288
|
+
"| File | Line | Function | Lines | Limit |",
|
|
289
|
+
"|------|------|----------|-------|-------|"
|
|
290
|
+
])
|
|
291
|
+
for v in sorted(func_v, key=lambda x: -x.actual):
|
|
292
|
+
lines.append(f"| `{v.file}` | {v.line} | `{v.name}` | {v.actual} | {v.limit} |")
|
|
293
|
+
lines.append("")
|
|
294
|
+
|
|
295
|
+
lines.extend([
|
|
296
|
+
"## Remediation",
|
|
297
|
+
"",
|
|
298
|
+
"### Large Files",
|
|
299
|
+
"- Identify logical groupings",
|
|
300
|
+
"- Extract to separate modules",
|
|
301
|
+
"",
|
|
302
|
+
"### Long Functions",
|
|
303
|
+
"- Extract helper functions",
|
|
304
|
+
"- Apply single responsibility principle"
|
|
305
|
+
])
|
|
306
|
+
|
|
307
|
+
return "\n".join(lines)
|
|
308
|
+
|
|
309
|
+
|
|
310
|
+
def main():
|
|
311
|
+
parser = argparse.ArgumentParser(description='Check code size limits (warning only)')
|
|
312
|
+
parser.add_argument('--path', default='.', help='Directory to scan')
|
|
313
|
+
parser.add_argument('--max-file-lines', type=int, default=500)
|
|
314
|
+
parser.add_argument('--max-function-lines', type=int, default=50)
|
|
315
|
+
parser.add_argument('--output', help='Output file (default: stdout)')
|
|
316
|
+
|
|
317
|
+
args = parser.parse_args()
|
|
318
|
+
violations = scan_directory(Path(args.path), args.max_file_lines, args.max_function_lines)
|
|
319
|
+
report = generate_report(violations)
|
|
320
|
+
|
|
321
|
+
if args.output:
|
|
322
|
+
with open(args.output, 'w') as f:
|
|
323
|
+
f.write(report)
|
|
324
|
+
print(f"Report saved to {args.output}")
|
|
325
|
+
else:
|
|
326
|
+
print(report)
|
|
327
|
+
|
|
328
|
+
# Exit 1 if violations (for CI awareness), but documented as warning only
|
|
329
|
+
sys.exit(1 if violations else 0)
|
|
330
|
+
|
|
331
|
+
|
|
332
|
+
if __name__ == '__main__':
|
|
333
|
+
main()
|
|
@@ -7,7 +7,43 @@ Core principles enforced across all agents and commands.
|
|
|
7
7
|
1. **YAGNI** - You Aren't Gonna Need It
|
|
8
8
|
2. **KISS** - Keep It Simple, Stupid
|
|
9
9
|
3. **DRY** - Don't Repeat Yourself
|
|
10
|
-
4. **
|
|
10
|
+
4. **SRP** - Single Responsibility Principle
|
|
11
|
+
5. **Security-First** - Security in every layer
|
|
12
|
+
|
|
13
|
+
## Design Principles Enforcement
|
|
14
|
+
|
|
15
|
+
### Size Limits (Warning level - non-blocking)
|
|
16
|
+
| Metric | Limit | Action if Exceeded |
|
|
17
|
+
|--------|-------|-------------------|
|
|
18
|
+
| File lines | 500 | Split into modules |
|
|
19
|
+
| Function lines | 50 | Extract helpers |
|
|
20
|
+
| Parameters | 4 | Use options object |
|
|
21
|
+
| Nesting levels | 3 | Use early returns |
|
|
22
|
+
|
|
23
|
+
### YAGNI Checklist
|
|
24
|
+
- Only build explicitly requested features
|
|
25
|
+
- Delete unused code immediately
|
|
26
|
+
- No "future-proofing" abstractions
|
|
27
|
+
- No unused parameters "for later"
|
|
28
|
+
|
|
29
|
+
### KISS Checklist
|
|
30
|
+
- Prefer explicit over implicit
|
|
31
|
+
- Prefer flat over nested
|
|
32
|
+
- Prefer composition over inheritance
|
|
33
|
+
- No clever code - readable > clever
|
|
34
|
+
|
|
35
|
+
### SRP Checklist
|
|
36
|
+
- One file = one concept
|
|
37
|
+
- One function = one operation
|
|
38
|
+
- If name needs "and" → split it
|
|
39
|
+
|
|
40
|
+
### Automated Checking
|
|
41
|
+
Run size checker (warning only):
|
|
42
|
+
```bash
|
|
43
|
+
python3 .claude/skills/quality-assurance/scripts/check-size.py
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Included in `/ai-sprint-review` and `/ai-sprint-validate` workflows.
|
|
11
47
|
|
|
12
48
|
## Date Handling
|
|
13
49
|
|