symfonia-ai-tools 1.0.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.
Files changed (74) hide show
  1. package/README.md +489 -0
  2. package/bin/cli.mjs +35 -0
  3. package/lib/installer.mjs +495 -0
  4. package/lib/questions.mjs +332 -0
  5. package/lib/ui.mjs +76 -0
  6. package/lib/utils.mjs +231 -0
  7. package/package.json +26 -0
  8. package/templates/base/CLAUDE.md +34 -0
  9. package/templates/base/_ai/_guidelines_header.md +70 -0
  10. package/templates/base/_ai/context/README.md +20 -0
  11. package/templates/base/_ai/prompts/codereview.prompt.md +324 -0
  12. package/templates/base/_ai/prompts/duplicate-code-analysis.prompt.md +128 -0
  13. package/templates/base/_ai/prompts/figma-analysis.prompt.md +155 -0
  14. package/templates/base/_ai/prompts/security-review.prompt.md +46 -0
  15. package/templates/base/_ai/skills/README.md +80 -0
  16. package/templates/base/_ai/skills/TEMPLATE.md +106 -0
  17. package/templates/base/_ai/skills/babysit-prs/SKILL.md +105 -0
  18. package/templates/base/_ai/skills/debug/SKILL.md +93 -0
  19. package/templates/base/_ai/skills/fill-worklogs/SKILL.md +158 -0
  20. package/templates/base/_ai/skills/hotfix/SKILL.md +52 -0
  21. package/templates/base/_ai/skills/jira-task/SKILL.md +170 -0
  22. package/templates/base/_ai/skills/my-prs/SKILL.md +78 -0
  23. package/templates/base/_ai/skills/pr-dashboard/SKILL.md +43 -0
  24. package/templates/base/_ai/skills/pr-prepare/SKILL.md +106 -0
  25. package/templates/base/_ai/skills/refactor/SKILL.md +87 -0
  26. package/templates/base/_ai/skills/write-tests/SKILL.md +109 -0
  27. package/templates/base/_claude/settings.local.json +37 -0
  28. package/templates/base/_cursor/rules/global.mdc +7 -0
  29. package/templates/base/_editorconfig +18 -0
  30. package/templates/base/_gemini/settings.json +3 -0
  31. package/templates/base/_github/copilot-instructions.md +1 -0
  32. package/templates/base/_github/pull_request_template.md +23 -0
  33. package/templates/base/_gitignore +22 -0
  34. package/templates/base/_junie/guidelines.md +1 -0
  35. package/templates/base/commit-instructions.md +92 -0
  36. package/templates/packs/docker/_ai/instructions/docker.instructions.md +193 -0
  37. package/templates/packs/docker/_guidelines.md +10 -0
  38. package/templates/packs/docker/pack.json +8 -0
  39. package/templates/packs/laravel/_ai/instructions/api-resource.instructions.md +251 -0
  40. package/templates/packs/laravel/_ai/instructions/module.instructions.md +133 -0
  41. package/templates/packs/laravel/_ai/instructions/service-repository.instructions.md +215 -0
  42. package/templates/packs/laravel/_ai/instructions/testing.instructions.md +278 -0
  43. package/templates/packs/laravel/_ai/skills/migration/SKILL.md +172 -0
  44. package/templates/packs/laravel/_ai/skills/new-endpoint/SKILL.md +165 -0
  45. package/templates/packs/laravel/_ai/skills/new-module/SKILL.md +208 -0
  46. package/templates/packs/laravel/_ai/skills/queued-job/SKILL.md +248 -0
  47. package/templates/packs/laravel/_ai/skills/testing-feature/SKILL.md +196 -0
  48. package/templates/packs/laravel/_ai/skills/testing-manual/SKILL.md +186 -0
  49. package/templates/packs/laravel/_ai/skills/testing-unit/SKILL.md +200 -0
  50. package/templates/packs/laravel/_guidelines.md +25 -0
  51. package/templates/packs/laravel/pack.json +6 -0
  52. package/templates/packs/playwright/_ai/instructions/playwright.instructions.md +219 -0
  53. package/templates/packs/playwright/_ai/skills/playwright/README.md +194 -0
  54. package/templates/packs/playwright/_ai/skills/playwright/SKILL.md +1245 -0
  55. package/templates/packs/playwright/_ai/skills/playwright-codereview/SKILL.md +642 -0
  56. package/templates/packs/playwright/_ai/skills/playwright-record/README.md +87 -0
  57. package/templates/packs/playwright/_ai/skills/playwright-record/SKILL.md +564 -0
  58. package/templates/packs/playwright/_guidelines.md +12 -0
  59. package/templates/packs/playwright/pack.json +9 -0
  60. package/templates/packs/storybook/_ai/instructions/storybook.instructions.md +181 -0
  61. package/templates/packs/storybook/pack.json +6 -0
  62. package/templates/packs/vitest/_ai/instructions/vitest.instructions.md +688 -0
  63. package/templates/packs/vitest/pack.json +6 -0
  64. package/templates/packs/vue3/_ai/instructions/api.instructions.md +163 -0
  65. package/templates/packs/vue3/_ai/instructions/coding-conventions.instructions.md +160 -0
  66. package/templates/packs/vue3/_ai/instructions/composables.instructions.md +218 -0
  67. package/templates/packs/vue3/_ai/instructions/forms.instructions.md +227 -0
  68. package/templates/packs/vue3/_ai/instructions/store.instructions.md +504 -0
  69. package/templates/packs/vue3/_ai/instructions/vue.instructions.md +339 -0
  70. package/templates/packs/vue3/_ai/skills/api-integration/SKILL.md +195 -0
  71. package/templates/packs/vue3/_ai/skills/new-component/SKILL.md +133 -0
  72. package/templates/packs/vue3/_ai/skills/new-module/SKILL.md +177 -0
  73. package/templates/packs/vue3/_guidelines.md +45 -0
  74. package/templates/packs/vue3/pack.json +11 -0
