specweave 0.32.3 → 0.32.6

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 (102) hide show
  1. package/CLAUDE.md +12 -9
  2. package/bin/specweave.js +78 -0
  3. package/dist/src/cli/commands/docs.d.ts +45 -0
  4. package/dist/src/cli/commands/docs.d.ts.map +1 -0
  5. package/dist/src/cli/commands/docs.js +307 -0
  6. package/dist/src/cli/commands/docs.js.map +1 -0
  7. package/dist/src/cli/commands/init-multiproject.js +1 -1
  8. package/dist/src/cli/commands/init-multiproject.js.map +1 -1
  9. package/dist/src/cli/commands/migrate-to-multiproject.js +2 -2
  10. package/dist/src/cli/commands/migrate-to-multiproject.js.map +1 -1
  11. package/dist/src/cli/helpers/init/repository-setup.d.ts +14 -2
  12. package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -1
  13. package/dist/src/cli/helpers/init/repository-setup.js +292 -188
  14. package/dist/src/cli/helpers/init/repository-setup.js.map +1 -1
  15. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  16. package/dist/src/cli/helpers/issue-tracker/index.js +9 -3
  17. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  18. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +18 -2
  19. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  20. package/dist/src/cli/helpers/issue-tracker/jira.js +325 -25
  21. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  22. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.d.ts.map +1 -1
  23. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js +61 -1
  24. package/dist/src/cli/helpers/issue-tracker/sync-config-writer.js.map +1 -1
  25. package/dist/src/cli/helpers/issue-tracker/types.d.ts +25 -0
  26. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/types.js.map +1 -1
  28. package/dist/src/core/living-docs/cross-project-sync.d.ts +97 -0
  29. package/dist/src/core/living-docs/cross-project-sync.d.ts.map +1 -0
  30. package/dist/src/core/living-docs/cross-project-sync.js +135 -0
  31. package/dist/src/core/living-docs/cross-project-sync.js.map +1 -0
  32. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts +106 -0
  33. package/dist/src/core/living-docs/external-sync-orchestrator.d.ts.map +1 -0
  34. package/dist/src/core/living-docs/external-sync-orchestrator.js +146 -0
  35. package/dist/src/core/living-docs/external-sync-orchestrator.js.map +1 -0
  36. package/dist/src/core/living-docs/intelligent-analyzer/organization-synthesizer.js +1 -1
  37. package/dist/src/core/living-docs/living-docs-sync.d.ts +1 -0
  38. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  39. package/dist/src/core/living-docs/living-docs-sync.js +77 -2
  40. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  41. package/dist/src/core/living-docs/sync-helpers/generators.d.ts +8 -1
  42. package/dist/src/core/living-docs/sync-helpers/generators.d.ts.map +1 -1
  43. package/dist/src/core/living-docs/sync-helpers/generators.js +18 -1
  44. package/dist/src/core/living-docs/sync-helpers/generators.js.map +1 -1
  45. package/dist/src/core/living-docs/sync-helpers/index.d.ts +1 -1
  46. package/dist/src/core/living-docs/sync-helpers/index.d.ts.map +1 -1
  47. package/dist/src/core/living-docs/sync-helpers/index.js.map +1 -1
  48. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts +3 -1
  49. package/dist/src/core/living-docs/sync-helpers/parsers.d.ts.map +1 -1
  50. package/dist/src/core/living-docs/sync-helpers/parsers.js +24 -2
  51. package/dist/src/core/living-docs/sync-helpers/parsers.js.map +1 -1
  52. package/dist/src/core/living-docs/types.d.ts +6 -0
  53. package/dist/src/core/living-docs/types.d.ts.map +1 -1
  54. package/dist/src/core/living-docs/validators/index.d.ts +7 -0
  55. package/dist/src/core/living-docs/validators/index.d.ts.map +1 -0
  56. package/dist/src/core/living-docs/validators/index.js +7 -0
  57. package/dist/src/core/living-docs/validators/index.js.map +1 -0
  58. package/dist/src/core/living-docs/validators/project-validator.d.ts +92 -0
  59. package/dist/src/core/living-docs/validators/project-validator.d.ts.map +1 -0
  60. package/dist/src/core/living-docs/validators/project-validator.js +142 -0
  61. package/dist/src/core/living-docs/validators/project-validator.js.map +1 -0
  62. package/dist/src/core/project/project-manager.d.ts.map +1 -1
  63. package/dist/src/core/project/project-manager.js +19 -17
  64. package/dist/src/core/project/project-manager.js.map +1 -1
  65. package/dist/src/core/types/config.d.ts +4 -2
  66. package/dist/src/core/types/config.d.ts.map +1 -1
  67. package/dist/src/core/types/config.js.map +1 -1
  68. package/dist/src/core/types/increment-metadata.d.ts +34 -0
  69. package/dist/src/core/types/increment-metadata.d.ts.map +1 -1
  70. package/dist/src/utils/cross-cutting-detector.d.ts +66 -0
  71. package/dist/src/utils/cross-cutting-detector.d.ts.map +1 -0
  72. package/dist/src/utils/cross-cutting-detector.js +179 -0
  73. package/dist/src/utils/cross-cutting-detector.js.map +1 -0
  74. package/dist/src/utils/docs-preview/config-generator.d.ts.map +1 -1
  75. package/dist/src/utils/docs-preview/config-generator.js +12 -16
  76. package/dist/src/utils/docs-preview/config-generator.js.map +1 -1
  77. package/dist/src/utils/docs-preview/docusaurus-setup.d.ts.map +1 -1
  78. package/dist/src/utils/docs-preview/docusaurus-setup.js +24 -5
  79. package/dist/src/utils/docs-preview/docusaurus-setup.js.map +1 -1
  80. package/dist/src/utils/docs-preview/package-installer.d.ts +3 -0
  81. package/dist/src/utils/docs-preview/package-installer.d.ts.map +1 -1
  82. package/dist/src/utils/docs-preview/package-installer.js +18 -9
  83. package/dist/src/utils/docs-preview/package-installer.js.map +1 -1
  84. package/dist/src/utils/docs-preview/server-manager.d.ts.map +1 -1
  85. package/dist/src/utils/docs-preview/server-manager.js +2 -1
  86. package/dist/src/utils/docs-preview/server-manager.js.map +1 -1
  87. package/dist/src/utils/project-detection.d.ts +12 -8
  88. package/dist/src/utils/project-detection.d.ts.map +1 -1
  89. package/dist/src/utils/project-detection.js +13 -19
  90. package/dist/src/utils/project-detection.js.map +1 -1
  91. package/dist/src/utils/validators/jira-validator.d.ts +25 -1
  92. package/dist/src/utils/validators/jira-validator.d.ts.map +1 -1
  93. package/dist/src/utils/validators/jira-validator.js +254 -172
  94. package/dist/src/utils/validators/jira-validator.js.map +1 -1
  95. package/package.json +1 -1
  96. package/plugins/specweave/commands/specweave-status.md +64 -0
  97. package/plugins/specweave/hooks/hooks.json +1 -1
  98. package/plugins/specweave/hooks/spec-project-validator.sh +2 -1
  99. package/plugins/specweave/lib/vendor/core/types/increment-metadata.d.ts +34 -0
  100. package/plugins/specweave/skills/increment-planner/SKILL.md +77 -2
  101. package/plugins/specweave/skills/spec-generator/SKILL.md +78 -7
  102. package/plugins/specweave/commands/specweave-switch-project.md +0 -168
