specweave 0.24.1 → 0.24.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 (173) hide show
  1. package/CLAUDE.md +106 -0
  2. package/README.md +34 -0
  3. package/dist/src/cli/commands/init.d.ts.map +1 -1
  4. package/dist/src/cli/commands/init.js +80 -41
  5. package/dist/src/cli/commands/init.js.map +1 -1
  6. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts +5 -2
  7. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  8. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +72 -6
  9. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  10. package/dist/src/cli/helpers/issue-tracker/github.d.ts +2 -1
  11. package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
  12. package/dist/src/cli/helpers/issue-tracker/github.js +4 -3
  13. package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
  14. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  15. package/dist/src/cli/helpers/issue-tracker/index.js +26 -9
  16. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  17. package/dist/src/cli/helpers/issue-tracker/types.d.ts +2 -1
  18. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  19. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  20. package/dist/src/config/types.d.ts +24 -24
  21. package/dist/src/core/config/types.d.ts +25 -0
  22. package/dist/src/core/config/types.d.ts.map +1 -1
  23. package/dist/src/core/config/types.js +6 -0
  24. package/dist/src/core/config/types.js.map +1 -1
  25. package/dist/src/core/repo-structure/git-error-handler.d.ts +37 -0
  26. package/dist/src/core/repo-structure/git-error-handler.d.ts.map +1 -0
  27. package/dist/src/core/repo-structure/git-error-handler.js +214 -0
  28. package/dist/src/core/repo-structure/git-error-handler.js.map +1 -0
  29. package/dist/src/core/repo-structure/git-provider.d.ts +183 -0
  30. package/dist/src/core/repo-structure/git-provider.d.ts.map +1 -0
  31. package/dist/src/core/repo-structure/git-provider.js +57 -0
  32. package/dist/src/core/repo-structure/git-provider.js.map +1 -0
  33. package/dist/src/core/repo-structure/github-validator.d.ts +1 -0
  34. package/dist/src/core/repo-structure/github-validator.d.ts.map +1 -1
  35. package/dist/src/core/repo-structure/github-validator.js +35 -9
  36. package/dist/src/core/repo-structure/github-validator.js.map +1 -1
  37. package/dist/src/core/repo-structure/platform-registry.d.ts +114 -0
  38. package/dist/src/core/repo-structure/platform-registry.d.ts.map +1 -0
  39. package/dist/src/core/repo-structure/platform-registry.js +195 -0
  40. package/dist/src/core/repo-structure/platform-registry.js.map +1 -0
  41. package/dist/src/core/repo-structure/prompt-consolidator.d.ts +30 -0
  42. package/dist/src/core/repo-structure/prompt-consolidator.d.ts.map +1 -1
  43. package/dist/src/core/repo-structure/prompt-consolidator.js +69 -0
  44. package/dist/src/core/repo-structure/prompt-consolidator.js.map +1 -1
  45. package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts +54 -0
  46. package/dist/src/core/repo-structure/providers/bitbucket-provider.d.ts.map +1 -0
  47. package/dist/src/core/repo-structure/providers/bitbucket-provider.js +104 -0
  48. package/dist/src/core/repo-structure/providers/bitbucket-provider.js.map +1 -0
  49. package/dist/src/core/repo-structure/providers/github-provider.d.ts +53 -0
  50. package/dist/src/core/repo-structure/providers/github-provider.d.ts.map +1 -0
  51. package/dist/src/core/repo-structure/providers/github-provider.js +239 -0
  52. package/dist/src/core/repo-structure/providers/github-provider.js.map +1 -0
  53. package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts +50 -0
  54. package/dist/src/core/repo-structure/providers/gitlab-provider.d.ts.map +1 -0
  55. package/dist/src/core/repo-structure/providers/gitlab-provider.js +97 -0
  56. package/dist/src/core/repo-structure/providers/gitlab-provider.js.map +1 -0
  57. package/dist/src/core/repo-structure/providers/index.d.ts +33 -0
  58. package/dist/src/core/repo-structure/providers/index.d.ts.map +1 -0
  59. package/dist/src/core/repo-structure/providers/index.js +60 -0
  60. package/dist/src/core/repo-structure/providers/index.js.map +1 -0
  61. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +33 -0
  62. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -0
  63. package/dist/src/core/repo-structure/repo-bulk-discovery.js +275 -0
  64. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -0
  65. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +18 -2
  66. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  67. package/dist/src/core/repo-structure/repo-structure-manager.js +303 -85
  68. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  69. package/dist/src/core/repo-structure/url-generator.d.ts +80 -0
  70. package/dist/src/core/repo-structure/url-generator.d.ts.map +1 -0
  71. package/dist/src/core/repo-structure/url-generator.js +110 -0
  72. package/dist/src/core/repo-structure/url-generator.js.map +1 -0
  73. package/dist/src/init/architecture/types.d.ts +6 -6
  74. package/dist/src/utils/plugin-validator.d.ts.map +1 -1
  75. package/dist/src/utils/plugin-validator.js +15 -14
  76. package/dist/src/utils/plugin-validator.js.map +1 -1
  77. package/package.json +4 -4
  78. package/plugins/specweave/.claude-plugin/plugin.json +4 -4
  79. package/plugins/specweave/agents/pm/AGENT.md +2 -0
  80. package/plugins/specweave/commands/specweave-do.md +0 -47
  81. package/plugins/specweave/commands/specweave-increment.md +0 -82
  82. package/plugins/specweave/commands/specweave-next.md +0 -47
  83. package/plugins/specweave/hooks/post-task-completion.sh +67 -6
  84. package/plugins/specweave/hooks/pre-edit-spec.sh +11 -0
  85. package/plugins/specweave/hooks/pre-task-completion.sh +69 -2
  86. package/plugins/specweave/hooks/pre-write-spec.sh +11 -0
  87. package/plugins/specweave/skills/increment-planner/SKILL.md +124 -4
  88. package/plugins/specweave-ado/lib/ado-multi-project-sync.js +0 -1
  89. package/plugins/specweave-jira/lib/enhanced-jira-sync.js +3 -3
  90. package/plugins/specweave/agents/pm/AGENT.md.bak +0 -1893
  91. package/plugins/specweave/hooks/docs-changed.sh.backup +0 -79
  92. package/plugins/specweave/hooks/human-input-required.sh.backup +0 -75
  93. package/plugins/specweave/hooks/lib/migrate-increment-work.sh.bak +0 -245
  94. package/plugins/specweave/hooks/lib/sync-spec-content.sh.bak +0 -149
  95. package/plugins/specweave/hooks/lib/validate-spec-status.sh.bak +0 -163
  96. package/plugins/specweave/hooks/post-first-increment.sh.backup +0 -61
  97. package/plugins/specweave/hooks/post-first-increment.sh.bak +0 -61
  98. package/plugins/specweave/hooks/post-increment-change.sh.backup +0 -98
  99. package/plugins/specweave/hooks/post-increment-completion.sh.backup +0 -231
  100. package/plugins/specweave/hooks/post-increment-planning.sh.backup +0 -1048
  101. package/plugins/specweave/hooks/post-increment-status-change.sh.backup +0 -147
  102. package/plugins/specweave/hooks/post-spec-update.sh.backup +0 -158
  103. package/plugins/specweave/hooks/post-spec-update.sh.bak +0 -158
  104. package/plugins/specweave/hooks/post-user-story-complete.sh.backup +0 -179
  105. package/plugins/specweave/hooks/post-user-story-complete.sh.bak +0 -179
  106. package/plugins/specweave/hooks/pre-command-deduplication.sh.backup +0 -83
  107. package/plugins/specweave/hooks/pre-command-deduplication.sh.bak +0 -83
  108. package/plugins/specweave/hooks/pre-implementation.sh.backup +0 -67
  109. package/plugins/specweave/hooks/pre-task-completion.sh.backup +0 -194
  110. package/plugins/specweave/hooks/pre-tool-use.sh.backup +0 -133
  111. package/plugins/specweave/hooks/user-prompt-submit.sh.backup +0 -386
  112. package/plugins/specweave/hooks/user-prompt-submit.sh.bak +0 -386
  113. package/plugins/specweave/lib/hooks/auto-transition.js.bak +0 -50
  114. package/plugins/specweave/lib/hooks/auto-transition.ts.bak +0 -84
  115. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.js.bak +0 -0
  116. package/plugins/specweave/lib/hooks/git-diff-analyzer.d.ts.bak +0 -89
  117. package/plugins/specweave/lib/hooks/git-diff-analyzer.js.bak +0 -142
  118. package/plugins/specweave/lib/hooks/git-diff-analyzer.ts.bak +0 -269
  119. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.js.bak +0 -0
  120. package/plugins/specweave/lib/hooks/invoke-translator-skill.d.ts.bak +0 -60
  121. package/plugins/specweave/lib/hooks/invoke-translator-skill.js.bak +0 -155
  122. package/plugins/specweave/lib/hooks/invoke-translator-skill.ts.bak +0 -264
  123. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.js.bak +0 -0
  124. package/plugins/specweave/lib/hooks/prepare-reflection-context.d.ts.bak +0 -42
  125. package/plugins/specweave/lib/hooks/prepare-reflection-context.js.bak +0 -110
  126. package/plugins/specweave/lib/hooks/prepare-reflection-context.ts.bak +0 -178
  127. package/plugins/specweave/lib/hooks/reflection-config-loader.d.js.bak +0 -0
  128. package/plugins/specweave/lib/hooks/reflection-config-loader.d.ts.bak +0 -45
  129. package/plugins/specweave/lib/hooks/reflection-config-loader.js.bak +0 -92
  130. package/plugins/specweave/lib/hooks/reflection-config-loader.ts.bak +0 -156
  131. package/plugins/specweave/lib/hooks/reflection-parser.d.js.bak +0 -0
  132. package/plugins/specweave/lib/hooks/reflection-parser.d.ts.bak +0 -33
  133. package/plugins/specweave/lib/hooks/reflection-parser.js.bak +0 -301
  134. package/plugins/specweave/lib/hooks/reflection-parser.ts.bak +0 -484
  135. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.js.bak +0 -0
  136. package/plugins/specweave/lib/hooks/reflection-prompt-builder.d.ts.bak +0 -56
  137. package/plugins/specweave/lib/hooks/reflection-prompt-builder.js.bak +0 -182
  138. package/plugins/specweave/lib/hooks/reflection-prompt-builder.ts.bak +0 -306
  139. package/plugins/specweave/lib/hooks/reflection-storage.d.js.bak +0 -0
  140. package/plugins/specweave/lib/hooks/reflection-storage.d.ts.bak +0 -64
  141. package/plugins/specweave/lib/hooks/reflection-storage.js.bak +0 -231
  142. package/plugins/specweave/lib/hooks/reflection-storage.ts.bak +0 -369
  143. package/plugins/specweave/lib/hooks/run-self-reflection.d.js.bak +0 -0
  144. package/plugins/specweave/lib/hooks/run-self-reflection.d.ts.bak +0 -43
  145. package/plugins/specweave/lib/hooks/run-self-reflection.js.bak +0 -132
  146. package/plugins/specweave/lib/hooks/run-self-reflection.ts.bak +0 -258
  147. package/plugins/specweave/lib/hooks/sync-cache.js.bak +0 -294
  148. package/plugins/specweave/lib/hooks/sync-living-docs.d.js.bak +0 -1
  149. package/plugins/specweave/lib/hooks/sync-living-docs.d.ts.bak +0 -27
  150. package/plugins/specweave/lib/hooks/sync-living-docs.js.bak +0 -339
  151. package/plugins/specweave/lib/hooks/sync-us-tasks.js.bak +0 -476
  152. package/plugins/specweave/lib/hooks/translate-file.d.js.bak +0 -0
  153. package/plugins/specweave/lib/hooks/translate-file.d.ts.bak +0 -59
  154. package/plugins/specweave/lib/hooks/translate-file.js.bak +0 -289
  155. package/plugins/specweave/lib/hooks/translate-file.ts.bak +0 -428
  156. package/plugins/specweave/lib/hooks/translate-living-docs.d.js.bak +0 -0
  157. package/plugins/specweave/lib/hooks/translate-living-docs.d.ts.bak +0 -13
  158. package/plugins/specweave/lib/hooks/translate-living-docs.js.bak +0 -119
  159. package/plugins/specweave/lib/hooks/translate-living-docs.ts.bak +0 -224
  160. package/plugins/specweave/lib/hooks/update-ac-status.js.bak +0 -51
  161. package/plugins/specweave/lib/hooks/update-ac-status.ts.bak +0 -103
  162. package/plugins/specweave/lib/hooks/update-tasks-md.d.js.bak +0 -1
  163. package/plugins/specweave/lib/hooks/update-tasks-md.d.ts.bak +0 -29
  164. package/plugins/specweave/lib/hooks/update-tasks-md.js.bak +0 -296
  165. package/plugins/specweave/lib/hooks/update-tasks-md.ts.bak +0 -489
  166. package/plugins/specweave-ado/hooks/post-living-docs-update.sh.backup +0 -353
  167. package/plugins/specweave-ado/hooks/post-task-completion.sh.backup +0 -172
  168. package/plugins/specweave-ado/lib/enhanced-ado-sync.js +0 -170
  169. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +0 -228
  170. package/plugins/specweave-github/hooks/post-task-completion.sh.backup +0 -258
  171. package/plugins/specweave-jira/hooks/post-task-completion.sh.backup +0 -172
  172. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +0 -444
  173. package/plugins/specweave-release/hooks/post-task-completion.sh.backup +0 -110