@@ -0,0 +1,495 @@
1
+ import { readdir, readFile, writeFile, mkdir, access, stat } from 'node:fs/promises';
2
+ import { join, resolve, relative } from 'node:path';
3
+ import { execSync } from 'node:child_process';
4
+ import {
5
+ section, success, error, warn, info, file, fileSkip, fileMirror,
6
+ step, bold, boldGreen, boldCyan, boldYellow, cyan, green, yellow, red,
7
+ dim, gray, box,
8
+ } from './ui.mjs';
9
+
10
+ const DETECT_FILES = ['.ai/guidelines.md', 'CLAUDE.md', '.github/copilot-instructions.md'];
11
+
12
+ const TOOL_DIR_MAP = {
13
+ '_claude': 'toolClaude',
14
+ '_github': 'toolCopilot',
15
+ '_cursor': 'toolCursor',
16
+ '_gemini': 'toolGemini',
17
+ '_junie': 'toolJunie',
18
+ };
19
+
20
+ const TOOL_FILE_MAP = {
21
+ 'CLAUDE.md': 'toolClaude',
22
+ };
23
+
24
+ // ─── Pack loading ───
25
+
26
+ export async function loadPacks(packsDir) {
27
+ const packs = {};
28
+ let entries;
29
+ try {
30
+ entries = await readdir(packsDir, { withFileTypes: true });
31
+ } catch {
32
+ return packs;
33
+ }
34
+
35
+ for (const entry of entries) {
36
+ if (!entry.isDirectory()) continue;
37
+ const packDir = join(packsDir, entry.name);
38
+ try {
39
+ const raw = await readFile(join(packDir, 'pack.json'), 'utf-8');
40
+ const pack = JSON.parse(raw);
41
+ pack._dir = packDir;
42
+ pack._id = entry.name;
43
+
44
+ // Count instructions and skills
45
+ pack._instrCount = await countFiles(join(packDir, '_ai', 'instructions'));
46
+ pack._skillCount = await countDirs(join(packDir, '_ai', 'skills'));
47
+
48
+ packs[entry.name] = pack;
49
+ } catch { /* skip dirs without pack.json */ }
50
+ }
51
+ return packs;
52
+ }
53
+
54
+ export async function loadBaseSkills(skillsDir) {
55
+ try {
56
+ const entries = await readdir(skillsDir, { withFileTypes: true });
57
+ return entries
58
+ .filter(e => e.isDirectory() && e.name !== 'README.md')
59
+ .map(e => e.name)
60
+ .sort();
61
+ } catch { return []; }
62
+ }
63
+
64
+ export async function loadPackSkills(selectedPacks, allPacks) {
65
+ const skills = [];
66
+ for (const packId of (selectedPacks || [])) {
67
+ const pack = allPacks[packId];
68
+ if (!pack) continue;
69
+ const skillsDir = join(pack._dir, '_ai', 'skills');
70
+ try {
71
+ const entries = await readdir(skillsDir, { withFileTypes: true });
72
+ for (const e of entries) {
73
+ if (e.isDirectory()) {
74
+ skills.push({ skill: e.name, packId, packName: pack.name });
75
+ }
76
+ }
77
+ } catch { /* no skills */ }
78
+ }
79
+ return skills;
80
+ }
81
+
82
+ async function countFiles(dir) {
83
+ try {
84
+ const entries = await readdir(dir);
85
+ return entries.filter(e => e.endsWith('.md')).length;
86
+ } catch { return 0; }
87
+ }
88
+
89
+ async function countDirs(dir) {
90
+ try {
91
+ const entries = await readdir(dir, { withFileTypes: true });
92
+ return entries.filter(e => e.isDirectory()).length;
93
+ } catch { return 0; }
94
+ }
95
+
96
+ // ─── Detection ───
97
+
98
+ export async function detectExisting(targetDir) {
99
+ const dir = resolve(targetDir);
100
+ const found = [];
101
+ for (const f of DETECT_FILES) {
102
+ try {
103
+ await access(join(dir, f));
104
+ found.push(f);
105
+ } catch { /* not found */ }
106
+ }
107
+ return found;
108
+ }
109
+
110
+ // ─── Install ───
111
+
112
+ export async function install(packageRoot, answers) {
113
+ const targetDir = resolve(answers.targetDir);
114
+ const templatesDir = join(packageRoot, 'templates');
115
+ const mode = answers.installMode || 'fresh';
116
+
117
+ const modeLabel = mode === 'fresh' ? boldGreen('nowy projekt')
118
+ : mode === 'overwrite' ? boldYellow('nadpisz wszystko')
119
+ : mode === 'skip-existing' ? boldCyan('tylko nowe pliki')
120
+ : boldCyan('tylko MCP');
121
+
122
+ section('Instalacja');
123
+ console.log(info(`Katalog: ${bold(targetDir)}`));
124
+ console.log(info(`Tryb: ${modeLabel}`));
125
+ console.log('');
126
+
127
+ const selectedPacks = answers.packs || [];
128
+ const totalSteps = mode === 'mcp-only' ? 2 : 5 + selectedPacks.length;
129
+ let currentStep = 0;
130
+
131
+ if (mode !== 'mcp-only') {
132
+ // 1. Copy base templates
133
+ step(++currentStep, totalSteps, 'Szablony bazowe');
134
+ const baseDir = join(templatesDir, 'base');
135
+ await copyTemplateDir(baseDir, targetDir, answers, mode);
136
+
137
+ // 2. Copy selected packs
138
+ const allPacks = await loadPacks(join(templatesDir, 'packs'));
139
+ for (const packId of selectedPacks) {
140
+ const pack = allPacks[packId];
141
+ if (!pack) continue;
142
+ step(++currentStep, totalSteps, `Pakiet ${cyan(pack.name)}`);
143
+ await copyTemplateDir(pack._dir, targetDir, answers, mode);
144
+ }
145
+
146
+ // 3. Assemble guidelines from header + pack sections
147
+ step(++currentStep, totalSteps, `Skladanie ${cyan('guidelines.md')}`);
148
+ await assembleGuidelines(templatesDir, targetDir, answers, selectedPacks, allPacks, mode);
149
+
150
+ // 4. Mirror .ai/ → .github/
151
+ if (answers.toolCopilot) {
152
+ step(++currentStep, totalSteps, `Mirror ${cyan('.ai/')} → ${cyan('.github/')}`);
153
+ await mirrorAiToGithub(targetDir, 'instructions', mode);
154
+ await mirrorAiToGithub(targetDir, 'prompts', mode);
155
+ await mirrorAiToGithub(targetDir, 'skills', mode);
156
+ } else {
157
+ ++currentStep;
158
+ }
159
+ }
160
+
161
+ // 5. MCP
162
+ step(++currentStep, totalSteps, 'Konfiguracja MCP');
163
+ await generateMcpConfig(targetDir, answers);
164
+
165
+ // 6. CLI
166
+ step(++currentStep, totalSteps, 'Instalacja CLI');
167
+ await installCliTools(answers);
168
+
169
+ printNextSteps(answers, mode);
170
+
171
+ if (mode !== 'mcp-only' && answers.toolClaude) {
172
+ await bootstrapGsd(targetDir, answers);
173
+ }
174
+ }
175
+
176
+ // ─── Guidelines assembly ───
177
+
178
+ async function assembleGuidelines(templatesDir, targetDir, answers, selectedPacks, allPacks, mode) {
179
+ const headerPath = join(templatesDir, 'base', '_ai', '_guidelines_header.md');
180
+ let content;
181
+ try {
182
+ content = await readFile(headerPath, 'utf-8');
183
+ } catch {
184
+ content = `# ${answers.projectName} - Guidelines\n`;
185
+ }
186
+
187
+ // Append guidelines sections from selected packs
188
+ for (const packId of selectedPacks) {
189
+ const pack = allPacks[packId];
190
+ if (!pack) continue;
191
+ const sectionPath = join(pack._dir, '_guidelines.md');
192
+ try {
193
+ const section = await readFile(sectionPath, 'utf-8');
194
+ content += '\n' + section;
195
+ } catch { /* pack has no guidelines section */ }
196
+ }
197
+
198
+ content = replacePlaceholders(content, answers);
199
+
200
+ const destPath = join(targetDir, '.ai', 'guidelines.md');
201
+ if (mode === 'skip-existing') {
202
+ try {
203
+ await access(destPath);
204
+ console.log(fileSkip('.ai/guidelines.md'));
205
+ return;
206
+ } catch { /* proceed */ }
207
+ }
208
+
209
+ await mkdir(join(targetDir, '.ai'), { recursive: true });
210
+ await writeFile(destPath, content, 'utf-8');
211
+ console.log(file('.ai/guidelines.md'));
212
+ }
213
+
214
+ // ─── Template copying ───
215
+
216
+ async function copyTemplateDir(srcDir, destDir, answers, mode) {
217
+ let entries;
218
+ try {
219
+ entries = await readdir(srcDir, { withFileTypes: true });
220
+ } catch {
221
+ return;
222
+ }
223
+
224
+ for (const entry of entries) {
225
+ const srcPath = join(srcDir, entry.name);
226
+
227
+ // Skip pack metadata and guidelines sections
228
+ if (entry.name === 'pack.json' || entry.name === '_guidelines.md') continue;
229
+ // Skip the guidelines header (assembled separately)
230
+ if (entry.name === '_guidelines_header.md') continue;
231
+
232
+ const destName = entry.name.startsWith('_') && !entry.name.startsWith('__')
233
+ ? '.' + entry.name.slice(1)
234
+ : entry.name;
235
+ const destPath = join(destDir, destName);
236
+
237
+ if (entry.isDirectory()) {
238
+ const requiredTool = TOOL_DIR_MAP[entry.name];
239
+ if (requiredTool && !answers[requiredTool]) continue;
240
+
241
+ // Filter skills by selection
242
+ const isInsideSkillsDir = srcDir.endsWith('/skills') || srcDir.endsWith('\\skills');
243
+ if (isInsideSkillsDir && answers.selectedSkills && !answers.selectedSkills.includes(entry.name)) continue;
244
+
245
+ await mkdir(destPath, { recursive: true });
246
+ await copyTemplateDir(srcPath, destPath, answers, mode);
247
+ } else if (entry.isFile()) {
248
+ const requiredTool = TOOL_FILE_MAP[entry.name];
249
+ if (requiredTool && !answers[requiredTool]) continue;
250
+
251
+ // Skip old guidelines files (we assemble guidelines separately)
252
+ if (entry.name === 'guidelines.md' && srcPath.includes('_ai')) continue;
253
+
254
+ const rel = relative(resolve(answers.targetDir), destPath);
255
+
256
+ if (mode === 'skip-existing') {
257
+ try {
258
+ await access(destPath);
259
+ console.log(fileSkip(rel));
260
+ continue;
261
+ } catch { /* proceed */ }
262
+ }
263
+
264
+ let content = await readFile(srcPath, 'utf-8');
265
+ content = replacePlaceholders(content, answers);
266
+ await mkdir(join(destPath, '..'), { recursive: true });
267
+ await writeFile(destPath, content, 'utf-8');
268
+ console.log(file(rel));
269
+ }
270
+ }
271
+ }
272
+
273
+ // ─── Mirror ───
274
+
275
+ async function mirrorDir(srcDir, destDir, targetDir, mode) {
276
+ let entries;
277
+ try {
278
+ entries = await readdir(srcDir, { withFileTypes: true });
279
+ } catch {
280
+ return;
281
+ }
282
+
283
+ for (const entry of entries) {
284
+ const srcPath = join(srcDir, entry.name);
285
+ const destPath = join(destDir, entry.name);
286
+
287
+ if (entry.isDirectory()) {
288
+ await mkdir(destPath, { recursive: true });
289
+ await mirrorDir(srcPath, destPath, targetDir, mode);
290
+ } else if (entry.isFile()) {
291
+ const rel = relative(targetDir, destPath);
292
+ if (mode === 'skip-existing') {
293
+ try {
294
+ await access(destPath);
295
+ console.log(fileSkip(rel));
296
+ continue;
297
+ } catch { /* proceed */ }
298
+ }
299
+ const content = await readFile(srcPath, 'utf-8');
300
+ await mkdir(join(destPath, '..'), { recursive: true });
301
+ await writeFile(destPath, content, 'utf-8');
302
+ console.log(fileMirror(rel));
303
+ }
304
+ }
305
+ }
306
+
307
+ async function mirrorAiToGithub(targetDir, subdir, mode) {
308
+ await mirrorDir(
309
+ join(targetDir, '.ai', subdir),
310
+ join(targetDir, '.github', subdir),
311
+ targetDir,
312
+ mode,
313
+ );
314
+ }
315
+
316
+ // ─── Placeholders ───
317
+
318
+ function replacePlaceholders(content, a) {
319
+ return content
320
+ .replace(/\{\{PROJECT_NAME\}\}/g, a.projectName || '')
321
+ .replace(/\{\{PROJECT_DESCRIPTION\}\}/g, a.projectDescription || '')
322
+ .replace(/\{\{TECH_STACK\}\}/g, a.techStack || '')
323
+ .replace(/\{\{CI_COMMAND\}\}/g, a.ciCommand || '')
324
+ .replace(/\{\{MODULE_PATH\}\}/g, a.modulePath || '')
325
+ .replace(/\{\{COMPONENT_LIB_PATH\}\}/g, a.componentLibPath || '')
326
+ .replace(/\{\{COMPONENT_LIB_IMPORT\}\}/g, a.componentLibImport || '')
327
+ .replace(/\{\{API_BASE_URL\}\}/g, a.apiBaseUrl || '')
328
+ .replace(/\{\{TEST_COMMAND\}\}/g, a.testCommand || '')
329
+ .replace(/\{\{BUILD_COMMAND\}\}/g, a.buildCommand || '')
330
+ .replace(/\{\{LINT_COMMAND\}\}/g, a.lintCommand || '')
331
+ .replace(/\{\{DOCKER_CONTAINER\}\}/g, a.dockerContainer || '')
332
+ .replace(/\{\{JIRA_PREFIX\}\}/g, a.jiraPrefix || '')
333
+ .replace(/\{\{PLAYWRIGHT_DIR\}\}/g, a.playwrightDir || '')
334
+ .replace(/\{\{BASE_URL\}\}/g, a.baseUrl || '')
335
+ .replace(/\{\{CURRENT_BRANCH\}\}/g, '$(git branch --show-current)')
336
+ .replace(/\{\{BASE_BRANCH\}\}/g, 'develop');
337
+ }
338
+
339
+ // ─── MCP ───
340
+
341
+ async function generateMcpConfig(targetDir, answers) {
342
+ const servers = {};
343
+ if (answers.mcpJira) {
344
+ servers.jira = { command: 'npx', args: ['mcp-jira-cloud'], env: { JIRA_BASE_URL: answers.jiraUrl, JIRA_EMAIL: answers.jiraEmail, JIRA_API_TOKEN: answers.jiraToken } };
345
+ }
346
+ if (answers.mcpBitbucket) {
347
+ servers.bitbucket = { command: 'npx', args: ['-y', '@aashari/mcp-server-atlassian-bitbucket'], env: { ATLASSIAN_USER_EMAIL: answers.bitbucketEmail, ATLASSIAN_API_TOKEN: answers.bitbucketToken } };
348
+ }
349
+ if (answers.mcpFigma) {
350
+ servers.figma = { command: 'npx', args: ['@anthropic-ai/mcp-server-figma'], env: { FIGMA_ACCESS_TOKEN: answers.figmaToken } };
351
+ }
352
+ if (answers.mcpContext7) {
353
+ servers.context7 = { command: 'npx', args: ['-y', '@upstash/context7-mcp'], env: {} };
354
+ }
355
+
356
+ if (Object.keys(servers).length === 0) {
357
+ console.log(` ${dim('Brak serwerow MCP do skonfigurowania')}`);
358
+ return;
359
+ }
360
+
361
+ if (answers.toolClaude) {
362
+ const settingsPath = join(targetDir, '.claude', 'settings.local.json');
363
+ let settings = {};
364
+ try { settings = JSON.parse(await readFile(settingsPath, 'utf-8')); } catch {}
365
+ settings.mcpServers = { ...settings.mcpServers, ...servers };
366
+ await mkdir(join(targetDir, '.claude'), { recursive: true });
367
+ await writeFile(settingsPath, JSON.stringify(settings, null, 2) + '\n', 'utf-8');
368
+ console.log(file('.claude/settings.local.json'));
369
+ await registerClaudeMcp(servers, targetDir);
370
+ }
371
+
372
+ if (answers.toolCursor) {
373
+ const cursorMcpPath = join(targetDir, '.cursor', 'mcp.json');
374
+ let cursorMcp = {};
375
+ try { cursorMcp = JSON.parse(await readFile(cursorMcpPath, 'utf-8')); } catch {}
376
+ cursorMcp.mcpServers = { ...cursorMcp.mcpServers, ...servers };
377
+ await mkdir(join(targetDir, '.cursor'), { recursive: true });
378
+ await writeFile(cursorMcpPath, JSON.stringify(cursorMcp, null, 2) + '\n', 'utf-8');
379
+ console.log(file('.cursor/mcp.json'));
380
+ }
381
+ }
382
+
383
+ async function registerClaudeMcp(servers, targetDir) {
384
+ if (!hasClaudeCli()) {
385
+ console.log(warn('Claude CLI nie znaleziony — MCP zapisany w pliku'));
386
+ return;
387
+ }
388
+ for (const [name, config] of Object.entries(servers)) {
389
+ try {
390
+ try { execSync(`claude mcp remove ${name} -s project`, { stdio: 'pipe', cwd: targetDir }); } catch {}
391
+ const envFlags = Object.entries(config.env || {}).filter(([, v]) => v).map(([k, v]) => `-e ${k}=${v}`).join(' ');
392
+ const cmd = `claude mcp add -s project ${envFlags ? envFlags + ' ' : ''}${name} -- ${config.command} ${config.args.join(' ')}`;
393
+ execSync(cmd, { stdio: 'pipe', cwd: targetDir });
394
+ console.log(success(`MCP ${bold(name)} zarejestrowany`));
395
+ } catch {
396
+ console.log(warn(`MCP ${name} — rejestracja nie powiodla sie`));
397
+ }
398
+ }
399
+ }
400
+
401
+ // ─── CLI install ───
402
+
403
+ async function installCliTools(answers) {
404
+ if (!answers.installClaudeCli && !answers.installGsd) {
405
+ console.log(` ${dim('Pominieto instalacje CLI')}`);
406
+ return;
407
+ }
408
+ if (answers.installClaudeCli) {
409
+ try {
410
+ console.log(info('Instaluje Claude Code CLI...'));
411
+ execSync('npm install -g @anthropic-ai/claude-code', { stdio: 'pipe' });
412
+ console.log(success('Claude Code CLI zainstalowany'));
413
+ } catch {
414
+ console.log(error('Blad instalacji Claude Code CLI'));
415
+ console.log(` ${dim('Sprobuj recznie:')} npm install -g @anthropic-ai/claude-code`);
416
+ }
417
+ }
418
+ if (answers.installGsd) {
419
+ try {
420
+ console.log(info('Instaluje GSD (Get Shit Done)...'));
421
+ execSync('npx get-shit-done-cc@latest --version', { stdio: 'pipe' });
422
+ console.log(success('GSD zainstalowany'));
423
+ } catch {
424
+ console.log(error('Blad instalacji GSD'));
425
+ console.log(` ${dim('Sprobuj recznie:')} npx get-shit-done-cc@latest`);
426
+ }
427
+ }
428
+ }
429
+
430
+ // ─── Next steps ───
431
+
432
+ function printNextSteps(answers, mode) {
433
+ console.log('');
434
+ box([
435
+ mode === 'mcp-only' ? boldGreen(' Konfiguracja MCP zakonczona!') : boldGreen(' Instalacja zakonczona!'),
436
+ ], boldGreen);
437
+ console.log('');
438
+
439
+ if (mode === 'mcp-only') {
440
+ if (answers.toolClaude) console.log(info(`Sprawdz MCP: ${cyan('claude mcp list')}`));
441
+ return;
442
+ }
443
+
444
+ const steps = [];
445
+ let n = 1;
446
+ if (!answers.installGsd) {
447
+ steps.push(` ${boldCyan(`${n++}.`)} Zainstaluj GSD ${dim('(wymagane)')}\n ${gray('npx get-shit-done-cc@latest')}`);
448
+ }
449
+ steps.push(` ${boldCyan(`${n++}.`)} Przejrzyj ${cyan('.ai/guidelines.md')} i dostosuj do projektu`);
450
+ if (answers.toolClaude && (answers.mcpJira || answers.mcpBitbucket || answers.mcpFigma || answers.mcpContext7)) {
451
+ steps.push(` ${boldCyan(`${n++}.`)} Sprawdz MCP\n ${gray('claude mcp list')}`);
452
+ }
453
+ if (!answers.toolClaude) {
454
+ steps.push(` ${boldCyan(`${n++}.`)} Rozpocznij prace z GSD\n ${gray('/gsd:new-project')}`);
455
+ }
456
+ console.log(` ${bold('Nastepne kroki:')}\n`);
457
+ console.log(steps.join('\n\n'));
458
+ console.log('');
459
+ }
460
+
461
+ // ─── GSD bootstrap ───
462
+
463
+ function hasClaudeCli() {
464
+ try { execSync('which claude', { stdio: 'pipe' }); return true; } catch { return false; }
465
+ }
466
+
467
+ async function bootstrapGsd(targetDir, answers) {
468
+ if (!hasClaudeCli()) {
469
+ console.log(warn('Claude CLI nie znaleziony — uruchom recznie po instalacji:'));
470
+ if (answers.projectType === 'existing') console.log(` ${gray('claude /gsd:map-codebase')}`);
471
+ console.log(` ${gray('claude /gsd:new-project')}`);
472
+ console.log('');
473
+ return;
474
+ }
475
+
476
+ if (answers.projectType === 'existing') {
477
+ section('GSD: Mapowanie codebase');
478
+ try {
479
+ execSync('claude /gsd:map-codebase', { stdio: 'inherit', cwd: targetDir });
480
+ console.log(success('map-codebase zakonczone'));
481
+ } catch {
482
+ console.log(error('map-codebase nie powiodl sie'));
483
+ console.log(` ${dim('Sprobuj recznie:')} ${gray('claude /gsd:map-codebase')}`);
484
+ }
485
+ }
486
+
487
+ section('GSD: Inicjalizacja projektu');
488
+ try {
489
+ execSync('claude /gsd:new-project', { stdio: 'inherit', cwd: targetDir });
490
+ console.log(success('new-project zakonczone'));
491
+ } catch {
492
+ console.log(error('new-project nie powiodl sie'));
493
+ console.log(` ${dim('Sprobuj recznie:')} ${gray('claude /gsd:new-project')}`);
494
+ }
495
+ }