@@ -1 +1 @@
1
- {"version":3,"file":"repository-setup.d.ts","sourceRoot":"","sources":["../../../../../src/cli/helpers/init/repository-setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AA6DrE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA6cD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,2DAA2D;IAC3D,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,gCAAgC;IAChC,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAoID;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+K5G"}
1
+ {"version":3,"file":"repository-setup.d.ts","sourceRoot":"","sources":["../../../../../src/cli/helpers/init/repository-setup.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,OAAO,KAAK,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAClE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AA6DrE;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,OAAO,CAAC;IACd,YAAY,EAAE,YAAY,GAAG,IAAI,CAAC;IAClC,QAAQ,CAAC,EAAE,iBAAiB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,MAAM,gBAAgB,GAAG,KAAK,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;AAEjF;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,QAAQ,EAAE,gBAAgB,CAAC;IAC3B,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AAGD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,2BAA2B,CAAC;AACrE,YAAY,EAAE,mBAAmB,EAAE,CAAC;AAEpC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,OAAO,EAAE,iBAAiB,CAAC;IAC3B,WAAW,EAAE,OAAO,CAAC;IACrB,0FAA0F;IAC1F,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,4BAA4B;IAC5B,qBAAqB,CAAC,EAAE,qBAAqB,CAAC;IAC9C,gDAAgD;IAChD,mBAAmB,CAAC,EAAE,mBAAmB,CAAC;CAC3C;AAopBD;;GAEG;AACH,MAAM,MAAM,sBAAsB,GAAG,KAAK,GAAG,cAAc,GAAG,eAAe,GAAG,MAAM,CAAC;AAEvF;;GAEG;AACH,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,sBAAsB,CAAC;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB;AA6GD;;;;;GAKG;AACH,wBAAsB,sBAAsB,CAAC,OAAO,EAAE,sBAAsB,GAAG,OAAO,CAAC,qBAAqB,CAAC,CA+G5G"}
@@ -56,6 +56,110 @@ async function safeParseJsonResponse(response, context) {
56
56
  ` Response preview: ${preview}${responseText.length > 100 ? '...' : ''}`);
57
57
  }
58
58
  }
