jumpstart-mode 1.0.6 → 1.0.8

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.
Files changed (38) hide show
  1. package/.cursorrules +3 -0
  2. package/.github/agents/jumpstart-analyst.agent.md +28 -7
  3. package/.github/agents/jumpstart-architect.agent.md +28 -7
  4. package/.github/agents/jumpstart-challenger.agent.md +37 -4
  5. package/.github/agents/jumpstart-developer.agent.md +19 -8
  6. package/.github/agents/jumpstart-facilitator.agent.md +75 -0
  7. package/.github/agents/jumpstart-pm.agent.md +13 -6
  8. package/.github/agents/jumpstart-scout.agent.md +50 -0
  9. package/.github/copilot-instructions.md +13 -5
  10. package/.github/instructions/specs.instructions.md +2 -0
  11. package/.github/prompts/jumpstart-review.prompt.md +2 -0
  12. package/.github/prompts/jumpstart-status.prompt.md +8 -1
  13. package/.jumpstart/agents/analyst.md +235 -28
  14. package/.jumpstart/agents/architect.md +163 -15
  15. package/.jumpstart/agents/challenger.md +91 -6
  16. package/.jumpstart/agents/developer.md +55 -2
  17. package/.jumpstart/agents/facilitator.md +227 -0
  18. package/.jumpstart/agents/pm.md +40 -5
  19. package/.jumpstart/agents/scout.md +358 -0
  20. package/.jumpstart/commands/commands.md +102 -26
  21. package/.jumpstart/config.yaml +46 -1
  22. package/.jumpstart/domain-complexity.csv +15 -0
  23. package/.jumpstart/roadmap.md +85 -0
  24. package/.jumpstart/templates/agents-md.md +93 -0
  25. package/.jumpstart/templates/architecture.md +48 -8
  26. package/.jumpstart/templates/challenger-brief.md +6 -4
  27. package/.jumpstart/templates/codebase-context.md +265 -0
  28. package/.jumpstart/templates/implementation-plan.md +9 -5
  29. package/.jumpstart/templates/insights.md +18 -18
  30. package/.jumpstart/templates/prd.md +7 -5
  31. package/.jumpstart/templates/product-brief.md +6 -4
  32. package/.jumpstart/templates/roadmap.md +79 -0
  33. package/AGENTS.md +31 -1
  34. package/CLAUDE.md +3 -0
  35. package/README.md +67 -8
  36. package/bin/cli.js +199 -3
  37. package/bin/context7-setup.js +405 -0
  38. package/package.json +1 -1
