ai-sprint-kit 2.0.4 → 2.1.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.
@@ -0,0 +1,248 @@
1
+ const fs = require('fs/promises');
2
+ const path = require('path');
3
+
4
+ /**
5
+ * Generate GitHub Actions workflow
6
+ */
7
+ async function generateGitHubActions(targetDir) {
8
+ const workflowDir = path.join(targetDir, '.github/workflows');
9
+ const workflowPath = path.join(workflowDir, 'ai-sprint-validate.yml');
10
+
11
+ const workflow = `name: AI Sprint Validation
12
+
13
+ on:
14
+ pull_request:
15
+ branches: [main, develop]
16
+ push:
17
+ branches: [main, develop]
18
+
19
+ jobs:
20
+ validate:
21
+ runs-on: ubuntu-latest
22
+
23
+ steps:
24
+ - name: Checkout code
25
+ uses: actions/checkout@v4
26
+
27
+ - name: Setup Node.js
28
+ uses: actions/setup-node@v4
29
+ with:
30
+ node-version: '18'
31
+
32
+ - name: Install AI Sprint Kit CLI
33
+ run: npm install -g ai-sprint-kit
34
+
35
+ - name: Validate Installation
36
+ run: ai-sprint validate
37
+
38
+ - name: Run Diagnostics
39
+ run: ai-sprint doctor
40
+ `;
41
+
42
+ await fs.mkdir(workflowDir, { recursive: true });
43
+ await fs.writeFile(workflowPath, workflow, 'utf8');
44
+
45
+ return workflowPath;
46
+ }
47
+
48
+ /**
49
+ * Generate GitLab CI configuration
50
+ */
51
+ async function generateGitLabCI(targetDir) {
52
+ const gitlabCIPath = path.join(targetDir, '.gitlab-ci.yml');
53
+
54
+ const gitlabCI = `stages:
55
+ - validate
56
+
57
+ variables:
58
+ NODE_VERSION: "18"
59
+
60
+ ai-sprint-validate:
61
+ stage: validate
62
+ image: node:18
63
+
64
+ before_script:
65
+ - npm install -g ai-sprint-kit
66
+
67
+ script:
68
+ - ai-sprint validate
69
+ - ai-sprint doctor
70
+
71
+ only:
72
+ - merge_requests
73
+ - main
74
+ - develop
75
+ `;
76
+
77
+ await fs.writeFile(gitlabCIPath, gitlabCI, 'utf8');
78
+
79
+ return gitlabCIPath;
80
+ }
81
+
82
+ /**
83
+ * Generate Bitbucket Pipelines configuration
84
+ */
85
+ async function generateBitbucketPipelines(targetDir) {
86
+ const pipelinesPath = path.join(targetDir, 'bitbucket-pipelines.yml');
87
+
88
+ const pipelines = `image: node:18
89
+
90
+ pipelines:
91
+ pull-requests:
92
+ '**':
93
+ - step:
94
+ name: AI Sprint Validation
95
+ script:
96
+ - npm install -g ai-sprint-kit
97
+ - ai-sprint validate
98
+ - ai-sprint doctor
99
+
100
+ branches:
101
+ main:
102
+ - step:
103
+ name: AI Sprint Validation
104
+ script:
105
+ - npm install -g ai-sprint-kit
106
+ - ai-sprint validate
107
+ - ai-sprint doctor
108
+ develop:
109
+ - step:
110
+ name: AI Sprint Validation
111
+ script:
112
+ - npm install -g ai-sprint-kit
113
+ - ai-sprint validate
114
+ - ai-sprint doctor
115
+ `;
116
+
117
+ await fs.writeFile(pipelinesPath, pipelines, 'utf8');
118
+
119
+ return pipelinesPath;
120
+ }
121
+
122
+ /**
123
+ * Detect which CI platform is being used
124
+ */
125
+ async function detectCIPlatform(targetDir) {
126
+ // Check for GitHub Actions
127
+ const githubDir = path.join(targetDir, '.github');
128
+ try {
129
+ await fs.access(githubDir);
130
+ return 'github';
131
+ } catch {}
132
+
133
+ // Check for GitLab CI
134
+ const gitlabCI = path.join(targetDir, '.gitlab-ci.yml');
135
+ try {
136
+ await fs.access(gitlabCI);
137
+ return 'gitlab';
138
+ } catch {}
139
+
140
+ // Check for Bitbucket Pipelines
141
+ const bitbucketCI = path.join(targetDir, 'bitbucket-pipelines.yml');
142
+ try {
143
+ await fs.access(bitbucketCI);
144
+ return 'bitbucket';
145
+ } catch {}
146
+
147
+ // No CI detected
148
+ return null;
149
+ }
150
+
151
+ /**
152
+ * Initialize CI configuration
153
+ */
154
+ async function initCI(targetDir, platform) {
155
+ const detected = await detectCIPlatform(targetDir);
156
+ const selectedPlatform = platform || detected || 'github';
157
+
158
+ let generatedPath;
159
+
160
+ switch (selectedPlatform) {
161
+ case 'github':
162
+ generatedPath = await generateGitHubActions(targetDir);
163
+ break;
164
+ case 'gitlab':
165
+ generatedPath = await generateGitLabCI(targetDir);
166
+ break;
167
+ case 'bitbucket':
168
+ generatedPath = await generateBitbucketPipelines(targetDir);
169
+ break;
170
+ default:
171
+ throw new Error(`Unsupported platform: ${selectedPlatform}`);
172
+ }
173
+
174
+ return {
175
+ platform: selectedPlatform,
176
+ path: generatedPath,
177
+ detected: detected || 'none'
178
+ };
179
+ }
180
+
181
+ /**
182
+ * Run CI validation (headless mode for CI systems)
183
+ */
184
+ async function runCIValidation(targetDir) {
185
+ const { validateInstallation } = require('./validator');
186
+
187
+ const result = await validateInstallation(targetDir);
188
+
189
+ // Return CI-friendly output
190
+ return {
191
+ success: result.isValid(),
192
+ summary: result.summary(),
193
+ passed: result.passed,
194
+ failed: result.failed,
195
+ warnings: result.warnings,
196
+ // Exit code for CI
197
+ exitCode: result.isValid() ? 0 : 1
198
+ };
199
+ }
200
+
201
+ /**
202
+ * Format CI validation output for logging
203
+ */
204
+ function formatCIOutput(ciResult) {
205
+ const lines = [];
206
+
207
+ lines.push('=== AI Sprint CI Validation ===\n');
208
+
209
+ if (ciResult.success) {
210
+ lines.push(`āœ… PASSED: ${ciResult.summary.passed} checks passed`);
211
+ } else {
212
+ lines.push(`āŒ FAILED: ${ciResult.summary.failed} checks failed`);
213
+ }
214
+
215
+ if (ciResult.summary.warnings > 0) {
216
+ lines.push(`āš ļø WARNINGS: ${ciResult.summary.warnings} warnings`);
217
+ }
218
+
219
+ lines.push(`\nTotal: ${ciResult.summary.total} checks`);
220
+
221
+ if (!ciResult.success && ciResult.failed.length > 0) {
222
+ lines.push('\nFailed checks:');
223
+ ciResult.failed.forEach(({ check, suggestion }) => {
224
+ lines.push(` āœ— ${check}`);
225
+ lines.push(` → ${suggestion}`);
226
+ });
227
+ }
228
+
229
+ if (ciResult.warnings.length > 0) {
230
+ lines.push('\nWarnings:');
231
+ ciResult.warnings.forEach(({ check, message }) => {
232
+ lines.push(` ! ${check}`);
233
+ lines.push(` → ${message}`);
234
+ });
235
+ }
236
+
237
+ return lines.join('\n');
238
+ }
239
+
240
+ module.exports = {
241
+ generateGitHubActions,
242
+ generateGitLabCI,
243
+ generateBitbucketPipelines,
244
+ detectCIPlatform,
245
+ initCI,
246
+ runCIValidation,
247
+ formatCIOutput
248
+ };
@@ -0,0 +1,249 @@
1
+ const readline = require('readline');
2
+ const chalk = require('chalk');
3
+
4
+ /**
5
+ * Create readline interface for interactive prompts
6
+ */
7
+ function createReadline() {
8
+ return readline.createInterface({
9
+ input: process.stdin,
10
+ output: process.stdout
11
+ });
12
+ }
13
+
14
+ /**
15
+ * Prompt user with a yes/no question
16
+ */
17
+ function promptQuestion(rl, question) {
18
+ return new Promise((resolve) => {
19
+ rl.question(`${question} (y/n): `, (answer) => {
20
+ resolve(answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes');
21
+ });
22
+ });
23
+ }
24
+
25
+ /**
26
+ * Prompt user to select from options
27
+ */
28
+ function promptChoice(rl, question, options) {
29
+ return new Promise((resolve) => {
30
+ console.log(chalk.cyan(`\n${question}`));
31
+ options.forEach((opt, i) => {
32
+ console.log(chalk.gray(` ${i + 1}. ${opt.label}`));
33
+ });
34
+
35
+ rl.question(chalk.cyan('\nSelect option (number): '), (answer) => {
36
+ const index = parseInt(answer) - 1;
37
+ if (index >= 0 && index < options.length) {
38
+ resolve(options[index].value);
39
+ } else {
40
+ resolve(options[0].value); // Default to first option
41
+ }
42
+ });
43
+ });
44
+ }
45
+
46
+ /**
47
+ * Interactive installation mode
48
+ */
49
+ async function interactiveInstall() {
50
+ const rl = createReadline();
51
+
52
+ console.log(chalk.blue.bold('\nšŸŽÆ Interactive Installation\n'));
53
+ console.log(chalk.gray('Customize your AI Sprint Kit setup:\n'));
54
+
55
+ const preferences = {
56
+ includeHooks: true,
57
+ includeBrowserTesting: false,
58
+ ciPlatform: 'none'
59
+ };
60
+
61
+ // Ask about hooks
62
+ preferences.includeHooks = await promptQuestion(
63
+ rl,
64
+ chalk.yellow('Include automation hooks? (auto-format, lint-check)')
65
+ );
66
+
67
+ // Ask about browser testing
68
+ preferences.includeBrowserTesting = await promptQuestion(
69
+ rl,
70
+ chalk.yellow('Include browser testing infrastructure?')
71
+ );
72
+
73
+ // Ask about CI platform
74
+ console.log(chalk.cyan('\nCI/CD Platform:'));
75
+ console.log(chalk.gray(' Choose a platform for automated validation\n'));
76
+
77
+ const ciOptions = [
78
+ { label: 'None (skip CI setup)', value: 'none' },
79
+ { label: 'GitHub Actions', value: 'github' },
80
+ { label: 'GitLab CI', value: 'gitlab' },
81
+ { label: 'Bitbucket Pipelines', value: 'bitbucket' }
82
+ ];
83
+
84
+ preferences.ciPlatform = await promptChoice(
85
+ rl,
86
+ 'Which CI/CD platform do you use?',
87
+ ciOptions
88
+ );
89
+
90
+ rl.close();
91
+
92
+ return preferences;
93
+ }
94
+
95
+ /**
96
+ * Show dry-run preview of what will be installed
97
+ */
98
+ async function dryRunPreview(targetDir) {
99
+ const path = require('path');
100
+ const fs = require('fs/promises');
101
+
102
+ console.log(chalk.blue.bold('\nšŸ” Dry Run - Installation Preview\n'));
103
+ console.log(chalk.cyan('Target directory:'));
104
+ console.log(chalk.gray(` ${targetDir}\n`));
105
+
106
+ console.log(chalk.cyan('Files that will be created:\n'));
107
+
108
+ const filesToCreate = [
109
+ { path: '.claude/settings.json', desc: 'Claude Code configuration' },
110
+ { path: '.claude/.env.example', desc: 'Environment variables template' },
111
+ { path: '.claude/agents/', desc: '9 agent files (planner, implementer, tester, etc.)' },
112
+ { path: '.claude/commands/', desc: '13 command files (/plan, /code, /test, etc.)' },
113
+ { path: '.claude/workflows/', desc: 'Development workflow rules' },
114
+ { path: '.claude/hooks/', desc: 'Automation hooks (optional)' },
115
+ { path: '.claude/skills/', desc: 'Python scripts for advanced features' },
116
+ { path: 'ai_context/', desc: 'Context memory directories' },
117
+ { path: 'CLAUDE.md', desc: 'Framework documentation' },
118
+ { path: '.claude/docs/', desc: 'Additional documentation' }
119
+ ];
120
+
121
+ filesToCreate.forEach(file => {
122
+ const exists = file.path.endsWith('/')
123
+ ? checkDirExists(targetDir, file.path)
124
+ : checkFileExists(targetDir, file.path);
125
+
126
+ const status = exists
127
+ ? chalk.yellow('⚠ OVERWRITE')
128
+ : chalk.green('āœ“ CREATE');
129
+
130
+ console.log(` ${status} ${chalk.cyan(file.path.padEnd(30))} ${chalk.gray(file.desc)}`);
131
+ });
132
+
133
+ console.log(chalk.cyan('\nSummary:'));
134
+ console.log(chalk.gray(` • ${filesToCreate.length} locations will be created/modified`));
135
+ console.log(chalk.gray(` • Target: ${targetDir}`));
136
+ console.log(chalk.gray(` • Run without --dry-run to proceed\n`));
137
+ }
138
+
139
+ /**
140
+ * Check if directory exists
141
+ */
142
+ async function checkDirExists(targetDir, dirPath) {
143
+ const fs = require('fs/promises');
144
+ const path = require('path');
145
+ try {
146
+ await fs.access(path.join(targetDir, dirPath));
147
+ return true;
148
+ } catch {
149
+ return false;
150
+ }
151
+ }
152
+
153
+ /**
154
+ * Check if file exists
155
+ */
156
+ async function checkFileExists(targetDir, filePath) {
157
+ const fs = require('fs/promises');
158
+ const path = require('path');
159
+ try {
160
+ await fs.access(path.join(targetDir, filePath));
161
+ return true;
162
+ } catch {
163
+ return false;
164
+ }
165
+ }
166
+
167
+ /**
168
+ * Show installation progress with detailed steps
169
+ */
170
+ class InstallProgress {
171
+ constructor() {
172
+ this.steps = [];
173
+ this.currentStep = 0;
174
+ }
175
+
176
+ addStep(name, description) {
177
+ this.steps.push({ name, description, status: 'pending' });
178
+ }
179
+
180
+ startStep(stepIndex) {
181
+ this.currentStep = stepIndex;
182
+ this.steps[stepIndex].status = 'running';
183
+ }
184
+
185
+ completeStep(stepIndex) {
186
+ this.steps[stepIndex].status = 'complete';
187
+ }
188
+
189
+ failStep(stepIndex, error) {
190
+ this.steps[stepIndex].status = 'failed';
191
+ this.steps[stepIndex].error = error;
192
+ }
193
+
194
+ render() {
195
+ const lines = [];
196
+ lines.push(chalk.cyan('\nšŸ“¦ Installation Progress:\n'));
197
+
198
+ this.steps.forEach((step, index) => {
199
+ const isCurrent = index === this.currentStep;
200
+
201
+ let status;
202
+ switch (step.status) {
203
+ case 'complete':
204
+ status = chalk.green('āœ“');
205
+ break;
206
+ case 'failed':
207
+ status = chalk.red('āœ—');
208
+ break;
209
+ case 'running':
210
+ status = chalk.yellow('⟳');
211
+ break;
212
+ default:
213
+ status = chalk.gray('ā—‹');
214
+ }
215
+
216
+ const name = isCurrent ? chalk.bold(step.name) : step.name;
217
+ lines.push(` ${status} ${name}`);
218
+
219
+ if (step.description && step.status !== 'complete') {
220
+ lines.push(chalk.gray(` ${step.description}`));
221
+ }
222
+
223
+ if (step.error) {
224
+ lines.push(chalk.red(` Error: ${step.error}`));
225
+ }
226
+ });
227
+
228
+ lines.push('');
229
+ return lines.join('\n');
230
+ }
231
+
232
+ getProgress() {
233
+ const complete = this.steps.filter(s => s.status === 'complete').length;
234
+ return {
235
+ current: complete,
236
+ total: this.steps.length,
237
+ percent: Math.round((complete / this.steps.length) * 100)
238
+ };
239
+ }
240
+ }
241
+
242
+ module.exports = {
243
+ createReadline,
244
+ promptQuestion,
245
+ promptChoice,
246
+ interactiveInstall,
247
+ dryRunPreview,
248
+ InstallProgress
249
+ };