@@ -23,16 +23,22 @@ import { execSync } from 'child_process';
23
23
  import { execFileNoThrowSync } from '../../utils/execFileNoThrow.js';
24
24
  import { generateRepoIdSmart, ensureUniqueId, validateRepoId, suggestRepoName } from './repo-id-generator.js';
25
25
  import { SetupStateManager } from './setup-state-manager.js';
26
- import { validateRepository, validateOwner } from './github-validator.js';
27
26
  import { generateEnvFile } from '../../utils/env-file-generator.js';
28
27
  import { generateSetupSummary } from './setup-summary.js';
29
- import { getArchitecturePrompt, getParentRepoBenefits, getVisibilityPrompt } from './prompt-consolidator.js';
28
+ import { getArchitecturePrompt, getParentRepoBenefits, getVisibilityPrompt, getUrlTypePrompt } from './prompt-consolidator.js';
30
29
  import { detectRepositoryHints } from './folder-detector.js';
30
+ import { discoverRepositories } from './repo-bulk-discovery.js';
31
+ import { Octokit } from '@octokit/rest';
32
+ import { initializeProviders } from './providers/index.js';
33
+ import { getPlatformRegistry } from './platform-registry.js';
34
+ import { getPlatformSelectionPrompt } from './prompt-consolidator.js';
31
35
  export class RepoStructureManager {
32
36
  constructor(projectPath, githubToken) {
33
37
  this.projectPath = projectPath;
34
38
  this.githubToken = githubToken;
35
39
  this.stateManager = new SetupStateManager(projectPath);
40
+ // Initialize Git providers on instantiation
41
+ initializeProviders();
36
42
  }
37
43
  /**
38
44
  * Prompt user for repository structure decisions
@@ -73,14 +79,53 @@ export class RepoStructureManager {
73
79
  })),
74
80
  default: 'single'
75
81
  }]);
82
+ // Step 2: Ask about Git hosting platform
83
+ const registry = getPlatformRegistry();
84
+ const platformOptions = registry.getPlatformOptions(true); // Include unsupported platforms
85
+ const platformPromptData = getPlatformSelectionPrompt();
86
+ console.log(chalk.cyan('\n' + platformPromptData.message));
87
+ const { platform } = await inquirer.prompt([{
88
+ type: 'list',
89
+ name: 'platform',
90
+ message: platformPromptData.question,
91
+ choices: platformOptions.map(opt => ({
92
+ name: opt.disabled
93
+ ? `${opt.name}\n${chalk.gray(opt.description)}\n${chalk.yellow('⚠️ ' + opt.disabled)}`
94
+ : `${opt.name}\n${chalk.gray(opt.description)}`,
95
+ value: opt.value,
96
+ short: opt.name,
97
+ disabled: opt.disabled ? opt.disabled : false
98
+ })),
99
+ default: 'github'
100
+ }]);
101
+ // Get provider instance
102
+ const provider = registry.getProvider(platform);
103
+ if (!provider) {
104
+ throw new Error(`Platform ${platform} is not available. This should not happen!`);
105
+ }
106
+ console.log(chalk.green(`\n✓ Using ${provider.config.name} as Git hosting platform\n`));
107
+ // Step 3: Ask about Git remote URL format (SSH vs HTTPS)
108
+ const urlTypePromptData = getUrlTypePrompt();
109
+ const { urlType } = await inquirer.prompt([{
110
+ type: 'list',
111
+ name: 'urlType',
112
+ message: urlTypePromptData.question,
113
+ choices: urlTypePromptData.options.map(opt => ({
114
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
115
+ value: opt.value,
116
+ short: opt.label
117
+ })),
118
+ default: urlTypePromptData.default
119
+ }]);
120
+ console.log(chalk.green(`\n✓ Using ${urlType.toUpperCase()} remote URLs\n`));
76
121
  // Map ArchitectureChoice to internal architecture
77
122
  const mappedArch = this.mapArchitectureChoice(architecture);
78
123
  switch (mappedArch) {
79
124
  case 'single':
80
- return this.configureSingleRepo();
125
+ return this.configureSingleRepo(urlType, platform, provider);
81
126
  case 'parent':
82
127
  // GitHub parent repo (pushed to GitHub)
83
- return this.configureMultiRepo(true, false);
128
+ return this.configureMultiRepo(true, false, urlType, platform, provider);
84
129
  default:
85
130
  throw new Error(`Unknown architecture: ${architecture}`);
86
131
  }
@@ -102,9 +147,18 @@ export class RepoStructureManager {
102
147
  * Resume setup from saved state
103
148
  */
104
149
  async resumeSetup(state) {
150
+ // Default to GitHub platform for resumed setups (backward compatibility)
151
+ const registry = getPlatformRegistry();
152
+ const provider = registry.getProvider('github');
153
+ if (!provider) {
154
+ throw new Error('GitHub provider not available. This should not happen!');
155
+ }
105
156
  // Convert saved state back to config format
106
157
  const config = {
107
158
  architecture: state.architecture,
159
+ urlType: 'ssh', // Default to SSH for resumed setups
160
+ platform: 'github', // Default to GitHub for backward compatibility
161
+ provider: provider,
108
162
  parentRepo: state.parentRepo,
109
163
  repositories: state.repos.map(r => ({
110
164
  id: r.id,
@@ -124,7 +178,7 @@ export class RepoStructureManager {
124
178
  /**
125
179
  * Configure single repository
126
180
  */
127
- async configureSingleRepo() {
181
+ async configureSingleRepo(urlType = 'ssh', platform = 'github', provider) {
128
182
  console.log(chalk.cyan('\n📦 Single Repository Configuration\n'));
129
183
  // Check if repo already exists
130
184
  const hasGit = existsSync(path.join(this.projectPath, '.git'));
@@ -170,6 +224,9 @@ export class RepoStructureManager {
170
224
  }
171
225
  return {
172
226
  architecture: 'single',
227
+ urlType,
228
+ platform,
229
+ provider,
173
230
  repositories: [{
174
231
  id: 'main',
175
232
  name: repo,
@@ -235,6 +292,9 @@ export class RepoStructureManager {
235
292
  }
236
293
  return {
237
294
  architecture: 'single',
295
+ urlType,
296
+ platform,
297
+ provider,
238
298
  repositories: [{
239
299
  id: 'main',
240
300
  name: answers.repo,
@@ -251,12 +311,15 @@ export class RepoStructureManager {
251
311
  * Configure multi-repository architecture
252
312
  * @param useParent - Whether to use parent repository/folder
253
313
  * @param isLocalParent - If true, parent folder is local only (NOT pushed to GitHub)
314
+ * @param urlType - Git remote URL format (ssh or https)
315
+ * @param platform - Git hosting platform type
316
+ * @param provider - Git provider instance for API operations
254
317
  *
255
318
  * NOTE: This is primarily user-facing output (console.log/console.error).
256
319
  * All console.* calls in this method are legitimate user-facing exceptions
257
320
  * as defined in CONTRIBUTING.md (colored messages, confirmations, formatted output).
258
321
  */
259
- async configureMultiRepo(useParent = true, isLocalParent = false) {
322
+ async configureMultiRepo(useParent = true, isLocalParent = false, urlType = 'ssh', platform = 'github', provider) {
260
323
  console.log(chalk.cyan('\n🎯 Multi-Repository Configuration\n'));
261
324
  console.log(chalk.gray('This creates separate repositories for each service/component.\n'));
262
325
  // Show parent repo benefits if using parent approach
@@ -266,6 +329,9 @@ export class RepoStructureManager {
266
329
  }
267
330
  const config = {
268
331
  architecture: useParent ? 'parent' : 'multi-repo',
332
+ urlType,
333
+ platform,
334
+ provider,
269
335
  repositories: []
270
336
  };
271
337
  // Save state: architecture selected
@@ -300,15 +366,15 @@ export class RepoStructureManager {
300
366
  {
301
367
  type: 'input',
302
368
  name: 'owner',
303
- message: 'GitHub owner/organization for IMPLEMENTATION repos:',
369
+ message: `${provider.config.name} owner/organization for IMPLEMENTATION repos:`,
304
370
  validate: async (input) => {
305
371
  if (!input.trim())
306
372
  return 'Owner is required';
307
- // Validate owner exists on GitHub
373
+ // Validate owner exists on the platform
308
374
  if (this.githubToken) {
309
- const result = await validateOwner(input, this.githubToken);
375
+ const result = await provider.validateOwner(input, this.githubToken);
310
376
  if (!result.valid) {
311
- return result.error || 'Invalid GitHub owner';
377
+ return result.error || `Invalid ${provider.config.name} owner`;
312
378
  }
313
379
  }
314
380
  return true;
@@ -349,15 +415,15 @@ export class RepoStructureManager {
349
415
  {
350
416
  type: 'input',
351
417
  name: 'owner',
352
- message: 'GitHub owner/organization:',
418
+ message: `${provider.config.name} owner/organization:`,
353
419
  validate: async (input) => {
354
420
  if (!input.trim())
355
421
  return 'Owner is required';
356
- // Validate owner exists on GitHub
422
+ // Validate owner exists on the platform
357
423
  if (this.githubToken) {
358
- const result = await validateOwner(input, this.githubToken);
424
+ const result = await provider.validateOwner(input, this.githubToken);
359
425
  if (!result.valid) {
360
- return result.error || 'Invalid GitHub owner';
426
+ return result.error || `Invalid ${provider.config.name} owner`;
361
427
  }
362
428
  }
363
429
  return true;
@@ -374,11 +440,11 @@ export class RepoStructureManager {
374
440
  validate: async (input) => {
375
441
  if (!input.trim())
376
442
  return 'Repository name is required';
377
- // Validate repository EXISTS on GitHub
443
+ // Validate repository EXISTS on the platform
378
444
  if (this.githubToken && ownerPrompt.owner) {
379
- const result = await validateRepository(ownerPrompt.owner, input, this.githubToken);
445
+ const result = await provider.validateRepository(ownerPrompt.owner, input, this.githubToken);
380
446
  if (!result.exists) {
381
- return `Repository ${ownerPrompt.owner}/${input} not found on GitHub. Please check the name or choose 'Create new'.`;
447
+ return `Repository ${ownerPrompt.owner}/${input} not found on ${provider.config.name}. Please check the name or choose 'Create new'.`;
382
448
  }
383
449
  }
384
450
  return true;
@@ -423,15 +489,15 @@ export class RepoStructureManager {
423
489
  {
424
490
  type: 'input',
425
491
  name: 'owner',
426
- message: 'GitHub owner/organization for ALL repos:',
492
+ message: `${provider.config.name} owner/organization for ALL repos:`,
427
493
  validate: async (input) => {
428
494
  if (!input.trim())
429
495
  return 'Owner is required';
430
- // Validate owner exists on GitHub
496
+ // Validate owner exists on the platform
431
497
  if (this.githubToken) {
432
- const result = await validateOwner(input, this.githubToken);
498
+ const result = await provider.validateOwner(input, this.githubToken);
433
499
  if (!result.valid) {
434
- return result.error || 'Invalid GitHub owner';
500
+ return result.error || `Invalid ${provider.config.name} owner`;
435
501
  }
436
502
  }
437
503
  return true;
@@ -450,7 +516,7 @@ export class RepoStructureManager {
450
516
  return 'Repository name is required';
451
517
  // Validate repository DOESN'T exist
452
518
  if (this.githubToken && ownerPrompt.owner) {
453
- const result = await validateRepository(ownerPrompt.owner, input, this.githubToken);
519
+ const result = await provider.validateRepository(ownerPrompt.owner, input, this.githubToken);
454
520
  if (result.exists) {
455
521
  return `Repository ${ownerPrompt.owner}/${input} already exists at ${result.url}. Please choose 'Use existing' or pick a different name.`;
456
522
  }
@@ -565,27 +631,92 @@ export class RepoStructureManager {
565
631
  console.log(chalk.green(`\n✓ Total repositories to create: ${totalRepos} (1 parent + ${repoCount} implementation)\n`));
566
632
  }
567
633
  }
634
+ // Bulk repository discovery (optimization for many repos)
635
+ let discoveredRepos = [];
636
+ let bulkDiscoveryStrategy = 'manual';
637
+ if (this.githubToken && config.parentRepo) {
638
+ const octokit = new Octokit({ auth: this.githubToken });
639
+ const owner = config.parentRepo.owner;
640
+ const isOrg = await provider.isOrganization(owner, this.githubToken);
641
+ // Retry loop for pattern adjustment
642
+ let discoveryResult = null;
643
+ while (discoveryResult === null) {
644
+ discoveryResult = await discoverRepositories(octokit, owner, isOrg, repoCount);
645
+ // If null, user selected "go back and adjust pattern", loop will retry
646
+ // If user selected "manual", discoveryResult will be { repositories: [], strategy: 'manual' }
647
+ }
648
+ if (discoveryResult) {
649
+ bulkDiscoveryStrategy = discoveryResult.strategy;
650
+ if (discoveryResult.strategy !== 'manual') {
651
+ discoveredRepos = discoveryResult.repositories;
652
+ // Update repoCount to match discovered repos
653
+ if (discoveredRepos.length !== repoCount) {
654
+ console.log(chalk.yellow(`\n📝 Adjusting repository count from ${repoCount} to ${discoveredRepos.length} (based on discovery)\n`));
655
+ // Override repoCount with discovered count
656
+ repoCount = discoveredRepos.length;
657
+ }
658
+ }
659
+ }
660
+ }
568
661
  // Configure each repository
569
662
  console.log(chalk.cyan('\n📦 Configure Each Repository:\n'));
570
663
  const usedIds = new Set();
571
664
  const configuredRepoNames = []; // Track configured repo names for smart ID generation
572
665
  for (let i = 0; i < repoCount; i++) {
666
+ const discoveredRepo = discoveredRepos[i]; // May be undefined if manual
667
+ const isDiscovered = bulkDiscoveryStrategy !== 'manual' && discoveredRepo;
573
668
  console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}:`));
574
669
  // Smart suggestion for ALL repos (not just first one!)
575
670
  const projectName = path.basename(this.projectPath);
576
- const suggestedName = suggestRepoName(projectName, i, repoCount);
671
+ const suggestedName = isDiscovered ? discoveredRepo.name : suggestRepoName(projectName, i, repoCount);
672
+ // If discovered, show preview and skip prompts (or allow confirmation)
673
+ if (isDiscovered) {
674
+ console.log(chalk.green(` ✓ Discovered: ${chalk.bold(discoveredRepo.name)}`));
675
+ console.log(chalk.gray(` Description: ${discoveredRepo.description || '(none)'}`));
676
+ console.log(chalk.gray(` Visibility: ${discoveredRepo.private ? 'Private' : 'Public'}`));
677
+ const { confirmDiscovered } = await inquirer.prompt([{
678
+ type: 'confirm',
679
+ name: 'confirmDiscovered',
680
+ message: 'Use this repository configuration?',
681
+ default: true
682
+ }]);
683
+ if (!confirmDiscovered) {
684
+ // Allow manual override
685
+ console.log(chalk.yellow(` → Switching to manual entry for this repository\n`));
686
+ }
687
+ else {
688
+ // Use discovered repo configuration directly
689
+ const smartId = generateRepoIdSmart(discoveredRepo.name, configuredRepoNames);
690
+ const { id: suggestedId } = ensureUniqueId(smartId, usedIds);
691
+ console.log(chalk.green(` ✓ Repository ID: ${chalk.bold(suggestedId)} ${chalk.gray('(auto-generated)')}`));
692
+ usedIds.add(suggestedId);
693
+ configuredRepoNames.push(discoveredRepo.name);
694
+ config.repositories.push({
695
+ id: suggestedId,
696
+ name: discoveredRepo.name,
697
+ owner: discoveredRepo.owner,
698
+ description: discoveredRepo.description || `${discoveredRepo.name} service`,
699
+ path: useParent ? suggestedId : suggestedId,
700
+ visibility: discoveredRepo.private ? 'private' : 'public',
701
+ createOnGitHub: false, // Already exists!
702
+ isNested: useParent
703
+ });
704
+ continue; // Skip manual prompts
705
+ }
706
+ }
707
+ // Manual entry (original behavior)
577
708
  const repoAnswers = await inquirer.prompt([
578
709
  {
579
710
  type: 'input',
580
711
  name: 'name',
581
712
  message: 'Repository name:',
582
- default: suggestedName, // Now suggests for ALL repos!
713
+ default: suggestedName,
583
714
  validate: async (input) => {
584
715
  if (!input.trim())
585
716
  return 'Repository name is required';
586
- // Validate repository doesn't exist
587
- if (this.githubToken && config.parentRepo) {
588
- const result = await validateRepository(config.parentRepo.owner, input, this.githubToken);
717
+ // Validate repository doesn't exist (skip for discovered repos)
718
+ if (!isDiscovered && this.githubToken && config.parentRepo) {
719
+ const result = await provider.validateRepository(config.parentRepo.owner, input, this.githubToken);
589
720
  if (result.exists) {
590
721
  return `Repository ${config.parentRepo.owner}/${input} already exists at ${result.url}`;
591
722
  }
@@ -603,7 +734,7 @@ export class RepoStructureManager {
603
734
  type: 'confirm',
604
735
  name: 'createOnGitHub',
605
736
  message: 'Create this repository on GitHub?',
606
- default: true
737
+ default: !isDiscovered // Default to true for new repos, false for discovered
607
738
  }
608
739
  ]);
609
740
  // Smart auto-generate ID from repository name (context-aware)
@@ -649,19 +780,23 @@ export class RepoStructureManager {
649
780
  }
650
781
  usedIds.add(id);
651
782
  configuredRepoNames.push(repoAnswers.name);
652
- // Ask about visibility
653
- const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
654
- const { visibility } = await inquirer.prompt([{
655
- type: 'list',
656
- name: 'visibility',
657
- message: visibilityPrompt.question,
658
- choices: visibilityPrompt.options.map(opt => ({
659
- name: `${opt.label}\n${chalk.gray(opt.description)}`,
660
- value: opt.value,
661
- short: opt.label
662
- })),
663
- default: visibilityPrompt.default
664
- }]);
783
+ // Ask about visibility only if creating a new repository
784
+ let visibility = 'private';
785
+ if (repoAnswers.createOnGitHub) {
786
+ const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
787
+ const result = await inquirer.prompt([{
788
+ type: 'list',
789
+ name: 'visibility',
790
+ message: visibilityPrompt.question,
791
+ choices: visibilityPrompt.options.map(opt => ({
792
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
793
+ value: opt.value,
794
+ short: opt.label
795
+ })),
796
+ default: visibilityPrompt.default
797
+ }]);
798
+ visibility = result.visibility;
799
+ }
665
800
  config.repositories.push({
666
801
  id: id,
667
802
  name: repoAnswers.name,
@@ -707,7 +842,7 @@ export class RepoStructureManager {
707
842
  /**
708
843
  * Configure monorepo
709
844
  */
710
- async configureMonorepo() {
845
+ async configureMonorepo(urlType = 'ssh', platform = 'github', provider) {
711
846
  console.log(chalk.cyan('\n📚 Monorepo Configuration\n'));
712
847
  console.log(chalk.gray('Single repository with multiple projects/packages.\n'));
713
848
  const answers = await inquirer.prompt([
@@ -749,22 +884,29 @@ export class RepoStructureManager {
749
884
  default: !existsSync(path.join(this.projectPath, '.git'))
750
885
  }
751
886
  ]);
752
- // Ask about visibility
753
- const visibilityPrompt = getVisibilityPrompt(answers.repo);
754
- const { visibility } = await inquirer.prompt([{
755
- type: 'list',
756
- name: 'visibility',
757
- message: visibilityPrompt.question,
758
- choices: visibilityPrompt.options.map(opt => ({
759
- name: `${opt.label}\n${chalk.gray(opt.description)}`,
760
- value: opt.value,
761
- short: opt.label
762
- })),
763
- default: visibilityPrompt.default
764
- }]);
887
+ // Ask about visibility only if creating a new repository
888
+ let visibility = 'private';
889
+ if (answers.createOnGitHub) {
890
+ const visibilityPrompt = getVisibilityPrompt(answers.repo);
891
+ const result = await inquirer.prompt([{
892
+ type: 'list',
893
+ name: 'visibility',
894
+ message: visibilityPrompt.question,
895
+ choices: visibilityPrompt.options.map(opt => ({
896
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
897
+ value: opt.value,
898
+ short: opt.label
899
+ })),
900
+ default: visibilityPrompt.default
901
+ }]);
902
+ visibility = result.visibility;
903
+ }
765
904
  const projects = answers.projects.split(',').map((p) => p.trim());
766
905
  return {
767
906
  architecture: 'monorepo',
907
+ urlType,
908
+ platform,
909
+ provider,
768
910
  repositories: [{
769
911
  id: 'main',
770
912
  name: answers.repo,
@@ -779,28 +921,33 @@ export class RepoStructureManager {
779
921
  };
780
922
  }
781
923
  /**
782
- * Create repositories on GitHub via API
924
+ * Create repositories on Git hosting platform via API
783
925
  */
784
- async createGitHubRepositories(config) {
926
+ async createRepositories(config) {
785
927
  if (!this.githubToken) {
786
- console.log(chalk.yellow('\n⚠️ No GitHub token available'));
787
- console.log(chalk.gray(' Skipping GitHub repository creation'));
928
+ console.log(chalk.yellow(`\n⚠️ No ${config.provider.config.name} token available`));
929
+ console.log(chalk.gray(` Skipping ${config.provider.config.name} repository creation`));
788
930
  console.log(chalk.gray(' You can create repositories manually later\n'));
789
931
  return;
790
932
  }
791
- const spinner = ora('Creating GitHub repositories...').start();
933
+ const spinner = ora(`Creating ${config.provider.config.name} repositories...`).start();
792
934
  const created = [];
793
935
  const failed = [];
794
936
  // Create parent repository if needed
795
937
  if (config.parentRepo?.createOnGitHub) {
796
938
  try {
797
- await this.createGitHubRepo(config.parentRepo.owner, config.parentRepo.name, config.parentRepo.description, config.parentRepo.visibility);
939
+ await config.provider.createRepository({
940
+ owner: config.parentRepo.owner,
941
+ name: config.parentRepo.name,
942
+ description: config.parentRepo.description,
943
+ visibility: config.parentRepo.visibility
944
+ }, this.githubToken);
798
945
  created.push(`${config.parentRepo.owner}/${config.parentRepo.name}`);
799
946
  // Save state: parent repo created
800
947
  await this.saveSetupState({
801
948
  version: '1.0.0',
802
949
  architecture: config.architecture,
803
- parentRepo: { ...config.parentRepo, url: `https://github.com/${config.parentRepo.owner}/${config.parentRepo.name}` },
950
+ parentRepo: { ...config.parentRepo, url: config.provider.getRemoteUrl(config.parentRepo.owner, config.parentRepo.name, config.urlType) },
804
951
  repos: [],
805
952
  currentStep: 'parent-repo-created',
806
953
  timestamp: new Date().toISOString(),
@@ -815,7 +962,12 @@ export class RepoStructureManager {
815
962
  for (const repo of config.repositories) {
816
963
  if (repo.createOnGitHub) {
817
964
  try {
818
- await this.createGitHubRepo(repo.owner, repo.name, repo.description, repo.visibility);
965
+ await config.provider.createRepository({
966
+ owner: repo.owner,
967
+ name: repo.name,
968
+ description: repo.description,
969
+ visibility: repo.visibility
970
+ }, this.githubToken);
819
971
  created.push(`${repo.owner}/${repo.name}`);
820
972
  }
821
973
  catch (error) {
@@ -904,7 +1056,7 @@ export class RepoStructureManager {
904
1056
  path: r.path,
905
1057
  visibility: r.visibility,
906
1058
  displayName: r.name,
907
- url: `https://github.com/${r.owner}/${r.name}`,
1059
+ url: config.provider.getRemoteUrl(r.owner, r.name, config.urlType),
908
1060
  created: false
909
1061
  })),
910
1062
  currentStep: 'complete',
@@ -975,6 +1127,81 @@ export class RepoStructureManager {
975
1127
  }
976
1128
  return false;
977
1129
  }
1130
+ /**
1131
+ * Check if a repository exists on GitHub
1132
+ */
1133
+ async repositoryExistsOnGitHub(owner, repo) {
1134
+ if (!this.githubToken) {
1135
+ return false;
1136
+ }
1137
+ try {
1138
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
1139
+ headers: {
1140
+ 'Authorization': `Bearer ${this.githubToken}`,
1141
+ 'Accept': 'application/vnd.github+json'
1142
+ }
1143
+ });
1144
+ return response.ok;
1145
+ }
1146
+ catch {
1147
+ return false;
1148
+ }
1149
+ }
1150
+ /**
1151
+ * Clone or initialize a repository
1152
+ * If the repo exists on the platform, clone it; otherwise, init + add remote
1153
+ */
1154
+ async cloneOrInitRepository(repoPath, owner, name, createOnGitHub, urlType = 'ssh', provider) {
1155
+ // If .git already exists, skip
1156
+ if (existsSync(path.join(repoPath, '.git'))) {
1157
+ return;
1158
+ }
1159
+ const remoteUrl = provider.getRemoteUrl(owner, name, urlType);
1160
+ // Check if repository exists on GitHub
1161
+ const repoExists = await this.repositoryExistsOnGitHub(owner, name);
1162
+ if (repoExists) {
1163
+ // Repository exists - clone it
1164
+ console.log(chalk.gray(` → Cloning existing repository from GitHub...`));
1165
+ try {
1166
+ // Remove directory if it exists and is empty
1167
+ if (existsSync(repoPath)) {
1168
+ const files = require('fs').readdirSync(repoPath);
1169
+ if (files.length === 0) {
1170
+ require('fs').rmdirSync(repoPath);
1171
+ }
1172
+ }
1173
+ // Clone the repository
1174
+ const parentDir = path.dirname(repoPath);
1175
+ const repoName = path.basename(repoPath);
1176
+ execFileNoThrowSync('git', ['clone', remoteUrl, repoName], { cwd: parentDir });
1177
+ console.log(chalk.green(` ✓ Cloned ${owner}/${name}`));
1178
+ }
1179
+ catch (error) {
1180
+ console.log(chalk.yellow(` ⚠️ Clone failed: ${error.message}`));
1181
+ console.log(chalk.gray(` → Falling back to init + remote`));
1182
+ // Fallback: init + add remote
1183
+ if (!existsSync(repoPath)) {
1184
+ mkdirSync(repoPath, { recursive: true });
1185
+ }
1186
+ execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1187
+ execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1188
+ }
1189
+ }
1190
+ else {
1191
+ // Repository doesn't exist - init + add remote
1192
+ if (!createOnGitHub) {
1193
+ console.log(chalk.gray(` → Repository will be created later (skipping for now)`));
1194
+ return;
1195
+ }
1196
+ console.log(chalk.gray(` → Initializing empty git repository...`));
1197
+ if (!existsSync(repoPath)) {
1198
+ mkdirSync(repoPath, { recursive: true });
1199
+ }
1200
+ execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1201
+ execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1202
+ console.log(chalk.green(` ✓ Initialized ${owner}/${name}`));
1203
+ }
1204
+ }
978
1205
  /**
979
1206
  * Initialize local git repositories
980
1207
  */
@@ -987,40 +1214,31 @@ export class RepoStructureManager {
987
1214
  if (!existsSync(path.join(this.projectPath, '.git'))) {
988
1215
  execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
989
1216
  if (config.parentRepo) {
990
- const remoteUrl = `https://github.com/${config.parentRepo.owner}/${config.parentRepo.name}.git`;
1217
+ const remoteUrl = config.provider.getRemoteUrl(config.parentRepo.owner, config.parentRepo.name, config.urlType);
991
1218
  execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: this.projectPath });
992
1219
  }
993
1220
  }
994
1221
  // Initialize implementation repos at ROOT LEVEL
995
1222
  for (const repo of config.repositories) {
996
1223
  const repoPath = path.join(this.projectPath, repo.path);
997
- // Create directory if needed
998
- if (!existsSync(repoPath)) {
999
- mkdirSync(repoPath, { recursive: true });
1000
- }
1001
- // Initialize git
1002
- if (!existsSync(path.join(repoPath, '.git'))) {
1003
- execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1004
- const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
1005
- execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1224
+ // Clone or initialize repository
1225
+ await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub, config.urlType, config.provider);
1226
+ // Create basic structure (only if repo was just initialized, not cloned)
1227
+ if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
1228
+ this.createBasicRepoStructure(repoPath, repo.id);
1006
1229
  }
1007
- // Create basic structure
1008
- this.createBasicRepoStructure(repoPath, repo.id);
1009
1230
  }
1010
1231
  }
1011
1232
  else if (config.architecture === 'multi-repo') {
1012
1233
  // Standard multi-repo: repos as subdirectories
1013
1234
  for (const repo of config.repositories) {
1014
1235
  const repoPath = path.join(this.projectPath, repo.path);
1015
- if (!existsSync(repoPath)) {
1016
- mkdirSync(repoPath, { recursive: true });
1017
- }
1018
- if (!existsSync(path.join(repoPath, '.git'))) {
1019
- execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1020
- const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
1021
- execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1236
+ // Clone or initialize repository
1237
+ await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub, config.urlType, config.provider);
1238
+ // Create basic structure (only if repo was just initialized, not cloned)
1239
+ if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
1240
+ this.createBasicRepoStructure(repoPath, repo.id);
1022
1241
  }
1023
- this.createBasicRepoStructure(repoPath, repo.id);
1024
1242
  }
1025
1243
  }
1026
1244
  else {
@@ -1029,7 +1247,7 @@ export class RepoStructureManager {
1029
1247
  execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
1030
1248
  const repo = config.repositories[0];
1031
1249
  if (repo) {
1032
- const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
1250
+ const remoteUrl = config.provider.getRemoteUrl(repo.owner, repo.name, config.urlType);
1033
1251
  execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: this.projectPath });
1034
1252
  }
1035
1253
  }