ai-sprint-kit 1.2.1 → 1.3.1
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/lib/installer.js +79 -55
- package/lib/scanner.js +65 -85
- package/package.json +1 -1
- package/templates/.claude/agents/debugger.md +3 -2
- package/templates/.claude/agents/devops.md +3 -2
- package/templates/.claude/agents/docs.md +3 -2
- package/templates/.claude/agents/implementer.md +53 -0
- package/templates/.claude/agents/planner.md +30 -0
- package/templates/.claude/agents/researcher.md +3 -2
- package/templates/.claude/agents/reviewer.md +37 -3
- package/templates/.claude/agents/security.md +3 -2
- package/templates/.claude/agents/tester.md +45 -2
- package/templates/.claude/commands/ai-sprint-debug.md +2 -2
- 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/templates/CLAUDE.md +8 -5
- package/templates/docs/user-guide-th.md +3 -3
- package/templates/docs/user-guide.md +9 -9
package/lib/installer.js
CHANGED
|
@@ -17,64 +17,72 @@ async function checkExisting(targetDir) {
|
|
|
17
17
|
*/
|
|
18
18
|
async function install(targetDir, options = {}) {
|
|
19
19
|
const { force = false, skipInstall = false } = options;
|
|
20
|
-
|
|
21
20
|
const templateDir = path.join(__dirname, '../templates');
|
|
22
21
|
const claudeDir = path.join(targetDir, '.claude');
|
|
23
22
|
|
|
24
|
-
|
|
25
|
-
if (force && await fs.pathExists(claudeDir)) {
|
|
26
|
-
const spinner = ora('Removing existing .claude/ directory...').start();
|
|
27
|
-
await fs.remove(claudeDir);
|
|
28
|
-
spinner.succeed('Removed existing .claude/ directory');
|
|
29
|
-
}
|
|
23
|
+
await removeExistingIfForce(claudeDir, force);
|
|
30
24
|
|
|
31
|
-
// Copy template directory
|
|
32
25
|
const spinner = ora('Installing framework...').start();
|
|
33
|
-
|
|
34
26
|
try {
|
|
35
|
-
|
|
36
|
-
await fs.copy(
|
|
37
|
-
path.join(templateDir, '.claude'),
|
|
38
|
-
claudeDir,
|
|
39
|
-
{ overwrite: force }
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
// Copy root files (including MCP config template)
|
|
43
|
-
const rootFiles = ['CLAUDE.md', 'README.md', '.mcp.json.example'];
|
|
44
|
-
for (const file of rootFiles) {
|
|
45
|
-
const srcPath = path.join(templateDir, file);
|
|
46
|
-
const destPath = path.join(targetDir, file);
|
|
47
|
-
|
|
48
|
-
if (await fs.pathExists(srcPath)) {
|
|
49
|
-
// Only copy if doesn't exist or force flag
|
|
50
|
-
if (!await fs.pathExists(destPath) || force) {
|
|
51
|
-
await fs.copy(srcPath, destPath);
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
// Copy docs directory (user guide)
|
|
57
|
-
const docsDir = path.join(templateDir, 'docs');
|
|
58
|
-
if (await fs.pathExists(docsDir)) {
|
|
59
|
-
await fs.copy(docsDir, path.join(targetDir, 'docs'), { overwrite: force });
|
|
60
|
-
}
|
|
61
|
-
|
|
27
|
+
await copyTemplateFiles(templateDir, targetDir, claudeDir, force);
|
|
62
28
|
spinner.succeed('Framework installed');
|
|
63
29
|
|
|
64
|
-
// Install skill dependencies
|
|
65
30
|
if (!skipInstall) {
|
|
66
31
|
await installSkillDependencies(claudeDir);
|
|
67
32
|
}
|
|
68
|
-
|
|
69
|
-
// Create initial directories
|
|
70
33
|
await createInitialDirs(targetDir);
|
|
71
|
-
|
|
72
34
|
} catch (error) {
|
|
73
35
|
spinner.fail('Installation failed');
|
|
74
36
|
throw error;
|
|
75
37
|
}
|
|
76
38
|
}
|
|
77
39
|
|
|
40
|
+
/**
|
|
41
|
+
* Remove existing .claude/ directory if force flag is set
|
|
42
|
+
*/
|
|
43
|
+
async function removeExistingIfForce(claudeDir, force) {
|
|
44
|
+
if (force && await fs.pathExists(claudeDir)) {
|
|
45
|
+
const spinner = ora('Removing existing .claude/ directory...').start();
|
|
46
|
+
await fs.remove(claudeDir);
|
|
47
|
+
spinner.succeed('Removed existing .claude/ directory');
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Copy template files to target directory
|
|
53
|
+
*/
|
|
54
|
+
async function copyTemplateFiles(templateDir, targetDir, claudeDir, force) {
|
|
55
|
+
// Copy .claude/ directory
|
|
56
|
+
await fs.copy(path.join(templateDir, '.claude'), claudeDir, { overwrite: force });
|
|
57
|
+
|
|
58
|
+
// Copy root files
|
|
59
|
+
await copyRootFiles(templateDir, targetDir, force);
|
|
60
|
+
|
|
61
|
+
// Copy user guide to .claude/docs (avoid overwriting user's docs/)
|
|
62
|
+
const docsDir = path.join(templateDir, 'docs');
|
|
63
|
+
if (await fs.pathExists(docsDir)) {
|
|
64
|
+
await fs.copy(docsDir, path.join(claudeDir, 'docs'), { overwrite: force });
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Copy root template files (CLAUDE.md, README.md, etc.)
|
|
70
|
+
*/
|
|
71
|
+
async function copyRootFiles(templateDir, targetDir, force) {
|
|
72
|
+
const rootFiles = ['CLAUDE.md', 'README.md', '.mcp.json.example'];
|
|
73
|
+
|
|
74
|
+
for (const file of rootFiles) {
|
|
75
|
+
const srcPath = path.join(templateDir, file);
|
|
76
|
+
const destPath = path.join(targetDir, file);
|
|
77
|
+
|
|
78
|
+
if (await fs.pathExists(srcPath)) {
|
|
79
|
+
if (!await fs.pathExists(destPath) || force) {
|
|
80
|
+
await fs.copy(srcPath, destPath);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
78
86
|
/**
|
|
79
87
|
* Install Python dependencies for skills
|
|
80
88
|
*/
|
|
@@ -129,19 +137,42 @@ async function createInitialDirs(targetDir) {
|
|
|
129
137
|
'ai_context/plans',
|
|
130
138
|
'ai_context/docs',
|
|
131
139
|
'ai_context/reports',
|
|
132
|
-
'ai_context/
|
|
133
|
-
'
|
|
134
|
-
'
|
|
135
|
-
'
|
|
140
|
+
'ai_context/reports/research',
|
|
141
|
+
'ai_context/reports/review',
|
|
142
|
+
'ai_context/reports/security',
|
|
143
|
+
'ai_context/reports/test',
|
|
144
|
+
'ai_context/reports/debug',
|
|
145
|
+
'ai_context/reports/deploy',
|
|
146
|
+
'ai_context/reports/docs',
|
|
147
|
+
'ai_context/memory'
|
|
136
148
|
];
|
|
137
149
|
|
|
138
150
|
for (const dir of dirs) {
|
|
139
|
-
|
|
140
|
-
|
|
151
|
+
await fs.ensureDir(path.join(targetDir, dir));
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
await createMemoryFiles(targetDir);
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Create initial memory template files
|
|
159
|
+
*/
|
|
160
|
+
async function createMemoryFiles(targetDir) {
|
|
161
|
+
const memoryFiles = getMemoryFileTemplates();
|
|
162
|
+
|
|
163
|
+
for (const [file, content] of Object.entries(memoryFiles)) {
|
|
164
|
+
const filePath = path.join(targetDir, file);
|
|
165
|
+
if (!await fs.pathExists(filePath)) {
|
|
166
|
+
await fs.writeFile(filePath, content);
|
|
167
|
+
}
|
|
141
168
|
}
|
|
169
|
+
}
|
|
142
170
|
|
|
143
|
-
|
|
144
|
-
|
|
171
|
+
/**
|
|
172
|
+
* Get memory file templates
|
|
173
|
+
*/
|
|
174
|
+
function getMemoryFileTemplates() {
|
|
175
|
+
return {
|
|
145
176
|
'ai_context/memory/learning.md': `# Learning Log
|
|
146
177
|
|
|
147
178
|
Lessons learned from past development sessions.
|
|
@@ -190,13 +221,6 @@ _None_
|
|
|
190
221
|
_None_
|
|
191
222
|
`
|
|
192
223
|
};
|
|
193
|
-
|
|
194
|
-
for (const [file, content] of Object.entries(memoryFiles)) {
|
|
195
|
-
const filePath = path.join(targetDir, file);
|
|
196
|
-
if (!await fs.pathExists(filePath)) {
|
|
197
|
-
await fs.writeFile(filePath, content);
|
|
198
|
-
}
|
|
199
|
-
}
|
|
200
224
|
}
|
|
201
225
|
|
|
202
226
|
module.exports = {
|
package/lib/scanner.js
CHANGED
|
@@ -65,60 +65,43 @@ async function checkRepomix() {
|
|
|
65
65
|
|
|
66
66
|
/**
|
|
67
67
|
* Run repomix scan on target directory
|
|
68
|
-
* @param {string} targetDir - Directory to scan
|
|
69
|
-
* @param {string} outputDir - Output directory for scan results
|
|
70
|
-
* @param {object} options - Scan options
|
|
71
|
-
* @returns {Promise<object>} - Scan results
|
|
72
68
|
*/
|
|
73
69
|
async function runRepomixScan(targetDir, outputDir, options = {}) {
|
|
74
70
|
const { useNpx = false } = options;
|
|
75
|
-
|
|
76
|
-
// Create output directory
|
|
77
71
|
await fs.ensureDir(outputDir);
|
|
78
72
|
|
|
79
73
|
const xmlOutput = path.join(outputDir, 'repomix-output.xml');
|
|
80
74
|
const mdOutput = path.join(outputDir, 'overview.md');
|
|
81
75
|
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
const baseArgs = useNpx ? ['repomix'] : [];
|
|
76
|
+
await runRepomixCommand(targetDir, xmlOutput, 'xml', useNpx);
|
|
77
|
+
await runRepomixCommand(targetDir, mdOutput, 'markdown', useNpx);
|
|
85
78
|
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
...baseArgs,
|
|
89
|
-
'--compress',
|
|
90
|
-
'--style', 'xml',
|
|
91
|
-
'-o', xmlOutput
|
|
92
|
-
];
|
|
79
|
+
return parseRepomixStats(xmlOutput);
|
|
80
|
+
}
|
|
93
81
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
82
|
+
/**
|
|
83
|
+
* Execute repomix command with specified style
|
|
84
|
+
*/
|
|
85
|
+
async function runRepomixCommand(targetDir, outputPath, style, useNpx) {
|
|
86
|
+
const baseCmd = useNpx ? 'npx' : 'repomix';
|
|
87
|
+
const baseArgs = useNpx ? ['repomix'] : [];
|
|
98
88
|
|
|
99
|
-
|
|
100
|
-
const mdArgs = [
|
|
101
|
-
...baseArgs,
|
|
102
|
-
'--compress',
|
|
103
|
-
'--style', 'markdown',
|
|
104
|
-
'-o', mdOutput
|
|
105
|
-
];
|
|
89
|
+
const args = [...baseArgs, '--compress', '--style', style, '-o', outputPath];
|
|
106
90
|
|
|
107
|
-
await execa(baseCmd,
|
|
91
|
+
await execa(baseCmd, args, {
|
|
108
92
|
cwd: targetDir,
|
|
109
93
|
timeout: 300000
|
|
110
94
|
});
|
|
95
|
+
}
|
|
111
96
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
};
|
|
97
|
+
/**
|
|
98
|
+
* Parse repomix XML output for statistics
|
|
99
|
+
*/
|
|
100
|
+
async function parseRepomixStats(xmlPath) {
|
|
101
|
+
const stats = { totalFiles: 0, totalTokens: 0, compressedTokens: 0 };
|
|
118
102
|
|
|
119
103
|
try {
|
|
120
|
-
const xmlContent = await fs.readFile(
|
|
121
|
-
// Count file entries in XML
|
|
104
|
+
const xmlContent = await fs.readFile(xmlPath, 'utf-8');
|
|
122
105
|
const fileMatches = xmlContent.match(/<file path="/g);
|
|
123
106
|
if (fileMatches) {
|
|
124
107
|
stats.totalFiles = fileMatches.length;
|
|
@@ -256,25 +239,36 @@ async function writeMetadata(outputDir, stats) {
|
|
|
256
239
|
|
|
257
240
|
/**
|
|
258
241
|
* Main entry point for codebase scanning
|
|
259
|
-
* @param {string} targetDir - Directory to scan
|
|
260
|
-
* @param {object} options - Scan options
|
|
261
|
-
* @returns {Promise<object>} - Scan results
|
|
262
242
|
*/
|
|
263
243
|
async function scanCodebase(targetDir, options = {}) {
|
|
264
244
|
const { silent = false } = options;
|
|
265
245
|
const outputDir = path.join(targetDir, 'ai_context', 'codebase');
|
|
266
|
-
const startTime = Date.now();
|
|
267
246
|
|
|
268
|
-
//
|
|
247
|
+
// Pre-flight checks
|
|
248
|
+
const preflight = await runPreflightChecks(targetDir, silent);
|
|
249
|
+
if (preflight.skipped) return preflight;
|
|
250
|
+
|
|
251
|
+
const spinner = silent ? null : ora('Scanning codebase...').start();
|
|
252
|
+
|
|
253
|
+
try {
|
|
254
|
+
const result = await executeScan(targetDir, outputDir, preflight.command, spinner);
|
|
255
|
+
if (spinner) spinner.succeed(`Codebase scanned (${result.stats.totalFiles} files, ${result.stats.duration}s)`);
|
|
256
|
+
return result;
|
|
257
|
+
} catch (error) {
|
|
258
|
+
return handleScanError(error, spinner, silent);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Run pre-flight checks before scanning
|
|
264
|
+
*/
|
|
265
|
+
async function runPreflightChecks(targetDir, silent) {
|
|
269
266
|
const hasSource = await detectSourceCode(targetDir);
|
|
270
267
|
if (!hasSource) {
|
|
271
|
-
if (!silent)
|
|
272
|
-
console.log(chalk.gray(' No source code detected. Skipping scan.'));
|
|
273
|
-
}
|
|
268
|
+
if (!silent) console.log(chalk.gray(' No source code detected. Skipping scan.'));
|
|
274
269
|
return { skipped: true, reason: 'no-source' };
|
|
275
270
|
}
|
|
276
271
|
|
|
277
|
-
// Check repomix availability
|
|
278
272
|
const { available, command } = await checkRepomix();
|
|
279
273
|
if (!available) {
|
|
280
274
|
if (!silent) {
|
|
@@ -284,54 +278,40 @@ async function scanCodebase(targetDir, options = {}) {
|
|
|
284
278
|
return { skipped: true, reason: 'no-repomix' };
|
|
285
279
|
}
|
|
286
280
|
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
try {
|
|
290
|
-
// Create output directory
|
|
291
|
-
await fs.ensureDir(outputDir);
|
|
292
|
-
|
|
293
|
-
// Create default ignore file
|
|
294
|
-
await createRepomixIgnore(outputDir);
|
|
295
|
-
|
|
296
|
-
// Run repomix scan
|
|
297
|
-
const useNpx = command.includes('npx');
|
|
298
|
-
const stats = await runRepomixScan(targetDir, outputDir, { useNpx });
|
|
299
|
-
|
|
300
|
-
// Generate structure
|
|
301
|
-
if (spinner) spinner.text = 'Generating structure...';
|
|
302
|
-
await generateStructure(targetDir, outputDir);
|
|
281
|
+
return { skipped: false, command };
|
|
282
|
+
}
|
|
303
283
|
|
|
304
|
-
|
|
305
|
-
|
|
284
|
+
/**
|
|
285
|
+
* Execute the actual scan operation
|
|
286
|
+
*/
|
|
287
|
+
async function executeScan(targetDir, outputDir, command, spinner) {
|
|
288
|
+
const startTime = Date.now();
|
|
306
289
|
|
|
307
|
-
|
|
308
|
-
|
|
290
|
+
await fs.ensureDir(outputDir);
|
|
291
|
+
await createRepomixIgnore(outputDir);
|
|
309
292
|
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
}
|
|
293
|
+
const useNpx = command.includes('npx');
|
|
294
|
+
const stats = await runRepomixScan(targetDir, outputDir, { useNpx });
|
|
313
295
|
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
outputDir,
|
|
317
|
-
stats: metadata
|
|
318
|
-
};
|
|
296
|
+
if (spinner) spinner.text = 'Generating structure...';
|
|
297
|
+
await generateStructure(targetDir, outputDir);
|
|
319
298
|
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
spinner.fail('Codebase scan failed');
|
|
323
|
-
}
|
|
299
|
+
stats.duration = Math.round((Date.now() - startTime) / 1000 * 10) / 10;
|
|
300
|
+
const metadata = await writeMetadata(outputDir, stats);
|
|
324
301
|
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
console.log(chalk.gray(' Run /scan manually after fixing the issue.'));
|
|
328
|
-
}
|
|
302
|
+
return { success: true, outputDir, stats: metadata };
|
|
303
|
+
}
|
|
329
304
|
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
305
|
+
/**
|
|
306
|
+
* Handle scan errors
|
|
307
|
+
*/
|
|
308
|
+
function handleScanError(error, spinner, silent) {
|
|
309
|
+
if (spinner) spinner.fail('Codebase scan failed');
|
|
310
|
+
if (!silent) {
|
|
311
|
+
console.log(chalk.yellow(` ⚠️ ${error.message}`));
|
|
312
|
+
console.log(chalk.gray(' Run /scan manually after fixing the issue.'));
|
|
334
313
|
}
|
|
314
|
+
return { success: false, error: error.message };
|
|
335
315
|
}
|
|
336
316
|
|
|
337
317
|
module.exports = {
|
package/package.json
CHANGED
|
@@ -80,7 +80,8 @@ ai_context/
|
|
|
80
80
|
│ ├── learning.md # Bug patterns to watch for
|
|
81
81
|
│ └── decisions.md # Fix decisions log
|
|
82
82
|
└── reports/
|
|
83
|
-
└── debug
|
|
83
|
+
└── debug/
|
|
84
|
+
└── debug-251224-login-bug.md
|
|
84
85
|
```
|
|
85
86
|
|
|
86
87
|
## Workflow
|
|
@@ -111,7 +112,7 @@ ai_context/
|
|
|
111
112
|
|
|
112
113
|
### Phase 4: Document
|
|
113
114
|
```
|
|
114
|
-
1. Call Write: ai_context/reports/ai-sprint-debug-{timestamp}-{slug}.md
|
|
115
|
+
1. Call Write: ai_context/reports/debug/ai-sprint-debug-{timestamp}-{slug}.md
|
|
115
116
|
2. Update ai_context/memory/learning.md with new pattern
|
|
116
117
|
```
|
|
117
118
|
|
|
@@ -76,7 +76,8 @@ ai_context/
|
|
|
76
76
|
│ ├── learning.md # DevOps lessons learned
|
|
77
77
|
│ └── decisions.md # Infrastructure decisions
|
|
78
78
|
└── reports/
|
|
79
|
-
└── deploy
|
|
79
|
+
└── deploy/
|
|
80
|
+
└── deploy-251224.md
|
|
80
81
|
```
|
|
81
82
|
|
|
82
83
|
## Workflow
|
|
@@ -108,7 +109,7 @@ ai_context/
|
|
|
108
109
|
|
|
109
110
|
### Phase 4: Documentation
|
|
110
111
|
```
|
|
111
|
-
1. Call Write: ai_context/reports/ai-sprint-deploy-{timestamp}.md
|
|
112
|
+
1. Call Write: ai_context/reports/deploy/ai-sprint-deploy-{timestamp}.md
|
|
112
113
|
2. Document rollback procedures
|
|
113
114
|
3. Update ai_context/memory/decisions.md
|
|
114
115
|
```
|
|
@@ -77,7 +77,8 @@ ai_context/
|
|
|
77
77
|
├── memory/
|
|
78
78
|
│ └── learning.md # Documentation lessons
|
|
79
79
|
└── reports/
|
|
80
|
-
└── docs
|
|
80
|
+
└── docs/
|
|
81
|
+
└── docs-audit-251224.md
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
## Workflow
|
|
@@ -107,7 +108,7 @@ ai_context/
|
|
|
107
108
|
|
|
108
109
|
### Phase 4: Report
|
|
109
110
|
```
|
|
110
|
-
1. Call Write: ai_context/reports/ai-sprint-docs-audit-{timestamp}.md
|
|
111
|
+
1. Call Write: ai_context/reports/docs/ai-sprint-docs-audit-{timestamp}.md
|
|
111
112
|
2. Document what was updated
|
|
112
113
|
```
|
|
113
114
|
|
|
@@ -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
|
|
@@ -82,7 +82,8 @@ ai_context/
|
|
|
82
82
|
├── memory/
|
|
83
83
|
│ └── learning.md # Research lessons learned
|
|
84
84
|
└── reports/
|
|
85
|
-
└── research
|
|
85
|
+
└── research/
|
|
86
|
+
└── research-{topic}-251224.md
|
|
86
87
|
```
|
|
87
88
|
|
|
88
89
|
## Workflow
|
|
@@ -110,7 +111,7 @@ ai_context/
|
|
|
110
111
|
|
|
111
112
|
### Phase 4: Report
|
|
112
113
|
```
|
|
113
|
-
1. Call Write: ai_context/reports/research-{topic}-{timestamp}.md
|
|
114
|
+
1. Call Write: ai_context/reports/research/research-{topic}-{timestamp}.md
|
|
114
115
|
2. Include all citations with links
|
|
115
116
|
3. Provide actionable recommendations
|
|
116
117
|
```
|
|
@@ -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
|
|
@@ -74,7 +107,8 @@ ai_context/
|
|
|
74
107
|
│ ├── learning.md # Review lessons learned
|
|
75
108
|
│ └── decisions.md # Code decisions log
|
|
76
109
|
└── reports/
|
|
77
|
-
└── review
|
|
110
|
+
└── review/
|
|
111
|
+
└── review-251224.md
|
|
78
112
|
```
|
|
79
113
|
|
|
80
114
|
## Workflow
|
|
@@ -98,7 +132,7 @@ ai_context/
|
|
|
98
132
|
|
|
99
133
|
### Phase 3: Reporting
|
|
100
134
|
```
|
|
101
|
-
1. Call Write: ai_context/reports/ai-sprint-review-{timestamp}.md
|
|
135
|
+
1. Call Write: ai_context/reports/review/ai-sprint-review-{timestamp}.md
|
|
102
136
|
2. Categorize by severity (Critical/High/Medium/Low)
|
|
103
137
|
3. Provide before/after code examples
|
|
104
138
|
4. Include rationale for each suggestion
|
|
@@ -77,7 +77,8 @@ ai_context/
|
|
|
77
77
|
│ ├── learning.md # Past security issues to watch for
|
|
78
78
|
│ └── decisions.md # Security decisions log
|
|
79
79
|
└── reports/
|
|
80
|
-
└── security
|
|
80
|
+
└── security/
|
|
81
|
+
└── security-251224-2115.md # Security scan results
|
|
81
82
|
```
|
|
82
83
|
|
|
83
84
|
## Workflow
|
|
@@ -100,7 +101,7 @@ ai_context/
|
|
|
100
101
|
|
|
101
102
|
### Phase 3: Reporting
|
|
102
103
|
```
|
|
103
|
-
1. Call Write: ai_context/reports/security-{timestamp}.md
|
|
104
|
+
1. Call Write: ai_context/reports/security/security-{timestamp}.md
|
|
104
105
|
2. Include severity ratings and fixes
|
|
105
106
|
3. Update ai_context/memory/learning.md if new patterns found
|
|
106
107
|
```
|
|
@@ -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
|
|
|
@@ -77,7 +119,8 @@ ai_context/
|
|
|
77
119
|
├── memory/
|
|
78
120
|
│ └── learning.md # Testing lessons learned
|
|
79
121
|
└── reports/
|
|
80
|
-
└── test
|
|
122
|
+
└── test/
|
|
123
|
+
└── test-coverage-251224.md
|
|
81
124
|
```
|
|
82
125
|
|
|
83
126
|
## Workflow
|
|
@@ -108,7 +151,7 @@ ai_context/
|
|
|
108
151
|
|
|
109
152
|
### Phase 4: Reporting
|
|
110
153
|
```
|
|
111
|
-
1. Call Write: ai_context/reports/ai-sprint-test-coverage-{timestamp}.md
|
|
154
|
+
1. Call Write: ai_context/reports/test/ai-sprint-test-coverage-{timestamp}.md
|
|
112
155
|
2. Document coverage metrics
|
|
113
156
|
3. Note gaps and recommendations
|
|
114
157
|
```
|
|
@@ -252,11 +252,11 @@ Before debugging:
|
|
|
252
252
|
|
|
253
253
|
After debugging:
|
|
254
254
|
- Update `ai_context/memory/learning.md` with new bug pattern
|
|
255
|
-
- Save report to `ai_context/reports/ai-sprint-debug-{timestamp}-{slug}.md`
|
|
255
|
+
- Save report to `ai_context/reports/debug/ai-sprint-debug-{timestamp}-{slug}.md`
|
|
256
256
|
|
|
257
257
|
## Debug Report
|
|
258
258
|
|
|
259
|
-
Save to: `ai_context/reports/ai-sprint-debug-YYMMDD-slug.md`
|
|
259
|
+
Save to: `ai_context/reports/debug/ai-sprint-debug-YYMMDD-slug.md`
|
|
260
260
|
|
|
261
261
|
```markdown
|
|
262
262
|
# Bug Fix Report
|
|
@@ -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
|
|
package/templates/CLAUDE.md
CHANGED
|
@@ -118,11 +118,14 @@ project/
|
|
|
118
118
|
├── ai_context/ # AI artifacts (not project code)
|
|
119
119
|
│ ├── plans/ # Implementation plans
|
|
120
120
|
│ ├── docs/ # AI-generated documentation
|
|
121
|
-
│ ├── reports/ # Agent outputs
|
|
122
|
-
│ │ ├──
|
|
123
|
-
│ │ ├──
|
|
124
|
-
│ │ ├── security
|
|
125
|
-
│ │
|
|
121
|
+
│ ├── reports/ # Agent outputs (organized by type)
|
|
122
|
+
│ │ ├── research/ # Research reports
|
|
123
|
+
│ │ ├── review/ # Code review reports
|
|
124
|
+
│ │ ├── security/ # Security scan results
|
|
125
|
+
│ │ ├── test/ # Test coverage reports
|
|
126
|
+
│ │ ├── debug/ # Debugging analysis
|
|
127
|
+
│ │ ├── deploy/ # Deployment reports
|
|
128
|
+
│ │ └── docs/ # Documentation audit reports
|
|
126
129
|
│ └── memory/
|
|
127
130
|
│ │ ├── learning.md # Lessons learned
|
|
128
131
|
│ │ ├── decisions.md # Key decisions
|
|
@@ -337,11 +337,11 @@ git commit -m "เพิ่มฟีเจอร์ใหม่"
|
|
|
337
337
|
|
|
338
338
|
### 3. อ่านรายงานที่ AI สร้าง
|
|
339
339
|
|
|
340
|
-
AI จะบันทึกผลการทำงานไว้ใน `ai_context/reports/`
|
|
340
|
+
AI จะบันทึกผลการทำงานไว้ใน `ai_context/reports/` (แยกโฟลเดอร์ตามประเภท)
|
|
341
341
|
|
|
342
342
|
```bash
|
|
343
343
|
# ดูรายงานความปลอดภัย
|
|
344
|
-
cat ai_context/reports/security
|
|
344
|
+
cat ai_context/reports/security/
|
|
345
345
|
```
|
|
346
346
|
|
|
347
347
|
### 4. ใช้ /ai-sprint-auto สำหรับงานง่ายๆ
|
|
@@ -391,7 +391,7 @@ cat ai_context/reports/security-*.md
|
|
|
391
391
|
|
|
392
392
|
```bash
|
|
393
393
|
# ดูรายงาน
|
|
394
|
-
cat ai_context/reports/security
|
|
394
|
+
cat ai_context/reports/security/
|
|
395
395
|
|
|
396
396
|
# แก้ไข
|
|
397
397
|
/ai-sprint-code "แก้ไขปัญหาความปลอดภัยตามรายงาน"
|
|
@@ -105,7 +105,7 @@ For more control over each phase:
|
|
|
105
105
|
```bash
|
|
106
106
|
# Step 1: Investigate the issue
|
|
107
107
|
/ai-sprint-debug "users getting 500 error when submitting registration form"
|
|
108
|
-
# Review the analysis in ai_context/reports/
|
|
108
|
+
# Review the analysis in ai_context/reports/debug/
|
|
109
109
|
|
|
110
110
|
# Step 2: Implement the fix
|
|
111
111
|
/ai-sprint-code "fix the registration error based on debug analysis"
|
|
@@ -124,7 +124,7 @@ For more control over each phase:
|
|
|
124
124
|
# Scan specific directory
|
|
125
125
|
/ai-sprint-secure src/auth/
|
|
126
126
|
|
|
127
|
-
# Review findings in ai_context/reports/security
|
|
127
|
+
# Review findings in ai_context/reports/security/
|
|
128
128
|
```
|
|
129
129
|
|
|
130
130
|
### Tutorial 5: Pre-Commit Validation
|
|
@@ -211,7 +211,7 @@ Generates tests and runs them with coverage.
|
|
|
211
211
|
/ai-sprint-test "add integration tests for payment API"
|
|
212
212
|
```
|
|
213
213
|
|
|
214
|
-
**Output:** `ai_context/reports/test
|
|
214
|
+
**Output:** `ai_context/reports/test/`
|
|
215
215
|
|
|
216
216
|
**Requirements:** 80%+ coverage required.
|
|
217
217
|
|
|
@@ -230,7 +230,7 @@ Analyzes code quality and best practices.
|
|
|
230
230
|
/ai-sprint-review src/auth/login.ts
|
|
231
231
|
```
|
|
232
232
|
|
|
233
|
-
**Output:** `ai_context/reports/review
|
|
233
|
+
**Output:** `ai_context/reports/review/`
|
|
234
234
|
|
|
235
235
|
**Checks:**
|
|
236
236
|
- Code quality and patterns
|
|
@@ -254,7 +254,7 @@ Comprehensive security analysis.
|
|
|
254
254
|
/ai-sprint-secure src/auth/
|
|
255
255
|
```
|
|
256
256
|
|
|
257
|
-
**Output:** `ai_context/reports/security
|
|
257
|
+
**Output:** `ai_context/reports/security/`
|
|
258
258
|
|
|
259
259
|
**Scans:**
|
|
260
260
|
- SAST (static analysis)
|
|
@@ -310,7 +310,7 @@ Investigates issues and bugs.
|
|
|
310
310
|
/ai-sprint-debug "slow API response times on /users endpoint"
|
|
311
311
|
```
|
|
312
312
|
|
|
313
|
-
**Output:** `ai_context/reports/debug
|
|
313
|
+
**Output:** `ai_context/reports/debug/`
|
|
314
314
|
|
|
315
315
|
**Provides:**
|
|
316
316
|
- Root cause analysis
|
|
@@ -475,8 +475,8 @@ After every `/ai-sprint-secure` scan:
|
|
|
475
475
|
|
|
476
476
|
```bash
|
|
477
477
|
# Check reports
|
|
478
|
-
ls ai_context/reports/security
|
|
479
|
-
cat ai_context/reports/security
|
|
478
|
+
ls ai_context/reports/security/
|
|
479
|
+
cat ai_context/reports/security/security-*.md
|
|
480
480
|
```
|
|
481
481
|
|
|
482
482
|
### 5. Configure MCP Tools
|
|
@@ -532,7 +532,7 @@ For complex features, maintain control:
|
|
|
532
532
|
|
|
533
533
|
```bash
|
|
534
534
|
# View the report
|
|
535
|
-
cat ai_context/reports/security
|
|
535
|
+
cat ai_context/reports/security/
|
|
536
536
|
|
|
537
537
|
# Fix issues
|
|
538
538
|
/ai-sprint-code "fix security issues from the security scan"
|