59
+ /**
60
+ * Fetch all accessible projects for an ADO organization
61
+ */
62
+ async function fetchAdoProjects(org, pat) {
63
+ const auth = Buffer.from(`:${pat}`).toString('base64');
64
+ const endpoint = `https://dev.azure.com/${org}/_apis/projects?api-version=7.0`;
65
+ const response = await fetch(endpoint, {
66
+ headers: { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json' }
67
+ });
68
+ if (!response.ok) {
69
+ // Read response body to check for HTML error pages
70
+ const errorBody = await response.text();
71
+ const looksLikeHtml = errorBody.trim().startsWith('<!DOCTYPE') || errorBody.trim().startsWith('<html');
72
+ if (looksLikeHtml) {
73
+ throw new Error(`Azure DevOps returned an error page (HTTP ${response.status}).\n` +
74
+ ` This usually indicates an authentication or configuration issue.\n` +
75
+ ` \n` +
76
+ ` Possible causes:\n` +
77
+ ` • Invalid or expired Personal Access Token (PAT)\n` +
78
+ ` • Incorrect organization name "${org}"\n` +
79
+ ` • Corporate firewall or proxy intercepting the request\n` +
80
+ ` • SSO/authentication redirect (try accessing Azure DevOps in browser first)`);
81
+ }
82
+ throw new Error(`Failed to fetch projects: ${response.status} - ${errorBody.substring(0, 200)}`);
83
+ }
84
+ // Use safe JSON parser to detect HTML responses even on 200 OK
85
+ const data = await safeParseJsonResponse(response, 'Azure DevOps');
86
+ return (data.value || []).map((p) => ({ name: p.name, id: p.id }));
87
+ }
88
+ /**
89
+ * Prompt user for ADO organization, credentials, and project selection
90
+ * For repository cloning in multi-repo setups
91
+ */
92
+ async function promptAdoProjectSelection(targetDir, strings) {
93
+ // Check for existing credentials in .env or environment
94
+ let existingOrg;
95
+ let existingPat;
96
+ const envContent = readEnvFile(targetDir);
97
+ if (envContent) {
98
+ const parsed = parseEnvFile(envContent);
99
+ existingOrg = parsed.AZURE_DEVOPS_ORG;
100
+ existingPat = parsed.AZURE_DEVOPS_PAT;
101
+ }
102
+ // Fall back to environment variables
103
+ if (!existingOrg || !existingPat) {
104
+ const auth = getAzureDevOpsAuth();
105
+ if (auth) {
106
+ existingOrg = existingOrg || auth.org;
107
+ existingPat = existingPat || auth.pat;
108
+ }
109
+ }
110
+ // Prompt for organization
111
+ const org = await input({
112
+ message: strings.adoOrgPrompt,
113
+ default: existingOrg,
114
+ validate: (value) => {
115
+ if (!value.trim())
116
+ return 'Organization name is required';
117
+ return true;
118
+ }
119
+ });
120
+ // Prompt for PAT
121
+ const pat = await password({
122
+ message: strings.adoPatPrompt,
123
+ mask: true,
124
+ validate: (value) => {
125
+ if (!value.trim())
126
+ return 'Personal Access Token is required';
127
+ return true;
128
+ }
129
+ });
130
+ // Fetch projects
131
+ const spinner = ora(strings.adoFetchingProjects).start();
132
+ let projects;
133
+ try {
134
+ projects = await fetchAdoProjects(org, pat);
135
+ spinner.succeed();
136
+ }
137
+ catch (error) {
138
+ spinner.fail(strings.adoConnectionFailed);
139
+ console.log(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
140
+ return null;
141
+ }
142
+ if (projects.length === 0) {
143
+ console.log(chalk.yellow(` ${strings.adoNoProjects}`));
144
+ return null;
145
+ }
146
+ // Let user select projects
147
+ const selectedProjects = await checkbox({
148
+ message: strings.adoProjectPrompt,
149
+ choices: projects.map((p, index) => ({
150
+ name: p.name,
151
+ value: p.name,
152
+ checked: index === 0 // First project selected by default
153
+ })),
154
+ required: true
155
+ });
156
+ console.log(chalk.green(` āœ“ ${strings.adoProjectsSelected.replace('{count}', String(selectedProjects.length))}`));
157
+ return {
158
+ org,
159
+ pat,
160
+ projects: selectedProjects
161
+ };
162
+ }
59
163
  /**
60
164
  * Get translated strings for repository setup
61
165
  */
@@ -77,7 +181,7 @@ function getRepoStrings(language) {
77
181
  other: 'Other (GitLab, etc - coming soon)',
78
182
  adoCloneQuestion: 'Enter repo name pattern to clone (e.g., sw-* or leave empty to skip):',
79
183
  adoCloneSkip: 'Skipping repo cloning - you can configure later',
80
- // New unified selection strings
184
+ // Unified selection strings
81
185
  adoSelectStrategy: 'How do you want to select repositories to clone?',
82
186
  adoAllRepos: 'All',
83
187
  adoAllReposDesc: 'Clone all repositories from the project',
@@ -105,6 +209,13 @@ function getRepoStrings(language) {
105
209
  adoFetchingProjects: 'Fetching projects...',
106
210
  adoNoProjects: 'No projects found in this organization',
107
211
  adoConnectionFailed: 'Failed to connect to Azure DevOps',
212
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
213
+ githubMultiRepoHeader: 'šŸ“ GitHub Multi-Repository Selection',
214
+ githubMultiRepoDesc: 'Select which repositories to work with from your GitHub organization.',
215
+ githubSelectStrategy: 'How do you want to select GitHub repositories?',
216
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket Multi-Repository Selection',
217
+ bitbucketMultiRepoDesc: 'Select which repositories to work with from your Bitbucket workspace.',
218
+ bitbucketSelectStrategy: 'How do you want to select Bitbucket repositories?',
108
219
  },
109
220
  ru: {
110
221
  header: 'šŸ“¦ Єостинг Ń€ŠµŠæŠ¾Š·ŠøŃ‚Š¾Ń€ŠøŃ',
@@ -122,7 +233,7 @@ function getRepoStrings(language) {
122
233
  other: 'Š”Ń€ŃƒŠ³Š¾Š¹ (GitLab Šø т.Š“. - скоро)',
123
234
  adoCloneQuestion: 'ВвеГите шаблон имени репо Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ (напр., sw-* или Š¾ŃŃ‚Š°Š²ŃŒŃ‚Šµ ŠæŃƒŃŃ‚Ń‹Š¼):',
124
235
  adoCloneSkip: 'ŠŸŃ€Š¾ŠæŃƒŃŠŗ ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ - можно Š½Š°ŃŃ‚Ń€Š¾ŠøŃ‚ŃŒ позже',
125
- // New unified selection strings
236
+ // Unified selection strings
126
237
  adoSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории Š“Š»Ń ŠŗŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Š½ŠøŃ?',
127
238
  adoAllRepos: 'Все',
128
239
  adoAllReposDesc: 'ŠšŠ»Š¾Š½ŠøŃ€Š¾Š²Š°Ń‚ŃŒ все репозитории ŠøŠ· проекта',
@@ -150,6 +261,13 @@ function getRepoStrings(language) {
150
261
  adoFetchingProjects: 'Š—Š°Š³Ń€ŃƒŠ·ŠŗŠ° проектов...',
151
262
  adoNoProjects: 'ŠŸŃ€Š¾ŠµŠŗŃ‚Ń‹ не найГены в ŃŃ‚Š¾Š¹ организации',
152
263
  adoConnectionFailed: 'ŠŠµ уГалось ŠæŠ¾Š“ŠŗŠ»ŃŽŃ‡ŠøŃ‚ŃŒŃŃ Šŗ Azure DevOps',
264
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
265
+ githubMultiRepoHeader: 'šŸ“ Выбор репозиториев GitHub',
266
+ githubMultiRepoDesc: 'Выберите репозитории Š“Š»Ń работы ŠøŠ· вашей организации GitHub.',
267
+ githubSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории GitHub?',
268
+ bitbucketMultiRepoHeader: 'šŸ“ Выбор репозиториев Bitbucket',
269
+ bitbucketMultiRepoDesc: 'Выберите репозитории Š“Š»Ń работы ŠøŠ· вашего рабочего пространства Bitbucket.',
270
+ bitbucketSelectStrategy: 'Как вы хотите Š²Ń‹Š±Ń€Š°Ń‚ŃŒ репозитории Bitbucket?',
153
271
  },
154
272
  es: {
155
273
  header: 'šŸ“¦ Alojamiento del repositorio',
@@ -167,7 +285,7 @@ function getRepoStrings(language) {
167
285
  other: 'Otro (GitLab, etc - próximamente)',
168
286
  adoCloneQuestion: 'Ingrese patrón de nombre de repo a clonar (ej., sw-* o deje vacĆ­o):',
169
287
  adoCloneSkip: 'Omitiendo clonación - puede configurar despuĆ©s',
170
- // New unified selection strings
288
+ // Unified selection strings
171
289
  adoSelectStrategy: 'ĀæCómo quiere seleccionar repositorios para clonar?',
172
290
  adoAllRepos: 'Todos',
173
291
  adoAllReposDesc: 'Clonar todos los repositorios del proyecto',
@@ -195,6 +313,13 @@ function getRepoStrings(language) {
195
313
  adoFetchingProjects: 'Obteniendo proyectos...',
196
314
  adoNoProjects: 'No se encontraron proyectos en esta organización',
197
315
  adoConnectionFailed: 'Error al conectar con Azure DevOps',
316
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
317
+ githubMultiRepoHeader: 'šŸ“ Selección de repositorios GitHub',
318
+ githubMultiRepoDesc: 'Seleccione los repositorios de su organización GitHub.',
319
+ githubSelectStrategy: '¿Cómo desea seleccionar los repositorios de GitHub?',
320
+ bitbucketMultiRepoHeader: 'šŸ“ Selección de repositorios Bitbucket',
321
+ bitbucketMultiRepoDesc: 'Seleccione los repositorios de su espacio de trabajo Bitbucket.',
322
+ bitbucketSelectStrategy: '¿Cómo desea seleccionar los repositorios de Bitbucket?',
198
323
  },
199
324
  zh: {
200
325
  header: 'šŸ“¦ ä»“åŗ“ę‰˜ē®”',
@@ -212,7 +337,7 @@ function getRepoStrings(language) {
212
337
  other: 'å…¶ä»–ļ¼ˆGitLab ē­‰ - å³å°†ęŽØå‡ŗļ¼‰',
213
338
  adoCloneQuestion: 'č¾“å…„č¦å…‹éš†ēš„ä»“åŗ“åē§°ęØ”å¼ļ¼ˆä¾‹å¦‚ sw-* ęˆ–ē•™ē©ŗč·³čæ‡ļ¼‰ļ¼š',
214
339
  adoCloneSkip: 'č·³čæ‡å…‹éš† - ēØåŽåÆä»„é…ē½®',
215
- // New unified selection strings
340
+ // Unified selection strings
216
341
  adoSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹©č¦å…‹éš†ēš„ä»“åŗ“ļ¼Ÿ',
217
342
  adoAllRepos: 'å…ØéƒØ',
218
343
  adoAllReposDesc: 'å…‹éš†é”¹ē›®äø­ēš„ę‰€ęœ‰ä»“åŗ“',
@@ -240,6 +365,13 @@ function getRepoStrings(language) {
240
365
  adoFetchingProjects: 'ę­£åœØčŽ·å–é”¹ē›®...',
241
366
  adoNoProjects: 'åœØę­¤ē»„ē»‡äø­ęœŖę‰¾åˆ°é”¹ē›®',
242
367
  adoConnectionFailed: 'ę— ę³•čæžęŽ„åˆ° Azure DevOps',
368
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
369
+ githubMultiRepoHeader: 'šŸ“ GitHub å¤šä»“åŗ“é€‰ę‹©',
370
+ githubMultiRepoDesc: 'ä»Žę‚Øēš„ GitHub ē»„ē»‡äø­é€‰ę‹©č¦ä½æē”Øēš„ä»“åŗ“ć€‚',
371
+ githubSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹© GitHub ä»“åŗ“ļ¼Ÿ',
372
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket å¤šä»“åŗ“é€‰ę‹©',
373
+ bitbucketMultiRepoDesc: 'ä»Žę‚Øēš„ Bitbucket å·„ä½œåŒŗäø­é€‰ę‹©č¦ä½æē”Øēš„ä»“åŗ“ć€‚',
374
+ bitbucketSelectStrategy: 'ę‚Øęƒ³å¦‚ä½•é€‰ę‹© Bitbucket ä»“åŗ“ļ¼Ÿ',
243
375
  },
244
376
  de: {
245
377
  header: 'šŸ“¦ Repository-Hosting',
@@ -257,7 +389,7 @@ function getRepoStrings(language) {
257
389
  other: 'Andere (GitLab, etc - kommt bald)',
258
390
  adoCloneQuestion: 'Repo-Namensmuster zum Klonen eingeben (z.B. sw-* oder leer lassen):',
259
391
  adoCloneSkip: 'Klonen übersprungen - spƤter konfigurierbar',
260
- // New unified selection strings
392
+ // Unified selection strings
261
393
  adoSelectStrategy: 'Wie mƶchten Sie Repositories zum Klonen auswƤhlen?',
262
394
  adoAllRepos: 'Alle',
263
395
  adoAllReposDesc: 'Alle Repositories aus dem Projekt klonen',
@@ -285,6 +417,13 @@ function getRepoStrings(language) {
285
417
  adoFetchingProjects: 'Projekte werden abgerufen...',
286
418
  adoNoProjects: 'Keine Projekte in dieser Organisation gefunden',
287
419
  adoConnectionFailed: 'Verbindung zu Azure DevOps fehlgeschlagen',
420
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
421
+ githubMultiRepoHeader: 'šŸ“ GitHub Multi-Repository Auswahl',
422
+ githubMultiRepoDesc: 'WƤhlen Sie die Repositories aus Ihrer GitHub-Organisation.',
423
+ githubSelectStrategy: 'Wie mƶchten Sie GitHub-Repositories auswƤhlen?',
424
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket Multi-Repository Auswahl',
425
+ bitbucketMultiRepoDesc: 'WƤhlen Sie die Repositories aus Ihrem Bitbucket-Workspace.',
426
+ bitbucketSelectStrategy: 'Wie mƶchten Sie Bitbucket-Repositories auswƤhlen?',
288
427
  },
289
428
  fr: {
290
429
  header: 'šŸ“¦ HĆ©bergement du dĆ©pĆ“t',
@@ -302,7 +441,7 @@ function getRepoStrings(language) {
302
441
  other: 'Autre (GitLab, etc - bientĆ“t)',
303
442
  adoCloneQuestion: 'Entrez le modĆØle de nom de repo Ć  cloner (ex. sw-* ou laissez vide):',
304
443
  adoCloneSkip: 'Clonage ignorĆ© - configurable plus tard',
305
- // New unified selection strings
444
+ // Unified selection strings
306
445
  adoSelectStrategy: 'Comment voulez-vous sĆ©lectionner les repos Ć  cloner?',
307
446
  adoAllRepos: 'Tous',
308
447
  adoAllReposDesc: 'Cloner tous les repos du projet',
@@ -330,6 +469,13 @@ function getRepoStrings(language) {
330
469
  adoFetchingProjects: 'RĆ©cupĆ©ration des projets...',
331
470
  adoNoProjects: 'Aucun projet trouvĆ© dans cette organisation',
332
471
  adoConnectionFailed: 'Ɖchec de connexion Ć  Azure DevOps',
472
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
473
+ githubMultiRepoHeader: 'šŸ“ SĆ©lection multi-dĆ©pĆ“t GitHub',
474
+ githubMultiRepoDesc: 'SƩlectionnez les dƩpƓts de votre organisation GitHub.',
475
+ githubSelectStrategy: 'Comment voulez-vous sƩlectionner les dƩpƓts GitHub?',
476
+ bitbucketMultiRepoHeader: 'šŸ“ SĆ©lection multi-dĆ©pĆ“t Bitbucket',
477
+ bitbucketMultiRepoDesc: 'SƩlectionnez les dƩpƓts de votre espace de travail Bitbucket.',
478
+ bitbucketSelectStrategy: 'Comment voulez-vous sƩlectionner les dƩpƓts Bitbucket?',
333
479
  },
334
480
  ja: {
335
481
  header: 'šŸ“¦ ćƒŖćƒć‚øćƒˆćƒŖćƒ›ć‚¹ćƒ†ć‚£ćƒ³ć‚°',
@@ -347,7 +493,7 @@ function getRepoStrings(language) {
347
493
  other: 'ćć®ä»–ļ¼ˆGitLabなど - 近旄公開)',
348
494
  adoCloneQuestion: 'ć‚Æćƒ­ćƒ¼ćƒ³ć™ć‚‹ćƒŖćƒåćƒ‘ć‚æćƒ¼ćƒ³ć‚’å…„åŠ›ļ¼ˆä¾‹: sw-* ć¾ćŸćÆē©ŗē™½ć§ć‚¹ć‚­ćƒƒćƒ—ļ¼‰:',
349
495
  adoCloneSkip: 'ć‚Æćƒ­ćƒ¼ćƒ³ć‚’ć‚¹ć‚­ćƒƒćƒ— - å¾Œć§čØ­å®šåÆčƒ½',
350
- // New unified selection strings
496
+ // Unified selection strings
351
497
  adoSelectStrategy: 'ć‚Æćƒ­ćƒ¼ćƒ³ć™ć‚‹ćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
352
498
  adoAllRepos: 'すべて',
353
499
  adoAllReposDesc: 'ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆć®ć™ć¹ć¦ć®ćƒŖćƒć‚øćƒˆćƒŖć‚’ć‚Æćƒ­ćƒ¼ćƒ³',
@@ -375,6 +521,13 @@ function getRepoStrings(language) {
375
521
  adoFetchingProjects: 'ćƒ—ćƒ­ć‚øć‚§ć‚Æćƒˆć‚’å–å¾—äø­...',
376
522
  adoNoProjects: 'ć“ć®ēµ„ē¹”ć«ćƒ—ćƒ­ć‚øć‚§ć‚ÆćƒˆćŒč¦‹ć¤ć‹ć‚Šć¾ć›ć‚“',
377
523
  adoConnectionFailed: 'Azure DevOps ćøć®ęŽ„ē¶šć«å¤±ę•—ć—ć¾ć—ćŸ',
524
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
525
+ githubMultiRepoHeader: 'šŸ“ GitHub ćƒžćƒ«ćƒćƒŖćƒć‚øćƒˆćƒŖéøęŠž',
526
+ githubMultiRepoDesc: 'GitHubēµ„ē¹”ć‹ć‚‰ćƒŖćƒć‚øćƒˆćƒŖć‚’éøęŠžć—ć¦ćć ć•ć„ć€‚',
527
+ githubSelectStrategy: 'GitHubćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
528
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket ćƒžćƒ«ćƒćƒŖćƒć‚øćƒˆćƒŖéøęŠž',
529
+ bitbucketMultiRepoDesc: 'BitbucketćƒÆćƒ¼ć‚Æć‚¹ćƒšćƒ¼ć‚¹ć‹ć‚‰ćƒŖćƒć‚øćƒˆćƒŖć‚’éøęŠžć—ć¦ćć ć•ć„ć€‚',
530
+ bitbucketSelectStrategy: 'BitbucketćƒŖćƒć‚øćƒˆćƒŖć‚’ć©ć®ć‚ˆć†ć«éøęŠžć—ć¾ć™ć‹ļ¼Ÿ',
378
531
  },
379
532
  ko: {
380
533
  header: 'šŸ“¦ ģ €ģž„ģ†Œ ķ˜øģŠ¤ķŒ…',
@@ -392,7 +545,7 @@ function getRepoStrings(language) {
392
545
  other: 'źø°ķƒ€ (GitLab 등 - ź³§ ģ¶œģ‹œ)',
393
546
  adoCloneQuestion: 'ė³µģ œķ•  ģ €ģž„ģ†Œ ģ“ė¦„ ķŒØķ„“ ģž…ė „ (예: sw-* ė˜ėŠ” ė¹„ģ›Œė‘źø°):',
394
547
  adoCloneSkip: '복제 ź±“ė„ˆėœ€ - ė‚˜ģ¤‘ģ— 설정 ź°€ėŠ„',
395
- // New unified selection strings
548
+ // Unified selection strings
396
549
  adoSelectStrategy: 'ė³µģ œķ•  ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
397
550
  adoAllRepos: '모두',
398
551
  adoAllReposDesc: 'ķ”„ė”œģ ķŠøģ˜ ėŖØė“  ģ €ģž„ģ†Œ 복제',
@@ -420,6 +573,13 @@ function getRepoStrings(language) {
420
573
  adoFetchingProjects: 'ķ”„ė”œģ ķŠø ź°€ģ øģ˜¤ėŠ” 중...',
421
574
  adoNoProjects: 'ģ“ ģ”°ģ§ģ—ģ„œ ķ”„ė”œģ ķŠøė„¼ ģ°¾ģ„ 수 ģ—†ģŠµė‹ˆė‹¤',
422
575
  adoConnectionFailed: 'Azure DevOps ģ—°ź²° ģ‹¤ķŒØ',
576
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
577
+ githubMultiRepoHeader: 'šŸ“ GitHub 다중 ģ €ģž„ģ†Œ ģ„ ķƒ',
578
+ githubMultiRepoDesc: 'GitHub ģ”°ģ§ģ—ģ„œ ģ €ģž„ģ†Œė„¼ ģ„ ķƒķ•˜ģ„øģš”.',
579
+ githubSelectStrategy: 'GitHub ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
580
+ bitbucketMultiRepoHeader: 'šŸ“ Bitbucket 다중 ģ €ģž„ģ†Œ ģ„ ķƒ',
581
+ bitbucketMultiRepoDesc: 'Bitbucket ģ›Œķ¬ģŠ¤ķŽ˜ģ“ģŠ¤ģ—ģ„œ ģ €ģž„ģ†Œė„¼ ģ„ ķƒķ•˜ģ„øģš”.',
582
+ bitbucketSelectStrategy: 'Bitbucket ģ €ģž„ģ†Œė„¼ ģ–“ė–»ź²Œ ģ„ ķƒķ•˜ģ‹œź² ģŠµė‹ˆź¹Œ?',
423
583
  },
424
584
  pt: {
425
585
  header: 'šŸ“¦ Hospedagem do repositório',
@@ -437,7 +597,7 @@ function getRepoStrings(language) {
437
597
  other: 'Outro (GitLab, etc - em breve)',
438
598
  adoCloneQuestion: 'Digite padrĆ£o de nome de repo para clonar (ex. sw-* ou deixe vazio):',
439
599
  adoCloneSkip: 'Clonagem ignorada - configurĆ”vel depois',
440
- // New unified selection strings
600
+ // Unified selection strings
441
601
  adoSelectStrategy: 'Como vocĆŖ quer selecionar repositórios para clonar?',
442
602
  adoAllRepos: 'Todos',
443
603
  adoAllReposDesc: 'Clonar todos os repositórios do projeto',
@@ -465,113 +625,112 @@ function getRepoStrings(language) {
465
625
  adoFetchingProjects: 'Buscando projetos...',
466
626
  adoNoProjects: 'Nenhum projeto encontrado nesta organização',
467
627
  adoConnectionFailed: 'Falha ao conectar ao Azure DevOps',
628
+ // GitHub/Bitbucket multi-repo strings (v0.32.6+)
629
+ githubMultiRepoHeader: 'šŸ“ Seleção de mĆŗltiplos repositórios GitHub',
630
+ githubMultiRepoDesc: 'Selecione os repositórios da sua organização GitHub.',
631
+ githubSelectStrategy: 'Como você quer selecionar os repositórios do GitHub?',
632
+ bitbucketMultiRepoHeader: 'šŸ“ Seleção de mĆŗltiplos repositórios Bitbucket',
633
+ bitbucketMultiRepoDesc: 'Selecione os repositórios do seu workspace Bitbucket.',
634
+ bitbucketSelectStrategy: 'Como você quer selecionar os repositórios do Bitbucket?',
468
635
  },
469
636
  };
470
637
  return strings[language] || strings.en;
471
638
  }
472
639
  /**
473
- * Fetch all accessible projects for an ADO organization
640
+ * Prompt for multi-repo pattern selection (unified for GitHub, Bitbucket, ADO)
641
+ *
642
+ * @param provider - The git provider ('github' | 'bitbucket' | 'ado')
643
+ * @param strings - Localized strings
644
+ * @returns Selected pattern result
474
645
  */
475
- async function fetchAdoProjects(org, pat) {
476
- const auth = Buffer.from(`:${pat}`).toString('base64');
477
- const endpoint = `https://dev.azure.com/${org}/_apis/projects?api-version=7.0`;
478
- const response = await fetch(endpoint, {
479
- headers: { 'Authorization': `Basic ${auth}`, 'Accept': 'application/json' }
480
- });
481
- if (!response.ok) {
482
- // Read response body to check for HTML error pages
483
- const errorBody = await response.text();
484
- const looksLikeHtml = errorBody.trim().startsWith('<!DOCTYPE') || errorBody.trim().startsWith('<html');
485
- if (looksLikeHtml) {
486
- throw new Error(`Azure DevOps returned an error page (HTTP ${response.status}).\n` +
487
- ` This usually indicates an authentication or configuration issue.\n` +
488
- ` \n` +
489
- ` Possible causes:\n` +
490
- ` • Invalid or expired Personal Access Token (PAT)\n` +
491
- ` • Incorrect organization name "${org}"\n` +
492
- ` • Corporate firewall or proxy intercepting the request\n` +
493
- ` • SSO/authentication redirect (try accessing Azure DevOps in browser first)`);
494
- }
495
- throw new Error(`Failed to fetch projects: ${response.status} - ${errorBody.substring(0, 200)}`);
646
+ async function promptMultiRepoPatternSelection(provider, strings) {
647
+ // Display provider-specific header
648
+ if (provider === 'github') {
649
+ console.log(chalk.blue(`\n${strings.githubMultiRepoHeader}\n`));
650
+ console.log(chalk.gray(` ${strings.githubMultiRepoDesc}\n`));
496
651
  }
497
- // Use safe JSON parser to detect HTML responses even on 200 OK
498
- const data = await safeParseJsonResponse(response, 'Azure DevOps');
499
- return (data.value || []).map((p) => ({ name: p.name, id: p.id }));
500
- }
501
- /**
502
- * Prompt user for ADO organization, credentials, and project selection
503
- * For repository cloning in multi-repo setups
504
- */
505
- async function promptAdoProjectSelection(targetDir, strings) {
506
- // Check for existing credentials in .env or environment
507
- let existingOrg;
508
- let existingPat;
509
- const envContent = readEnvFile(targetDir);
510
- if (envContent) {
511
- const parsed = parseEnvFile(envContent);
512
- existingOrg = parsed.AZURE_DEVOPS_ORG;
513
- existingPat = parsed.AZURE_DEVOPS_PAT;
652
+ else if (provider === 'bitbucket') {
653
+ console.log(chalk.blue(`\n${strings.bitbucketMultiRepoHeader}\n`));
654
+ console.log(chalk.gray(` ${strings.bitbucketMultiRepoDesc}\n`));
514
655
  }
515
- // Fall back to environment variables
516
- if (!existingOrg || !existingPat) {
517
- const auth = getAzureDevOpsAuth();
518
- if (auth) {
519
- existingOrg = existingOrg || auth.org;
520
- existingPat = existingPat || auth.pat;
521
- }
656
+ else {
657
+ console.log(chalk.blue('\nšŸ“ Azure DevOps Repository Selection\n'));
658
+ console.log(chalk.gray(' Select which repositories to clone from your ADO project.\n'));
522
659
  }
523
- // Prompt for organization
524
- const org = await input({
525
- message: strings.adoOrgPrompt,
526
- default: existingOrg,
527
- validate: (value) => {
528
- if (!value.trim())
529
- return 'Organization name is required';
530
- return true;
531
- }
660
+ // Get provider-specific strategy message
661
+ const strategyMessage = provider === 'github'
662
+ ? strings.githubSelectStrategy
663
+ : provider === 'bitbucket'
664
+ ? strings.bitbucketSelectStrategy
665
+ : strings.adoSelectStrategy;
666
+ const strategyChoices = [
667
+ {
668
+ name: `${chalk.green('āœ“')} ${strings.adoAllRepos} ${chalk.gray(`- ${strings.adoAllReposDesc}`)}`,
669
+ value: 'all',
670
+ },
671
+ {
672
+ name: `${strings.adoPatternGlob} ${chalk.gray(`- ${strings.adoPatternGlobDesc}`)}`,
673
+ value: 'pattern-glob',
674
+ },
675
+ {
676
+ name: `${strings.adoPatternRegex} ${chalk.gray(`- ${strings.adoPatternRegexDesc}`)}`,
677
+ value: 'pattern-regex',
678
+ },
679
+ {
680
+ name: `${strings.adoSkipOption} ${chalk.gray(`- ${strings.adoSkipDesc}`)}`,
681
+ value: 'skip',
682
+ },
683
+ ];
684
+ const strategy = await select({
685
+ message: strategyMessage,
686
+ choices: strategyChoices,
687
+ default: 'all',
532
688
  });
533
- // Prompt for PAT
534
- const pat = await password({
535
- message: strings.adoPatPrompt,
536
- mask: true,
537
- validate: (value) => {
538
- if (!value.trim())
539
- return 'Personal Access Token is required';
540
- return true;
689
+ switch (strategy) {
690
+ case 'all': {
691
+ console.log(chalk.green(` āœ“ ${strings.adoSelectedAll}`));
692
+ return { strategy: 'all', pattern: '*' };
693
+ }
694
+ case 'pattern-glob': {
695
+ console.log(chalk.gray(`\n šŸ’” ${strings.adoPatternHint}`));
696
+ console.log(chalk.gray(` šŸ’” ${strings.adoShortcutsHint}\n`));
697
+ const pattern = await input({
698
+ message: strings.adoPatternPrompt,
699
+ validate: (value) => {
700
+ if (!value.trim())
701
+ return strings.adoPatternRequired;
702
+ return true;
703
+ },
704
+ });
705
+ // Parse shortcuts (starts:, ends:, contains:)
706
+ const parsedPattern = parsePatternShortcut(pattern.trim());
707
+ const message = strings.adoSelectedPattern.replace('{pattern}', parsedPattern);
708
+ console.log(chalk.green(` āœ“ ${message}`));
709
+ return { strategy: 'pattern-glob', pattern: parsedPattern };
710
+ }
711
+ case 'pattern-regex': {
712
+ console.log(chalk.gray(`\n šŸ’” ${strings.adoRegexHint}\n`));
713
+ const pattern = await input({
714
+ message: strings.adoRegexPrompt,
715
+ validate: (value) => {
716
+ if (!value.trim())
717
+ return strings.adoPatternRequired;
718
+ const validation = validateRegex(value.trim());
719
+ if (validation !== true) {
720
+ return `${strings.adoInvalidRegex}: ${validation}`;
721
+ }
722
+ return true;
723
+ },
724
+ });
725
+ const message = strings.adoSelectedRegex.replace('{pattern}', pattern.trim());
726
+ console.log(chalk.green(` āœ“ ${message}`));
727
+ return { strategy: 'pattern-regex', pattern: pattern.trim(), isRegex: true };
728
+ }
729
+ case 'skip': {
730
+ console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
731
+ return { strategy: 'skip' };
541
732
  }
542
- });
543
- // Fetch projects
544
- const spinner = ora(strings.adoFetchingProjects).start();
545
- let projects;
546
- try {
547
- projects = await fetchAdoProjects(org, pat);
548
- spinner.succeed();
549
- }
550
- catch (error) {
551
- spinner.fail(strings.adoConnectionFailed);
552
- console.log(chalk.red(` ${error instanceof Error ? error.message : 'Unknown error'}`));
553
- return null;
554
- }
555
- if (projects.length === 0) {
556
- console.log(chalk.yellow(` ${strings.adoNoProjects}`));
557
- return null;
558
733
  }
559
- // Let user select projects
560
- const selectedProjects = await checkbox({
561
- message: strings.adoProjectPrompt,
562
- choices: projects.map((p, index) => ({
563
- name: p.name,
564
- value: p.name,
565
- checked: index === 0 // First project selected by default
566
- })),
567
- required: true
568
- });
569
- console.log(chalk.green(` āœ“ ${strings.adoProjectsSelected.replace('{count}', String(selectedProjects.length))}`));
570
- return {
571
- org,
572
- pat,
573
- projects: selectedProjects
574
- };
575
734
  }
576
735
  /**
577
736
  * Prompt user for repository hosting configuration
@@ -643,95 +802,40 @@ export async function setupRepositoryHosting(options) {
643
802
  else {
644
803
  repositoryHosting = `${provider}-${structure}`;
645
804
  }
646
- // Step 3: For ADO multi-repo, prompt for project selection and clone pattern
805
+ // Step 3: For multi-repo setups, prompt for pattern selection
806
+ // Now unified for ADO, GitHub, and Bitbucket (v0.32.6+)
647
807
  let adoClonePattern;
648
808
  let adoClonePatternResult;
649
809
  let adoProjectSelection;
650
- if (provider === 'ado' && isMultiRepo) {
651
- // Step 3a: Prompt for ADO organization and project selection
652
- console.log(chalk.blue('\nšŸ“ Azure DevOps Project Selection\n'));
653
- console.log(chalk.gray(' Select which project(s) to clone repositories from.\n'));
654
- const projectSelection = await promptAdoProjectSelection(options.targetDir, strings);
655
- if (projectSelection) {
656
- adoProjectSelection = projectSelection;
657
- }
658
- // Step 3b: Show unified strategy selection
659
- const strategyChoices = [
660
- {
661
- name: `${chalk.green('āœ“')} ${strings.adoAllRepos} ${chalk.gray(`- ${strings.adoAllReposDesc}`)}`,
662
- value: 'all',
663
- },
664
- {
665
- name: `${strings.adoPatternGlob} ${chalk.gray(`- ${strings.adoPatternGlobDesc}`)}`,
666
- value: 'pattern-glob',
667
- },
668
- {
669
- name: `${strings.adoPatternRegex} ${chalk.gray(`- ${strings.adoPatternRegexDesc}`)}`,
670
- value: 'pattern-regex',
671
- },
672
- {
673
- name: `${strings.adoSkipOption} ${chalk.gray(`- ${strings.adoSkipDesc}`)}`,
674
- value: 'skip',
675
- },
676
- ];
677
- const strategy = await select({
678
- message: strings.adoSelectStrategy,
679
- choices: strategyChoices,
680
- default: 'all',
681
- });
682
- switch (strategy) {
683
- case 'all': {
684
- adoClonePattern = '*';
685
- adoClonePatternResult = { strategy: 'all', pattern: '*' };
686
- console.log(chalk.green(` āœ“ ${strings.adoSelectedAll}`));
687
- break;
688
- }
689
- case 'pattern-glob': {
690
- console.log(chalk.gray(`\n šŸ’” ${strings.adoPatternHint}`));
691
- console.log(chalk.gray(` šŸ’” ${strings.adoShortcutsHint}\n`));
692
- const pattern = await input({
693
- message: strings.adoPatternPrompt,
694
- validate: (value) => {
695
- if (!value.trim())
696
- return strings.adoPatternRequired;
697
- return true;
698
- },
699
- });
700
- // Parse shortcuts (starts:, ends:, contains:)
701
- const parsedPattern = parsePatternShortcut(pattern.trim());
702
- adoClonePattern = parsedPattern;
703
- adoClonePatternResult = { strategy: 'pattern-glob', pattern: parsedPattern };
704
- const message = strings.adoSelectedPattern.replace('{pattern}', parsedPattern);
705
- console.log(chalk.green(` āœ“ ${message}`));
706
- break;
707
- }
708
- case 'pattern-regex': {
709
- console.log(chalk.gray(`\n šŸ’” ${strings.adoRegexHint}\n`));
710
- const pattern = await input({
711
- message: strings.adoRegexPrompt,
712
- validate: (value) => {
713
- if (!value.trim())
714
- return strings.adoPatternRequired;
715
- const validation = validateRegex(value.trim());
716
- if (validation !== true) {
717
- return `${strings.adoInvalidRegex}: ${validation}`;
718
- }
719
- return true;
720
- },
721
- });
722
- // Store as regex: prefix for downstream processing
723
- adoClonePattern = `regex:${pattern.trim()}`;
724
- adoClonePatternResult = { strategy: 'pattern-regex', pattern: pattern.trim(), isRegex: true };
725
- const message = strings.adoSelectedRegex.replace('{pattern}', pattern.trim());
726
- console.log(chalk.green(` āœ“ ${message}`));
727
- break;
728
- }
729
- case 'skip': {
730
- adoClonePatternResult = { strategy: 'skip' };
731
- console.log(chalk.gray(` → ${strings.adoCloneSkip}`));
732
- break;
810
+ if (isMultiRepo && (provider === 'ado' || provider === 'github' || provider === 'bitbucket')) {
811
+ // Step 3a: For ADO only - prompt for organization and project selection
812
+ if (provider === 'ado') {
813
+ console.log(chalk.blue('\nšŸ“ Azure DevOps Project Selection\n'));
814
+ console.log(chalk.gray(' Select which project(s) to clone repositories from.\n'));
815
+ const projectSelection = await promptAdoProjectSelection(options.targetDir, strings);
816
+ if (projectSelection) {
817
+ adoProjectSelection = projectSelection;
733
818
  }
734
819
  }
820
+ // Step 3b: Show unified strategy selection (ALL providers)
821
+ const patternResult = await promptMultiRepoPatternSelection(provider, strings);
822
+ // Map to ADO-style result for backward compatibility
823
+ adoClonePatternResult = {
824
+ strategy: patternResult.strategy,
825
+ pattern: patternResult.pattern,
826
+ isRegex: patternResult.isRegex,
827
+ };
828
+ // Set the clone pattern string
829
+ if (patternResult.strategy === 'all') {
830
+ adoClonePattern = '*';
831
+ }
832
+ else if (patternResult.strategy === 'pattern-glob') {
833
+ adoClonePattern = patternResult.pattern;
834
+ }
835
+ else if (patternResult.strategy === 'pattern-regex') {
836
+ adoClonePattern = `regex:${patternResult.pattern}`;
837
+ }
838
+ // For 'skip', adoClonePattern remains undefined
735
839
  }
736
840
  return { hosting: repositoryHosting, isMultiRepo, adoClonePattern, adoClonePatternResult, adoProjectSelection };
737
841
  }