ai-sprint-kit 1.1.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/README.md +299 -0
- package/bin/cli.js +135 -0
- package/lib/installer.js +205 -0
- package/lib/scanner.js +341 -0
- package/package.json +55 -0
- package/templates/.claude/.env.example +13 -0
- package/templates/.claude/agents/debugger.md +667 -0
- package/templates/.claude/agents/devops.md +727 -0
- package/templates/.claude/agents/docs.md +661 -0
- package/templates/.claude/agents/implementer.md +235 -0
- package/templates/.claude/agents/planner.md +243 -0
- package/templates/.claude/agents/researcher.md +448 -0
- package/templates/.claude/agents/reviewer.md +610 -0
- package/templates/.claude/agents/security.md +202 -0
- package/templates/.claude/agents/tester.md +604 -0
- package/templates/.claude/commands/auto.md +85 -0
- package/templates/.claude/commands/code.md +301 -0
- package/templates/.claude/commands/debug.md +449 -0
- package/templates/.claude/commands/deploy.md +475 -0
- package/templates/.claude/commands/docs.md +519 -0
- package/templates/.claude/commands/plan.md +57 -0
- package/templates/.claude/commands/review.md +412 -0
- package/templates/.claude/commands/scan.md +146 -0
- package/templates/.claude/commands/secure.md +88 -0
- package/templates/.claude/commands/test.md +352 -0
- package/templates/.claude/commands/validate.md +238 -0
- package/templates/.claude/settings.json +27 -0
- package/templates/.claude/skills/codebase-context/SKILL.md +68 -0
- package/templates/.claude/skills/codebase-context/references/reading-context.md +68 -0
- package/templates/.claude/skills/codebase-context/references/refresh-triggers.md +82 -0
- package/templates/.claude/skills/implementation/SKILL.md +70 -0
- package/templates/.claude/skills/implementation/references/error-handling.md +106 -0
- package/templates/.claude/skills/implementation/references/security-patterns.md +73 -0
- package/templates/.claude/skills/implementation/references/validation-patterns.md +107 -0
- package/templates/.claude/skills/memory/SKILL.md +67 -0
- package/templates/.claude/skills/memory/references/decisions-format.md +68 -0
- package/templates/.claude/skills/memory/references/learning-format.md +74 -0
- package/templates/.claude/skills/planning/SKILL.md +72 -0
- package/templates/.claude/skills/planning/references/plan-templates.md +81 -0
- package/templates/.claude/skills/planning/references/research-phase.md +62 -0
- package/templates/.claude/skills/planning/references/solution-design.md +66 -0
- package/templates/.claude/skills/quality-assurance/SKILL.md +79 -0
- package/templates/.claude/skills/quality-assurance/references/review-checklist.md +72 -0
- package/templates/.claude/skills/quality-assurance/references/security-checklist.md +70 -0
- package/templates/.claude/skills/quality-assurance/references/testing-strategy.md +85 -0
- package/templates/.claude/statusline.sh +126 -0
- package/templates/.claude/workflows/development-rules.md +97 -0
- package/templates/.claude/workflows/orchestration-protocol.md +194 -0
- package/templates/.mcp.json.example +36 -0
- package/templates/CLAUDE.md +409 -0
- package/templates/README.md +331 -0
- package/templates/ai_context/codebase/.gitkeep +0 -0
- package/templates/ai_context/memory/active.md +15 -0
- package/templates/ai_context/memory/decisions.md +18 -0
- package/templates/ai_context/memory/learning.md +22 -0
- package/templates/ai_context/plans/.gitkeep +0 -0
- package/templates/ai_context/reports/.gitkeep +0 -0
- package/templates/docs/user-guide-th.md +454 -0
- package/templates/docs/user-guide.md +595 -0
package/lib/scanner.js
ADDED
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
const fs = require('fs-extra');
|
|
2
|
+
const path = require('path');
|
|
3
|
+
const ora = require('ora');
|
|
4
|
+
const chalk = require('chalk');
|
|
5
|
+
const execa = require('execa');
|
|
6
|
+
|
|
7
|
+
// Common source code directories to detect
|
|
8
|
+
const SOURCE_DIRS = ['src', 'app', 'lib', 'pages', 'components', 'packages', 'modules'];
|
|
9
|
+
const SOURCE_EXTENSIONS = ['.js', '.ts', '.jsx', '.tsx', '.py', '.go', '.rs', '.java', '.rb', '.php'];
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Check if target directory contains source code
|
|
13
|
+
* @param {string} targetDir - Directory to check
|
|
14
|
+
* @returns {Promise<boolean>} - True if source code detected
|
|
15
|
+
*/
|
|
16
|
+
async function detectSourceCode(targetDir) {
|
|
17
|
+
// Check for common source directories
|
|
18
|
+
for (const dir of SOURCE_DIRS) {
|
|
19
|
+
const dirPath = path.join(targetDir, dir);
|
|
20
|
+
if (await fs.pathExists(dirPath)) {
|
|
21
|
+
const stats = await fs.stat(dirPath);
|
|
22
|
+
if (stats.isDirectory()) {
|
|
23
|
+
return true;
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// Check for source files in root
|
|
29
|
+
try {
|
|
30
|
+
const files = await fs.readdir(targetDir);
|
|
31
|
+
for (const file of files) {
|
|
32
|
+
const ext = path.extname(file).toLowerCase();
|
|
33
|
+
if (SOURCE_EXTENSIONS.includes(ext)) {
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
} catch (error) {
|
|
38
|
+
// Ignore read errors
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
return false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Check if repomix is available
|
|
46
|
+
* @returns {Promise<{available: boolean, command: string}>}
|
|
47
|
+
*/
|
|
48
|
+
async function checkRepomix() {
|
|
49
|
+
// Check global installation
|
|
50
|
+
try {
|
|
51
|
+
await execa('repomix', ['--version']);
|
|
52
|
+
return { available: true, command: 'repomix' };
|
|
53
|
+
} catch (error) {
|
|
54
|
+
// Not globally installed, will use npx
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Check if npx is available
|
|
58
|
+
try {
|
|
59
|
+
await execa('npx', ['--version']);
|
|
60
|
+
return { available: true, command: 'npx repomix' };
|
|
61
|
+
} catch (error) {
|
|
62
|
+
return { available: false, command: null };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
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
|
+
*/
|
|
73
|
+
async function runRepomixScan(targetDir, outputDir, options = {}) {
|
|
74
|
+
const { useNpx = false } = options;
|
|
75
|
+
|
|
76
|
+
// Create output directory
|
|
77
|
+
await fs.ensureDir(outputDir);
|
|
78
|
+
|
|
79
|
+
const xmlOutput = path.join(outputDir, 'repomix-output.xml');
|
|
80
|
+
const mdOutput = path.join(outputDir, 'overview.md');
|
|
81
|
+
|
|
82
|
+
// Build base command
|
|
83
|
+
const baseCmd = useNpx ? 'npx' : 'repomix';
|
|
84
|
+
const baseArgs = useNpx ? ['repomix'] : [];
|
|
85
|
+
|
|
86
|
+
// Generate XML output (for AI consumption)
|
|
87
|
+
const xmlArgs = [
|
|
88
|
+
...baseArgs,
|
|
89
|
+
'--compress',
|
|
90
|
+
'--style', 'xml',
|
|
91
|
+
'-o', xmlOutput
|
|
92
|
+
];
|
|
93
|
+
|
|
94
|
+
await execa(baseCmd, xmlArgs, {
|
|
95
|
+
cwd: targetDir,
|
|
96
|
+
timeout: 300000 // 5 minute timeout
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// Generate Markdown output (for human reading)
|
|
100
|
+
const mdArgs = [
|
|
101
|
+
...baseArgs,
|
|
102
|
+
'--compress',
|
|
103
|
+
'--style', 'markdown',
|
|
104
|
+
'-o', mdOutput
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
await execa(baseCmd, mdArgs, {
|
|
108
|
+
cwd: targetDir,
|
|
109
|
+
timeout: 300000
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
// Parse XML to get stats
|
|
113
|
+
let stats = {
|
|
114
|
+
totalFiles: 0,
|
|
115
|
+
totalTokens: 0,
|
|
116
|
+
compressedTokens: 0
|
|
117
|
+
};
|
|
118
|
+
|
|
119
|
+
try {
|
|
120
|
+
const xmlContent = await fs.readFile(xmlOutput, 'utf-8');
|
|
121
|
+
// Count file entries in XML
|
|
122
|
+
const fileMatches = xmlContent.match(/<file path="/g);
|
|
123
|
+
if (fileMatches) {
|
|
124
|
+
stats.totalFiles = fileMatches.length;
|
|
125
|
+
}
|
|
126
|
+
} catch (error) {
|
|
127
|
+
// Stats extraction failed, continue with defaults
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
return stats;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Generate directory structure file
|
|
135
|
+
* @param {string} targetDir - Directory to scan
|
|
136
|
+
* @param {string} outputDir - Output directory
|
|
137
|
+
*/
|
|
138
|
+
async function generateStructure(targetDir, outputDir) {
|
|
139
|
+
const structureFile = path.join(outputDir, 'structure.md');
|
|
140
|
+
let content = '# Project Structure\n\n```\n';
|
|
141
|
+
|
|
142
|
+
try {
|
|
143
|
+
// Try using tree command
|
|
144
|
+
const { stdout } = await execa('tree', [
|
|
145
|
+
'-I', 'node_modules|.git|.venv|__pycache__|dist|build|.next|coverage',
|
|
146
|
+
'-L', '4',
|
|
147
|
+
'--noreport'
|
|
148
|
+
], {
|
|
149
|
+
cwd: targetDir,
|
|
150
|
+
timeout: 30000
|
|
151
|
+
});
|
|
152
|
+
content += stdout;
|
|
153
|
+
} catch (error) {
|
|
154
|
+
// Fallback: simple directory listing
|
|
155
|
+
content += await generateSimpleTree(targetDir, '', 0, 4);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
content += '\n```\n';
|
|
159
|
+
await fs.writeFile(structureFile, content);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Simple tree generator fallback
|
|
164
|
+
*/
|
|
165
|
+
async function generateSimpleTree(dir, prefix, depth, maxDepth) {
|
|
166
|
+
if (depth >= maxDepth) return '';
|
|
167
|
+
|
|
168
|
+
const ignoreDirs = ['node_modules', '.git', '.venv', '__pycache__', 'dist', 'build', '.next', 'coverage'];
|
|
169
|
+
let result = '';
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
173
|
+
const filtered = entries.filter(e => !ignoreDirs.includes(e.name) && !e.name.startsWith('.'));
|
|
174
|
+
|
|
175
|
+
for (let i = 0; i < filtered.length; i++) {
|
|
176
|
+
const entry = filtered[i];
|
|
177
|
+
const isLast = i === filtered.length - 1;
|
|
178
|
+
const connector = isLast ? '└── ' : '├── ';
|
|
179
|
+
const newPrefix = prefix + (isLast ? ' ' : '│ ');
|
|
180
|
+
|
|
181
|
+
result += prefix + connector + entry.name + '\n';
|
|
182
|
+
|
|
183
|
+
if (entry.isDirectory()) {
|
|
184
|
+
result += await generateSimpleTree(
|
|
185
|
+
path.join(dir, entry.name),
|
|
186
|
+
newPrefix,
|
|
187
|
+
depth + 1,
|
|
188
|
+
maxDepth
|
|
189
|
+
);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
} catch (error) {
|
|
193
|
+
// Ignore errors
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
return result;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
/**
|
|
200
|
+
* Create default .repomixignore file
|
|
201
|
+
* @param {string} outputDir - Output directory
|
|
202
|
+
*/
|
|
203
|
+
async function createRepomixIgnore(outputDir) {
|
|
204
|
+
const ignorePath = path.join(outputDir, '.repomixignore');
|
|
205
|
+
|
|
206
|
+
if (await fs.pathExists(ignorePath)) {
|
|
207
|
+
return; // Don't overwrite existing
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
const content = `# AI Sprint default ignore patterns
|
|
211
|
+
node_modules/
|
|
212
|
+
.git/
|
|
213
|
+
dist/
|
|
214
|
+
build/
|
|
215
|
+
.next/
|
|
216
|
+
.venv/
|
|
217
|
+
__pycache__/
|
|
218
|
+
*.pyc
|
|
219
|
+
.env*
|
|
220
|
+
*.log
|
|
221
|
+
coverage/
|
|
222
|
+
.nyc_output/
|
|
223
|
+
*.min.js
|
|
224
|
+
*.min.css
|
|
225
|
+
package-lock.json
|
|
226
|
+
yarn.lock
|
|
227
|
+
pnpm-lock.yaml
|
|
228
|
+
`;
|
|
229
|
+
|
|
230
|
+
await fs.writeFile(ignorePath, content);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
/**
|
|
234
|
+
* Write scan metadata
|
|
235
|
+
* @param {string} outputDir - Output directory
|
|
236
|
+
* @param {object} stats - Scan statistics
|
|
237
|
+
*/
|
|
238
|
+
async function writeMetadata(outputDir, stats) {
|
|
239
|
+
const metadataPath = path.join(outputDir, 'scan-metadata.json');
|
|
240
|
+
|
|
241
|
+
const metadata = {
|
|
242
|
+
scanDate: new Date().toISOString(),
|
|
243
|
+
scanDuration: stats.duration || 0,
|
|
244
|
+
totalFiles: stats.totalFiles || 0,
|
|
245
|
+
totalTokens: stats.totalTokens || 0,
|
|
246
|
+
compressedTokens: stats.compressedTokens || 0,
|
|
247
|
+
compressionRatio: stats.totalTokens > 0
|
|
248
|
+
? Math.round((1 - stats.compressedTokens / stats.totalTokens) * 100) / 100
|
|
249
|
+
: 0,
|
|
250
|
+
securityIssues: stats.securityIssues || 0
|
|
251
|
+
};
|
|
252
|
+
|
|
253
|
+
await fs.writeFile(metadataPath, JSON.stringify(metadata, null, 2));
|
|
254
|
+
return metadata;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* 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
|
+
*/
|
|
263
|
+
async function scanCodebase(targetDir, options = {}) {
|
|
264
|
+
const { silent = false } = options;
|
|
265
|
+
const outputDir = path.join(targetDir, 'ai_context', 'codebase');
|
|
266
|
+
const startTime = Date.now();
|
|
267
|
+
|
|
268
|
+
// Check for source code
|
|
269
|
+
const hasSource = await detectSourceCode(targetDir);
|
|
270
|
+
if (!hasSource) {
|
|
271
|
+
if (!silent) {
|
|
272
|
+
console.log(chalk.gray(' No source code detected. Skipping scan.'));
|
|
273
|
+
}
|
|
274
|
+
return { skipped: true, reason: 'no-source' };
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
// Check repomix availability
|
|
278
|
+
const { available, command } = await checkRepomix();
|
|
279
|
+
if (!available) {
|
|
280
|
+
if (!silent) {
|
|
281
|
+
console.log(chalk.yellow(' ⚠️ Repomix not available. Skipping scan.'));
|
|
282
|
+
console.log(chalk.gray(' Install with: npm install -g repomix'));
|
|
283
|
+
}
|
|
284
|
+
return { skipped: true, reason: 'no-repomix' };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const spinner = silent ? null : ora('Scanning codebase...').start();
|
|
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);
|
|
303
|
+
|
|
304
|
+
// Calculate duration
|
|
305
|
+
stats.duration = Math.round((Date.now() - startTime) / 1000 * 10) / 10;
|
|
306
|
+
|
|
307
|
+
// Write metadata
|
|
308
|
+
const metadata = await writeMetadata(outputDir, stats);
|
|
309
|
+
|
|
310
|
+
if (spinner) {
|
|
311
|
+
spinner.succeed(`Codebase scanned (${stats.totalFiles} files, ${stats.duration}s)`);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
return {
|
|
315
|
+
success: true,
|
|
316
|
+
outputDir,
|
|
317
|
+
stats: metadata
|
|
318
|
+
};
|
|
319
|
+
|
|
320
|
+
} catch (error) {
|
|
321
|
+
if (spinner) {
|
|
322
|
+
spinner.fail('Codebase scan failed');
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
if (!silent) {
|
|
326
|
+
console.log(chalk.yellow(` ⚠️ ${error.message}`));
|
|
327
|
+
console.log(chalk.gray(' Run /scan manually after fixing the issue.'));
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
return {
|
|
331
|
+
success: false,
|
|
332
|
+
error: error.message
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
module.exports = {
|
|
338
|
+
scanCodebase,
|
|
339
|
+
detectSourceCode,
|
|
340
|
+
checkRepomix
|
|
341
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "ai-sprint-kit",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"description": "CLI installer for autonomous coding agent framework - security-first, production-grade Claude Code setup",
|
|
5
|
+
"main": "lib/installer.js",
|
|
6
|
+
"bin": {
|
|
7
|
+
"ai-sprint": "bin/cli.js"
|
|
8
|
+
},
|
|
9
|
+
"scripts": {
|
|
10
|
+
"test": "node bin/cli.js --help",
|
|
11
|
+
"link": "npm link",
|
|
12
|
+
"unlink": "npm unlink -g ai-sprint"
|
|
13
|
+
},
|
|
14
|
+
"keywords": [
|
|
15
|
+
"claude-code",
|
|
16
|
+
"autonomous-agents",
|
|
17
|
+
"ai-development",
|
|
18
|
+
"security",
|
|
19
|
+
"cli",
|
|
20
|
+
"devtools",
|
|
21
|
+
"code-generation",
|
|
22
|
+
"ai-sprint"
|
|
23
|
+
],
|
|
24
|
+
"author": "Your Name",
|
|
25
|
+
"license": "MIT",
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "https://github.com/yourusername/ai-sprint.git"
|
|
29
|
+
},
|
|
30
|
+
"bugs": {
|
|
31
|
+
"url": "https://github.com/yourusername/ai-sprint/issues"
|
|
32
|
+
},
|
|
33
|
+
"homepage": "https://github.com/yourusername/ai-sprint#readme",
|
|
34
|
+
"files": [
|
|
35
|
+
"bin/",
|
|
36
|
+
"lib/",
|
|
37
|
+
"templates/",
|
|
38
|
+
"README.md",
|
|
39
|
+
"LICENSE"
|
|
40
|
+
],
|
|
41
|
+
"engines": {
|
|
42
|
+
"node": ">=18.0.0"
|
|
43
|
+
},
|
|
44
|
+
"dependencies": {
|
|
45
|
+
"commander": "^11.1.0",
|
|
46
|
+
"chalk": "^4.1.2",
|
|
47
|
+
"ora": "^5.4.1",
|
|
48
|
+
"inquirer": "^8.2.6",
|
|
49
|
+
"fs-extra": "^11.2.0",
|
|
50
|
+
"execa": "^5.1.1"
|
|
51
|
+
},
|
|
52
|
+
"publishConfig": {
|
|
53
|
+
"access": "public"
|
|
54
|
+
}
|
|
55
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# AI Sprint Framework - Environment Variables
|
|
2
|
+
|
|
3
|
+
# Security Scanning (Optional)
|
|
4
|
+
# Get tokens from: https://snyk.io, https://semgrep.dev
|
|
5
|
+
SNYK_TOKEN=
|
|
6
|
+
SEMGREP_APP_TOKEN=
|
|
7
|
+
|
|
8
|
+
# Claude API (Optional - only if using custom models)
|
|
9
|
+
# Get from: https://console.anthropic.com
|
|
10
|
+
ANTHROPIC_API_KEY=
|
|
11
|
+
|
|
12
|
+
# Project Settings
|
|
13
|
+
NODE_ENV=development
|