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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-sprint-kit",
3
- "version": "1.2.1",
3
+ "version": "1.3.0",
4
4
  "description": "CLI installer for autonomous coding agent framework - security-first, production-grade Claude Code setup",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -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. **Security-First** - Security in every layer
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