@haposoft/cafekit 0.3.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 (49) hide show
  1. package/README.md +866 -0
  2. package/bin/install.js +773 -0
  3. package/package.json +40 -0
  4. package/src/antigravity/GEMINI.md +162 -0
  5. package/src/antigravity/workflows/code.md +16 -0
  6. package/src/antigravity/workflows/docs-init.md +432 -0
  7. package/src/antigravity/workflows/docs-update.md +245 -0
  8. package/src/antigravity/workflows/review.md +15 -0
  9. package/src/antigravity/workflows/spec-design.md +220 -0
  10. package/src/antigravity/workflows/spec-init.md +78 -0
  11. package/src/antigravity/workflows/spec-requirements.md +122 -0
  12. package/src/antigravity/workflows/spec-status.md +95 -0
  13. package/src/antigravity/workflows/spec-tasks.md +156 -0
  14. package/src/antigravity/workflows/spec-validate.md +106 -0
  15. package/src/antigravity/workflows/test.md +13 -0
  16. package/src/claude/ROUTING.md +101 -0
  17. package/src/claude/agents/code-reviewer.md +131 -0
  18. package/src/claude/agents/debugger.md +137 -0
  19. package/src/claude/agents/fullstack-developer.md +95 -0
  20. package/src/claude/agents/tester.md +105 -0
  21. package/src/claude/commands/code.md +17 -0
  22. package/src/claude/commands/docs.md +609 -0
  23. package/src/claude/commands/review/codebase/parallel.md +122 -0
  24. package/src/claude/commands/review/codebase.md +49 -0
  25. package/src/claude/commands/review.md +16 -0
  26. package/src/claude/commands/spec-design.md +247 -0
  27. package/src/claude/commands/spec-init.md +118 -0
  28. package/src/claude/commands/spec-requirements.md +138 -0
  29. package/src/claude/commands/spec-status.md +98 -0
  30. package/src/claude/commands/spec-tasks.md +173 -0
  31. package/src/claude/commands/spec-validate.md +118 -0
  32. package/src/claude/commands/test.md +8 -0
  33. package/src/claude/migration-manifest.json +39 -0
  34. package/src/common/skills/specs/SKILL.md +101 -0
  35. package/src/common/skills/specs/rules/design-discovery-full.md +93 -0
  36. package/src/common/skills/specs/rules/design-discovery-light.md +49 -0
  37. package/src/common/skills/specs/rules/design-principles.md +182 -0
  38. package/src/common/skills/specs/rules/design-review.md +110 -0
  39. package/src/common/skills/specs/rules/ears-format.md +49 -0
  40. package/src/common/skills/specs/rules/gap-analysis.md +144 -0
  41. package/src/common/skills/specs/rules/steering-principles.md +90 -0
  42. package/src/common/skills/specs/rules/tasks-generation.md +131 -0
  43. package/src/common/skills/specs/rules/tasks-parallel-analysis.md +34 -0
  44. package/src/common/skills/specs/templates/design.md +276 -0
  45. package/src/common/skills/specs/templates/init.json +41 -0
  46. package/src/common/skills/specs/templates/requirements-init.md +9 -0
  47. package/src/common/skills/specs/templates/requirements.md +26 -0
  48. package/src/common/skills/specs/templates/research.md +61 -0
  49. package/src/common/skills/specs/templates/tasks.md +21 -0
