specweave 0.23.8 → 0.23.12

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 (130) hide show
  1. package/.claude-plugin/marketplace.json +7 -7
  2. package/CLAUDE.md +391 -1338
  3. package/dist/src/cli/commands/cleanup-cache.d.ts +14 -0
  4. package/dist/src/cli/commands/cleanup-cache.d.ts.map +1 -0
  5. package/dist/src/cli/commands/cleanup-cache.js +63 -0
  6. package/dist/src/cli/commands/cleanup-cache.js.map +1 -0
  7. package/dist/src/cli/commands/init.js +40 -0
  8. package/dist/src/cli/commands/init.js.map +1 -1
  9. package/dist/src/cli/commands/migrate-config.d.ts +22 -0
  10. package/dist/src/cli/commands/migrate-config.d.ts.map +1 -0
  11. package/dist/src/cli/commands/migrate-config.js +149 -0
  12. package/dist/src/cli/commands/migrate-config.js.map +1 -0
  13. package/dist/src/cli/helpers/async-project-loader.d.ts +148 -0
  14. package/dist/src/cli/helpers/async-project-loader.d.ts.map +1 -0
  15. package/dist/src/cli/helpers/async-project-loader.js +351 -0
  16. package/dist/src/cli/helpers/async-project-loader.js.map +1 -0
  17. package/dist/src/cli/helpers/cancelation-handler.d.ts +123 -0
  18. package/dist/src/cli/helpers/cancelation-handler.d.ts.map +1 -0
  19. package/dist/src/cli/helpers/cancelation-handler.js +187 -0
  20. package/dist/src/cli/helpers/cancelation-handler.js.map +1 -0
  21. package/dist/src/cli/helpers/import-strategy-prompter.d.ts +43 -0
  22. package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -0
  23. package/dist/src/cli/helpers/import-strategy-prompter.js +136 -0
  24. package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -0
  25. package/dist/src/cli/helpers/issue-tracker/ado.d.ts +5 -2
  26. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  27. package/dist/src/cli/helpers/issue-tracker/ado.js +90 -40
  28. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  29. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  30. package/dist/src/cli/helpers/issue-tracker/index.js +112 -60
  31. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  32. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +26 -2
  33. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  34. package/dist/src/cli/helpers/issue-tracker/jira.js +197 -132
  35. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  36. package/dist/src/cli/helpers/progress-tracker.d.ts +121 -0
  37. package/dist/src/cli/helpers/progress-tracker.d.ts.map +1 -0
  38. package/dist/src/cli/helpers/progress-tracker.js +202 -0
  39. package/dist/src/cli/helpers/progress-tracker.js.map +1 -0
  40. package/dist/src/cli/helpers/project-count-fetcher.d.ts +69 -0
  41. package/dist/src/cli/helpers/project-count-fetcher.d.ts.map +1 -0
  42. package/dist/src/cli/helpers/project-count-fetcher.js +173 -0
  43. package/dist/src/cli/helpers/project-count-fetcher.js.map +1 -0
  44. package/dist/src/config/types.d.ts +14 -14
  45. package/dist/src/core/cache/cache-manager.d.ts +119 -0
  46. package/dist/src/core/cache/cache-manager.d.ts.map +1 -0
  47. package/dist/src/core/cache/cache-manager.js +304 -0
  48. package/dist/src/core/cache/cache-manager.js.map +1 -0
  49. package/dist/src/core/cache/rate-limit-checker.d.ts +92 -0
  50. package/dist/src/core/cache/rate-limit-checker.d.ts.map +1 -0
  51. package/dist/src/core/cache/rate-limit-checker.js +160 -0
  52. package/dist/src/core/cache/rate-limit-checker.js.map +1 -0
  53. package/dist/src/core/config/config-manager.d.ts +135 -0
  54. package/dist/src/core/config/config-manager.d.ts.map +1 -0
  55. package/dist/src/core/config/config-manager.js +341 -0
  56. package/dist/src/core/config/config-manager.js.map +1 -0
  57. package/dist/src/core/config/config-migrator.d.ts +102 -0
  58. package/dist/src/core/config/config-migrator.d.ts.map +1 -0
  59. package/dist/src/core/config/config-migrator.js +367 -0
  60. package/dist/src/core/config/config-migrator.js.map +1 -0
  61. package/dist/src/core/config/index.d.ts +10 -0
  62. package/dist/src/core/config/index.d.ts.map +1 -0
  63. package/dist/src/core/config/index.js +10 -0
  64. package/dist/src/core/config/index.js.map +1 -0
  65. package/dist/src/core/config/types.d.ts +216 -0
  66. package/dist/src/core/config/types.d.ts.map +1 -0
  67. package/dist/src/core/config/types.js +32 -0
  68. package/dist/src/core/config/types.js.map +1 -0
  69. package/dist/src/core/progress/cancelation-handler.d.ts +79 -0
  70. package/dist/src/core/progress/cancelation-handler.d.ts.map +1 -0
  71. package/dist/src/core/progress/cancelation-handler.js +111 -0
  72. package/dist/src/core/progress/cancelation-handler.js.map +1 -0
  73. package/dist/src/core/progress/import-state.d.ts +71 -0
  74. package/dist/src/core/progress/import-state.d.ts.map +1 -0
  75. package/dist/src/core/progress/import-state.js +96 -0
  76. package/dist/src/core/progress/import-state.js.map +1 -0
  77. package/dist/src/core/progress/progress-tracker.d.ts +139 -0
  78. package/dist/src/core/progress/progress-tracker.d.ts.map +1 -0
  79. package/dist/src/core/progress/progress-tracker.js +223 -0
  80. package/dist/src/core/progress/progress-tracker.js.map +1 -0
  81. package/dist/src/init/architecture/types.d.ts +6 -6
  82. package/dist/src/integrations/ado/ado-client.d.ts +25 -0
  83. package/dist/src/integrations/ado/ado-client.d.ts.map +1 -1
  84. package/dist/src/integrations/ado/ado-client.js +67 -0
  85. package/dist/src/integrations/ado/ado-client.js.map +1 -1
  86. package/dist/src/integrations/ado/ado-dependency-loader.d.ts +99 -0
  87. package/dist/src/integrations/ado/ado-dependency-loader.d.ts.map +1 -0
  88. package/dist/src/integrations/ado/ado-dependency-loader.js +207 -0
  89. package/dist/src/integrations/ado/ado-dependency-loader.js.map +1 -0
  90. package/dist/src/integrations/jira/jira-client.d.ts +32 -0
  91. package/dist/src/integrations/jira/jira-client.d.ts.map +1 -1
  92. package/dist/src/integrations/jira/jira-client.js +81 -0
  93. package/dist/src/integrations/jira/jira-client.js.map +1 -1
  94. package/dist/src/integrations/jira/jira-dependency-loader.d.ts +101 -0
  95. package/dist/src/integrations/jira/jira-dependency-loader.d.ts.map +1 -0
  96. package/dist/src/integrations/jira/jira-dependency-loader.js +200 -0
  97. package/dist/src/integrations/jira/jira-dependency-loader.js.map +1 -0
  98. package/dist/src/integrations/jira/jira-hierarchy-mapper.d.ts +104 -0
  99. package/dist/src/integrations/jira/jira-hierarchy-mapper.d.ts.map +1 -0
  100. package/dist/src/integrations/jira/jira-hierarchy-mapper.js +178 -0
  101. package/dist/src/integrations/jira/jira-hierarchy-mapper.js.map +1 -0
  102. package/package.json +1 -1
  103. package/plugins/specweave/.claude-plugin/plugin.json +20 -0
  104. package/plugins/specweave/agents/architect/AGENT.md +100 -602
  105. package/plugins/specweave/agents/pm/AGENT.md +96 -597
  106. package/plugins/specweave/agents/pm/AGENT.md.bak +1893 -0
  107. package/plugins/specweave/agents/pm/AGENT.md.bak2 +1754 -0
  108. package/plugins/specweave/commands/check-hooks.md +257 -0
  109. package/plugins/specweave/commands/migrate-config.md +104 -0
  110. package/plugins/specweave/hooks/post-edit-spec.sh +202 -31
  111. package/plugins/specweave/hooks/post-task-completion.sh +225 -228
  112. package/plugins/specweave/hooks/post-write-spec.sh +207 -31
  113. package/plugins/specweave/hooks/pre-edit-spec.sh +151 -0
  114. package/plugins/specweave/hooks/pre-task-completion.sh +5 -7
  115. package/plugins/specweave/hooks/pre-write-spec.sh +151 -0
  116. package/plugins/specweave/hooks/test-pretooluse-env.sh +72 -0
  117. package/plugins/specweave/skills/compliance-architecture/SKILL.md +374 -0
  118. package/plugins/specweave/skills/external-sync-wizard/SKILL.md +610 -0
  119. package/plugins/specweave/skills/pm-closure-validation/SKILL.md +541 -0
  120. package/plugins/specweave/skills/roadmap-planner/SKILL.md +473 -0
  121. package/plugins/specweave-ado/commands/refresh-cache.js +25 -0
  122. package/plugins/specweave-ado/commands/refresh-cache.ts +40 -0
  123. package/plugins/specweave-ado/hooks/post-task-completion.sh +1 -1
  124. package/plugins/specweave-github/hooks/post-task-completion.sh +1 -1
  125. package/plugins/specweave-jira/commands/refresh-cache.js +25 -0
  126. package/plugins/specweave-jira/commands/refresh-cache.ts +40 -0
  127. package/plugins/specweave-jira/hooks/post-task-completion.sh +1 -1
  128. package/plugins/specweave-kafka-streams/commands/topology.md +437 -0
  129. package/plugins/specweave-n8n/commands/workflow-template.md +262 -0
  130. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +228 -6333
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Migrate Config Command
3
+ *
4
+ * Migrates old .env-only configuration to split secrets/config format
5
+ *
6
+ * @module cli/commands/migrate-config
7
+ */
8
+ import chalk from 'chalk';
9
+ import inquirer from 'inquirer';
10
+ import ora from 'ora';
11
+ import { ConfigMigrator } from '../../core/config/config-migrator.js';
12
+ /**
13
+ * Migrate configuration from .env-only to split format
14
+ *
15
+ * @param options - Command options
16
+ */
17
+ export async function migrateConfig(options = {}) {
18
+ const { dryRun = false, force = false, yes = false } = options;
19
+ console.log(chalk.bold.cyan('\n🔄 SpecWeave Configuration Migration\n'));
20
+ console.log(chalk.gray('Migrating from .env-only to split secrets/config format\n'));
21
+ const migrator = new ConfigMigrator(process.cwd());
22
+ // Check if migration is needed
23
+ if (!force) {
24
+ const spinner = ora('Checking if migration is needed...').start();
25
+ const needed = await migrator.needsMigration();
26
+ if (!needed) {
27
+ spinner.succeed('Migration not needed');
28
+ console.log(chalk.green('\n✅ Your project already uses the split format!'));
29
+ console.log(chalk.gray(' Secrets are in .env, config is in .specweave/config.json\n'));
30
+ return;
31
+ }
32
+ spinner.succeed('Migration needed - old format detected');
33
+ }
34
+ // Preview migration
35
+ console.log(chalk.bold('\n📋 Migration Preview\n'));
36
+ const spinner = ora('Analyzing .env file...').start();
37
+ let preview;
38
+ try {
39
+ preview = await migrator.preview();
40
+ spinner.succeed('Analysis complete');
41
+ }
42
+ catch (error) {
43
+ spinner.fail('Analysis failed');
44
+ console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
45
+ return;
46
+ }
47
+ // Display classification results
48
+ console.log(chalk.bold('\nClassification Results:'));
49
+ console.log(chalk.green(` Secrets: ${preview.secretsCount} variables`));
50
+ console.log(chalk.blue(` Config: ${preview.configCount} variables`));
51
+ if (preview.warnings.length > 0) {
52
+ console.log(chalk.yellow('\n⚠️ Warnings:'));
53
+ preview.warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
54
+ }
55
+ // Show detailed breakdown
56
+ console.log(chalk.bold('\n📊 Detailed Breakdown:\n'));
57
+ const secretVars = preview.classified.filter(c => c.type === 'secret');
58
+ const configVars = preview.classified.filter(c => c.type === 'config');
59
+ if (secretVars.length > 0) {
60
+ console.log(chalk.green.bold(' Secrets (will stay in .env):'));
61
+ secretVars.forEach(v => {
62
+ const maskedValue = maskSecret(v.value);
63
+ console.log(chalk.gray(` ${v.key}=${maskedValue}`));
64
+ console.log(chalk.gray(` └─ ${v.reason}\n`));
65
+ });
66
+ }
67
+ if (configVars.length > 0) {
68
+ console.log(chalk.blue.bold(' Configuration (will move to config.json):'));
69
+ configVars.forEach(v => {
70
+ console.log(chalk.gray(` ${v.key}=${v.value}`));
71
+ console.log(chalk.gray(` └─ ${v.reason}\n`));
72
+ });
73
+ }
74
+ // Dry run mode - stop here
75
+ if (dryRun) {
76
+ console.log(chalk.yellow('\n🔍 Dry run mode - no changes made\n'));
77
+ console.log(chalk.gray('Run without --dry-run to perform migration\n'));
78
+ return;
79
+ }
80
+ // Confirm migration
81
+ if (!yes) {
82
+ console.log(chalk.bold('\n❓ Migration Actions:\n'));
83
+ console.log(chalk.gray(' 1. Backup .env → .env.backup.TIMESTAMP'));
84
+ console.log(chalk.gray(' 2. Update .env (keep only secrets)'));
85
+ console.log(chalk.gray(' 3. Create/update .specweave/config.json (add config)'));
86
+ console.log(chalk.gray(' 4. Generate .env.example (team template)\n'));
87
+ const { confirm } = await inquirer.prompt([
88
+ {
89
+ type: 'confirm',
90
+ name: 'confirm',
91
+ message: 'Proceed with migration?',
92
+ default: false
93
+ }
94
+ ]);
95
+ if (!confirm) {
96
+ console.log(chalk.yellow('\n⚠️ Migration cancelled\n'));
97
+ return;
98
+ }
99
+ }
100
+ // Perform migration
101
+ console.log(chalk.bold('\n🚀 Performing Migration...\n'));
102
+ const migrationSpinner = ora('Migrating configuration...').start();
103
+ let result;
104
+ try {
105
+ result = await migrator.migrate({ backup: true, force });
106
+ migrationSpinner.succeed('Migration complete');
107
+ }
108
+ catch (error) {
109
+ migrationSpinner.fail('Migration failed');
110
+ console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
111
+ return;
112
+ }
113
+ // Display results
114
+ console.log(chalk.bold.green('\n✅ Migration Successful!\n'));
115
+ console.log(chalk.bold('Summary:'));
116
+ console.log(chalk.green(` ✓ ${result.secretsCount} secrets kept in .env`));
117
+ console.log(chalk.blue(` ✓ ${result.configCount} config items moved to config.json`));
118
+ if (result.backupPath) {
119
+ console.log(chalk.gray(` ✓ Backup created: ${result.backupPath}`));
120
+ }
121
+ console.log(chalk.gray(` ✓ .env.example generated\n`));
122
+ // Show next steps
123
+ console.log(chalk.bold('📝 Next Steps:\n'));
124
+ console.log(chalk.cyan(' 1. Review .specweave/config.json (commit to git)'));
125
+ console.log(chalk.cyan(' 2. Share .env.example with team (commit to git)'));
126
+ console.log(chalk.cyan(' 3. Team members: cp .env.example .env (fill in tokens)'));
127
+ console.log(chalk.cyan(' 4. Verify: specweave init . (should detect existing config)\n'));
128
+ console.log(chalk.bold('🎯 Benefits:\n'));
129
+ console.log(chalk.green(' ✓ Team shares configuration via git'));
130
+ console.log(chalk.green(' ✓ Secrets stay local (never committed)'));
131
+ console.log(chalk.green(' ✓ Type-safe configuration with validation'));
132
+ console.log(chalk.green(' ✓ Easy onboarding for new team members\n'));
133
+ }
134
+ /**
135
+ * Mask secret value for display
136
+ *
137
+ * @param value - Secret value
138
+ * @returns Masked value
139
+ */
140
+ function maskSecret(value) {
141
+ if (value.length <= 8) {
142
+ return '***';
143
+ }
144
+ const visible = 4;
145
+ const start = value.substring(0, visible);
146
+ const end = value.substring(value.length - visible);
147
+ return `${start}${'*'.repeat(value.length - visible * 2)}${end}`;
148
+ }
149
+ //# sourceMappingURL=migrate-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-config.js","sourceRoot":"","sources":["../../../../src/cli/commands/migrate-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,QAAQ,MAAM,UAAU,CAAC;AAChC,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,MAAM,sCAAsC,CAAC;AAYtE;;;;GAIG;AACH,MAAM,CAAC,KAAK,UAAU,aAAa,CAAC,UAAgC,EAAE;IACpE,MAAM,EAAE,MAAM,GAAG,KAAK,EAAE,KAAK,GAAG,KAAK,EAAE,GAAG,GAAG,KAAK,EAAE,GAAG,OAAO,CAAC;IAE/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2DAA2D,CAAC,CAAC,CAAC;IAErF,MAAM,QAAQ,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;IAEnD,+BAA+B;IAC/B,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,OAAO,GAAG,GAAG,CAAC,oCAAoC,CAAC,CAAC,KAAK,EAAE,CAAC;QAClE,MAAM,MAAM,GAAG,MAAM,QAAQ,CAAC,cAAc,EAAE,CAAC;QAE/C,IAAI,CAAC,MAAM,EAAE,CAAC;YACZ,OAAO,CAAC,OAAO,CAAC,sBAAsB,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,iDAAiD,CAAC,CAAC,CAAC;YAC5E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,+DAA+D,CAAC,CAAC,CAAC;YACzF,OAAO;QACT,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,wCAAwC,CAAC,CAAC;IAC5D,CAAC;IAED,oBAAoB;IACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;IACpD,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;IAEtD,IAAI,OAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,OAAO,GAAG,MAAM,QAAQ,CAAC,OAAO,EAAE,CAAC;QACnC,OAAO,CAAC,OAAO,CAAC,mBAAmB,CAAC,CAAC;IACvC,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAChC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,iCAAiC;IACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;IACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,cAAc,OAAO,CAAC,YAAY,YAAY,CAAC,CAAC,CAAC;IACzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,cAAc,OAAO,CAAC,WAAW,YAAY,CAAC,CAAC,CAAC;IAEvE,IAAI,OAAO,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAChC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACvE,CAAC;IAED,0BAA0B;IAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;IAEtD,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IACvE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,QAAQ,CAAC,CAAC;IAEvE,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAC;QAChE,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACrB,MAAM,WAAW,GAAG,UAAU,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;YACxC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,WAAW,EAAE,CAAC,CAAC,CAAC;YACvD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,IAAI,UAAU,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QAC1B,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,6CAA6C,CAAC,CAAC,CAAC;QAC5E,UAAU,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE;YACrB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;YACnD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;QAClD,CAAC,CAAC,CAAC;IACL,CAAC;IAED,2BAA2B;IAC3B,IAAI,MAAM,EAAE,CAAC;QACX,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,uCAAuC,CAAC,CAAC,CAAC;QACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;QACxE,OAAO;IACT,CAAC;IAED,oBAAoB;IACpB,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0BAA0B,CAAC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0CAA0C,CAAC,CAAC,CAAC;QACpE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,sCAAsC,CAAC,CAAC,CAAC;QAChE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wDAAwD,CAAC,CAAC,CAAC;QAClF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;QAExE,MAAM,EAAE,OAAO,EAAE,GAAG,MAAM,QAAQ,CAAC,MAAM,CAAuB;YAC9D;gBACE,IAAI,EAAE,SAAS;gBACf,IAAI,EAAE,SAAS;gBACf,OAAO,EAAE,yBAAyB;gBAClC,OAAO,EAAE,KAAK;aACf;SACF,CAAC,CAAC;QAEH,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,6BAA6B,CAAC,CAAC,CAAC;YACzD,OAAO;QACT,CAAC;IACH,CAAC;IAED,oBAAoB;IACpB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gCAAgC,CAAC,CAAC,CAAC;IAC1D,MAAM,gBAAgB,GAAG,GAAG,CAAC,4BAA4B,CAAC,CAAC,KAAK,EAAE,CAAC;IAEnE,IAAI,MAAuB,CAAC;IAC5B,IAAI,CAAC;QACH,MAAM,GAAG,MAAM,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACzD,gBAAgB,CAAC,OAAO,CAAC,oBAAoB,CAAC,CAAC;IACjD,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,gBAAgB,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC;QAC1C,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,cAAc,KAAK,CAAC,OAAO,IAAI,CAAC,CAAC,CAAC;QAC1D,OAAO;IACT,CAAC;IAED,kBAAkB;IAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,6BAA6B,CAAC,CAAC,CAAC;IAE7D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;IACpC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,MAAM,CAAC,YAAY,uBAAuB,CAAC,CAAC,CAAC;IAC5E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,OAAO,MAAM,CAAC,WAAW,oCAAoC,CAAC,CAAC,CAAC;IAEvF,IAAI,MAAM,CAAC,UAAU,EAAE,CAAC;QACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,uBAAuB,MAAM,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,CAAC,CAAC,CAAC;IAExD,kBAAkB;IAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,CAAC;IAC5C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,oDAAoD,CAAC,CAAC,CAAC;IAC9E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mDAAmD,CAAC,CAAC,CAAC;IAC7E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,0DAA0D,CAAC,CAAC,CAAC;IACpF,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iEAAiE,CAAC,CAAC,CAAC;IAE3F,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,gBAAgB,CAAC,CAAC,CAAC;IAC1C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,uCAAuC,CAAC,CAAC,CAAC;IAClE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,0CAA0C,CAAC,CAAC,CAAC;IACrE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,6CAA6C,CAAC,CAAC,CAAC;IACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,4CAA4C,CAAC,CAAC,CAAC;AACzE,CAAC;AAED;;;;;GAKG;AACH,SAAS,UAAU,CAAC,KAAa;IAC/B,IAAI,KAAK,CAAC,MAAM,IAAI,CAAC,EAAE,CAAC;QACtB,OAAO,KAAK,CAAC;IACf,CAAC;IAED,MAAM,OAAO,GAAG,CAAC,CAAC;IAClB,MAAM,KAAK,GAAG,KAAK,CAAC,SAAS,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC;IAC1C,MAAM,GAAG,GAAG,KAAK,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,CAAC,CAAC;IAEpD,OAAO,GAAG,KAAK,GAAG,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,OAAO,GAAG,CAAC,CAAC,GAAG,GAAG,EAAE,CAAC;AACnE,CAAC"}
@@ -0,0 +1,148 @@
1
+ /**
2
+ * Async Project Loader
3
+ *
4
+ * Advanced batch fetching with:
5
+ * - Smart pagination (50-project batches)
6
+ * - Real-time progress tracking
7
+ * - Graceful cancelation (Ctrl+C)
8
+ * - Retry logic with exponential backoff
9
+ * - Rate limit handling
10
+ * - Graceful degradation (reduce batch size on timeout)
11
+ * - Continue-on-failure error handling
12
+ * - Comprehensive error logging
13
+ *
14
+ * @module cli/helpers/async-project-loader
15
+ */
16
+ import { Logger } from '../../utils/logger.js';
17
+ import type { JiraCredentials, AdoCredentials } from './project-count-fetcher.js';
18
+ /**
19
+ * Project provider type
20
+ */
21
+ export type ProjectProvider = 'jira' | 'ado';
22
+ /**
23
+ * Project metadata
24
+ */
25
+ export interface Project {
26
+ id: string;
27
+ key: string;
28
+ name: string;
29
+ projectTypeKey?: string;
30
+ simplified?: boolean;
31
+ }
32
+ /**
33
+ * Fetch options
34
+ */
35
+ export interface FetchOptions {
36
+ batchSize?: number;
37
+ updateFrequency?: number;
38
+ showEta?: boolean;
39
+ stateFile?: string;
40
+ errorLogFile?: string;
41
+ logger?: Logger;
42
+ }
43
+ /**
44
+ * Fetch result
45
+ */
46
+ export interface FetchResult {
47
+ projects: Project[];
48
+ succeeded: number;
49
+ failed: number;
50
+ skipped: number;
51
+ errors: FetchError[];
52
+ canceled?: boolean;
53
+ }
54
+ /**
55
+ * Fetch error with context
56
+ */
57
+ export interface FetchError {
58
+ projectKey: string;
59
+ error: string;
60
+ timestamp: string;
61
+ suggestion: string;
62
+ retryAttempts?: number;
63
+ }
64
+ /**
65
+ * Async Project Loader
66
+ *
67
+ * Handles batch fetching from issue trackers with advanced features:
68
+ * - Smart pagination (50-project batches for optimal performance)
69
+ * - Real-time progress tracking with ETA
70
+ * - Graceful cancelation with state persistence
71
+ * - Retry logic with exponential backoff (1s, 2s, 4s)
72
+ * - Rate limit detection and throttling
73
+ * - Graceful degradation (reduce batch size on timeout)
74
+ * - Continue-on-failure (single error doesn't block batch)
75
+ * - Comprehensive error logging
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * const loader = new AsyncProjectLoader(credentials, 'jira', {
80
+ * batchSize: 50,
81
+ * showEta: true
82
+ * });
83
+ *
84
+ * const result = await loader.fetchAllProjects(127);
85
+ * console.log(`Imported ${result.succeeded}/${result.projects.length} projects`);
86
+ * ```
87
+ */
88
+ export declare class AsyncProjectLoader {
89
+ private credentials;
90
+ private provider;
91
+ private options;
92
+ private progressTracker?;
93
+ private cancelHandler?;
94
+ private currentBatchSize;
95
+ constructor(credentials: JiraCredentials | AdoCredentials, provider: ProjectProvider, options?: FetchOptions);
96
+ /**
97
+ * Fetch all projects with smart pagination
98
+ *
99
+ * @param totalCount - Total number of projects to fetch
100
+ * @returns Fetch result with projects and statistics
101
+ */
102
+ fetchAllProjects(totalCount: number): Promise<FetchResult>;
103
+ /**
104
+ * Fetch single batch with pagination
105
+ *
106
+ * @param offset - Starting offset
107
+ * @param limit - Number of projects to fetch
108
+ * @returns Array of projects
109
+ */
110
+ fetchBatch(offset: number, limit: number): Promise<Project[]>;
111
+ /**
112
+ * Fetch JIRA batch with pagination
113
+ */
114
+ private fetchJiraBatch;
115
+ /**
116
+ * Fetch Azure DevOps batch with pagination
117
+ */
118
+ private fetchAdoBatch;
119
+ /**
120
+ * Fetch batch with retry logic and exponential backoff
121
+ */
122
+ private fetchBatchWithRetry;
123
+ /**
124
+ * Check rate limit headers and throttle if needed
125
+ */
126
+ private checkRateLimit;
127
+ /**
128
+ * Save partial state on cancelation
129
+ */
130
+ private savePartialState;
131
+ /**
132
+ * Log error to file
133
+ */
134
+ private logError;
135
+ /**
136
+ * Get suggestion for error
137
+ */
138
+ private getSuggestion;
139
+ /**
140
+ * Check if error is auth-related (4XX)
141
+ */
142
+ private isAuthError;
143
+ /**
144
+ * Check if error is timeout-related
145
+ */
146
+ private isTimeoutError;
147
+ }
148
+ //# sourceMappingURL=async-project-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"async-project-loader.d.ts","sourceRoot":"","sources":["../../../../src/cli/helpers/async-project-loader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,EAAE,MAAM,EAAiB,MAAM,uBAAuB,CAAC;AAG9D,OAAO,KAAK,EAAE,eAAe,EAAE,cAAc,EAAoB,MAAM,4BAA4B,CAAC;AAEpG;;GAEG;AACH,MAAM,MAAM,eAAe,GAAG,MAAM,GAAG,KAAK,CAAC;AAE7C;;GAEG;AACH,MAAM,WAAW,OAAO;IACtB,EAAE,EAAE,MAAM,CAAC;IACX,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,UAAU,CAAC,EAAE,OAAO,CAAC;CACtB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED;;GAEG;AACH,MAAM,WAAW,WAAW;IAC1B,QAAQ,EAAE,OAAO,EAAE,CAAC;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,UAAU,EAAE,CAAC;IACrB,QAAQ,CAAC,EAAE,OAAO,CAAC;CACpB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,qBAAa,kBAAkB;IAC7B,OAAO,CAAC,WAAW,CAAmC;IACtD,OAAO,CAAC,QAAQ,CAAkB;IAClC,OAAO,CAAC,OAAO,CAAyB;IAExC,OAAO,CAAC,eAAe,CAAC,CAAkB;IAC1C,OAAO,CAAC,aAAa,CAAC,CAAqB;IAE3C,OAAO,CAAC,gBAAgB,CAAS;gBAG/B,WAAW,EAAE,eAAe,GAAG,cAAc,EAC7C,QAAQ,EAAE,eAAe,EACzB,OAAO,GAAE,YAAiB;IAkB5B;;;;;OAKG;IACG,gBAAgB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAwGhE;;;;;;OAMG;IACG,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,EAAE,CAAC;IAUnE;;OAEG;YACW,cAAc;IAwC5B;;OAEG;YACW,aAAa;IA8B3B;;OAEG;YACW,mBAAmB;IA4BjC;;OAEG;YACW,cAAc;IAiB5B;;OAEG;YACW,gBAAgB;IAgC9B;;OAEG;YACW,QAAQ;IAgBtB;;OAEG;IACH,OAAO,CAAC,aAAa;IA0BrB;;OAEG;IACH,OAAO,CAAC,WAAW;IAKnB;;OAEG;IACH,OAAO,CAAC,cAAc;CAKvB"}
@@ -0,0 +1,351 @@
1
+ /**
2
+ * Async Project Loader
3
+ *
4
+ * Advanced batch fetching with:
5
+ * - Smart pagination (50-project batches)
6
+ * - Real-time progress tracking
7
+ * - Graceful cancelation (Ctrl+C)
8
+ * - Retry logic with exponential backoff
9
+ * - Rate limit handling
10
+ * - Graceful degradation (reduce batch size on timeout)
11
+ * - Continue-on-failure error handling
12
+ * - Comprehensive error logging
13
+ *
14
+ * @module cli/helpers/async-project-loader
15
+ */
16
+ import { promises as fs } from 'fs';
17
+ import path from 'path';
18
+ import chalk from 'chalk';
19
+ import { consoleLogger } from '../../utils/logger.js';
20
+ import { ProgressTracker } from './progress-tracker.js';
21
+ import { CancelationHandler } from './cancelation-handler.js';
22
+ /**
23
+ * Async Project Loader
24
+ *
25
+ * Handles batch fetching from issue trackers with advanced features:
26
+ * - Smart pagination (50-project batches for optimal performance)
27
+ * - Real-time progress tracking with ETA
28
+ * - Graceful cancelation with state persistence
29
+ * - Retry logic with exponential backoff (1s, 2s, 4s)
30
+ * - Rate limit detection and throttling
31
+ * - Graceful degradation (reduce batch size on timeout)
32
+ * - Continue-on-failure (single error doesn't block batch)
33
+ * - Comprehensive error logging
34
+ *
35
+ * @example
36
+ * ```typescript
37
+ * const loader = new AsyncProjectLoader(credentials, 'jira', {
38
+ * batchSize: 50,
39
+ * showEta: true
40
+ * });
41
+ *
42
+ * const result = await loader.fetchAllProjects(127);
43
+ * console.log(`Imported ${result.succeeded}/${result.projects.length} projects`);
44
+ * ```
45
+ */
46
+ export class AsyncProjectLoader {
47
+ constructor(credentials, provider, options = {}) {
48
+ this.credentials = credentials;
49
+ this.provider = provider;
50
+ // Set defaults
51
+ this.options = {
52
+ batchSize: options.batchSize ?? 50,
53
+ updateFrequency: options.updateFrequency ?? 5,
54
+ showEta: options.showEta ?? true,
55
+ stateFile: options.stateFile ?? '.specweave/cache/import-state.json',
56
+ errorLogFile: options.errorLogFile ?? '.specweave/logs/import-errors.log',
57
+ logger: options.logger ?? consoleLogger
58
+ };
59
+ this.currentBatchSize = this.options.batchSize;
60
+ }
61
+ /**
62
+ * Fetch all projects with smart pagination
63
+ *
64
+ * @param totalCount - Total number of projects to fetch
65
+ * @returns Fetch result with projects and statistics
66
+ */
67
+ async fetchAllProjects(totalCount) {
68
+ const projects = [];
69
+ const errors = [];
70
+ // Initialize progress tracker
71
+ this.progressTracker = new ProgressTracker({
72
+ total: totalCount,
73
+ updateFrequency: this.options.updateFrequency,
74
+ showEta: this.options.showEta,
75
+ logger: this.options.logger
76
+ });
77
+ // Initialize cancelation handler
78
+ this.cancelHandler = new CancelationHandler({
79
+ stateFile: this.options.stateFile,
80
+ logger: this.options.logger
81
+ });
82
+ // Register cleanup callback (save state on Ctrl+C)
83
+ this.cancelHandler.onCleanup(async () => {
84
+ await this.savePartialState(projects, totalCount, errors);
85
+ });
86
+ // Main fetch loop
87
+ let offset = 0;
88
+ while (offset < totalCount) {
89
+ // Check for cancelation
90
+ if (this.cancelHandler.shouldCancel()) {
91
+ this.progressTracker.cancel();
92
+ this.cancelHandler.suggestResume('/specweave-jira:import-projects --resume');
93
+ return {
94
+ projects,
95
+ succeeded: this.progressTracker.getSummary().succeeded,
96
+ failed: this.progressTracker.getSummary().failed,
97
+ skipped: this.progressTracker.getSummary().skipped,
98
+ errors,
99
+ canceled: true
100
+ };
101
+ }
102
+ // Calculate batch size (last batch may be smaller)
103
+ const limit = Math.min(this.currentBatchSize, totalCount - offset);
104
+ try {
105
+ // Fetch batch with retry logic
106
+ const batch = await this.fetchBatchWithRetry(offset, limit);
107
+ projects.push(...batch);
108
+ // Update progress for each item in batch
109
+ batch.forEach(project => {
110
+ this.progressTracker.update(project.key, 'success');
111
+ });
112
+ }
113
+ catch (error) {
114
+ // Continue-on-failure: Log error, skip batch, continue
115
+ const batchKey = `BATCH_${offset}-${offset + limit}`;
116
+ const fetchError = {
117
+ projectKey: batchKey,
118
+ error: error.message,
119
+ timestamp: new Date().toISOString(),
120
+ suggestion: this.getSuggestion(error),
121
+ retryAttempts: 3
122
+ };
123
+ errors.push(fetchError);
124
+ await this.logError(fetchError);
125
+ // Update progress (mark all as failed)
126
+ for (let i = 0; i < limit; i++) {
127
+ this.progressTracker.update(`${batchKey}_${i}`, 'failure');
128
+ }
129
+ // Check if we should reduce batch size (graceful degradation)
130
+ if (this.isTimeoutError(error) && this.currentBatchSize > 10) {
131
+ const oldSize = this.currentBatchSize;
132
+ this.currentBatchSize = Math.max(10, Math.floor(this.currentBatchSize / 2));
133
+ this.options.logger.log(chalk.yellow(`⚠️ Timeout detected. Reducing batch size: ${oldSize} → ${this.currentBatchSize}`));
134
+ }
135
+ }
136
+ offset += limit;
137
+ }
138
+ // Finish progress tracking
139
+ this.progressTracker.finish();
140
+ // Clear cancelation state (successful completion)
141
+ await this.cancelHandler.clearState();
142
+ // Cleanup
143
+ this.cancelHandler.dispose();
144
+ return {
145
+ projects,
146
+ succeeded: this.progressTracker.getSummary().succeeded,
147
+ failed: this.progressTracker.getSummary().failed,
148
+ skipped: this.progressTracker.getSummary().skipped,
149
+ errors
150
+ };
151
+ }
152
+ /**
153
+ * Fetch single batch with pagination
154
+ *
155
+ * @param offset - Starting offset
156
+ * @param limit - Number of projects to fetch
157
+ * @returns Array of projects
158
+ */
159
+ async fetchBatch(offset, limit) {
160
+ if (this.provider === 'jira') {
161
+ return this.fetchJiraBatch(offset, limit);
162
+ }
163
+ else if (this.provider === 'ado') {
164
+ return this.fetchAdoBatch(offset, limit);
165
+ }
166
+ else {
167
+ throw new Error(`Unsupported provider: ${this.provider}`);
168
+ }
169
+ }
170
+ /**
171
+ * Fetch JIRA batch with pagination
172
+ */
173
+ async fetchJiraBatch(offset, limit) {
174
+ const creds = this.credentials;
175
+ const { domain, email, token, instanceType } = creds;
176
+ const apiVersion = instanceType === 'cloud' ? '3' : '2';
177
+ const endpoint = instanceType === 'cloud'
178
+ ? `/rest/api/${apiVersion}/project/search?startAt=${offset}&maxResults=${limit}`
179
+ : `/rest/api/${apiVersion}/project?startAt=${offset}&maxResults=${limit}`;
180
+ const url = `https://${domain}${endpoint}`;
181
+ const response = await fetch(url, {
182
+ method: 'GET',
183
+ headers: {
184
+ Authorization: `Basic ${Buffer.from(`${email}:${token}`).toString('base64')}`,
185
+ Accept: 'application/json',
186
+ 'Content-Type': 'application/json'
187
+ },
188
+ signal: AbortSignal.timeout(30000) // 30 second timeout
189
+ });
190
+ // Check rate limit
191
+ await this.checkRateLimit(response.headers);
192
+ if (!response.ok) {
193
+ const errorBody = await response.text().catch(() => 'No error body');
194
+ throw new Error(`JIRA API error: ${response.status} ${response.statusText}. ${errorBody}`);
195
+ }
196
+ if (instanceType === 'cloud') {
197
+ const data = (await response.json());
198
+ return data.values;
199
+ }
200
+ else {
201
+ // Server returns array directly
202
+ return (await response.json());
203
+ }
204
+ }
205
+ /**
206
+ * Fetch Azure DevOps batch with pagination
207
+ */
208
+ async fetchAdoBatch(offset, limit) {
209
+ const creds = this.credentials;
210
+ const { organization, pat } = creds;
211
+ const url = `https://dev.azure.com/${organization}/_apis/projects?$top=${limit}&$skip=${offset}&api-version=7.0`;
212
+ const response = await fetch(url, {
213
+ method: 'GET',
214
+ headers: {
215
+ Authorization: `Basic ${Buffer.from(`:${pat}`).toString('base64')}`,
216
+ Accept: 'application/json'
217
+ },
218
+ signal: AbortSignal.timeout(30000) // 30 second timeout
219
+ });
220
+ if (!response.ok) {
221
+ const errorBody = await response.text().catch(() => 'No error body');
222
+ throw new Error(`Azure DevOps API error: ${response.status} ${response.statusText}. ${errorBody}`);
223
+ }
224
+ const data = (await response.json());
225
+ return data.value.map(p => ({
226
+ id: p.id,
227
+ key: p.name.toUpperCase().replace(/\s+/g, '-'),
228
+ name: p.name
229
+ }));
230
+ }
231
+ /**
232
+ * Fetch batch with retry logic and exponential backoff
233
+ */
234
+ async fetchBatchWithRetry(offset, limit) {
235
+ const delays = [1000, 2000, 4000]; // 1s, 2s, 4s
236
+ let lastError = null;
237
+ for (let attempt = 0; attempt < 3; attempt++) {
238
+ try {
239
+ return await this.fetchBatch(offset, limit);
240
+ }
241
+ catch (error) {
242
+ lastError = error;
243
+ // Don't retry on auth failures (4XX except 429)
244
+ if (this.isAuthError(error) && !error.message.includes('429')) {
245
+ throw error;
246
+ }
247
+ // Retry on network errors, 5XX, or 429 errors
248
+ if (attempt < 2) {
249
+ this.options.logger.log(chalk.yellow(`⚠️ Retry ${attempt + 1}/3 after ${delays[attempt]}ms... (${error.message})`));
250
+ await new Promise(resolve => setTimeout(resolve, delays[attempt]));
251
+ }
252
+ }
253
+ }
254
+ throw lastError || new Error('Failed to fetch batch after 3 attempts');
255
+ }
256
+ /**
257
+ * Check rate limit headers and throttle if needed
258
+ */
259
+ async checkRateLimit(headers) {
260
+ const remaining = headers.get('X-RateLimit-Remaining');
261
+ const reset = headers.get('X-RateLimit-Reset');
262
+ if (remaining && parseInt(remaining, 10) < 10) {
263
+ const resetTime = reset ? parseInt(reset, 10) * 1000 : Date.now() + 60000;
264
+ const waitMs = Math.max(0, resetTime - Date.now());
265
+ const waitSec = Math.ceil(waitMs / 1000);
266
+ this.options.logger.log(chalk.yellow(`⚠️ Rate limit threshold reached (${remaining} requests remaining). Pausing ${waitSec}s...`));
267
+ await new Promise(resolve => setTimeout(resolve, waitMs + 1000)); // Add 1s buffer
268
+ }
269
+ }
270
+ /**
271
+ * Save partial state on cancelation
272
+ */
273
+ async savePartialState(projects, total, errors) {
274
+ const summary = this.progressTracker.getSummary();
275
+ // Calculate remaining projects
276
+ const completed = projects.length;
277
+ const remaining = [];
278
+ // Note: We don't have the list of all projects here, so remaining will be empty
279
+ // In a real implementation, this would be populated from the initial project list
280
+ const state = {
281
+ operation: `${this.provider}-import`,
282
+ provider: this.provider,
283
+ domain: 'credentials' in this.credentials ? this.credentials.domain : undefined,
284
+ timestamp: new Date().toISOString(),
285
+ version: '1.0',
286
+ total,
287
+ completed,
288
+ succeeded: summary.succeeded,
289
+ failed: summary.failed,
290
+ skipped: summary.skipped,
291
+ remaining,
292
+ errors
293
+ };
294
+ await this.cancelHandler.saveState(state);
295
+ }
296
+ /**
297
+ * Log error to file
298
+ */
299
+ async logError(error) {
300
+ try {
301
+ // Ensure log directory exists
302
+ const logDir = path.dirname(this.options.errorLogFile);
303
+ await fs.mkdir(logDir, { recursive: true });
304
+ // Format log entry
305
+ const logEntry = `[${error.timestamp}] ${error.projectKey}: ${error.error} (${error.suggestion})\n`;
306
+ // Append to log file
307
+ await fs.appendFile(this.options.errorLogFile, logEntry, 'utf-8');
308
+ }
309
+ catch (logError) {
310
+ this.options.logger.error('Failed to write error log:', logError);
311
+ }
312
+ }
313
+ /**
314
+ * Get suggestion for error
315
+ */
316
+ getSuggestion(error) {
317
+ const message = error.message || '';
318
+ if (message.includes('401') || message.includes('403')) {
319
+ return 'Check credentials and project permissions';
320
+ }
321
+ if (message.includes('404')) {
322
+ return 'Project may have been deleted or archived';
323
+ }
324
+ if (message.includes('429')) {
325
+ return 'Rate limit exceeded (throttling applied)';
326
+ }
327
+ if (message.includes('ETIMEDOUT') || message.includes('timeout')) {
328
+ return 'Network timeout (try again or reduce batch size)';
329
+ }
330
+ if (message.includes('5')) {
331
+ return 'API server error (retrying with backoff)';
332
+ }
333
+ return 'Unknown error (check logs for details)';
334
+ }
335
+ /**
336
+ * Check if error is auth-related (4XX)
337
+ */
338
+ isAuthError(error) {
339
+ const message = error.message || '';
340
+ return /\b40[0134]\b/.test(message);
341
+ }
342
+ /**
343
+ * Check if error is timeout-related
344
+ */
345
+ isTimeoutError(error) {
346
+ const message = error.message || '';
347
+ const code = error.code || '';
348
+ return message.includes('timeout') || code === 'ETIMEDOUT' || message.includes('ETIMEDOUT');
349
+ }
350
+ }
351
+ //# sourceMappingURL=async-project-loader.js.map