specweave 0.24.0 → 0.24.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 (42) hide show
  1. package/.claude-plugin/marketplace.json +55 -0
  2. package/CLAUDE.md +42 -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/types.d.ts +1 -1
  7. package/dist/src/cli/helpers/issue-tracker/types.d.ts.map +1 -1
  8. package/dist/src/config/types.d.ts +24 -24
  9. package/dist/src/core/config/types.d.ts +25 -0
  10. package/dist/src/core/config/types.d.ts.map +1 -1
  11. package/dist/src/core/config/types.js +6 -0
  12. package/dist/src/core/config/types.js.map +1 -1
  13. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +33 -0
  14. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -0
  15. package/dist/src/core/repo-structure/repo-bulk-discovery.js +275 -0
  16. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -0
  17. package/dist/src/core/repo-structure/repo-structure-manager.d.ts +9 -0
  18. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  19. package/dist/src/core/repo-structure/repo-structure-manager.js +255 -87
  20. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  21. package/dist/src/init/architecture/types.d.ts +6 -6
  22. package/dist/src/utils/plugin-validator.d.ts.map +1 -1
  23. package/dist/src/utils/plugin-validator.js +15 -14
  24. package/dist/src/utils/plugin-validator.js.map +1 -1
  25. package/package.json +4 -4
  26. package/plugins/specweave/.claude-plugin/plugin.json +4 -4
  27. package/plugins/specweave/agents/pm/AGENT.md +2 -0
  28. package/plugins/specweave/commands/specweave-do.md +0 -47
  29. package/plugins/specweave/commands/specweave-increment.md +0 -82
  30. package/plugins/specweave/commands/specweave-next.md +0 -47
  31. package/plugins/specweave/hooks/post-task-completion.sh +67 -6
  32. package/plugins/specweave/hooks/pre-edit-spec.sh +11 -0
  33. package/plugins/specweave/hooks/pre-task-completion.sh +69 -2
  34. package/plugins/specweave/hooks/pre-write-spec.sh +11 -0
  35. package/plugins/specweave/skills/increment-planner/SKILL.md +124 -4
  36. package/plugins/specweave-frontend/agents/frontend-architect/AGENT.md +21 -0
  37. package/plugins/specweave-github/hooks/.specweave/logs/hooks-debug.log +150 -0
  38. package/plugins/specweave-payments/commands/stripe-setup.md +931 -0
  39. package/plugins/specweave-payments/commands/subscription-flow.md +1193 -0
  40. package/plugins/specweave-payments/commands/subscription-manage.md +386 -0
  41. package/plugins/specweave-payments/commands/webhook-setup.md +295 -0
  42. package/plugins/specweave-testing/agents/qa-engineer/AGENT.md +21 -0
@@ -14,7 +14,7 @@
14
14
  * - Organize specs per project/team
15
15
  * - Split tasks between repositories
16
16
  */
17
- import fs from 'fs-extra';
17
+ import { existsSync, mkdirSync, writeFileSync } from 'fs';
18
18
  import path from 'path';
19
19
  import chalk from 'chalk';
20
20
  import inquirer from 'inquirer';
@@ -28,6 +28,8 @@ import { generateEnvFile } from '../../utils/env-file-generator.js';
28
28
  import { generateSetupSummary } from './setup-summary.js';
29
29
  import { getArchitecturePrompt, getParentRepoBenefits, getVisibilityPrompt } from './prompt-consolidator.js';
30
30
  import { detectRepositoryHints } from './folder-detector.js';