package/bin/install.js ADDED
@@ -0,0 +1,773 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * CafeKit Installer
5
+ * Multi-platform installer for AI coding assistants
6
+ *
7
+ * Supported platforms:
8
+ * - claude: Claude Code (.claude/)
9
+ * - antigravity: Antigravity (.agent/)
10
+ *
11
+ * To add a new platform:
12
+ * 1. Add to PLATFORMS registry below
13
+ * 2. Add platform-specific commands in src/[platform]/commands/
14
+ * 3. Update detectPlatforms() if using different folder names
15
+ */
16
+
17
+ const fs = require('fs');
18
+ const path = require('path');
19
+ const readline = require('readline');
20
+ const packageJson = require('../package.json');
21
+
22
+ function loadClaudeMigrationManifest() {
23
+ const manifestPath = path.join(__dirname, '../src/claude/migration-manifest.json');
24
+
25
+ if (!fs.existsSync(manifestPath)) {
26
+ return null;
27
+ }
28
+
29
+ try {
30
+ return JSON.parse(fs.readFileSync(manifestPath, 'utf8'));
31
+ } catch (error) {
32
+ console.warn(`⚠ Failed to parse Claude migration manifest: ${error.message}`);
33
+ return null;
34
+ }
35
+ }
36
+
37
+ const CLAUDE_MIGRATION_MANIFEST = loadClaudeMigrationManifest();
38
+
39
+ const DEPENDENCY_TEMPLATES = {
40
+ commands: {
41
+ claude: {},
42
+ antigravity: {
43
+ 'code.md': `---
44
+ description: Implement approved work from specification tasks and then hand off to test and review.
45
+ allowed-tools: Read, Glob, Grep, Edit, Write, Bash
46
+ argument-hint: <feature-name>
47
+ ---
48
+
49
+ # /code - Implement from spec tasks
50
+
51
+ Use this workflow after /spec-tasks.
52
+
53
+ 1. Read .specs/$ARGUMENTS/tasks.md and identify the next pending task.
54
+ 2. Implement only that task following project standards.
55
+ 3. Run /test.
56
+ 4. Run /review.
57
+
58
+ Preferred flow: /spec-init -> /spec-requirements -> /spec-design -> /spec-tasks -> /code -> /test -> /review
59
+ `,
60
+ 'test.md': `---
61
+ description: Run project tests and report failures concisely.
62
+ allowed-tools: Bash, Read, Grep
63
+ argument-hint: [scope]
64
+ ---
65
+
66
+ # /test
67
+
68
+ Run the project's test command and report:
69
+ - total passed/failed
70
+ - failing test names
71
+ - root cause hints
72
+ - next fix action
73
+ `,
74
+ 'review.md': `---
75
+ description: Review recent code changes for quality, security, and maintainability.
76
+ allowed-tools: Bash, Read, Grep, Glob
77
+ argument-hint: [scope]
78
+ ---
79
+
80
+ # /review
81
+
82
+ Review recent code changes. Prioritize:
83
+ - correctness
84
+ - security
85
+ - regressions
86
+ - maintainability
87
+
88
+ Output findings by severity and include concrete fixes.
89
+ `
90
+ }
91
+ },
92
+ agents: {
93
+ claude: {},
94
+ antigravity: {
95
+ 'frontend-specialist.md': `---
96
+ name: frontend-specialist
97
+ description: Implement approved UI and interaction tasks from specifications.
98
+ ---
99
+
100
+ Implement UI tasks from approved specs with accessibility and responsive behavior.
101
+ `,
102
+ 'test-engineer.md': `---
103
+ name: test-engineer
104
+ description: Execute tests and report reliability issues.
105
+ ---
106
+
107
+ Run test suites, highlight failures, and propose precise fixes.
108
+ `,
109
+ 'code-archaeologist.md': `---
110
+ name: code-archaeologist
111
+ description: Review recent changes for regressions and hidden impacts.
112
+ ---
113
+
114
+ Inspect changed code paths, dependencies, and potential regressions.
115
+ `
116
+ }
117
+ }
118
+ };
119
+
120
+ // ═══════════════════════════════════════════════════════════
121
+ // PLATFORM REGISTRY - Add new platforms here
122
+ // ═══════════════════════════════════════════════════════════
123
+ const PLATFORMS = {
124
+ claude: {
125
+ id: 'claude',
126
+ name: 'Claude Code',
127
+ description: 'Anthropic\'s Claude Code CLI',
128
+ folder: '.claude',
129
+ commandsDir: '.claude/commands',
130
+ skillsDir: '.claude/skills',
131
+ agentsDir: '.claude/agents',
132
+ skillsRef: '.claude/skills',
133
+ commandPrefix: '/',
134
+ sourceDir: 'claude', // Maps to src/claude/
135
+ sourceSubdir: 'commands' // Source subfolder within src/claude/
136
+ },
137
+ antigravity: {
138
+ id: 'antigravity',
139
+ name: 'Antigravity',
140
+ description: 'Google\'s Antigravity Kit',
141
+ folder: '.agent',
142
+ commandsDir: '.agent/workflows', // Antigravity uses workflows/ not commands/
143
+ skillsDir: '.agent/skills',
144
+ agentsDir: '.agent/agents',
145
+ skillsRef: '.agent/skills',
146
+ commandPrefix: '/',
147
+ sourceDir: 'antigravity', // Maps to src/antigravity/
148
+ sourceSubdir: 'workflows' // Source subfolder within src/antigravity/
149
+ }
150
+ // Add new platforms here:
151
+ // cursor: {
152
+ // id: 'cursor',
153
+ // name: 'Cursor',
154
+ // description: 'Cursor IDE',
155
+ // folder: '.cursor',
156
+ // commandsDir: '.cursor/commands',
157
+ // sourceDir: 'cursor',
158
+ // sourceSubdir: 'commands'
159
+ // }
160
+ };
161
+
162
+ // ═══════════════════════════════════════════════════════════
163
+ // DETECTION
164
+ // ═══════════════════════════════════════════════════════════
165
+
166
+ function detectPlatforms() {
167
+ const detected = [];
168
+
169
+ for (const [key, config] of Object.entries(PLATFORMS)) {
170
+ if (fs.existsSync(config.folder)) {
171
+ detected.push(key);
172
+ }
173
+ }
174
+
175
+ return detected;
176
+ }
177
+
178
+ function formatPlatformList() {
179
+ return Object.entries(PLATFORMS)
180
+ .map(([key, config], index) => {
181
+ return `${index + 1}) ${config.name} (${config.folder}/)\n ${config.description}`;
182
+ })
183
+ .join('\n');
184
+ }
185
+
186
+ function getPlatformKeys() {
187
+ return Object.keys(PLATFORMS);
188
+ }
189
+
190
+ function parseInstallerArgs(argv) {
191
+ const args = {
192
+ upgrade: false
193
+ };
194
+
195
+ for (let i = 2; i < argv.length; i++) {
196
+ const arg = argv[i];
197
+
198
+ if (arg === '--upgrade' || arg === '-u' || arg === '--force' || arg === '-f') {
199
+ args.upgrade = true;
200
+ }
201
+ }
202
+
203
+ return args;
204
+ }
205
+
206
+ // ═══════════════════════════════════════════════════════════
207
+ // USER INTERACTION
208
+ // ═══════════════════════════════════════════════════════════
209
+
210
+ async function promptPlatformSelection() {
211
+ const rl = readline.createInterface({
212
+ input: process.stdin,
213
+ output: process.stdout
214
+ });
215
+
216
+ const maxChoice = Object.keys(PLATFORMS).length + 1;
217
+
218
+ console.log('╔════════════════════════════════════════════════════════╗');
219
+ console.log('║ CafeKit - Platform Selection ║');
220
+ console.log('╚════════════════════════════════════════════════════════╝');
221
+ console.log();
222
+ console.log('No existing AI editor configuration detected.\n');
223
+ console.log('Select which platform(s) to install for:\n');
224
+ console.log(formatPlatformList());
225
+ console.log(`${maxChoice}) All platforms`);
226
+ console.log('0) Cancel');
227
+ console.log();
228
+
229
+ return new Promise((resolve) => {
230
+ rl.question(`Select (0-${maxChoice}): `, (answer) => {
231
+ rl.close();
232
+ const choice = parseInt(answer.trim(), 10);
233
+
234
+ if (choice === 0 || isNaN(choice)) {
235
+ resolve([]);
236
+ } else if (choice === maxChoice) {
237
+ resolve(getPlatformKeys());
238
+ } else if (choice >= 1 && choice <= Object.keys(PLATFORMS).length) {
239
+ resolve([getPlatformKeys()[choice - 1]]);
240
+ } else {
241
+ console.log('Invalid selection. Please run the installer again.');
242
+ resolve([]);
243
+ }
244
+ });
245
+ });
246
+ }
247
+
248
+ async function promptMultiPlatformConfirm(detected) {
249
+ const rl = readline.createInterface({
250
+ input: process.stdin,
251
+ output: process.stdout
252
+ });
253
+
254
+ const platformNames = detected.map(key => PLATFORMS[key].name).join(', ');
255
+
256
+ console.log(`\nDetected existing configurations: ${platformNames}`);
257
+ console.log();
258
+
259
+ return new Promise((resolve) => {
260
+ rl.question('Install for all detected platforms? (Y/n): ', (answer) => {
261
+ rl.close();
262
+ const response = answer.trim().toLowerCase();
263
+ resolve(response === '' || response === 'y' || response === 'yes');
264
+ });
265
+ });
266
+ }
267
+
268
+ // ═══════════════════════════════════════════════════════════
269
+ // FILE OPERATIONS
270
+ // ═══════════════════════════════════════════════════════════
271
+
272
+ function copyRecursive(src, dest, options = {}) {
273
+ const exists = fs.existsSync(src);
274
+ const stats = exists && fs.statSync(src);
275
+ const isDirectory = exists && stats.isDirectory();
276
+ const shouldOverwriteManagedFiles = Boolean(options.upgrade);
277
+
278
+ if (isDirectory) {
279
+ if (!fs.existsSync(dest)) {
280
+ fs.mkdirSync(dest, { recursive: true });
281
+ }
282
+ fs.readdirSync(src).forEach(childItemName => {
283
+ copyRecursive(path.join(src, childItemName), path.join(dest, childItemName), options);
284
+ });
285
+ } else {
286
+ if (fs.existsSync(dest) && !shouldOverwriteManagedFiles) {
287
+ return;
288
+ }
289
+
290
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
291
+ fs.copyFileSync(src, dest);
292
+ }
293
+ }
294
+
295
+ function ensureDependencyFile(targetPath, content, results, label, options = {}) {
296
+ const destinationExists = fs.existsSync(targetPath);
297
+ const shouldOverwriteManagedFiles = Boolean(options.upgrade);
298
+
299
+ if (destinationExists && !shouldOverwriteManagedFiles) {
300
+ console.log(` → Dependency exists: ${label}`);
301
+ results.dependencyChecks++;
302
+ return;
303
+ }
304
+
305
+ if (!content) {
306
+ console.log(` ⚠ Missing dependency template: ${label}`);
307
+ results.dependencyChecks++;
308
+ results.missingDependencies++;
309
+ return;
310
+ }
311
+
312
+ fs.mkdirSync(path.dirname(targetPath), { recursive: true });
313
+ fs.writeFileSync(targetPath, content, 'utf8');
314
+
315
+ if (destinationExists && shouldOverwriteManagedFiles) {
316
+ console.log(` ↻ Dependency updated: ${label}`);
317
+ results.updated++;
318
+ } else {
319
+ console.log(` ✓ Dependency installed: ${label}`);
320
+ }
321
+
322
+ results.dependencyChecks++;
323
+ results.installedDependencies++;
324
+ }
325
+
326
+ function ensureWorkflowDependencies(platformKey, platform, results, options = {}) {
327
+ const commandTemplates = DEPENDENCY_TEMPLATES.commands[platformKey] || {};
328
+ Object.entries(commandTemplates).forEach(([fileName, content]) => {
329
+ const targetPath = path.join(platform.commandsDir, fileName);
330
+ ensureDependencyFile(targetPath, content, results, path.join(platform.commandsDir, fileName), options);
331
+ });
332
+
333
+ const agentTemplates = DEPENDENCY_TEMPLATES.agents[platformKey] || {};
334
+ Object.entries(agentTemplates).forEach(([fileName, content]) => {
335
+ const targetPath = path.join(platform.agentsDir, fileName);
336
+ ensureDependencyFile(targetPath, content, results, path.join(platform.agentsDir, fileName), options);
337
+ });
338
+ }
339
+
340
+ function getPlatformSpecFiles(platformKey) {
341
+ if (platformKey === 'claude') {
342
+ const manifestCommands = CLAUDE_MIGRATION_MANIFEST?.commands?.core;
343
+ if (Array.isArray(manifestCommands) && manifestCommands.length > 0) {
344
+ return manifestCommands;
345
+ }
346
+
347
+ return [
348
+ 'spec-init.md',
349
+ 'spec-requirements.md',
350
+ 'spec-design.md',
351
+ 'spec-validate.md',
352
+ 'spec-tasks.md',
353
+ 'spec-status.md',
354
+ 'code.md',
355
+ 'test.md',
356
+ 'review.md',
357
+ 'review/codebase.md',
358
+ 'review/codebase/parallel.md',
359
+ 'docs.md'
360
+ ];
361
+ }
362
+
363
+ if (platformKey === 'antigravity') {
364
+ return [
365
+ 'spec-init.md',
366
+ 'spec-requirements.md',
367
+ 'spec-design.md',
368
+ 'spec-validate.md',
369
+ 'spec-tasks.md',
370
+ 'spec-status.md',
371
+ 'docs-init.md',
372
+ 'docs-update.md'
373
+ ];
374
+ }
375
+
376
+ return [];
377
+ }
378
+
379
+ function copyPlatformFiles(platformKey, results, options = {}) {
380
+ const platform = PLATFORMS[platformKey];
381
+ const shouldOverwriteManagedFiles = Boolean(options.upgrade);
382
+ const recordWriteResult = (didOverwrite) => {
383
+ if (didOverwrite) {
384
+ results.updated++;
385
+ } else {
386
+ results.copied++;
387
+ }
388
+ };
389
+
390
+ // Source directories - support different subfolder names per platform
391
+ const sourceSubdir = platform.sourceSubdir || 'commands';
392
+ const commandsSourceDir = path.join(__dirname, `../src/${platform.sourceDir}/${sourceSubdir}`);
393
+ const skillsSourceDir = path.join(__dirname, '../src/common/skills');
394
+ const agentsSourceDir = path.join(__dirname, `../src/${platform.sourceDir}/agents`);
395
+
396
+ // Create directories
397
+ if (!fs.existsSync(platform.commandsDir)) {
398
+ fs.mkdirSync(platform.commandsDir, { recursive: true });
399
+ }
400
+ if (!fs.existsSync(platform.skillsDir)) {
401
+ fs.mkdirSync(platform.skillsDir, { recursive: true });
402
+ }
403
+ if (!fs.existsSync(platform.agentsDir)) {
404
+ fs.mkdirSync(platform.agentsDir, { recursive: true });
405
+ }
406
+
407
+ // Copy skills (shared across all platforms)
408
+ if (fs.existsSync(skillsSourceDir)) {
409
+ const specSkillSource = path.join(skillsSourceDir, 'specs');
410
+ const specSkillDest = path.join(platform.skillsDir, 'specs');
411
+
412
+ if (fs.existsSync(specSkillSource)) {
413
+ const skillExisted = fs.existsSync(specSkillDest);
414
+ copyRecursive(specSkillSource, specSkillDest, options);
415
+ results.installedSkills++;
416
+
417
+ if (shouldOverwriteManagedFiles && skillExisted) {
418
+ console.log(` ↻ Skill updated: specs`);
419
+ results.updated++;
420
+ } else {
421
+ console.log(` ✓ Skill installed: specs`);
422
+ }
423
+ }
424
+
425
+ // Keep templates in sync for claude command runtime copies under .claude/skills/specs
426
+ if (platformKey === 'claude') {
427
+ const specTemplates = [
428
+ 'init.json',
429
+ 'requirements-init.md',
430
+ 'requirements.md',
431
+ 'design.md',
432
+ 'research.md',
433
+ 'tasks.md'
434
+ ];
435
+
436
+ specTemplates.forEach((fileName) => {
437
+ const source = path.join(specSkillSource, 'templates', fileName);
438
+ const dest = path.join(platform.skillsDir, 'specs', 'templates', fileName);
439
+
440
+ if (!fs.existsSync(source)) {
441
+ console.log(` ⚠ Missing dependency template: ${path.join(platform.skillsDir, 'specs', 'templates', fileName)}`);
442
+ results.missingDependencies++;
443
+ results.dependencyChecks++;
444
+ return;
445
+ }
446
+
447
+ const destinationExists = fs.existsSync(dest);
448
+
449
+ if (destinationExists && !shouldOverwriteManagedFiles) {
450
+ console.log(` → Dependency exists: ${path.join(platform.skillsDir, 'specs', 'templates', fileName)}`);
451
+ results.dependencyChecks++;
452
+ return;
453
+ }
454
+
455
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
456
+ fs.copyFileSync(source, dest);
457
+ results.dependencyChecks++;
458
+ results.installedDependencies++;
459
+
460
+ if (destinationExists && shouldOverwriteManagedFiles) {
461
+ console.log(` ↻ Dependency updated: ${path.join(platform.skillsDir, 'specs', 'templates', fileName)}`);
462
+ results.updated++;
463
+ } else {
464
+ console.log(` ✓ Dependency installed: ${path.join(platform.skillsDir, 'specs', 'templates', fileName)}`);
465
+ }
466
+ });
467
+ }
468
+
469
+ if (platformKey === 'claude') {
470
+ const requiredSkills = CLAUDE_MIGRATION_MANIFEST?.skills?.required || [];
471
+
472
+ requiredSkills
473
+ .filter((skillName) => skillName !== 'specs')
474
+ .forEach((skillName) => {
475
+ const skillSource = path.join(skillsSourceDir, skillName);
476
+ const skillDest = path.join(platform.skillsDir, skillName);
477
+
478
+ if (fs.existsSync(skillSource)) {
479
+ const skillExisted = fs.existsSync(skillDest);
480
+ copyRecursive(skillSource, skillDest, options);
481
+ results.installedSkills++;
482
+
483
+ if (shouldOverwriteManagedFiles && skillExisted) {
484
+ console.log(` ↻ Skill updated: ${skillName}`);
485
+ results.updated++;
486
+ } else {
487
+ console.log(` ✓ Skill installed: ${skillName}`);
488
+ }
489
+ } else {
490
+ results.missingDependencies++;
491
+ console.log(` ⚠ Missing dependency template: ${path.join(platform.skillsDir, skillName)}`);
492
+ }
493
+ });
494
+ }
495
+ }
496
+
497
+ // Copy agents
498
+ if (platformKey === 'claude') {
499
+ if (fs.existsSync(agentsSourceDir)) {
500
+ const requiredAgents = CLAUDE_MIGRATION_MANIFEST?.agents?.required || [
501
+ 'tester.md',
502
+ 'code-reviewer.md',
503
+ 'fullstack-developer.md',
504
+ 'debugger.md'
505
+ ];
506
+
507
+ requiredAgents.forEach((fileName) => {
508
+ const source = path.join(agentsSourceDir, fileName);
509
+ const dest = path.join(platform.agentsDir, fileName);
510
+
511
+ if (!fs.existsSync(source)) {
512
+ console.log(` ⚠ Missing dependency template: ${path.join(platform.agentsDir, fileName)}`);
513
+ results.missingDependencies++;
514
+ results.dependencyChecks++;
515
+ return;
516
+ }
517
+
518
+ const destinationExists = fs.existsSync(dest);
519
+
520
+ if (destinationExists && !shouldOverwriteManagedFiles) {
521
+ console.log(` → Dependency exists: ${path.join(platform.agentsDir, fileName)}`);
522
+ results.dependencyChecks++;
523
+ return;
524
+ }
525
+
526
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
527
+ fs.copyFileSync(source, dest);
528
+ results.dependencyChecks++;
529
+ results.installedDependencies++;
530
+
531
+ if (destinationExists && shouldOverwriteManagedFiles) {
532
+ console.log(` ↻ Dependency updated: ${path.join(platform.agentsDir, fileName)}`);
533
+ results.updated++;
534
+ } else {
535
+ console.log(` ✓ Dependency installed: ${path.join(platform.agentsDir, fileName)}`);
536
+ }
537
+ });
538
+ } else {
539
+ results.missingDependencies++;
540
+ console.log(` ⚠ Missing dependency template: ${platform.agentsDir}`);
541
+ }
542
+
543
+ }
544
+
545
+ ensureWorkflowDependencies(platformKey, platform, results, options);
546
+
547
+ // Copy commands/workflows
548
+ const specFiles = getPlatformSpecFiles(platformKey);
549
+
550
+ specFiles.forEach(file => {
551
+ const source = path.join(commandsSourceDir, file);
552
+ const dest = path.join(platform.commandsDir, file);
553
+ const destinationExists = fs.existsSync(dest);
554
+
555
+ if (!fs.existsSync(source)) {
556
+ console.error(` ✗ Error: Source file not found: ${file}`);
557
+ results.errors++;
558
+ return;
559
+ }
560
+
561
+ if (destinationExists && !shouldOverwriteManagedFiles) {
562
+ console.log(` → Skipped: ${file} (already exists)`);
563
+ results.skipped++;
564
+ return;
565
+ }
566
+
567
+ fs.mkdirSync(path.dirname(dest), { recursive: true });
568
+ let content = fs.readFileSync(source, 'utf8');
569
+ content = content.replace(/\{\{SKILLS_DIR\}\}/g, platform.skillsRef);
570
+ fs.writeFileSync(dest, content);
571
+
572
+ if (destinationExists && shouldOverwriteManagedFiles) {
573
+ console.log(` ↻ Updated: ${file}`);
574
+ recordWriteResult(true);
575
+ } else {
576
+ console.log(` ✓ Copied: ${file}`);
577
+ recordWriteResult(false);
578
+ }
579
+ });
580
+ }
581
+
582
+ // Copy ROUTING.md to .claude/
583
+ function copyRoutingFile(platformKey, results, options = {}) {
584
+ const platform = PLATFORMS[platformKey];
585
+ const shouldOverwriteManagedFiles = Boolean(options.upgrade);
586
+ const source = path.join(__dirname, `../src/${platform.sourceDir}/ROUTING.md`);
587
+ const dest = path.join(platform.folder, 'ROUTING.md');
588
+
589
+ if (fs.existsSync(source)) {
590
+ const destinationExists = fs.existsSync(dest);
591
+
592
+ if (destinationExists && !shouldOverwriteManagedFiles) {
593
+ console.log(` → Skipped: ROUTING.md (already exists)`);
594
+ results.skipped++;
595
+ return;
596
+ }
597
+
598
+ let content = fs.readFileSync(source, 'utf8');
599
+ content = content.replace(/\{\{SKILLS_DIR\}\}/g, platform.skillsRef);
600
+ fs.writeFileSync(dest, content);
601
+
602
+ if (destinationExists && shouldOverwriteManagedFiles) {
603
+ console.log(` ↻ Updated: ROUTING.md`);
604
+ results.updated++;
605
+ } else {
606
+ console.log(` ✓ Copied: ROUTING.md`);
607
+ results.copied++;
608
+ }
609
+ }
610
+ }
611
+
612
+ // Copy GEMINI.md rule file to .agent/rules/ for Antigravity
613
+ function copyGeminiFile(platformKey, results, options = {}) {
614
+ const platform = PLATFORMS[platformKey];
615
+ const shouldOverwriteManagedFiles = Boolean(options.upgrade);
616
+ const rulesDir = path.join(platform.folder, 'rules');
617
+ const source = path.join(__dirname, `../src/${platform.sourceDir}/GEMINI.md`);
618
+ const dest = path.join(rulesDir, 'GEMINI.md');
619
+
620
+ if (fs.existsSync(source)) {
621
+ // Create rules directory if not exists
622
+ if (!fs.existsSync(rulesDir)) {
623
+ fs.mkdirSync(rulesDir, { recursive: true });
624
+ }
625
+
626
+ const destinationExists = fs.existsSync(dest);
627
+
628
+ if (destinationExists && !shouldOverwriteManagedFiles) {
629
+ console.log(` → Skipped: rules/GEMINI.md (already exists)`);
630
+ results.skipped++;
631
+ return;
632
+ }
633
+
634
+ fs.copyFileSync(source, dest);
635
+
636
+ if (destinationExists && shouldOverwriteManagedFiles) {
637
+ console.log(` ↻ Updated: rules/GEMINI.md`);
638
+ results.updated++;
639
+ } else {
640
+ console.log(` ✓ Copied: rules/GEMINI.md`);
641
+ results.copied++;
642
+ }
643
+ }
644
+ }
645
+
646
+ // ═══════════════════════════════════════════════════════════
647
+ // MAIN
648
+ // ═══════════════════════════════════════════════════════════
649
+
650
+ async function main() {
651
+ const installerOptions = parseInstallerArgs(process.argv);
652
+
653
+ console.log();
654
+ console.log('╔════════════════════════════════════════════════════════╗');
655
+ console.log(`║ CafeKit Installer v${String(packageJson.version).padEnd(5, ' ')} ║`);
656
+ console.log('║ Multi-platform SDD Workflow ║');
657
+ console.log('╚════════════════════════════════════════════════════════╝');
658
+ console.log();
659
+
660
+ let platforms = detectPlatforms();
661
+
662
+ if (platforms.length === 0) {
663
+ // No platforms detected - prompt user
664
+ platforms = await promptPlatformSelection();
665
+
666
+ if (platforms.length === 0) {
667
+ console.log('\nInstallation cancelled.');
668
+ process.exit(0);
669
+ }
670
+ } else if (platforms.length > 1) {
671
+ // Multiple platforms detected - confirm with user
672
+ const proceed = await promptMultiPlatformConfirm(platforms);
673
+ if (!proceed) {
674
+ platforms = await promptPlatformSelection();
675
+ if (platforms.length === 0) {
676
+ console.log('\nInstallation cancelled.');
677
+ process.exit(0);
678
+ }
679
+ }
680
+ }
681
+
682
+ // Show detected/selected platforms
683
+ const platformNames = platforms.map(key => PLATFORMS[key].name).join(', ');
684
+ console.log(`\nInstalling for: ${platformNames}`);
685
+
686
+ if (installerOptions.upgrade) {
687
+ console.log('Mode: upgrade (overwrite managed files)\n');
688
+ } else {
689
+ console.log('Mode: install (skip existing files)\n');
690
+ }
691
+
692
+ const results = {
693
+ copied: 0,
694
+ updated: 0,
695
+ skipped: 0,
696
+ installedSkills: 0,
697
+ dependencyChecks: 0,
698
+ installedDependencies: 0,
699
+ missingDependencies: 0,
700
+ errors: 0,
701
+ targets: []
702
+ };
703
+
704
+ try {
705
+ for (const platformKey of platforms) {
706
+ const platform = PLATFORMS[platformKey];
707
+ console.log(`${platform.name} (${platform.folder}/)`);
708
+ console.log('-'.repeat(40));
709
+
710
+ copyPlatformFiles(platformKey, results, installerOptions);
711
+
712
+ // Copy ROUTING.md for Claude Code platform
713
+ if (platformKey === 'claude') {
714
+ copyRoutingFile(platformKey, results, installerOptions);
715
+ }
716
+
717
+ // Copy GEMINI.md for Antigravity platform
718
+ if (platformKey === 'antigravity') {
719
+ copyGeminiFile(platformKey, results, installerOptions);
720
+ }
721
+
722
+ results.targets.push(platform.commandsDir);
723
+ console.log();
724
+ }
725
+
726
+ // Note: CLAUDE.md and docs/ are generated via /docs init command
727
+
728
+ // Summary
729
+ console.log('╔════════════════════════════════════════════════════════╗');
730
+ console.log('║ Installation Complete! ║');
731
+ console.log('╚════════════════════════════════════════════════════════╝');
732
+ console.log();
733
+ console.log(` Copied Files: ${results.copied}`);
734
+ console.log(` Updated Files: ${results.updated}`);
735
+ console.log(` Skipped Files: ${results.skipped}`);
736
+ console.log(` Installed Skills: ${results.installedSkills > 0 ? 'Yes ✓' : 'No'}`);
737
+ console.log(` Dependency Checks: ${results.dependencyChecks}`);
738
+ console.log(` Installed Deps: ${results.installedDependencies}`);
739
+ console.log(` Missing Deps: ${results.missingDependencies}`);
740
+ console.log(` Target Directories: ${results.targets.join(', ')}`);
741
+ if (results.errors > 0) {
742
+ console.log(` Errors: ${results.errors} ⚠`);
743
+ }
744
+ console.log();
745
+ console.log('Next steps:');
746
+ console.log(' 1. Start your AI editor');
747
+
748
+ // Show platform-specific commands
749
+ for (const platformKey of platforms) {
750
+ const platform = PLATFORMS[platformKey];
751
+ console.log(`\n For ${platform.name}:`);
752
+ console.log(` Run: ${platform.commandPrefix}spec-init <feature-name>`);
753
+ }
754
+
755
+ console.log('\n 2. Follow the workflow: requirements - design - tasks - code - test - review');
756
+ if (!installerOptions.upgrade) {
757
+ console.log(' 3. To refresh managed templates later, run installer with --upgrade');
758
+ }
759
+ console.log();
760
+ console.log('Documentation: https://github.com/haposoft/cafekit');
761
+ if (results.missingDependencies > 0) {
762
+ console.log('Note: some dependency templates could not be installed. Please check command/agent directories.');
763
+ }
764
+ console.log();
765
+
766
+ process.exit(results.errors > 0 ? 1 : 0);
767
+ } catch (error) {
768
+ console.error(`\n✗ Installation failed: ${error.message}`);
769
+ process.exit(1);
770
+ }
771
+ }
772
+
773
+ main();