@@ -0,0 +1,405 @@
1
+ #!/usr/bin/env node
2
+ // ============================================================================
3
+ // Context7 MCP Setup Module
4
+ // ============================================================================
5
+ // Handles interactive setup of the Context7 MCP (Model Context Protocol)
6
+ // server for AI coding assistants. Integrates with the JumpStart CLI to
7
+ // optionally configure Context7 during framework installation.
8
+ //
9
+ // Supported clients:
10
+ // - VS Code (GitHub Copilot)
11
+ // - Cursor
12
+ // - Claude Code (CLI)
13
+ // - Claude Desktop
14
+ // - Windsurf
15
+ // - Generic (manual instructions)
16
+ // ============================================================================
17
+
18
+ const fs = require('fs');
19
+ const path = require('path');
20
+ const chalk = require('chalk');
21
+ const prompts = require('prompts');
22
+ const { execSync } = require('child_process');
23
+
24
+ // ---------------------------------------------------------------------------
25
+ // Constants
26
+ // ---------------------------------------------------------------------------
27
+
28
+ const API_KEY_PREFIX = 'ctx7sk-';
29
+
30
+ // Client configuration templates
31
+ const CLIENT_CONFIGS = {
32
+ vscode: {
33
+ name: 'VS Code (GitHub Copilot)',
34
+ configFileName: '.vscode/mcp.json',
35
+ generateConfig: (apiKey) => ({
36
+ mcp: {
37
+ servers: {
38
+ context7: {
39
+ type: 'stdio',
40
+ command: 'npx',
41
+ args: ['-y', '@upstash/context7-mcp', '--api-key', apiKey]
42
+ }
43
+ }
44
+ }
45
+ })
46
+ },
47
+ cursor: {
48
+ name: 'Cursor',
49
+ configFileName: '.cursor/mcp.json',
50
+ generateConfig: (apiKey) => ({
51
+ mcpServers: {
52
+ context7: {
53
+ command: 'npx',
54
+ args: ['-y', '@upstash/context7-mcp', '--api-key', apiKey]
55
+ }
56
+ }
57
+ })
58
+ },
59
+ 'claude-code': {
60
+ name: 'Claude Code (CLI)',
61
+ useCli: true,
62
+ cliCommand: (apiKey) =>
63
+ `claude mcp add context7 -- npx -y @upstash/context7-mcp --api-key ${apiKey}`
64
+ },
65
+ 'claude-desktop': {
66
+ name: 'Claude Desktop',
67
+ configFileName: null, // Platform-specific path resolved at runtime
68
+ getConfigPath: () => {
69
+ if (process.platform === 'win32') {
70
+ return path.join(
71
+ process.env.APPDATA || '',
72
+ 'Claude',
73
+ 'claude_desktop_config.json'
74
+ );
75
+ } else if (process.platform === 'darwin') {
76
+ return path.join(
77
+ process.env.HOME || '',
78
+ 'Library',
79
+ 'Application Support',
80
+ 'Claude',
81
+ 'claude_desktop_config.json'
82
+ );
83
+ }
84
+ return path.join(process.env.HOME || '', '.config', 'claude', 'claude_desktop_config.json');
85
+ },
86
+ generateConfig: (apiKey) => ({
87
+ mcpServers: {
88
+ context7: {
89
+ command: 'npx',
90
+ args: ['-y', '@upstash/context7-mcp', '--api-key', apiKey]
91
+ }
92
+ }
93
+ })
94
+ },
95
+ windsurf: {
96
+ name: 'Windsurf',
97
+ configFileName: null,
98
+ getConfigPath: () => {
99
+ if (process.platform === 'win32') {
100
+ return path.join(
101
+ process.env.APPDATA || process.env.USERPROFILE || '',
102
+ '.codeium',
103
+ 'windsurf',
104
+ 'mcp_config.json'
105
+ );
106
+ }
107
+ return path.join(process.env.HOME || '', '.codeium', 'windsurf', 'mcp_config.json');
108
+ },
109
+ generateConfig: (apiKey) => ({
110
+ mcpServers: {
111
+ context7: {
112
+ command: 'npx',
113
+ args: ['-y', '@upstash/context7-mcp', '--api-key', apiKey]
114
+ }
115
+ }
116
+ })
117
+ }
118
+ };
119
+
120
+ // ---------------------------------------------------------------------------
121
+ // Validation
122
+ // ---------------------------------------------------------------------------
123
+
124
+ /**
125
+ * Validates that an API key has the expected Context7 format.
126
+ */
127
+ function validateApiKey(value) {
128
+ if (!value || typeof value !== 'string') {
129
+ return 'API key is required';
130
+ }
131
+ value = value.trim();
132
+ if (!value.startsWith(API_KEY_PREFIX)) {
133
+ return `Invalid format. Context7 API keys start with "${API_KEY_PREFIX}"`;
134
+ }
135
+ if (value.length < 10) {
136
+ return 'API key appears too short';
137
+ }
138
+ return true;
139
+ }
140
+
141
+ // ---------------------------------------------------------------------------
142
+ // Configuration writers
143
+ // ---------------------------------------------------------------------------
144
+
145
+ /**
146
+ * Merges the Context7 MCP server block into an existing JSON config file,
147
+ * or creates the file if it doesn't exist.
148
+ */
149
+ function mergeJsonConfig(filePath, newConfig) {
150
+ let existing = {};
151
+
152
+ if (fs.existsSync(filePath)) {
153
+ try {
154
+ const raw = fs.readFileSync(filePath, 'utf8');
155
+ existing = JSON.parse(raw);
156
+ } catch {
157
+ // If the file is malformed, back it up and start fresh
158
+ const backup = filePath + '.bak';
159
+ fs.copyFileSync(filePath, backup);
160
+ console.log(chalk.yellow(` ⚠ Backed up malformed ${path.basename(filePath)} to ${path.basename(backup)}`));
161
+ }
162
+ }
163
+
164
+ // Deep-merge: add context7 into the appropriate nested key
165
+ const merged = deepMerge(existing, newConfig);
166
+
167
+ const dir = path.dirname(filePath);
168
+ if (!fs.existsSync(dir)) {
169
+ fs.mkdirSync(dir, { recursive: true });
170
+ }
171
+
172
+ fs.writeFileSync(filePath, JSON.stringify(merged, null, 2) + '\n', 'utf8');
173
+ }
174
+
175
+ /**
176
+ * Simple recursive merge (source wins on conflict).
177
+ */
178
+ function deepMerge(target, source) {
179
+ const output = { ...target };
180
+ for (const key of Object.keys(source)) {
181
+ if (
182
+ source[key] &&
183
+ typeof source[key] === 'object' &&
184
+ !Array.isArray(source[key]) &&
185
+ target[key] &&
186
+ typeof target[key] === 'object' &&
187
+ !Array.isArray(target[key])
188
+ ) {
189
+ output[key] = deepMerge(target[key], source[key]);
190
+ } else {
191
+ output[key] = source[key];
192
+ }
193
+ }
194
+ return output;
195
+ }
196
+
197
+ // ---------------------------------------------------------------------------
198
+ // Client installers
199
+ // ---------------------------------------------------------------------------
200
+
201
+ /**
202
+ * Installs MCP configuration for a specific client.
203
+ * @returns {{ success: boolean, message: string }}
204
+ */
205
+ function installForClient(clientKey, apiKey, targetDir) {
206
+ const client = CLIENT_CONFIGS[clientKey];
207
+ if (!client) {
208
+ return { success: false, message: `Unknown client: ${clientKey}` };
209
+ }
210
+
211
+ // CLI-based installation (Claude Code)
212
+ if (client.useCli) {
213
+ const cmd = client.cliCommand(apiKey);
214
+ try {
215
+ execSync(cmd, { stdio: 'pipe', cwd: targetDir });
216
+ return { success: true, message: `Configured via CLI: ${cmd.split('--api-key')[0].trim()}...` };
217
+ } catch (err) {
218
+ return {
219
+ success: false,
220
+ message: `CLI command failed. You can run it manually:\n ${cmd}`
221
+ };
222
+ }
223
+ }
224
+
225
+ // File-based installation
226
+ let configPath;
227
+ if (client.getConfigPath) {
228
+ configPath = client.getConfigPath();
229
+ } else if (client.configFileName) {
230
+ configPath = path.join(targetDir, client.configFileName);
231
+ } else {
232
+ return { success: false, message: 'Cannot determine config path for this client.' };
233
+ }
234
+
235
+ try {
236
+ const config = client.generateConfig(apiKey);
237
+ mergeJsonConfig(configPath, config);
238
+ return { success: true, message: `Config written to ${configPath}` };
239
+ } catch (err) {
240
+ return { success: false, message: `Failed to write config: ${err.message}` };
241
+ }
242
+ }
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Interactive setup (exported for use in CLI)
246
+ // ---------------------------------------------------------------------------
247
+
248
+ /**
249
+ * Runs the interactive Context7 MCP setup flow.
250
+ * Called from the JumpStart CLI after the main installation.
251
+ *
252
+ * @param {Object} options
253
+ * @param {string} options.targetDir - Project target directory
254
+ * @param {boolean} [options.dryRun=false] - If true, show what would happen
255
+ * @returns {Promise<{ installed: boolean, clients: string[], apiKey?: string }>}
256
+ */
257
+ async function setupContext7({ targetDir, dryRun = false }) {
258
+ console.log(chalk.cyan('\n🔌 Context7 MCP Integration\n'));
259
+ console.log(
260
+ chalk.gray(
261
+ ' Context7 provides up-to-date library documentation to your AI coding\n' +
262
+ ' assistant via the Model Context Protocol (MCP). This is optional.\n' +
263
+ ' Get your API key at: https://context7.com\n'
264
+ )
265
+ );
266
+
267
+ // Step 1: Ask if user wants to install
268
+ const { installMcp } = await prompts({
269
+ type: 'confirm',
270
+ name: 'installMcp',
271
+ message: 'Would you like to install the Context7 MCP for your AI agents?',
272
+ initial: true
273
+ });
274
+
275
+ if (!installMcp) {
276
+ console.log(chalk.gray(' Skipped Context7 MCP setup.\n'));
277
+ return { installed: false, clients: [] };
278
+ }
279
+
280
+ // Step 2: Capture API key
281
+ const { apiKey } = await prompts({
282
+ type: 'text',
283
+ name: 'apiKey',
284
+ message: `Enter your Context7 API key (${API_KEY_PREFIX}...):`,
285
+ validate: validateApiKey
286
+ });
287
+
288
+ if (!apiKey) {
289
+ console.log(chalk.yellow(' No API key provided. Skipping Context7 setup.\n'));
290
+ return { installed: false, clients: [] };
291
+ }
292
+
293
+ const trimmedKey = apiKey.trim();
294
+
295
+ // Step 3: Select target clients
296
+ const clientChoices = Object.entries(CLIENT_CONFIGS).map(([key, cfg]) => ({
297
+ title: cfg.name,
298
+ value: key,
299
+ selected: key === 'vscode' // Pre-select VS Code
300
+ }));
301
+
302
+ const { selectedClients } = await prompts({
303
+ type: 'multiselect',
304
+ name: 'selectedClients',
305
+ message: 'Which AI clients should be configured?',
306
+ choices: clientChoices,
307
+ hint: '- Space to select, Enter to confirm',
308
+ instructions: false,
309
+ min: 1
310
+ });
311
+
312
+ if (!selectedClients || selectedClients.length === 0) {
313
+ console.log(chalk.yellow(' No clients selected. Skipping Context7 setup.\n'));
314
+ return { installed: false, clients: [] };
315
+ }
316
+
317
+ // Step 4: Security notice
318
+ console.log(
319
+ chalk.yellow(
320
+ '\n ⚠ Your API key will be stored in local configuration files.\n' +
321
+ ' Make sure these files are listed in your .gitignore.\n'
322
+ )
323
+ );
324
+
325
+ // Step 5: Install for each selected client
326
+ if (dryRun) {
327
+ console.log(chalk.yellow.bold(' [DRY RUN] Would configure Context7 for:'));
328
+ selectedClients.forEach((c) =>
329
+ console.log(chalk.gray(` - ${CLIENT_CONFIGS[c].name}`))
330
+ );
331
+ console.log('');
332
+ return { installed: false, clients: selectedClients };
333
+ }
334
+
335
+ const results = [];
336
+ for (const clientKey of selectedClients) {
337
+ const clientName = CLIENT_CONFIGS[clientKey].name;
338
+ const result = installForClient(clientKey, trimmedKey, targetDir);
339
+ results.push({ clientKey, ...result });
340
+
341
+ if (result.success) {
342
+ console.log(chalk.green(` ✅ ${clientName}: ${result.message}`));
343
+ } else {
344
+ console.log(chalk.red(` ❌ ${clientName}: ${result.message}`));
345
+ }
346
+ }
347
+
348
+ // Step 6: Ensure .gitignore entries exist
349
+ ensureGitignoreEntries(targetDir, selectedClients, dryRun);
350
+
351
+ const successClients = results.filter((r) => r.success).map((r) => r.clientKey);
352
+ console.log(chalk.green(`\n Context7 MCP configured for ${successClients.length} client(s).\n`));
353
+
354
+ return { installed: true, clients: successClients, apiKey: trimmedKey };
355
+ }
356
+
357
+ /**
358
+ * Ensures that MCP config files containing API keys are gitignored.
359
+ */
360
+ function ensureGitignoreEntries(targetDir, selectedClients, dryRun) {
361
+ const gitignorePath = path.join(targetDir, '.gitignore');
362
+ const entriesToAdd = [];
363
+
364
+ // Map client keys to gitignore patterns
365
+ const gitignorePatterns = {
366
+ vscode: '.vscode/mcp.json',
367
+ cursor: '.cursor/mcp.json'
368
+ };
369
+
370
+ for (const clientKey of selectedClients) {
371
+ if (gitignorePatterns[clientKey]) {
372
+ entriesToAdd.push(gitignorePatterns[clientKey]);
373
+ }
374
+ }
375
+
376
+ if (entriesToAdd.length === 0) return;
377
+
378
+ let existing = '';
379
+ if (fs.existsSync(gitignorePath)) {
380
+ existing = fs.readFileSync(gitignorePath, 'utf8');
381
+ }
382
+
383
+ const missing = entriesToAdd.filter((e) => !existing.includes(e));
384
+ if (missing.length === 0) return;
385
+
386
+ if (dryRun) {
387
+ console.log(chalk.gray(` Would add to .gitignore: ${missing.join(', ')}`));
388
+ return;
389
+ }
390
+
391
+ const block = '\n# Context7 MCP config (contains API key)\n' + missing.join('\n') + '\n';
392
+ fs.appendFileSync(gitignorePath, block, 'utf8');
393
+ console.log(chalk.gray(` Updated .gitignore with: ${missing.join(', ')}`));
394
+ }
395
+
396
+ // ---------------------------------------------------------------------------
397
+ // Exports
398
+ // ---------------------------------------------------------------------------
399
+
400
+ module.exports = {
401
+ setupContext7,
402
+ validateApiKey,
403
+ installForClient,
404
+ CLIENT_CONFIGS
405
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "jumpstart-mode",
3
- "version": "1.0.6",
3
+ "version": "1.0.8",
4
4
  "description": "Spec-driven agentic coding framework that transforms ideas into production code through AI-powered sequential phases",
5
5
  "keywords": [
6
6
  "jumpstart",