31
+ import { discoverRepositories } from './repo-bulk-discovery.js';
32
+ import { Octokit } from '@octokit/rest';
31
33
  export class RepoStructureManager {
32
34
  constructor(projectPath, githubToken) {
33
35
  this.projectPath = projectPath;
@@ -127,7 +129,7 @@ export class RepoStructureManager {
127
129
  async configureSingleRepo() {
128
130
  console.log(chalk.cyan('\nšŸ“¦ Single Repository Configuration\n'));
129
131
  // Check if repo already exists
130
- const hasGit = fs.existsSync(path.join(this.projectPath, '.git'));
132
+ const hasGit = existsSync(path.join(this.projectPath, '.git'));
131
133
  if (hasGit) {
132
134
  // Try to detect existing remote
133
135
  try {
@@ -147,15 +149,36 @@ export class RepoStructureManager {
147
149
  default: true
148
150
  }]);
149
151
  if (useExisting) {
152
+ // Fetch description and visibility from GitHub API
153
+ let description = `${repo} - SpecWeave project`;
154
+ let visibility = 'private';
155
+ if (this.githubToken) {
156
+ try {
157
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
158
+ headers: {
159
+ 'Authorization': `Bearer ${this.githubToken}`,
160
+ 'Accept': 'application/vnd.github+json'
161
+ }
162
+ });
163
+ if (response.ok) {
164
+ const data = await response.json();
165
+ description = data.description || description;
166
+ visibility = data.private ? 'private' : 'public';
167
+ }
168
+ }
169
+ catch {
170
+ // Use defaults if fetch fails
171
+ }
172
+ }
150
173
  return {
151
174
  architecture: 'single',
152
175
  repositories: [{
153
176
  id: 'main',
154
177
  name: repo,
155
178
  owner: owner,
156
- description: `${repo} - SpecWeave project`,
179
+ description: description,
157
180
  path: '.',
158
- visibility: 'private', // Default for existing repo
181
+ visibility: visibility, // Fetched from GitHub API
159
182
  createOnGitHub: false,
160
183
  isNested: false
161
184
  }]
@@ -195,19 +218,23 @@ export class RepoStructureManager {
195
218
  default: !hasGit
196
219
  }
197
220
  ]);
198
- // Ask about visibility
199
- const visibilityPrompt = getVisibilityPrompt(answers.repo);
200
- const { visibility } = await inquirer.prompt([{
201
- type: 'list',
202
- name: 'visibility',
203
- message: visibilityPrompt.question,
204
- choices: visibilityPrompt.options.map(opt => ({
205
- name: `${opt.label}\n${chalk.gray(opt.description)}`,
206
- value: opt.value,
207
- short: opt.label
208
- })),
209
- default: visibilityPrompt.default
210
- }]);
221
+ // Ask about visibility only if creating a new repository
222
+ let visibility = 'private';
223
+ if (answers.createOnGitHub) {
224
+ const visibilityPrompt = getVisibilityPrompt(answers.repo);
225
+ const result = await inquirer.prompt([{
226
+ type: 'list',
227
+ name: 'visibility',
228
+ message: visibilityPrompt.question,
229
+ choices: visibilityPrompt.options.map(opt => ({
230
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
231
+ value: opt.value,
232
+ short: opt.label
233
+ })),
234
+ default: visibilityPrompt.default
235
+ }]);
236
+ visibility = result.visibility;
237
+ }
211
238
  return {
212
239
  architecture: 'single',
213
240
  repositories: [{
@@ -360,8 +387,9 @@ export class RepoStructureManager {
360
387
  }
361
388
  }
362
389
  ]);
363
- // Fetch description from GitHub API (or use default)
390
+ // Fetch description and visibility from GitHub API (or use defaults)
364
391
  let description = 'SpecWeave parent repository - specs, docs, and architecture';
392
+ let existingVisibility = 'private';
365
393
  if (this.githubToken) {
366
394
  try {
367
395
  const response = await fetch(`https://api.github.com/repos/${ownerPrompt.owner}/${repoPrompt.parentName}`, {
@@ -373,17 +401,19 @@ export class RepoStructureManager {
373
401
  if (response.ok) {
374
402
  const data = await response.json();
375
403
  description = data.description || description;
404
+ existingVisibility = data.private ? 'private' : 'public';
376
405
  }
377
406
  }
378
407
  catch {
379
- // Use default if fetch fails
408
+ // Use defaults if fetch fails
380
409
  }
381
410
  }
382
411
  parentAnswers = {
383
412
  owner: ownerPrompt.owner,
384
413
  parentName: repoPrompt.parentName,
385
414
  description: description,
386
- createOnGitHub: false // Don't create, it already exists!
415
+ createOnGitHub: false, // Don't create, it already exists!
416
+ visibility: existingVisibility // Use existing visibility from GitHub
387
417
  };
388
418
  console.log(chalk.green(`\nāœ“ Using existing repository: ${ownerPrompt.owner}/${repoPrompt.parentName}\n`));
389
419
  }
@@ -447,9 +477,10 @@ export class RepoStructureManager {
447
477
  parentAnswers = { ...ownerPrompt, ...remainingAnswers };
448
478
  }
449
479
  }
450
- // Ask about visibility for parent repo (only if creating on GitHub)
480
+ // Ask about visibility for parent repo (only if creating NEW repo on GitHub)
451
481
  let parentVisibility = 'private';
452
- if (!isLocalParent) {
482
+ if (!isLocalParent && parentAnswers.createOnGitHub) {
483
+ // Only prompt for visibility when creating a NEW repository
453
484
  const parentVisibilityPrompt = getVisibilityPrompt(parentAnswers.parentName);
454
485
  const result = await inquirer.prompt([{
455
486
  type: 'list',
@@ -464,6 +495,10 @@ export class RepoStructureManager {
464
495
  }]);
465
496
  parentVisibility = result.parentVisibility;
466
497
  }
498
+ else if (!isLocalParent && parentAnswers.visibility) {
499
+ // Use existing repository's visibility (fetched from GitHub API)
500
+ parentVisibility = parentAnswers.visibility;
501
+ }
467
502
  config.parentRepo = {
468
503
  name: parentAnswers.parentName,
469
504
  owner: parentAnswers.owner,
@@ -532,26 +567,85 @@ export class RepoStructureManager {
532
567
  console.log(chalk.green(`\nāœ“ Total repositories to create: ${totalRepos} (1 parent + ${repoCount} implementation)\n`));
533
568
  }
534
569
  }
570
+ // Bulk repository discovery (optimization for many repos)
571
+ let discoveredRepos = [];
572
+ let bulkDiscoveryStrategy = 'manual';
573
+ if (this.githubToken && config.parentRepo) {
574
+ const octokit = new Octokit({ auth: this.githubToken });
575
+ const owner = config.parentRepo.owner;
576
+ const isOrg = await this.isGitHubOrganization(owner);
577
+ const discoveryResult = await discoverRepositories(octokit, owner, isOrg, repoCount);
578
+ if (discoveryResult) {
579
+ bulkDiscoveryStrategy = discoveryResult.strategy;
580
+ if (discoveryResult.strategy !== 'manual') {
581
+ discoveredRepos = discoveryResult.repositories;
582
+ // Update repoCount to match discovered repos
583
+ if (discoveredRepos.length !== repoCount) {
584
+ console.log(chalk.yellow(`\nšŸ“ Adjusting repository count from ${repoCount} to ${discoveredRepos.length} (based on discovery)\n`));
585
+ // Override repoCount with discovered count
586
+ repoCount = discoveredRepos.length;
587
+ }
588
+ }
589
+ }
590
+ }
535
591
  // Configure each repository
536
592
  console.log(chalk.cyan('\nšŸ“¦ Configure Each Repository:\n'));
537
593
  const usedIds = new Set();
538
594
  const configuredRepoNames = []; // Track configured repo names for smart ID generation
539
595
  for (let i = 0; i < repoCount; i++) {
596
+ const discoveredRepo = discoveredRepos[i]; // May be undefined if manual
597
+ const isDiscovered = bulkDiscoveryStrategy !== 'manual' && discoveredRepo;
540
598
  console.log(chalk.white(`\nRepository ${i + 1} of ${repoCount}:`));
541
599
  // Smart suggestion for ALL repos (not just first one!)
542
600
  const projectName = path.basename(this.projectPath);
543
- const suggestedName = suggestRepoName(projectName, i, repoCount);
601
+ const suggestedName = isDiscovered ? discoveredRepo.name : suggestRepoName(projectName, i, repoCount);
602
+ // If discovered, show preview and skip prompts (or allow confirmation)
603
+ if (isDiscovered) {
604
+ console.log(chalk.green(` āœ“ Discovered: ${chalk.bold(discoveredRepo.name)}`));
605
+ console.log(chalk.gray(` Description: ${discoveredRepo.description || '(none)'}`));
606
+ console.log(chalk.gray(` Visibility: ${discoveredRepo.private ? 'Private' : 'Public'}`));
607
+ const { confirmDiscovered } = await inquirer.prompt([{
608
+ type: 'confirm',
609
+ name: 'confirmDiscovered',
610
+ message: 'Use this repository configuration?',
611
+ default: true
612
+ }]);
613
+ if (!confirmDiscovered) {
614
+ // Allow manual override
615
+ console.log(chalk.yellow(` → Switching to manual entry for this repository\n`));
616
+ }
617
+ else {
618
+ // Use discovered repo configuration directly
619
+ const smartId = generateRepoIdSmart(discoveredRepo.name, configuredRepoNames);
620
+ const { id: suggestedId } = ensureUniqueId(smartId, usedIds);
621
+ console.log(chalk.green(` āœ“ Repository ID: ${chalk.bold(suggestedId)} ${chalk.gray('(auto-generated)')}`));
622
+ usedIds.add(suggestedId);
623
+ configuredRepoNames.push(discoveredRepo.name);
624
+ config.repositories.push({
625
+ id: suggestedId,
626
+ name: discoveredRepo.name,
627
+ owner: discoveredRepo.owner,
628
+ description: discoveredRepo.description || `${discoveredRepo.name} service`,
629
+ path: useParent ? suggestedId : suggestedId,
630
+ visibility: discoveredRepo.private ? 'private' : 'public',
631
+ createOnGitHub: false, // Already exists!
632
+ isNested: useParent
633
+ });
634
+ continue; // Skip manual prompts
635
+ }
636
+ }
637
+ // Manual entry (original behavior)
544
638
  const repoAnswers = await inquirer.prompt([
545
639
  {
546
640
  type: 'input',
547
641
  name: 'name',
548
642
  message: 'Repository name:',
549
- default: suggestedName, // Now suggests for ALL repos!
643
+ default: suggestedName,
550
644
  validate: async (input) => {
551
645
  if (!input.trim())
552
646
  return 'Repository name is required';
553
- // Validate repository doesn't exist
554
- if (this.githubToken && config.parentRepo) {
647
+ // Validate repository doesn't exist (skip for discovered repos)
648
+ if (!isDiscovered && this.githubToken && config.parentRepo) {
555
649
  const result = await validateRepository(config.parentRepo.owner, input, this.githubToken);
556
650
  if (result.exists) {
557
651
  return `Repository ${config.parentRepo.owner}/${input} already exists at ${result.url}`;
@@ -570,7 +664,7 @@ export class RepoStructureManager {
570
664
  type: 'confirm',
571
665
  name: 'createOnGitHub',
572
666
  message: 'Create this repository on GitHub?',
573
- default: true
667
+ default: !isDiscovered // Default to true for new repos, false for discovered
574
668
  }
575
669
  ]);
576
670
  // Smart auto-generate ID from repository name (context-aware)
@@ -616,19 +710,23 @@ export class RepoStructureManager {
616
710
  }
617
711
  usedIds.add(id);
618
712
  configuredRepoNames.push(repoAnswers.name);
619
- // Ask about visibility
620
- const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
621
- const { visibility } = await inquirer.prompt([{
622
- type: 'list',
623
- name: 'visibility',
624
- message: visibilityPrompt.question,
625
- choices: visibilityPrompt.options.map(opt => ({
626
- name: `${opt.label}\n${chalk.gray(opt.description)}`,
627
- value: opt.value,
628
- short: opt.label
629
- })),
630
- default: visibilityPrompt.default
631
- }]);
713
+ // Ask about visibility only if creating a new repository
714
+ let visibility = 'private';
715
+ if (repoAnswers.createOnGitHub) {
716
+ const visibilityPrompt = getVisibilityPrompt(repoAnswers.name);
717
+ const result = await inquirer.prompt([{
718
+ type: 'list',
719
+ name: 'visibility',
720
+ message: visibilityPrompt.question,
721
+ choices: visibilityPrompt.options.map(opt => ({
722
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
723
+ value: opt.value,
724
+ short: opt.label
725
+ })),
726
+ default: visibilityPrompt.default
727
+ }]);
728
+ visibility = result.visibility;
729
+ }
632
730
  config.repositories.push({
633
731
  id: id,
634
732
  name: repoAnswers.name,
@@ -713,22 +811,26 @@ export class RepoStructureManager {
713
811
  type: 'confirm',
714
812
  name: 'createOnGitHub',
715
813
  message: 'Create repository on GitHub?',
716
- default: !fs.existsSync(path.join(this.projectPath, '.git'))
814
+ default: !existsSync(path.join(this.projectPath, '.git'))
717
815
  }
718
816
  ]);
719
- // Ask about visibility
720
- const visibilityPrompt = getVisibilityPrompt(answers.repo);
721
- const { visibility } = await inquirer.prompt([{
722
- type: 'list',
723
- name: 'visibility',
724
- message: visibilityPrompt.question,
725
- choices: visibilityPrompt.options.map(opt => ({
726
- name: `${opt.label}\n${chalk.gray(opt.description)}`,
727
- value: opt.value,
728
- short: opt.label
729
- })),
730
- default: visibilityPrompt.default
731
- }]);
817
+ // Ask about visibility only if creating a new repository
818
+ let visibility = 'private';
819
+ if (answers.createOnGitHub) {
820
+ const visibilityPrompt = getVisibilityPrompt(answers.repo);
821
+ const result = await inquirer.prompt([{
822
+ type: 'list',
823
+ name: 'visibility',
824
+ message: visibilityPrompt.question,
825
+ choices: visibilityPrompt.options.map(opt => ({
826
+ name: `${opt.label}\n${chalk.gray(opt.description)}`,
827
+ value: opt.value,
828
+ short: opt.label
829
+ })),
830
+ default: visibilityPrompt.default
831
+ }]);
832
+ visibility = result.visibility;
833
+ }
732
834
  const projects = answers.projects.split(',').map((p) => p.trim());
733
835
  return {
734
836
  architecture: 'monorepo',
@@ -942,6 +1044,81 @@ export class RepoStructureManager {
942
1044
  }
943
1045
  return false;
944
1046
  }
1047
+ /**
1048
+ * Check if a repository exists on GitHub
1049
+ */
1050
+ async repositoryExistsOnGitHub(owner, repo) {
1051
+ if (!this.githubToken) {
1052
+ return false;
1053
+ }
1054
+ try {
1055
+ const response = await fetch(`https://api.github.com/repos/${owner}/${repo}`, {
1056
+ headers: {
1057
+ 'Authorization': `Bearer ${this.githubToken}`,
1058
+ 'Accept': 'application/vnd.github+json'
1059
+ }
1060
+ });
1061
+ return response.ok;
1062
+ }
1063
+ catch {
1064
+ return false;
1065
+ }
1066
+ }
1067
+ /**
1068
+ * Clone or initialize a repository
1069
+ * If the repo exists on GitHub, clone it; otherwise, init + add remote
1070
+ */
1071
+ async cloneOrInitRepository(repoPath, owner, name, createOnGitHub) {
1072
+ // If .git already exists, skip
1073
+ if (existsSync(path.join(repoPath, '.git'))) {
1074
+ return;
1075
+ }
1076
+ const remoteUrl = `https://github.com/${owner}/${name}.git`;
1077
+ // Check if repository exists on GitHub
1078
+ const repoExists = await this.repositoryExistsOnGitHub(owner, name);
1079
+ if (repoExists) {
1080
+ // Repository exists - clone it
1081
+ console.log(chalk.gray(` → Cloning existing repository from GitHub...`));
1082
+ try {
1083
+ // Remove directory if it exists and is empty
1084
+ if (existsSync(repoPath)) {
1085
+ const files = require('fs').readdirSync(repoPath);
1086
+ if (files.length === 0) {
1087
+ require('fs').rmdirSync(repoPath);
1088
+ }
1089
+ }
1090
+ // Clone the repository
1091
+ const parentDir = path.dirname(repoPath);
1092
+ const repoName = path.basename(repoPath);
1093
+ execFileNoThrowSync('git', ['clone', remoteUrl, repoName], { cwd: parentDir });
1094
+ console.log(chalk.green(` āœ“ Cloned ${owner}/${name}`));
1095
+ }
1096
+ catch (error) {
1097
+ console.log(chalk.yellow(` āš ļø Clone failed: ${error.message}`));
1098
+ console.log(chalk.gray(` → Falling back to init + remote`));
1099
+ // Fallback: init + add remote
1100
+ if (!existsSync(repoPath)) {
1101
+ mkdirSync(repoPath, { recursive: true });
1102
+ }
1103
+ execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1104
+ execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1105
+ }
1106
+ }
1107
+ else {
1108
+ // Repository doesn't exist - init + add remote
1109
+ if (!createOnGitHub) {
1110
+ console.log(chalk.gray(` → Repository will be created later (skipping for now)`));
1111
+ return;
1112
+ }
1113
+ console.log(chalk.gray(` → Initializing empty git repository...`));
1114
+ if (!existsSync(repoPath)) {
1115
+ mkdirSync(repoPath, { recursive: true });
1116
+ }
1117
+ execFileNoThrowSync('git', ['init'], { cwd: repoPath });
1118
+ execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1119
+ console.log(chalk.green(` āœ“ Initialized ${owner}/${name}`));
1120
+ }
1121
+ }
945
1122
  /**
946
1123
  * Initialize local git repositories
947
1124
  */
@@ -951,7 +1128,7 @@ export class RepoStructureManager {
951
1128
  if (config.architecture === 'parent') {
952
1129
  // Parent repo approach: ROOT-LEVEL cloning (not services/!)
953
1130
  // Initialize parent repo at root
954
- if (!fs.existsSync(path.join(this.projectPath, '.git'))) {
1131
+ if (!existsSync(path.join(this.projectPath, '.git'))) {
955
1132
  execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
956
1133
  if (config.parentRepo) {
957
1134
  const remoteUrl = `https://github.com/${config.parentRepo.owner}/${config.parentRepo.name}.git`;
@@ -961,38 +1138,29 @@ export class RepoStructureManager {
961
1138
  // Initialize implementation repos at ROOT LEVEL
962
1139
  for (const repo of config.repositories) {
963
1140
  const repoPath = path.join(this.projectPath, repo.path);
964
- // Create directory if needed
965
- if (!fs.existsSync(repoPath)) {
966
- fs.mkdirSync(repoPath, { recursive: true });
967
- }
968
- // Initialize git
969
- if (!fs.existsSync(path.join(repoPath, '.git'))) {
970
- execFileNoThrowSync('git', ['init'], { cwd: repoPath });
971
- const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
972
- execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1141
+ // Clone or initialize repository
1142
+ await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub);
1143
+ // Create basic structure (only if repo was just initialized, not cloned)
1144
+ if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
1145
+ this.createBasicRepoStructure(repoPath, repo.id);
973
1146
  }
974
- // Create basic structure
975
- this.createBasicRepoStructure(repoPath, repo.id);
976
1147
  }
977
1148
  }
978
1149
  else if (config.architecture === 'multi-repo') {
979
1150
  // Standard multi-repo: repos as subdirectories
980
1151
  for (const repo of config.repositories) {
981
1152
  const repoPath = path.join(this.projectPath, repo.path);
982
- if (!fs.existsSync(repoPath)) {
983
- fs.mkdirSync(repoPath, { recursive: true });
984
- }
985
- if (!fs.existsSync(path.join(repoPath, '.git'))) {
986
- execFileNoThrowSync('git', ['init'], { cwd: repoPath });
987
- const remoteUrl = `https://github.com/${repo.owner}/${repo.name}.git`;
988
- execFileNoThrowSync('git', ['remote', 'add', 'origin', remoteUrl], { cwd: repoPath });
1153
+ // Clone or initialize repository
1154
+ await this.cloneOrInitRepository(repoPath, repo.owner, repo.name, repo.createOnGitHub);
1155
+ // Create basic structure (only if repo was just initialized, not cloned)
1156
+ if (!repo.createOnGitHub || !await this.repositoryExistsOnGitHub(repo.owner, repo.name)) {
1157
+ this.createBasicRepoStructure(repoPath, repo.id);
989
1158
  }
990
- this.createBasicRepoStructure(repoPath, repo.id);
991
1159
  }
992
1160
  }
993
1161
  else {
994
1162
  // Single repo or monorepo
995
- if (!fs.existsSync(path.join(this.projectPath, '.git'))) {
1163
+ if (!existsSync(path.join(this.projectPath, '.git'))) {
996
1164
  execFileNoThrowSync('git', ['init'], { cwd: this.projectPath });
997
1165
  const repo = config.repositories[0];
998
1166
  if (repo) {
@@ -1004,8 +1172,8 @@ export class RepoStructureManager {
1004
1172
  if (config.architecture === 'monorepo' && config.monorepoProjects) {
1005
1173
  for (const project of config.monorepoProjects) {
1006
1174
  const projectPath = path.join(this.projectPath, 'packages', project);
1007
- if (!fs.existsSync(projectPath)) {
1008
- fs.mkdirSync(projectPath, { recursive: true });
1175
+ if (!existsSync(projectPath)) {
1176
+ mkdirSync(projectPath, { recursive: true });
1009
1177
  }
1010
1178
  this.createBasicRepoStructure(projectPath, project);
1011
1179
  }
@@ -1024,21 +1192,21 @@ export class RepoStructureManager {
1024
1192
  const dirs = ['src', 'tests'];
1025
1193
  for (const dir of dirs) {
1026
1194
  const dirPath = path.join(repoPath, dir);
1027
- if (!fs.existsSync(dirPath)) {
1028
- fs.mkdirSync(dirPath, { recursive: true });
1195
+ if (!existsSync(dirPath)) {
1196
+ mkdirSync(dirPath, { recursive: true });
1029
1197
  }
1030
1198
  }
1031
1199
  // Create README.md
1032
1200
  const readmePath = path.join(repoPath, 'README.md');
1033
- if (!fs.existsSync(readmePath)) {
1201
+ if (!existsSync(readmePath)) {
1034
1202
  const readmeContent = `# ${projectId}\n\n${projectId} service/component.\n\nPart of SpecWeave multi-repository project.\n`;
1035
- fs.writeFileSync(readmePath, readmeContent);
1203
+ writeFileSync(readmePath, readmeContent);
1036
1204
  }
1037
1205
  // Create .gitignore
1038
1206
  const gitignorePath = path.join(repoPath, '.gitignore');
1039
- if (!fs.existsSync(gitignorePath)) {
1207
+ if (!existsSync(gitignorePath)) {
1040
1208
  const gitignoreContent = `node_modules/\ndist/\n.env\n.DS_Store\n*.log\n`;
1041
- fs.writeFileSync(gitignorePath, gitignoreContent);
1209
+ writeFileSync(gitignorePath, gitignoreContent);
1042
1210
  }
1043
1211
  }
1044
1212
  /**
@@ -1054,8 +1222,8 @@ export class RepoStructureManager {
1054
1222
  // Monorepo: create specs folder for each project
1055
1223
  for (const project of config.monorepoProjects) {
1056
1224
  const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', project.toLowerCase());
1057
- if (!fs.existsSync(projectSpecPath)) {
1058
- fs.mkdirSync(projectSpecPath, { recursive: true });
1225
+ if (!existsSync(projectSpecPath)) {
1226
+ mkdirSync(projectSpecPath, { recursive: true });
1059
1227
  }
1060
1228
  console.log(chalk.gray(` āœ“ Created project structure: ${project}`));
1061
1229
  }
@@ -1064,8 +1232,8 @@ export class RepoStructureManager {
1064
1232
  // Multi-repo: create specs folder for each repository
1065
1233
  for (const repo of config.repositories) {
1066
1234
  const projectSpecPath = path.join(specweavePath, 'docs', 'internal', 'specs', repo.id);
1067
- if (!fs.existsSync(projectSpecPath)) {
1068
- fs.mkdirSync(projectSpecPath, { recursive: true });
1235
+ if (!existsSync(projectSpecPath)) {
1236
+ mkdirSync(projectSpecPath, { recursive: true });
1069
1237
  }
1070
1238
  console.log(chalk.gray(` āœ“ Created project structure: ${repo.id}`));
1071
1239
  }