specweave 0.26.14 → 0.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (167) hide show
  1. package/CLAUDE.md +73 -1
  2. package/README.md +245 -446
  3. package/dist/plugins/specweave-jira/lib/setup-wizard.d.ts.map +1 -1
  4. package/dist/plugins/specweave-jira/lib/setup-wizard.js +57 -78
  5. package/dist/plugins/specweave-jira/lib/setup-wizard.js.map +1 -1
  6. package/dist/src/cli/commands/import-docs.d.ts.map +1 -1
  7. package/dist/src/cli/commands/import-docs.js +23 -31
  8. package/dist/src/cli/commands/import-docs.js.map +1 -1
  9. package/dist/src/cli/commands/import-external.d.ts.map +1 -1
  10. package/dist/src/cli/commands/import-external.js +6 -10
  11. package/dist/src/cli/commands/import-external.js.map +1 -1
  12. package/dist/src/cli/commands/init-multiproject.d.ts.map +1 -1
  13. package/dist/src/cli/commands/init-multiproject.js +58 -73
  14. package/dist/src/cli/commands/init-multiproject.js.map +1 -1
  15. package/dist/src/cli/commands/init.d.ts +17 -11
  16. package/dist/src/cli/commands/init.d.ts.map +1 -1
  17. package/dist/src/cli/commands/init.js +221 -1874
  18. package/dist/src/cli/commands/init.js.map +1 -1
  19. package/dist/src/cli/commands/install.d.ts.map +1 -1
  20. package/dist/src/cli/commands/install.js +14 -22
  21. package/dist/src/cli/commands/install.js.map +1 -1
  22. package/dist/src/cli/commands/migrate-config.d.ts.map +1 -1
  23. package/dist/src/cli/commands/migrate-config.js +6 -10
  24. package/dist/src/cli/commands/migrate-config.js.map +1 -1
  25. package/dist/src/cli/commands/switch-project.d.ts.map +1 -1
  26. package/dist/src/cli/commands/switch-project.js.map +1 -1
  27. package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -1
  28. package/dist/src/cli/helpers/ado-area-path-mapper.js +36 -49
  29. package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -1
  30. package/dist/src/cli/helpers/github/increment-profile-selector.d.ts.map +1 -1
  31. package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
  32. package/dist/src/cli/helpers/github/profile-manager.d.ts.map +1 -1
  33. package/dist/src/cli/helpers/github/profile-manager.js +8 -11
  34. package/dist/src/cli/helpers/github/profile-manager.js.map +1 -1
  35. package/dist/src/cli/helpers/github-repo-selector.d.ts.map +1 -1
  36. package/dist/src/cli/helpers/github-repo-selector.js +26 -50
  37. package/dist/src/cli/helpers/github-repo-selector.js.map +1 -1
  38. package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -1
  39. package/dist/src/cli/helpers/import-strategy-prompter.js +39 -52
  40. package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -1
  41. package/dist/src/cli/helpers/init/config-detection.d.ts +40 -0
  42. package/dist/src/cli/helpers/init/config-detection.d.ts.map +1 -0
  43. package/dist/src/cli/helpers/init/config-detection.js +125 -0
  44. package/dist/src/cli/helpers/init/config-detection.js.map +1 -0
  45. package/dist/src/cli/helpers/init/directory-structure.d.ts +26 -0
  46. package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -0
  47. package/dist/src/cli/helpers/init/directory-structure.js +190 -0
  48. package/dist/src/cli/helpers/init/directory-structure.js.map +1 -0
  49. package/dist/src/cli/helpers/init/external-import.d.ts +15 -0
  50. package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -0
  51. package/dist/src/cli/helpers/init/external-import.js +251 -0
  52. package/dist/src/cli/helpers/init/external-import.js.map +1 -0
  53. package/dist/src/cli/helpers/init/index.d.ts +15 -0
  54. package/dist/src/cli/helpers/init/index.d.ts.map +1 -0
  55. package/dist/src/cli/helpers/init/index.js +26 -0
  56. package/dist/src/cli/helpers/init/index.js.map +1 -0
  57. package/dist/src/cli/helpers/init/next-steps.d.ts +15 -0
  58. package/dist/src/cli/helpers/init/next-steps.d.ts.map +1 -0
  59. package/dist/src/cli/helpers/init/next-steps.js +72 -0
  60. package/dist/src/cli/helpers/init/next-steps.js.map +1 -0
  61. package/dist/src/cli/helpers/init/path-utils.d.ts +41 -0
  62. package/dist/src/cli/helpers/init/path-utils.d.ts.map +1 -0
  63. package/dist/src/cli/helpers/init/path-utils.js +146 -0
  64. package/dist/src/cli/helpers/init/path-utils.js.map +1 -0
  65. package/dist/src/cli/helpers/init/plugin-installer.d.ts +28 -0
  66. package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -0
  67. package/dist/src/cli/helpers/init/plugin-installer.js +238 -0
  68. package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -0
  69. package/dist/src/cli/helpers/init/repository-setup.d.ts +28 -0
  70. package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -0
  71. package/dist/src/cli/helpers/init/repository-setup.js +78 -0
  72. package/dist/src/cli/helpers/init/repository-setup.js.map +1 -0
  73. package/dist/src/cli/helpers/init/smart-reinit.d.ts +30 -0
  74. package/dist/src/cli/helpers/init/smart-reinit.d.ts.map +1 -0
  75. package/dist/src/cli/helpers/init/smart-reinit.js +140 -0
  76. package/dist/src/cli/helpers/init/smart-reinit.js.map +1 -0
  77. package/dist/src/cli/helpers/init/testing-config.d.ts +27 -0
  78. package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -0
  79. package/dist/src/cli/helpers/init/testing-config.js +131 -0
  80. package/dist/src/cli/helpers/init/testing-config.js.map +1 -0
  81. package/dist/src/cli/helpers/init/types.d.ts +86 -0
  82. package/dist/src/cli/helpers/init/types.d.ts.map +1 -0
  83. package/dist/src/cli/helpers/init/types.js +5 -0
  84. package/dist/src/cli/helpers/init/types.js.map +1 -0
  85. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -1
  86. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +10 -12
  87. package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -1
  88. package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
  89. package/dist/src/cli/helpers/issue-tracker/ado.js +43 -60
  90. package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
  91. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
  92. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +193 -230
  93. package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
  94. package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
  95. package/dist/src/cli/helpers/issue-tracker/github.js +43 -54
  96. package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
  97. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  98. package/dist/src/cli/helpers/issue-tracker/index.js +27 -40
  99. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  100. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  101. package/dist/src/cli/helpers/issue-tracker/jira.js +54 -70
  102. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  103. package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -1
  104. package/dist/src/cli/helpers/smart-filter.js +62 -85
  105. package/dist/src/cli/helpers/smart-filter.js.map +1 -1
  106. package/dist/src/core/increment/auto-transition-manager.d.ts +12 -0
  107. package/dist/src/core/increment/auto-transition-manager.d.ts.map +1 -1
  108. package/dist/src/core/increment/auto-transition-manager.js +45 -0
  109. package/dist/src/core/increment/auto-transition-manager.js.map +1 -1
  110. package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
  111. package/dist/src/core/increment/metadata-manager.js +46 -0
  112. package/dist/src/core/increment/metadata-manager.js.map +1 -1
  113. package/dist/src/core/increment/status-change-sync-trigger.d.ts +12 -0
  114. package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
  115. package/dist/src/core/increment/status-change-sync-trigger.js +48 -2
  116. package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
  117. package/dist/src/core/living-docs/living-docs-sync.d.ts +13 -0
  118. package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
  119. package/dist/src/core/living-docs/living-docs-sync.js +40 -0
  120. package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
  121. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +5 -1
  122. package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -1
  123. package/dist/src/core/repo-structure/repo-bulk-discovery.js +68 -86
  124. package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -1
  125. package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
  126. package/dist/src/core/repo-structure/repo-structure-manager.js +345 -425
  127. package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
  128. package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
  129. package/dist/src/core/sync/bidirectional-engine.js +21 -29
  130. package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
  131. package/dist/src/init/InitFlow.js +15 -19
  132. package/dist/src/init/InitFlow.js.map +1 -1
  133. package/dist/src/init/repo/types.d.ts +1 -1
  134. package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -1
  135. package/dist/src/integrations/ado/area-path-mapper.js +19 -23
  136. package/dist/src/integrations/ado/area-path-mapper.js.map +1 -1
  137. package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
  138. package/dist/src/utils/external-resource-validator.js +41 -65
  139. package/dist/src/utils/external-resource-validator.js.map +1 -1
  140. package/dist/src/utils/project-detection.d.ts.map +1 -1
  141. package/dist/src/utils/project-detection.js +19 -21
  142. package/dist/src/utils/project-detection.js.map +1 -1
  143. package/dist/src/utils/project-validator.d.ts.map +1 -1
  144. package/dist/src/utils/project-validator.js +5 -7
  145. package/dist/src/utils/project-validator.js.map +1 -1
  146. package/package.json +2 -3
  147. package/plugins/specweave/agents/tech-lead/AGENT.md +9 -0
  148. package/plugins/specweave/hooks/post-edit-write-consolidated.sh +87 -0
  149. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.d.ts +12 -0
  150. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js +45 -0
  151. package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js.map +1 -1
  152. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +46 -0
  153. package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
  154. package/plugins/specweave/skills/increment-planner/SKILL.md +10 -5
  155. package/plugins/specweave/skills/specweave-framework/SKILL.md +6 -4
  156. package/plugins/specweave/templates/coding-standards.md.template +36 -0
  157. package/plugins/specweave-ado/lib/project-selector.js +56 -67
  158. package/plugins/specweave-ado/lib/project-selector.ts +72 -85
  159. package/plugins/specweave-github/lib/repo-selector.js +55 -66
  160. package/plugins/specweave-github/lib/repo-selector.ts +73 -84
  161. package/plugins/specweave-jira/commands/import-projects.js +3 -5
  162. package/plugins/specweave-jira/commands/import-projects.ts +3 -5
  163. package/plugins/specweave-jira/lib/project-selector.js +60 -71
  164. package/plugins/specweave-jira/lib/project-selector.ts +78 -91
  165. package/plugins/specweave-jira/lib/setup-wizard.js +51 -72
  166. package/plugins/specweave-jira/lib/setup-wizard.ts +56 -74
  167. package/src/templates/CLAUDE.md.template +14 -0
@@ -1,33 +1,33 @@
1
+ /**
2
+ * SpecWeave Init Command
3
+ *
4
+ * Initializes a new SpecWeave project with:
5
+ * - Directory structure (.specweave/)
6
+ * - Configuration files
7
+ * - Plugin installation (Claude Code)
8
+ * - Issue tracker setup
9
+ * - Initial increment
10
+ *
11
+ * Refactored: Logic extracted to src/cli/helpers/init/
12
+ */
1
13
  import * as fs from '../../utils/fs-native.js';
2
14
  import * as path from 'path';
3
- import os from 'os';
15
+ import * as os from 'os';
4
16
  import chalk from 'chalk';
5
17
  import ora from 'ora';
6
- import inquirer from 'inquirer';
18
+ import { select, input, confirm } from '@inquirer/prompts';
7
19
  import { execFileNoThrowSync } from '../../utils/execFileNoThrow.js';
8
- import { detectClaudeCli, getClaudeCliDiagnostic, getClaudeCliSuggestions } from '../../utils/claude-cli-detector.js';
9
20
  import { AdapterLoader } from '../../adapters/adapter-loader.js';
10
- import { ClaudeMdGenerator } from '../../adapters/claude-md-generator.js';
11
- import { AgentsMdGenerator } from '../../adapters/agents-md-generator.js';
12
21
  import { getDirname } from '../../utils/esm-helpers.js';
13
- import { LanguageManager, isLanguageSupported, getSupportedLanguages } from '../../core/i18n/language-manager.js';
22
+ import { isLanguageSupported, getSupportedLanguages } from '../../core/i18n/language-manager.js';
14
23
  import { getLocaleManager } from '../../core/i18n/locale-manager.js';
15
- import { consoleLogger } from '../../utils/logger.js';
16
- import { generateInitialIncrement } from '../helpers/init/initial-increment-generator.js';
17
- import { ImportCoordinator } from '../../importers/import-coordinator.js';
18
24
  import { StatusLineUpdater } from '../../core/status-line/status-line-updater.js';
19
- import { ItemConverter } from '../../importers/item-converter.js';
20
- import { loadImportConfig } from '../../config/import-config.js';
21
- const __dirname = getDirname(import.meta.url);
22
25
  import { readEnvFile, parseEnvFile } from '../../utils/env-file.js';
23
- import { selectRepositories } from '../helpers/github-repo-selector.js';
24
- import { Octokit } from '@octokit/rest';
26
+ // Import helpers
27
+ import { findSourceDir, findPackageRoot, detectNestedSpecweave, detectGitHubRemote, promptSmartReinit, installAllPlugins, setupRepositoryHosting, promptTestingConfig, updateConfigWithTesting, promptAndRunExternalImport, createDirectoryStructure, copyTemplates, createConfigFile, showNextSteps, generateInitialIncrement, } from '../helpers/init/index.js';
28
+ const __dirname = getDirname(import.meta.url);
25
29
  /**
26
30
  * Detect if we're in the SpecWeave framework repository itself
27
- * (development mode vs. user project mode)
28
- *
29
- * @param targetDir - Directory to check
30
- * @returns true if this is the SpecWeave framework repo
31
31
  */
32
32
  async function isSpecWeaveFrameworkRepo(targetDir) {
33
33
  try {
@@ -36,53 +36,32 @@ async function isSpecWeaveFrameworkRepo(targetDir) {
36
36
  return false;
37
37
  }
38
38
  const packageJson = await fs.readJson(packageJsonPath);
39
- // Check if this is the specweave package itself
40
39
  return packageJson.name === 'specweave';
41
40
  }
42
- catch (error) {
41
+ catch {
43
42
  return false;
44
43
  }
45
44
  }
46
45
  /**
47
46
  * Create Multi-Project Folders based on Issue Tracker Configuration
48
- *
49
- * After issue tracker setup, this function:
50
- * 1. Reads .env file to detect multi-project configuration (JIRA_PROJECTS, ADO_PROJECTS, etc.)
51
- * 2. Reads config.json to get sync profiles
52
- * 3. For each sync profile, creates project-specific folders
53
- *
54
- * Examples:
55
- * - JIRA with JIRA_PROJECTS=FE,BE → Creates .specweave/docs/internal/projects/fe/ and /be/
56
- * - ADO with ADO_PROJECTS=Frontend,Backend → Creates /frontend/ and /backend/
57
- * - GitHub (single repo) → Creates /default/ folder
58
- *
59
- * @param targetDir - Project root directory
60
47
  */
61
48
  async function createMultiProjectFolders(targetDir) {
62
49
  const envPath = path.join(targetDir, '.env');
63
50
  const configPath = path.join(targetDir, '.specweave', 'config.json');
64
- // Skip if no .env or config.json
65
51
  if (!fs.existsSync(envPath)) {
66
- return; // No issue tracker configured
52
+ return;
67
53
  }
68
- // Read and parse .env file
69
54
  const envContent = readEnvFile(envPath);
70
55
  const envVars = parseEnvFile(envContent);
71
- // Detect multi-project configuration
72
56
  const jiraProjects = envVars.JIRA_PROJECTS?.split(',').map((p) => p.trim()).filter(Boolean);
73
- const adoProjects = envVars.ADO_PROJECTS?.split(',').map((p) => p.trim()).filter(Boolean);
74
57
  const jiraStrategy = envVars.JIRA_STRATEGY;
75
- const adoStrategy = envVars.ADO_STRATEGY;
76
- // If no multi-project config, skip
77
- if (!jiraProjects?.length && !adoProjects?.length) {
58
+ if (!jiraProjects?.length) {
78
59
  return;
79
60
  }
80
- // Create sync profile if not exists
81
61
  let config = {};
82
62
  if (fs.existsSync(configPath)) {
83
63
  config = await fs.readJson(configPath);
84
64
  }
85
- // Initialize sync config if missing
86
65
  if (!config.sync) {
87
66
  config.sync = {
88
67
  enabled: true,
@@ -94,418 +73,145 @@ async function createMultiProjectFolders(targetDir) {
94
73
  }
95
74
  };
96
75
  }
97
- // Create JIRA sync profile from .env
98
76
  if (jiraProjects?.length && jiraStrategy === 'project-per-team') {
99
77
  const profileId = 'jira-default';
100
- if (!config.sync.profiles[profileId]) {
78
+ const syncConfig = config.sync;
79
+ const profiles = syncConfig.profiles;
80
+ if (!profiles[profileId]) {
101
81
  const jiraProfile = {
102
82
  provider: 'jira',
103
83
  displayName: 'Jira Default',
104
84
  config: {
105
85
  domain: envVars.JIRA_DOMAIN || '',
106
- projects: jiraProjects // Multi-project support
107
- },
108
- timeRange: {
109
- default: '1M',
110
- max: '6M'
86
+ projects: jiraProjects
111
87
  },
112
- rateLimits: {
113
- maxItemsPerSync: 500,
114
- warnThreshold: 100
115
- }
88
+ timeRange: { default: '1M', max: '6M' },
89
+ rateLimits: { maxItemsPerSync: 500, warnThreshold: 100 }
116
90
  };
117
- config.sync.profiles[profileId] = jiraProfile;
118
- config.sync.activeProfile = profileId;
119
- // Save config
91
+ profiles[profileId] = jiraProfile;
92
+ syncConfig.activeProfile = profileId;
120
93
  await fs.writeJson(configPath, config, { spaces: 2 });
121
94
  console.log(chalk.blue('\n📁 Creating Multi-Project Folders'));
122
- console.log(chalk.gray(` Detected: ${jiraProjects.length} Jira projects (${jiraProjects.join(', ')})`));
123
- // Create project-specific folders following SIMPLIFIED architecture (increment 0026)
124
- // Structure: .specweave/docs/internal/specs/{project-id}/
125
- // NOTE: Only specs folder is created. All docs live at root internal/ level.
95
+ console.log(chalk.gray(' Detected: ' + jiraProjects.length + ' Jira projects (' + jiraProjects.join(', ') + ')'));
126
96
  for (const projectKey of jiraProjects) {
127
- const projectId = projectKey.toLowerCase(); // FE → fe, BE → be
128
- const internalDocsPath = path.join(targetDir, '.specweave', 'docs', 'internal');
129
- // Create ONLY specs folder (simplified structure)
130
- const specsPath = path.join(internalDocsPath, 'specs', projectId);
97
+ const projectId = projectKey.toLowerCase();
98
+ const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectId);
131
99
  if (!fs.existsSync(specsPath)) {
132
100
  fs.mkdirSync(specsPath, { recursive: true });
133
101
  }
134
- console.log(chalk.green(` ✓ Created project: ${projectKey} (simplified structure)`));
102
+ console.log(chalk.green(' ✓ Created project: ' + projectKey + ' (simplified structure)'));
135
103
  }
136
104
  console.log('');
137
105
  }
138
106
  }
139
- // ADO multi-project support (future)
140
- if (adoProjects?.length && adoStrategy === 'project-per-team') {
141
- // TODO: Implement ADO multi-project folder creation
142
- // Similar logic to JIRA above
143
- }
144
107
  }
108
+ /**
109
+ * Main init command
110
+ */
145
111
  export async function initCommand(projectName, options = {}) {
146
- // Initialize logger (injectable for testing)
147
- const logger = options.logger ?? consoleLogger;
148
- // NOTE: This CLI command is 99% user-facing output (console.log/console.error).
149
- // All console.* calls in this function are legitimate user-facing exceptions
150
- // as defined in CONTRIBUTING.md (colored messages, confirmations, formatted output).
151
- // Logger is available for any internal debug logs if needed in future.
152
- // Detect CI/non-interactive environment (use throughout function)
112
+ // Detect CI/non-interactive environment
153
113
  const isCI = process.env.CI === 'true' ||
154
114
  process.env.GITHUB_ACTIONS === 'true' ||
155
115
  process.env.GITLAB_CI === 'true' ||
156
116
  process.env.CIRCLECI === 'true' ||
157
117
  !process.stdin.isTTY;
158
- // Validate and normalize language option
159
- const language = options.language?.toLowerCase() || 'en';
160
- // Validate language if provided
118
+ // Validate language
119
+ const language = (options.language?.toLowerCase() || 'en');
161
120
  if (options.language && !isLanguageSupported(language)) {
162
- const locale = getLocaleManager('en'); // Use English for error messages about invalid language
163
- console.error(chalk.red(`\n${locale.t('cli', 'init.errors.invalidLanguage', { language: options.language })}`));
164
- console.error(chalk.yellow(`${locale.t('cli', 'init.errors.supportedLanguages', { languages: getSupportedLanguages().join(', ') })}\n`));
121
+ const locale = getLocaleManager('en');
122
+ console.error(chalk.red('\n' + locale.t('cli', 'init.errors.invalidLanguage', { language: options.language })));
123
+ console.error(chalk.yellow(locale.t('cli', 'init.errors.supportedLanguages', { languages: getSupportedLanguages().join(', ') }) + '\n'));
165
124
  process.exit(1);
166
125
  }
167
- // Initialize LanguageManager and LocaleManager
168
- const i18n = new LanguageManager({ defaultLanguage: language });
169
126
  const locale = getLocaleManager(language);
170
- // Display welcome message in user's language
171
- console.log(chalk.blue.bold(`\n${locale.t('cli', 'init.welcome')}\n`));
127
+ console.log(chalk.blue.bold('\n' + locale.t('cli', 'init.welcome') + '\n'));
172
128
  let targetDir;
173
129
  let finalProjectName;
174
130
  let usedDotNotation = false;
175
- let continueExisting = false; // Track if user chose to continue with existing project
176
- // Handle "." for current directory initialization
131
+ let continueExisting = false;
132
+ // Handle "." for current directory
177
133
  if (projectName === '.') {
178
134
  usedDotNotation = true;
179
135
  targetDir = process.cwd();
180
- // CRITICAL SAFETY CHECK: Prevent initialization in home directory
181
- const homeDir = os.homedir();
182
- if (path.resolve(targetDir) === path.resolve(homeDir)) {
183
- console.log('');
184
- console.log(chalk.red.bold('❌ DANGEROUS: Cannot initialize SpecWeave in home directory!'));
185
- console.log('');
136
+ // Safety: Prevent init in home directory
137
+ if (path.resolve(targetDir) === path.resolve(os.homedir())) {
138
+ console.log(chalk.red.bold('\n❌ DANGEROUS: Cannot initialize SpecWeave in home directory!\n'));
186
139
  console.log(chalk.yellow(' Your home directory contains ALL your projects.'));
187
- console.log(chalk.yellow(' Initializing here would treat everything as one project.'));
188
- console.log('');
189
- console.log(chalk.cyan('💡 What to do instead:'));
190
- console.log(chalk.white(' 1. Create a project directory:'));
191
- console.log(chalk.gray(' mkdir ~/Projects/my-project'));
192
- console.log(chalk.white(' 2. Navigate to it:'));
193
- console.log(chalk.gray(' cd ~/Projects/my-project'));
194
- console.log(chalk.white(' 3. Initialize:'));
195
- console.log(chalk.gray(' specweave init .'));
196
- console.log('');
197
- console.log(chalk.white(' Or use a project name:'));
198
- console.log(chalk.gray(' specweave init my-project'));
199
- console.log('');
140
+ console.log(chalk.cyan('\n💡 What to do instead:'));
141
+ console.log(chalk.gray(' mkdir ~/Projects/my-project && cd ~/Projects/my-project && specweave init .\n'));
200
142
  process.exit(1);
201
143
  }
202
144
  const dirName = path.basename(targetDir);
203
- // Validate directory name is suitable for project name
145
+ // Validate directory name
204
146
  if (!/^[a-z0-9-]+$/.test(dirName)) {
205
147
  const suggestedName = dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
206
148
  if (isCI) {
207
- // CI mode: auto-sanitize directory name without prompting
208
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.invalidDirName', { dirName })}`));
209
- console.log(chalk.gray(` → CI mode: Auto-sanitizing to "${suggestedName}"`));
149
+ console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.invalidDirName', { dirName })));
150
+ console.log(chalk.gray(' CI mode: Auto-sanitizing to "' + suggestedName + '"'));
210
151
  finalProjectName = suggestedName;
211
152
  }
212
153
  else {
213
- // Interactive mode: prompt user
214
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.invalidDirName', { dirName })}`));
215
- const { name } = await inquirer.prompt([
216
- {
217
- type: 'input',
218
- name: 'name',
219
- message: 'Project name (for templates):',
220
- default: suggestedName,
221
- validate: (input) => {
222
- if (/^[a-z0-9-]+$/.test(input))
223
- return true;
224
- return 'Project name must be lowercase letters, numbers, and hyphens only';
225
- },
226
- },
227
- ]);
228
- finalProjectName = name;
154
+ console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.invalidDirName', { dirName })));
155
+ finalProjectName = await input({
156
+ message: 'Project name (for templates):',
157
+ default: suggestedName,
158
+ validate: (val) => /^[a-z0-9-]+$/.test(val) || 'Project name must be lowercase letters, numbers, and hyphens only',
159
+ });
229
160
  }
230
161
  }
231
162
  else {
232
163
  finalProjectName = dirName;
233
164
  }
234
- // Safety: Warn if directory is not empty
235
- const allFiles = fs.readdirSync(targetDir);
236
- const existingFiles = allFiles.filter(f => !f.startsWith('.')); // Ignore hidden files
165
+ // Warn if directory not empty
166
+ const existingFiles = fs.readdirSync(targetDir).filter(f => !f.startsWith('.'));
237
167
  if (existingFiles.length > 0 && !options.force) {
238
168
  if (isCI) {
239
- // CI mode: allow initialization in non-empty directory without prompting
240
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })}`));
241
- console.log(chalk.gray(` → CI mode: Proceeding with initialization`));
169
+ console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })));
170
+ console.log(chalk.gray(' CI mode: Proceeding with initialization'));
242
171
  }
243
172
  else {
244
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })}`));
245
- const { confirm } = await inquirer.prompt([
246
- {
247
- type: 'confirm',
248
- name: 'confirm',
249
- message: 'Initialize SpecWeave in current directory?',
250
- default: false,
251
- },
252
- ]);
253
- if (!confirm) {
173
+ console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })));
174
+ const proceed = await confirm({ message: 'Initialize SpecWeave in current directory?', default: false });
175
+ if (!proceed) {
254
176
  console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
255
177
  process.exit(0);
256
178
  }
257
179
  }
258
180
  }
259
- // Check if .specweave already exists - SMART RE-INITIALIZATION
181
+ // Smart re-initialization
260
182
  if (fs.existsSync(path.join(targetDir, '.specweave'))) {
261
- console.log(chalk.blue('\n📦 Existing SpecWeave project detected!'));
262
- console.log(chalk.gray(' Found .specweave/ folder with your increments, docs, and configuration.\n'));
263
- let action;
264
- if (options.force) {
265
- // ⚠️ CRITICAL WARNING: --force attempts fresh start
266
- console.log(chalk.red.bold('\n⛔ DANGER: --force DELETES ALL DATA!'));
267
- console.log(chalk.red(' This will permanently delete:'));
268
- console.log(chalk.red(' • All increments (.specweave/increments/)'));
269
- console.log(chalk.red(' • All documentation (.specweave/docs/)'));
270
- console.log(chalk.red(' • All configuration and history'));
271
- console.log(chalk.yellow('\n 💡 TIP: Use "specweave init ." (no --force) for safe updates\n'));
272
- if (isCI) {
273
- // CI mode: proceed with force deletion without prompting (test environment)
274
- console.log(chalk.gray(' → CI mode: Proceeding with force deletion'));
275
- action = 'fresh';
276
- }
277
- else {
278
- // Interactive mode: ALWAYS require confirmation, even in force mode (safety critical!)
279
- const { confirmDeletion } = await inquirer.prompt([
280
- {
281
- type: 'confirm',
282
- name: 'confirmDeletion',
283
- message: chalk.red('⚠️ Type "y" to PERMANENTLY DELETE all .specweave/ data:'),
284
- default: false,
285
- },
286
- ]);
287
- if (!confirmDeletion) {
288
- console.log(chalk.green('\n✅ Deletion cancelled. No data lost.'));
289
- console.log(chalk.gray(' → Run "specweave init ." (without --force) for safe updates'));
290
- process.exit(0);
291
- }
292
- action = 'fresh';
293
- }
294
- console.log(chalk.yellow('\n 🔄 Force mode: Proceeding with fresh start...'));
295
- }
296
- else if (isCI) {
297
- // CI mode: auto-continue with existing project
298
- console.log(chalk.gray(' → CI mode: Continuing with existing project'));
299
- action = 'continue';
300
- }
301
- else {
302
- // Interactive mode: Ask user what to do
303
- const result = await inquirer.prompt([
304
- {
305
- type: 'select',
306
- name: 'action',
307
- message: 'What would you like to do?',
308
- choices: [
309
- {
310
- name: '✨ Continue working (keep all existing increments, docs, and history)',
311
- value: 'continue',
312
- short: 'Continue'
313
- },
314
- {
315
- name: '🔄 Fresh start (delete .specweave/ and start from scratch)',
316
- value: 'fresh',
317
- short: 'Fresh start'
318
- },
319
- {
320
- name: '❌ Cancel (exit without changes)',
321
- value: 'cancel',
322
- short: 'Cancel'
323
- }
324
- ],
325
- default: 'continue'
326
- },
327
- ]);
328
- action = result.action;
329
- }
330
- if (action === 'cancel') {
331
- console.log(chalk.yellow('\n⏸️ Initialization cancelled. No changes made.'));
183
+ const result = await promptSmartReinit({ targetDir, isCI, hasForce: !!options.force });
184
+ if (result.action === 'cancel') {
332
185
  process.exit(0);
333
186
  }
334
- if (action === 'fresh') {
335
- if (!options.force) {
336
- if (isCI) {
337
- // CI mode: NEVER allow fresh start without explicit --force flag (safety critical!)
338
- console.log(chalk.red('\n⛔ ERROR: Cannot start fresh in CI mode without --force flag'));
339
- console.log(chalk.gray(' → Use "specweave init . --force" if you really want to delete all data'));
340
- process.exit(1);
341
- }
342
- // Interactive mode: Ask for confirmation (force mode already confirmed above)
343
- console.log(chalk.yellow('\n⚠️ WARNING: This will DELETE all increments, docs, and configuration!'));
344
- const { confirmFresh } = await inquirer.prompt([
345
- {
346
- type: 'confirm',
347
- name: 'confirmFresh',
348
- message: 'Are you sure you want to start fresh? (all .specweave/ content will be lost)',
349
- default: false,
350
- },
351
- ]);
352
- if (!confirmFresh) {
353
- console.log(chalk.yellow('\n⏸️ Fresh start cancelled. No changes made.'));
354
- process.exit(0);
355
- }
356
- }
357
- // Create backup before deletion (safety net!)
358
- const specweavePath = path.join(targetDir, '.specweave');
359
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-').split('T')[0];
360
- const backupPath = path.join(targetDir, `.specweave.backup-${timestamp}`);
361
- try {
362
- console.log(chalk.cyan('\n 📦 Creating backup before deletion...'));
363
- fs.copySync(specweavePath, backupPath);
364
- console.log(chalk.green(` ✅ Backup saved: ${path.relative(targetDir, backupPath)}`));
365
- console.log(chalk.gray(` To restore: mv ${path.basename(backupPath)} .specweave`));
366
- }
367
- catch (backupError) {
368
- console.log(chalk.yellow(' ⚠️ Could not create backup (proceeding anyway)'));
369
- }
370
- // Count files before deletion (for logging)
371
- let fileCount = 0;
372
- try {
373
- const countFiles = (dir) => {
374
- let count = 0;
375
- const items = fs.readdirSync(dir);
376
- for (const item of items) {
377
- const fullPath = path.join(dir, item);
378
- if (fs.statSync(fullPath).isDirectory()) {
379
- count += countFiles(fullPath);
380
- }
381
- else {
382
- count++;
383
- }
384
- }
385
- return count;
386
- };
387
- fileCount = countFiles(specweavePath);
388
- }
389
- catch (e) {
390
- // Ignore errors
391
- }
392
- // Delete .specweave/ for fresh start
393
- fs.removeSync(specweavePath);
394
- console.log(chalk.blue(`\n ♻️ Removed .specweave/ (${fileCount} files deleted)`));
395
- // NOTE: No need to delete .claude/ - marketplace is GLOBAL, not per-project
396
- console.log(chalk.green(' ✅ Starting fresh - will create new .specweave/ structure\n'));
397
- }
398
- else {
399
- // Continue working - keep everything
400
- continueExisting = true;
401
- console.log(chalk.green('\n✅ Continuing with existing project'));
402
- console.log(chalk.gray(' → Keeping all increments, docs, and history'));
403
- console.log(chalk.gray(' → Config will be updated if needed\n'));
404
- // NOTE: No need to refresh .claude/settings.json - marketplace is GLOBAL via CLI
405
- }
187
+ continueExisting = result.continueExisting;
406
188
  }
407
189
  }
408
190
  else {
409
- // Original behavior: create subdirectory
410
- // 1. Get project name if not provided
191
+ // Create subdirectory
411
192
  if (!projectName) {
412
- const answers = await inquirer.prompt([
413
- {
414
- type: 'input',
415
- name: 'projectName',
416
- message: 'Project name:',
417
- default: 'my-saas',
418
- validate: (input) => {
419
- if (/^[a-z0-9-]+$/.test(input))
420
- return true;
421
- return 'Project name must be lowercase letters, numbers, and hyphens only';
422
- },
423
- },
424
- ]);
425
- projectName = answers.projectName;
193
+ projectName = await input({
194
+ message: 'Project name:',
195
+ default: 'my-saas',
196
+ validate: (val) => /^[a-z0-9-]+$/.test(val) || 'Project name must be lowercase letters, numbers, and hyphens only',
197
+ });
426
198
  }
427
199
  targetDir = path.resolve(process.cwd(), projectName);
428
200
  finalProjectName = projectName;
429
- // 2. Check if directory exists
430
201
  if (fs.existsSync(targetDir)) {
431
- // Brownfield-safe: Check what's in the directory
432
- const allFiles = fs.readdirSync(targetDir);
433
- const existingFiles = allFiles.filter(f => !f.startsWith('.')); // Ignore hidden files
434
202
  const hasSpecweave = fs.existsSync(path.join(targetDir, '.specweave'));
435
- if (existingFiles.length > 0 || hasSpecweave) {
436
- console.log(chalk.yellow(`\nDirectory ${projectName} exists with ${existingFiles.length} file(s).`));
437
- if (hasSpecweave) {
438
- // SMART RE-INITIALIZATION (same as current directory case)
439
- console.log(chalk.blue('\n📦 Existing SpecWeave project detected!'));
440
- console.log(chalk.gray(' Found .specweave/ folder with your increments, docs, and configuration.\n'));
441
- const { action } = await inquirer.prompt([
442
- {
443
- type: 'select',
444
- name: 'action',
445
- message: 'What would you like to do?',
446
- choices: [
447
- {
448
- name: '✨ Continue working (keep all existing increments, docs, and history)',
449
- value: 'continue',
450
- short: 'Continue'
451
- },
452
- {
453
- name: '🔄 Fresh start (delete .specweave/ and start from scratch)',
454
- value: 'fresh',
455
- short: 'Fresh start'
456
- },
457
- {
458
- name: '❌ Cancel (exit without changes)',
459
- value: 'cancel',
460
- short: 'Cancel'
461
- }
462
- ],
463
- default: 'continue'
464
- },
465
- ]);
466
- if (action === 'cancel') {
467
- console.log(chalk.yellow('\n⏸️ Initialization cancelled. No changes made.'));
468
- process.exit(0);
469
- }
470
- if (action === 'fresh') {
471
- console.log(chalk.yellow('\n⚠️ WARNING: This will DELETE all increments, docs, and configuration!'));
472
- const { confirmFresh } = await inquirer.prompt([
473
- {
474
- type: 'confirm',
475
- name: 'confirmFresh',
476
- message: 'Are you sure you want to start fresh? (all .specweave/ content will be lost)',
477
- default: false,
478
- },
479
- ]);
480
- if (!confirmFresh) {
481
- console.log(chalk.yellow('\n⏸️ Fresh start cancelled. No changes made.'));
482
- process.exit(0);
483
- }
484
- // Delete .specweave/ for fresh start
485
- fs.removeSync(path.join(targetDir, '.specweave'));
486
- console.log(chalk.blue(' ♻️ Removed .specweave/ (fresh start)'));
487
- // NOTE: No need to delete .claude/ - marketplace is GLOBAL, not per-project
488
- console.log(chalk.green(' ✅ Starting fresh - will create new .specweave/ structure\n'));
489
- }
490
- else {
491
- // Continue working - keep everything
492
- continueExisting = true;
493
- console.log(chalk.green('\n✅ Continuing with existing project'));
494
- console.log(chalk.gray(' → Keeping all increments, docs, and history'));
495
- console.log(chalk.gray(' → Config will be updated if needed\n'));
496
- // NOTE: No need to refresh .claude/settings.json - marketplace is GLOBAL via CLI
497
- }
203
+ if (hasSpecweave) {
204
+ const result = await promptSmartReinit({ targetDir, isCI, hasForce: !!options.force });
205
+ if (result.action === 'cancel') {
206
+ process.exit(0);
498
207
  }
499
- else {
500
- // No .specweave/ folder, just brownfield directory with files
501
- const { initExisting } = await inquirer.prompt([
502
- {
503
- type: 'confirm',
504
- name: 'initExisting',
505
- message: 'Initialize SpecWeave in existing directory (non-destructive)?',
506
- default: false,
507
- },
508
- ]);
208
+ continueExisting = result.continueExisting;
209
+ }
210
+ else {
211
+ const existingFiles = fs.readdirSync(targetDir).filter(f => !f.startsWith('.'));
212
+ if (existingFiles.length > 0) {
213
+ console.log(chalk.yellow('\nDirectory ' + projectName + ' exists with ' + existingFiles.length + ' file(s).'));
214
+ const initExisting = await confirm({ message: 'Initialize SpecWeave in existing directory (non-destructive)?', default: false });
509
215
  if (!initExisting) {
510
216
  console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
511
217
  process.exit(0);
@@ -518,80 +224,29 @@ export async function initCommand(projectName, options = {}) {
518
224
  fs.mkdirSync(targetDir, { recursive: true });
519
225
  }
520
226
  }
521
- // 3. Check for nested .specweave/ (CRITICAL: prevent nested folders)
227
+ // Check for nested .specweave/
522
228
  const parentSpecweaveFolders = detectNestedSpecweave(targetDir);
523
229
  if (parentSpecweaveFolders && parentSpecweaveFolders.length > 0) {
524
- console.log('');
525
- console.log(chalk.red.bold(locale.t('cli', 'init.errors.nestedNotSupported')));
526
- console.log('');
527
- // Check if home directory has .specweave/ (special warning)
230
+ console.log(chalk.red.bold('\n' + locale.t('cli', 'init.errors.nestedNotSupported') + '\n'));
528
231
  const homeDirFolder = parentSpecweaveFolders.find(f => f.isHomeDir);
529
232
  if (homeDirFolder) {
530
233
  console.log(chalk.red.bold(' ⚠️ CRITICAL: Found .specweave/ in HOME DIRECTORY!'));
531
- console.log(chalk.yellow(` ${homeDirFolder.path}`));
532
- console.log('');
533
- console.log(chalk.yellow(' This is ALMOST ALWAYS a mistake!'));
534
- console.log(chalk.gray(' • Your home directory should NOT be a SpecWeave project'));
535
- console.log(chalk.gray(' • This treats ALL your files as one giant project'));
536
- console.log(chalk.gray(' • You likely ran "specweave init ." from home by accident'));
537
- console.log('');
538
- console.log(chalk.cyan.bold(' 💡 Quick fix:'));
539
- console.log(chalk.white(` rm -rf "${homeDirFolder.path}/.specweave"`));
540
- console.log(chalk.gray(' Then try your command again'));
541
- console.log('');
542
- }
543
- // Show all found .specweave/ folders
544
- if (parentSpecweaveFolders.length === 1 && !homeDirFolder) {
545
- console.log(chalk.yellow(` ${locale.t('cli', 'init.errors.parentFound')}`));
546
- console.log(chalk.white(` ${parentSpecweaveFolders[0].path}`));
547
- }
548
- else if (!homeDirFolder) {
549
- console.log(chalk.yellow(` Found ${parentSpecweaveFolders.length} parent .specweave/ folders:`));
550
- console.log('');
551
- // Sort by depth (closest first)
552
- const sortedFolders = [...parentSpecweaveFolders].sort((a, b) => a.depth - b.depth);
553
- sortedFolders.forEach((folder, index) => {
554
- const marker = index === 0 ? chalk.green('✓ CLOSEST') : chalk.gray(` ${folder.depth} level${folder.depth > 1 ? 's' : ''} up`);
555
- console.log(` ${marker}: ${chalk.white(folder.path)}`);
556
- });
557
- }
558
- console.log('');
559
- console.log(chalk.cyan(` ${locale.t('cli', 'init.info.nestedEnforcement')}`));
560
- console.log(chalk.gray(` ${locale.t('cli', 'init.info.nestedBullet1')}`));
561
- console.log(chalk.gray(` ${locale.t('cli', 'init.info.nestedBullet2')}`));
562
- console.log(chalk.gray(` ${locale.t('cli', 'init.info.nestedBullet3')}`));
563
- console.log('');
564
- // Suggest using the CLOSEST folder (most relevant)
565
- const closestFolder = parentSpecweaveFolders.reduce((closest, current) => current.depth < closest.depth ? current : closest);
566
- console.log(chalk.cyan(` ${locale.t('cli', 'init.info.nestedToFix')}`));
567
- console.log(chalk.green.bold(` Recommended: Use the CLOSEST .specweave/ folder`));
568
- console.log(chalk.white(` ${locale.t('cli', 'init.nestedCdCommand', { path: closestFolder.path })}`));
569
- console.log(chalk.white(` ${locale.t('cli', 'init.nestedIncCommand')}`));
570
- console.log('');
571
- // Provide cleanup option if user has multiple unnecessary folders
572
- if (parentSpecweaveFolders.length > 1) {
573
- console.log(chalk.yellow.bold(` 💡 Tip: Multiple .specweave/ folders detected`));
574
- console.log(chalk.gray(` If some are unnecessary, consider removing them:`));
575
- console.log('');
576
- parentSpecweaveFolders.forEach(folder => {
577
- console.log(chalk.gray(` rm -rf "${folder.path}/.specweave" # Remove if not needed`));
578
- });
579
- console.log('');
234
+ console.log(chalk.yellow(' ' + homeDirFolder.path));
235
+ console.log(chalk.cyan.bold('\n 💡 Quick fix:'));
236
+ console.log(chalk.white(' rm -rf "' + homeDirFolder.path + '/.specweave"\n'));
580
237
  }
581
238
  process.exit(1);
582
239
  }
583
240
  const spinner = ora('Creating SpecWeave project...').start();
584
241
  try {
585
- // 4. Detect or select tool
242
+ // Detect or select tool
586
243
  const adapterLoader = new AdapterLoader();
587
244
  let toolName;
588
245
  if (options.adapter) {
589
- // User explicitly chose a tool via --adapter flag
590
246
  toolName = options.adapter;
591
- spinner.text = `Using ${toolName}...`;
247
+ spinner.text = 'Using ' + toolName + '...';
592
248
  }
593
249
  else {
594
- // SMART CHECK: If continuing existing project, read existing adapter from config
595
250
  let existingAdapter = null;
596
251
  if (continueExisting) {
597
252
  const existingConfigPath = path.join(targetDir, '.specweave', 'config.json');
@@ -600,79 +255,45 @@ export async function initCommand(projectName, options = {}) {
600
255
  const existingConfig = fs.readJsonSync(existingConfigPath);
601
256
  existingAdapter = existingConfig?.adapters?.default || null;
602
257
  }
603
- catch (error) {
604
- // Invalid config, will proceed with detection
605
- }
258
+ catch { /* ignore */ }
606
259
  }
607
260
  }
608
- // Detect tool and always ask user (even if matches existing config)
609
261
  const detectedTool = await adapterLoader.detectTool();
610
262
  spinner.stop();
611
- console.log('');
612
- console.log(chalk.cyan(`🔍 ${locale.t('cli', 'init.toolDetection.header')}`));
613
- // Show existing adapter if present
263
+ console.log(chalk.cyan('\n🔍 ' + locale.t('cli', 'init.toolDetection.header')));
614
264
  if (existingAdapter) {
615
- console.log(chalk.blue(` 📋 Current adapter: ${existingAdapter}`));
616
- if (existingAdapter === detectedTool) {
617
- console.log(chalk.gray(` Detected tool matches current config`));
618
- }
619
- else {
620
- console.log(chalk.yellow(` ⚠️ Detected tool (${detectedTool}) differs from config`));
621
- }
265
+ console.log(chalk.blue(' 📋 Current adapter: ' + existingAdapter));
622
266
  }
623
267
  else {
624
- // No existing adapter (new project)
625
- if (detectedTool === 'claude') {
626
- console.log(chalk.gray(` Recommended: ${detectedTool} (no other tool detected)`));
627
- }
628
- else {
629
- console.log(chalk.gray(` ${locale.t('cli', 'init.toolDetection.detected', { tool: detectedTool })}`));
630
- }
268
+ console.log(chalk.gray(' ' + locale.t('cli', 'init.toolDetection.detected', { tool: detectedTool })));
631
269
  }
632
270
  console.log('');
633
- // Use function-level isCI (already defined at function start)
634
- let confirmTool = true; // Default to yes
635
271
  if (isCI) {
636
- // In CI, automatically use detected tool without prompting
637
- console.log(chalk.gray(` ${locale.t('cli', 'init.toolDetection.ciAutoConfirm', { tool: detectedTool })}`));
272
+ console.log(chalk.gray(' ' + locale.t('cli', 'init.toolDetection.ciAutoConfirm', { tool: detectedTool })));
638
273
  toolName = detectedTool;
639
274
  }
640
275
  else {
641
- // Interactive mode - ask for confirmation
642
- const response = await inquirer.prompt([
643
- {
644
- type: 'confirm',
645
- name: 'confirmTool',
646
- message: locale.t('cli', 'init.toolDetection.confirmPrompt', { tool: detectedTool }),
647
- default: true
648
- }
649
- ]);
650
- confirmTool = response.confirmTool;
651
- }
652
- if (!confirmTool) {
653
- // Let user choose from available tools
654
- const { selectedTool } = await inquirer.prompt([
655
- {
656
- type: 'select',
657
- name: 'selectedTool',
276
+ const confirmTool = await confirm({
277
+ message: locale.t('cli', 'init.toolDetection.confirmPrompt', { tool: detectedTool }),
278
+ default: true
279
+ });
280
+ if (!confirmTool) {
281
+ toolName = await select({
658
282
  message: locale.t('cli', 'init.toolDetection.selectPrompt'),
659
283
  choices: [
660
- { name: `Claude Code (Recommended - Full automation)`, value: 'claude' },
661
- { name: 'Cursor (Partial - AGENTS.md compilation, team commands, less reliable)', value: 'cursor' },
662
- { name: 'Other (Copilot, ChatGPT, Gemini - Limited: no hooks, manual workflow, high context usage)', value: 'generic' }
284
+ { name: 'Claude Code (Recommended - Full automation)', value: 'claude' },
285
+ { name: 'Cursor (Partial - AGENTS.md compilation)', value: 'cursor' },
286
+ { name: 'Other (Copilot, ChatGPT - Limited)', value: 'generic' }
663
287
  ]
664
- }
665
- ]);
666
- toolName = selectedTool;
667
- }
668
- else {
669
- // User confirmed detected tool
670
- toolName = detectedTool;
288
+ });
289
+ }
290
+ else {
291
+ toolName = detectedTool;
292
+ }
671
293
  }
672
- spinner.start(`Using ${toolName}...`);
294
+ spinner.start('Using ' + toolName + '...');
673
295
  }
674
- // 4. Create directory structure (adapter-specific)
675
- // Skip if continuing with existing project (directories already exist)
296
+ // Create directory structure
676
297
  if (!continueExisting) {
677
298
  createDirectoryStructure(targetDir, toolName);
678
299
  spinner.text = 'Directory structure created...';
@@ -680,162 +301,38 @@ export async function initCommand(projectName, options = {}) {
680
301
  else {
681
302
  spinner.text = 'Using existing directory structure...';
682
303
  }
683
- // 5. Configure GitHub marketplace for Claude Code
684
- // ✅ NEW APPROACH: Claude Code fetches plugins from GitHub (no local copying!)
304
+ // Configure GitHub marketplace for Claude Code
685
305
  if (toolName === 'claude') {
686
- try {
687
- spinner.text = 'Configuring GitHub marketplace...';
688
- // Settings.json will be created by setupClaudePluginAutoRegistration()
689
- // No need to copy marketplace or plugins - everything is fetched from GitHub
690
- spinner.succeed('GitHub marketplace configured');
691
- console.log(chalk.gray(` ✓ Marketplace: github.com/anton-abyzov/specweave/.claude-plugin`));
692
- console.log(chalk.gray(` ✓ Plugins fetch on-demand (no local copies = faster init)`));
693
- }
694
- catch (error) {
695
- // Log errors in debug mode for troubleshooting
696
- if (process.env.DEBUG) {
697
- spinner.stop();
698
- console.error(chalk.red(`\n❌ Marketplace setup error: ${error instanceof Error ? error.message : String(error)}`));
699
- if (error instanceof Error && error.stack) {
700
- console.error(chalk.gray(error.stack));
701
- }
702
- spinner.start();
703
- }
704
- console.warn(chalk.yellow(`\n${locale.t('cli', 'init.warnings.marketplaceCopyFailed')}`));
705
- }
306
+ spinner.text = 'Configuring GitHub marketplace...';
307
+ spinner.succeed('GitHub marketplace configured');
308
+ console.log(chalk.gray(' ✓ Marketplace: github.com/anton-abyzov/specweave/.claude-plugin'));
706
309
  }
707
- // 6. Copy base templates (config, README, CLAUDE.md - same for all)
708
- // Skip if continuing with existing project (files already exist)
310
+ // Copy templates
709
311
  if (!continueExisting) {
710
- const templatesDir = findSourceDir('templates');
312
+ const templatesDir = findSourceDir('templates', __dirname);
711
313
  await copyTemplates(templatesDir, targetDir, finalProjectName, language);
712
314
  spinner.text = 'Base templates copied...';
713
315
  }
714
- else {
715
- spinner.text = 'Skipping template copying (using existing files)...';
716
- }
717
- // 6. Install based on tool
316
+ // Install based on tool
718
317
  if (toolName === 'claude') {
719
- // DEFAULT: Native Claude Code plugins (installed globally via /plugin install)
720
- // No per-project copying needed - plugins work across all projects!
721
318
  spinner.text = 'Configuring for Claude Code...';
722
- console.log(`\n${locale.t('cli', 'init.claudeNativeComplete')}`);
723
- console.log(` ${locale.t('cli', 'init.claudeNativeBenefits')}`);
319
+ console.log('\n' + locale.t('cli', 'init.claudeNativeComplete'));
724
320
  }
725
321
  else {
726
- // Use adapter for non-Claude tools
727
- spinner.text = `Installing ${toolName} adapter...`;
728
- const adapter = adapterLoader.getAdapter(toolName);
729
- if (!adapter) {
730
- throw new Error(`Adapter not found: ${toolName}`);
731
- }
732
- await adapterLoader.checkRequirements(toolName);
733
- await adapter.install({
734
- projectPath: targetDir,
735
- projectName: finalProjectName,
736
- techStack: options.techStack ? { language: options.techStack } : undefined,
737
- docsApproach: 'incremental'
738
- });
739
- // 6. Copy plugins/ folder for non-Claude adapters
740
- // CRITICAL: Copilot/Cursor/Generic need local plugins/ folder!
741
- // AGENTS.md instructs AI to read plugins/specweave/commands/*.md
742
- // Without this folder, those commands don't exist in the project!
743
- if (toolName !== 'claude') {
744
- spinner.start('Copying plugins folder for command execution...');
745
- try {
746
- const specweavePackageRoot = findPackageRoot(__dirname);
747
- if (specweavePackageRoot) {
748
- const sourcePluginsDir = path.join(specweavePackageRoot, 'plugins');
749
- const targetPluginsDir = path.join(targetDir, 'plugins');
750
- if (fs.existsSync(sourcePluginsDir)) {
751
- // Copy entire plugins/ folder from SpecWeave package to user project
752
- fs.copySync(sourcePluginsDir, targetPluginsDir, {
753
- overwrite: true,
754
- filter: (src) => {
755
- // Exclude .DS_Store and other hidden files
756
- const basename = path.basename(src);
757
- return !basename.startsWith('.');
758
- }
759
- });
760
- spinner.succeed('Plugins folder copied successfully');
761
- console.log(chalk.green(' ✔ AI can now execute SpecWeave commands'));
762
- console.log(chalk.gray(' → Copilot/Cursor will read plugins/specweave/commands/*.md'));
763
- }
764
- else {
765
- spinner.warn('Could not find plugins/ in SpecWeave package');
766
- console.log(chalk.yellow(' → Command execution may not work without plugins/ folder'));
767
- }
768
- }
769
- else {
770
- spinner.warn('Could not locate SpecWeave package');
771
- console.log(chalk.yellow(' → Skipping plugins/ folder copy'));
772
- }
773
- }
774
- catch (error) {
775
- spinner.warn('Could not copy plugins folder');
776
- console.log(chalk.yellow(` ${error instanceof Error ? error.message : error}`));
777
- }
778
- }
779
- // 7. Install core plugin for non-Claude adapters
780
- // CRITICAL: Cursor/Copilot/Generic need plugin files in project!
781
- // Claude uses plugin system (global), but others need local files for AGENTS.md/instructions.md
782
- try {
783
- spinner.start('Installing SpecWeave core plugin...');
784
- // Load core plugin from plugins/specweave/
785
- const corePluginPath = findSourceDir('plugins/specweave');
786
- const { PluginLoader } = await import('../../core/plugin-loader.js');
787
- const loader = new PluginLoader();
788
- const corePlugin = await loader.loadFromDirectory(corePluginPath);
789
- // Compile for adapter (Cursor → AGENTS.md, Copilot → instructions.md, etc.)
790
- if (adapter.supportsPlugins()) {
791
- await adapter.compilePlugin(corePlugin);
792
- spinner.succeed('SpecWeave core plugin installed');
793
- console.log(chalk.green(' ✔ Skills, agents, commands added to project'));
794
- console.log(chalk.gray(` → ${corePlugin.skills.length} skills, ${corePlugin.agents.length} agents, ${corePlugin.commands.length} commands`));
795
- }
796
- else {
797
- spinner.warn('Adapter does not support plugins');
798
- console.log(chalk.yellow(' → Core functionality may be limited'));
799
- }
800
- }
801
- catch (error) {
802
- spinner.warn('Could not install core plugin');
803
- console.log(chalk.yellow(` ${error instanceof Error ? error.message : error}`));
804
- console.log(chalk.gray(' → You can manually reference plugin files if needed'));
805
- }
322
+ await installNonClaudeAdapter(adapterLoader, toolName, targetDir, finalProjectName, options, spinner);
806
323
  }
807
- // 9. Initialize git (skip if .git already exists)
324
+ // Initialize git
808
325
  const gitDir = path.join(targetDir, '.git');
809
326
  if (!fs.existsSync(gitDir)) {
810
- // Use secure command execution for git commands
811
327
  const gitInitResult = execFileNoThrowSync('git', ['init'], { cwd: targetDir, shell: false });
812
328
  if (gitInitResult.success) {
813
329
  spinner.text = 'Git repository initialized...';
814
- }
815
- else {
816
- spinner.warn('Git initialization skipped (git not found)');
817
- }
818
- // 10. Create initial commit (if git init succeeded)
819
- if (gitInitResult.success) {
820
- const gitAddResult = execFileNoThrowSync('git', ['add', '.'], { cwd: targetDir, shell: false });
821
- if (gitAddResult.success) {
822
- const gitCommitResult = execFileNoThrowSync('git', [
823
- 'commit',
824
- '-m',
825
- 'Initial commit with SpecWeave'
826
- ], { cwd: targetDir, shell: false });
827
- if (gitCommitResult.success) {
828
- spinner.text = 'Initial commit created...';
829
- }
830
- // Git commit might fail if no user configured - that's ok, no need to warn
831
- }
330
+ execFileNoThrowSync('git', ['add', '.'], { cwd: targetDir, shell: false });
331
+ execFileNoThrowSync('git', ['commit', '-m', 'Initial commit with SpecWeave'], { cwd: targetDir, shell: false });
832
332
  }
833
333
  }
834
- else {
835
- spinner.text = 'Using existing Git repository...';
836
- }
837
334
  spinner.succeed('SpecWeave project created successfully!');
838
- // 11. Show tool-specific next steps
335
+ // Post-install for non-Claude adapters
839
336
  if (toolName !== 'claude') {
840
337
  const adapter = adapterLoader.getAdapter(toolName);
841
338
  if (adapter) {
@@ -847,534 +344,40 @@ export async function initCommand(projectName, options = {}) {
847
344
  });
848
345
  }
849
346
  }
850
- // 12. Create config.json with basic settings (testing params added later)
347
+ // Create config.json
851
348
  createConfigFile(targetDir, finalProjectName, toolName, language, false);
852
- // 14. AUTO-INSTALL ALL PLUGINS via Claude CLI (Breaking Change: No selective loading)
853
- // NOTE: We do NOT create .claude/settings.json - marketplace registration via CLI is GLOBAL
854
- // and persists across all projects. settings.json would be redundant.
349
+ // Auto-install plugins for Claude
855
350
  let autoInstallSucceeded = false;
856
351
  if (toolName === 'claude') {
857
- // Pre-flight check: Is Claude CLI available? (ROBUST CHECK)
858
- const claudeStatus = detectClaudeCli();
859
- if (!claudeStatus.available) {
860
- // Claude CLI NOT working → explain clearly with actionable diagnostics
861
- const diagnostic = getClaudeCliDiagnostic(claudeStatus);
862
- const suggestions = getClaudeCliSuggestions(claudeStatus);
863
- spinner.warn(diagnostic);
864
- console.log('');
865
- console.log(chalk.yellow.bold('⚠️ Claude Code CLI Issue Detected'));
866
- console.log('');
867
- // Show detailed diagnostic info with MORE context
868
- if (claudeStatus.commandExists) {
869
- console.log(chalk.white('Found command in PATH, but verification failed:'));
870
- console.log('');
871
- if (claudeStatus.commandPath) {
872
- console.log(chalk.gray(` Path: ${claudeStatus.commandPath}`));
873
- }
874
- if (claudeStatus.exitCode !== undefined) {
875
- console.log(chalk.gray(` Exit code: ${claudeStatus.exitCode}`));
876
- }
877
- console.log(chalk.gray(` Issue: ${claudeStatus.error}`));
878
- console.log('');
879
- // Explain what this likely means
880
- if (claudeStatus.error === 'version_check_failed') {
881
- console.log(chalk.yellow('⚠️ This likely means:'));
882
- console.log(chalk.gray(' • You have a DIFFERENT tool named "claude" in PATH'));
883
- console.log(chalk.gray(' • It\'s not the Claude Code CLI from Anthropic'));
884
- console.log(chalk.gray(' • The command exists but doesn\'t respond to --version'));
885
- }
886
- }
887
- else {
888
- console.log(chalk.white('Claude CLI not found in PATH'));
889
- }
890
- console.log('');
891
- // Show actionable suggestions
892
- console.log(chalk.cyan('💡 How to fix:'));
893
- console.log('');
894
- suggestions.forEach(suggestion => {
895
- console.log(chalk.gray(` ${suggestion}`));
896
- });
897
- console.log('');
898
- // Only show alternatives if user is NOT using Claude already
899
- if (claudeStatus.error === 'command_not_found') {
900
- console.log(chalk.cyan('Alternative Options:'));
901
- console.log('');
902
- console.log(chalk.white('1️⃣ Use Claude Code IDE (no CLI needed):'));
903
- console.log(chalk.gray(' → Open this project in Claude Code'));
904
- console.log(chalk.gray(' → Run: /plugin install specweave'));
905
- console.log(chalk.gray(' → Works immediately, no npm installation!'));
906
- console.log('');
907
- console.log(chalk.white('2️⃣ Use Different AI Tool:'));
908
- console.log(chalk.gray(' → Run: specweave init --adapter cursor'));
909
- console.log(chalk.gray(' → Works without Claude CLI'));
910
- console.log(chalk.gray(' → Less automation but no CLI dependency'));
911
- console.log('');
912
- }
913
- autoInstallSucceeded = false;
914
- }
915
- else {
916
- // Claude CLI available → install ALL plugins from marketplace
917
- try {
918
- const marketplaceCachePath = path.join(os.homedir(), '.claude/plugins/marketplaces/specweave/.claude-plugin/marketplace.json');
919
- // ULTRAFAST: Check if cache is fresh (< 5 min old) and valid
920
- let needsRefresh = true;
921
- let cacheAlreadyValid = false;
922
- // Skip cache if forceRefresh flag is set
923
- if (!options.forceRefresh && fs.existsSync(marketplaceCachePath)) {
924
- const cacheStats = fs.statSync(marketplaceCachePath);
925
- const cacheAge = Date.now() - cacheStats.mtimeMs;
926
- const fiveMinutes = 5 * 60 * 1000;
927
- if (cacheAge < fiveMinutes) {
928
- try {
929
- const cacheData = JSON.parse(fs.readFileSync(marketplaceCachePath, 'utf-8'));
930
- const hasValidPlugins = cacheData.plugins &&
931
- cacheData.plugins.length >= 25 &&
932
- cacheData.plugins.every((p) => p.name && p.version && p.description);
933
- if (hasValidPlugins) {
934
- needsRefresh = false;
935
- cacheAlreadyValid = true;
936
- console.log(chalk.green(' ⚡ Using cached marketplace (fresh)'));
937
- }
938
- }
939
- catch {
940
- // Cache exists but invalid, needs refresh
941
- }
942
- }
943
- }
944
- if (needsRefresh) {
945
- // Step 1: Remove existing marketplace to force update
946
- spinner.start('Refreshing SpecWeave marketplace...');
947
- const listResult = execFileNoThrowSync('claude', [
948
- 'plugin',
949
- 'marketplace',
950
- 'list'
951
- ]);
952
- const marketplaceExists = listResult.success &&
953
- (listResult.stdout || '').toLowerCase().includes('specweave');
954
- if (marketplaceExists) {
955
- execFileNoThrowSync('claude', [
956
- 'plugin',
957
- 'marketplace',
958
- 'remove',
959
- 'specweave'
960
- ]);
961
- console.log(chalk.blue(' 🔄 Removed existing marketplace for update'));
962
- }
963
- // Step 2: Add marketplace from GitHub (always fresh)
964
- const addResult = execFileNoThrowSync('claude', [
965
- 'plugin',
966
- 'marketplace',
967
- 'add',
968
- 'anton-abyzov/specweave'
969
- ]);
970
- if (!addResult.success) {
971
- throw new Error('Failed to add marketplace from GitHub');
972
- }
973
- console.log(chalk.green(' ✔ Marketplace registered from GitHub'));
974
- // NO WAIT NEEDED: We load from source (npm package), not cache
975
- // The cache is populated asynchronously by Claude Code and isn't used during init
976
- spinner.succeed('SpecWeave marketplace ready');
977
- }
978
- // Step 2: Load marketplace.json to get ALL available plugins
979
- spinner.start('Loading available plugins...');
980
- const marketplaceJsonPath = findSourceDir('.claude-plugin/marketplace.json');
981
- if (!fs.existsSync(marketplaceJsonPath)) {
982
- throw new Error('marketplace.json not found - cannot determine plugins to install');
983
- }
984
- const marketplace = JSON.parse(fs.readFileSync(marketplaceJsonPath, 'utf-8'));
985
- const allPlugins = marketplace.plugins || [];
986
- if (allPlugins.length === 0) {
987
- throw new Error('No plugins found in marketplace.json');
988
- }
989
- console.log(chalk.blue(` 📦 Found ${allPlugins.length} plugins to install`));
990
- spinner.succeed(`Found ${allPlugins.length} plugins`);
991
- // Step 3: Install ALL plugins with retry logic (handles remaining race conditions)
992
- let successCount = 0;
993
- let failCount = 0;
994
- const failedPlugins = [];
995
- for (const plugin of allPlugins) {
996
- const pluginName = plugin.name;
997
- spinner.start(`Installing ${pluginName}...`);
998
- // Retry up to 3 times with exponential backoff
999
- let installed = false;
1000
- for (let attempt = 1; attempt <= 3; attempt++) {
1001
- const installResult = execFileNoThrowSync('claude', [
1002
- 'plugin',
1003
- 'install',
1004
- pluginName
1005
- ]);
1006
- if (installResult.success) {
1007
- installed = true;
1008
- break;
1009
- }
1010
- // If "not found" error and not last attempt, wait and retry
1011
- if (installResult.stderr?.includes('not found') && attempt < 3) {
1012
- spinner.text = `Installing ${pluginName}... (retry ${attempt}/3)`;
1013
- await new Promise(resolve => setTimeout(resolve, 500 * attempt)); // 500ms, 1s, 1.5s
1014
- continue;
1015
- }
1016
- // Other errors or final attempt - stop retrying
1017
- break;
1018
- }
1019
- if (installed) {
1020
- successCount++;
1021
- spinner.succeed(`${pluginName} installed`);
1022
- }
1023
- else {
1024
- failCount++;
1025
- failedPlugins.push(pluginName);
1026
- spinner.warn(`${pluginName} failed (will continue)`);
1027
- }
1028
- }
1029
- // Step 4: Report results
1030
- console.log('');
1031
- console.log(chalk.green.bold(`✅ Plugin Installation Complete`));
1032
- console.log(chalk.white(` Installed: ${successCount}/${allPlugins.length} plugins`));
1033
- if (failCount > 0) {
1034
- console.log(chalk.yellow(` Failed: ${failCount} plugins`));
1035
- console.log(chalk.gray(` Failed plugins: ${failedPlugins.join(', ')}`));
1036
- console.log(chalk.gray(` → You can install these manually later`));
1037
- }
1038
- console.log('');
1039
- console.log(chalk.cyan('📋 Available capabilities:'));
1040
- console.log(chalk.gray(' • /specweave:increment - Plan new features'));
1041
- console.log(chalk.gray(' • /specweave:do - Execute tasks'));
1042
- console.log(chalk.gray(' • /specweave-github:sync - GitHub integration'));
1043
- console.log(chalk.gray(' • /specweave-jira:sync - JIRA integration'));
1044
- console.log(chalk.gray(' • /specweave:docs preview - Documentation preview'));
1045
- console.log(chalk.gray(' • ...and more!'));
1046
- autoInstallSucceeded = successCount > 0;
1047
- }
1048
- catch (error) {
1049
- // Installation failed - provide helpful diagnostics
1050
- spinner.warn('Could not auto-install plugins');
1051
- console.log('');
1052
- // Diagnose error and provide actionable hints
1053
- if (error.message.includes('not found') || error.message.includes('ENOENT')) {
1054
- console.log(chalk.yellow(' Reason: Claude CLI found but command failed'));
1055
- console.log(chalk.gray(' → Try manually: /plugin install specweave'));
1056
- }
1057
- else if (error.message.includes('EACCES') || error.message.includes('permission')) {
1058
- console.log(chalk.yellow(' Reason: Permission denied'));
1059
- console.log(chalk.gray(' → Check file permissions or run with appropriate access'));
1060
- }
1061
- else if (error.message.includes('ECONNREFUSED') || error.message.includes('network')) {
1062
- console.log(chalk.yellow(' Reason: Network error'));
1063
- console.log(chalk.gray(' → Check internet connection and try again'));
1064
- }
1065
- else if (process.env.DEBUG) {
1066
- console.log(chalk.gray(` Error: ${error.message}`));
1067
- }
1068
- console.log('');
1069
- console.log(chalk.cyan('📦 Manual installation:'));
1070
- console.log(chalk.white(' /plugin install specweave'));
1071
- console.log(chalk.white(' /plugin install specweave-github'));
1072
- console.log(chalk.white(' ...etc.'));
1073
- console.log('');
1074
- autoInstallSucceeded = false;
1075
- }
1076
- }
1077
- // 10.4 Repository Hosting Setup (FUNDAMENTAL!)
1078
- // Ask about repository hosting BEFORE issue tracker
1079
- // This determines what issue tracker options are available
1080
- console.log('');
1081
- console.log(chalk.cyan.bold('📦 Repository Hosting'));
1082
- console.log('');
1083
- // Detect existing git remote
1084
- const gitRemoteDetection = detectGitHubRemote(targetDir);
1085
- let repositoryHosting = 'github-single';
1086
- let isMultiRepo = false;
1087
- let repoSelectionConfig = null;
1088
- if (!isCI) {
1089
- // Step 1: Ask about repository structure
1090
- const { structure } = await inquirer.prompt([{
1091
- type: 'select',
1092
- name: 'structure',
1093
- message: 'What is your repository structure?',
1094
- choices: [
1095
- {
1096
- name: 'single - One repository (monorepo or standard project)',
1097
- value: 'single'
1098
- },
1099
- {
1100
- name: 'multiple - Multiple repos (microservices, EDA, parent/child)',
1101
- value: 'multirepo'
1102
- }
1103
- ],
1104
- default: 'single'
1105
- }]);
1106
- isMultiRepo = structure === 'multirepo';
1107
- // Step 2: Ask about git provider
1108
- const { provider } = await inquirer.prompt([{
1109
- type: 'select',
1110
- name: 'provider',
1111
- message: 'Which Git provider do you use?',
1112
- choices: [
1113
- {
1114
- name: `🐙 GitHub ${gitRemoteDetection ? '(detected)' : '(recommended)'}`,
1115
- value: 'github'
1116
- },
1117
- {
1118
- name: '🪣 Bitbucket',
1119
- value: 'bitbucket'
1120
- },
1121
- {
1122
- name: '🔷 Azure DevOps',
1123
- value: 'ado'
1124
- },
1125
- {
1126
- name: '💻 Local (no remote)',
1127
- value: 'local'
1128
- },
1129
- {
1130
- name: '🔧 Other (GitLab, etc - coming soon)',
1131
- value: 'other'
1132
- }
1133
- ],
1134
- default: gitRemoteDetection ? 'github' : 'local'
1135
- }]);
1136
- // Combine structure + provider (except for local which doesn't need structure)
1137
- if (provider === 'local') {
1138
- repositoryHosting = 'local';
1139
- }
1140
- else {
1141
- repositoryHosting = `${provider}-${structure}`;
1142
- }
1143
- }
1144
- else {
1145
- // CI mode: auto-detect
1146
- repositoryHosting = gitRemoteDetection ? 'github-single' : 'local';
1147
- console.log(chalk.gray(` → CI mode: Auto-detected ${repositoryHosting} hosting\n`));
1148
- }
1149
- // 10.5 Issue Tracker Integration (CRITICAL!)
1150
- // MUST happen AFTER plugin installation is complete
1151
- // Asks user: Which tracker? (GitHub/Jira/ADO/None)
1152
- // Collects credentials and runs smart validation
1153
- //
1154
- // NEW: Always run for ALL projects (including framework repo)
1155
- // Detects existing config and asks user if they want to change it
352
+ const result = await installAllPlugins({ dirname: __dirname, forceRefresh: options.forceRefresh });
353
+ autoInstallSucceeded = result.success;
354
+ // Repository hosting setup
355
+ const gitHubRemote = detectGitHubRemote(targetDir);
356
+ const repoResult = await setupRepositoryHosting({ targetDir, isCI, gitHubRemote });
357
+ // Issue tracker setup
1156
358
  const isFrameworkRepo = await isSpecWeaveFrameworkRepo(targetDir);
1157
- try {
1158
- const { setupIssueTracker } = await import('../helpers/issue-tracker/index.js');
1159
- // Check if sync config already exists
1160
- const configPath = path.join(targetDir, '.specweave', 'config.json');
1161
- let existingTracker = null;
1162
- if (fs.existsSync(configPath)) {
1163
- const config = await fs.readJson(configPath);
1164
- if (config.sync?.activeProfile && config.sync?.profiles) {
1165
- const activeProfile = config.sync.profiles[config.sync.activeProfile];
1166
- if (activeProfile?.provider) {
1167
- existingTracker = activeProfile.provider;
1168
- }
1169
- }
1170
- }
1171
- if (existingTracker) {
1172
- // Existing config detected - ask if user wants to reconfigure
1173
- console.log(chalk.blue('\n🔍 Existing Issue Tracker Configuration Detected'));
1174
- console.log(chalk.gray(` Current: ${existingTracker.charAt(0).toUpperCase() + existingTracker.slice(1)}`));
1175
- console.log('');
1176
- if (isCI) {
1177
- // CI mode: keep existing configuration without prompting
1178
- console.log(chalk.gray(' → CI mode: Keeping existing configuration\n'));
1179
- }
1180
- else {
1181
- const { reconfigure } = await inquirer.prompt([{
1182
- type: 'confirm',
1183
- name: 'reconfigure',
1184
- message: 'Do you want to reconfigure your issue tracker?',
1185
- default: false
1186
- }]);
1187
- if (!reconfigure) {
1188
- console.log(chalk.gray(' ✓ Keeping existing configuration\n'));
1189
- }
1190
- else {
1191
- // User wants to reconfigure - run setup
1192
- await setupIssueTracker({
1193
- projectPath: targetDir,
1194
- language: language,
1195
- maxRetries: 3,
1196
- isFrameworkRepo,
1197
- repositoryHosting
1198
- });
1199
- }
1200
- }
1201
- }
1202
- else {
1203
- // No existing config - run setup
1204
- if (isFrameworkRepo) {
1205
- console.log(chalk.blue('\n🔍 Detected SpecWeave framework repository'));
1206
- console.log(chalk.gray(' Recommended: Configure GitHub sync with full permissions (upsert, update, status)'));
1207
- console.log('');
1208
- }
1209
- await setupIssueTracker({
1210
- projectPath: targetDir,
1211
- language: language,
1212
- maxRetries: 3,
1213
- isFrameworkRepo,
1214
- repositoryHosting
1215
- });
1216
- }
1217
- }
1218
- catch (error) {
1219
- // Non-critical error - log but continue
1220
- if (process.env.DEBUG) {
1221
- console.error(chalk.red(`\n❌ Issue tracker setup error: ${error.message}`));
1222
- }
1223
- console.log(chalk.yellow('\n⚠️ Issue tracker setup skipped (can configure later)'));
1224
- }
1225
- // 10.6 Create Multi-Project Folders (JIRA/ADO/GitHub)
1226
- // After issue tracker setup, read .env and create project-specific folders
1227
- try {
1228
- await createMultiProjectFolders(targetDir);
1229
- }
1230
- catch (error) {
1231
- // Non-critical - folders can be created manually later
1232
- if (process.env.DEBUG) {
1233
- console.error(chalk.yellow(`\n⚠️ Multi-project folder creation skipped: ${error.message}`));
1234
- }
1235
- }
1236
- // 10.6.5 External Tool Import (T-025)
1237
- // Import existing work items from GitHub, JIRA, or Azure DevOps
1238
- // ONLY run if NOT continuing existing project (fresh start or new project)
359
+ await setupIssueTrackerWrapper(targetDir, language, isFrameworkRepo, repoResult.hosting, isCI);
360
+ // Multi-project folders
361
+ await createMultiProjectFolders(targetDir);
362
+ // External import
1239
363
  if (!continueExisting) {
1240
364
  try {
1241
365
  const importResult = await promptAndRunExternalImport(targetDir, isCI);
1242
366
  if (importResult.totalCount > 0) {
1243
- console.log(chalk.green(`\n✅ Imported ${importResult.totalCount} items from ${importResult.platforms.join(', ')}`));
1244
- console.log(chalk.gray(' → Items saved to .specweave/docs/internal/specs/'));
1245
- console.log('');
367
+ console.log(chalk.green('\n✅ Imported ' + importResult.totalCount + ' items from ' + importResult.platforms.join(', ')));
1246
368
  }
1247
369
  }
1248
- catch (error) {
1249
- // Non-critical - can import later manually
1250
- if (process.env.DEBUG) {
1251
- console.error(chalk.red(`\n❌ Import error: ${error.message}`));
1252
- }
370
+ catch {
1253
371
  console.log(chalk.yellow('\n⚠️ External tool import skipped (can run later)'));
1254
- console.log(chalk.gray(' → Use: specweave import --from github'));
1255
372
  }
1256
373
  }
1257
374
  }
1258
- // 10.7 Testing Configuration (MOVED TO END - Better UX)
1259
- // Prompt for testing approach and coverage targets after all setup is complete
1260
- // This keeps the main flow fast and asks for preferences at the end
1261
- let testMode = 'test-after';
1262
- let coverageTarget = 80;
1263
- // Only prompt if interactive (use function-level isCI)
375
+ // Testing configuration
1264
376
  if (!isCI && !continueExisting) {
1265
- console.log('');
1266
- console.log(chalk.cyan.bold('🧪 Testing Configuration'));
1267
- console.log(chalk.gray(' Configure your default testing approach and coverage targets'));
1268
- console.log('');
1269
- // Add guidance on which testing approach to choose
1270
- console.log(chalk.white('💡 Which testing approach should you choose?'));
1271
- console.log('');
1272
- console.log(chalk.green(' ✓ TDD (Test-Driven Development)'));
1273
- console.log(chalk.gray(' Best for: Complex business logic, critical features, refactoring'));
1274
- console.log(chalk.gray(' Benefits: Better design, fewer bugs, confidence in changes'));
1275
- console.log(chalk.gray(' Tradeoff: Slower initial development, requires discipline'));
1276
- console.log('');
1277
- console.log(chalk.blue(' ✓ Test-After'));
1278
- console.log(chalk.gray(' Best for: Most projects, rapid prototyping, exploratory work'));
1279
- console.log(chalk.gray(' Benefits: Fast iteration, flexible design, good coverage'));
1280
- console.log(chalk.gray(' Tradeoff: May miss edge cases, harder to test after design'));
1281
- console.log('');
1282
- console.log(chalk.yellow(' ✓ Manual Testing'));
1283
- console.log(chalk.gray(' Best for: Quick prototypes, proof-of-concepts, learning'));
1284
- console.log(chalk.gray(' Benefits: Fastest development, no test maintenance'));
1285
- console.log(chalk.gray(' Tradeoff: No safety net, regressions, hard to refactor'));
1286
- console.log('');
1287
- const { selectedTestMode } = await inquirer.prompt([
1288
- {
1289
- type: 'select',
1290
- name: 'selectedTestMode',
1291
- message: 'Select your testing approach:',
1292
- choices: [
1293
- {
1294
- name: '🔴 TDD - Write tests first (recommended for production apps)',
1295
- value: 'TDD',
1296
- short: 'TDD'
1297
- },
1298
- {
1299
- name: '🔵 Test-After - Implement first, test later (balanced approach)',
1300
- value: 'test-after',
1301
- short: 'Test-After'
1302
- },
1303
- {
1304
- name: '🟡 Manual - No automated tests (prototypes only)',
1305
- value: 'manual',
1306
- short: 'Manual'
1307
- }
1308
- ],
1309
- default: 'test-after'
1310
- }
1311
- ]);
1312
- testMode = selectedTestMode;
1313
- // Only ask for coverage if not manual testing
1314
- if (testMode !== 'manual') {
1315
- const { selectedCoverageLevel } = await inquirer.prompt([
1316
- {
1317
- type: 'select',
1318
- name: 'selectedCoverageLevel',
1319
- message: 'Select your coverage target level:',
1320
- choices: [
1321
- {
1322
- name: '70% - Acceptable (core paths covered)',
1323
- value: 70,
1324
- short: '70%'
1325
- },
1326
- {
1327
- name: '80% - Good (recommended - most paths covered)',
1328
- value: 80,
1329
- short: '80%'
1330
- },
1331
- {
1332
- name: '90% - Excellent (comprehensive coverage)',
1333
- value: 90,
1334
- short: '90%'
1335
- },
1336
- {
1337
- name: 'Custom (enter your own value)',
1338
- value: 'custom',
1339
- short: 'Custom'
1340
- }
1341
- ],
1342
- default: 80
1343
- }
1344
- ]);
1345
- if (selectedCoverageLevel === 'custom') {
1346
- const { customCoverage } = await inquirer.prompt([
1347
- {
1348
- type: 'number',
1349
- name: 'customCoverage',
1350
- message: 'Enter custom coverage target (70-95):',
1351
- default: 80,
1352
- validate: (input) => {
1353
- if (input >= 70 && input <= 95)
1354
- return true;
1355
- return 'Coverage target must be between 70% and 95%';
1356
- }
1357
- }
1358
- ]);
1359
- coverageTarget = customCoverage;
1360
- }
1361
- else {
1362
- coverageTarget = selectedCoverageLevel;
1363
- }
1364
- }
1365
- console.log('');
1366
- console.log(chalk.green(` ✔ Testing: ${testMode}`));
1367
- if (testMode !== 'manual') {
1368
- console.log(chalk.green(` ✔ Coverage Target: ${coverageTarget}%`));
1369
- }
1370
- console.log('');
1371
- // Update config.json with testing configuration
1372
- updateConfigWithTesting(targetDir, testMode, coverageTarget);
377
+ const testingResult = await promptTestingConfig();
378
+ updateConfigWithTesting(targetDir, testingResult.testMode, testingResult.coverageTarget);
1373
379
  }
1374
- // 10.8 Create Initial Increment (CRITICAL: Users need somewhere to start!)
1375
- // ONLY create if:
1376
- // 1. New project (not continuing existing)
1377
- // 2. Increments directory is empty
380
+ // Initial increment
1378
381
  const incrementsDir = path.join(targetDir, '.specweave', 'increments');
1379
382
  const existingIncrements = fs.existsSync(incrementsDir)
1380
383
  ? fs.readdirSync(incrementsDir).filter(dir => {
@@ -1383,772 +386,116 @@ export async function initCommand(projectName, options = {}) {
1383
386
  })
1384
387
  : [];
1385
388
  if (!continueExisting && existingIncrements.length === 0) {
1386
- console.log('');
1387
- console.log(chalk.cyan.bold('📦 Creating Initial Increment'));
1388
- console.log(chalk.gray(' Setting up 0001-project-setup so you can start working immediately'));
1389
- console.log('');
389
+ console.log(chalk.cyan.bold('\n📦 Creating Initial Increment'));
1390
390
  try {
1391
391
  const incrementId = await generateInitialIncrement({
1392
392
  projectPath: targetDir,
1393
393
  projectName: finalProjectName,
1394
394
  techStack: options.techStack,
1395
- language: language
395
+ language
1396
396
  });
1397
- console.log(chalk.green(` ✔ Created initial increment: ${incrementId}`));
1398
- console.log(chalk.gray(' ✔ Status: ACTIVE (ready to work)'));
1399
- console.log(chalk.gray(' ✔ Files: spec.md, plan.md, tasks.md, metadata.json'));
1400
- // Initialize status line cache for the new increment
1401
- try {
1402
- const statusLineUpdater = new StatusLineUpdater(targetDir);
1403
- await statusLineUpdater.update();
1404
- console.log(chalk.gray(' ✔ Status line initialized'));
1405
- }
1406
- catch (statusLineError) {
1407
- // Non-critical: Status line will be created on first task completion
1408
- if (process.env.DEBUG) {
1409
- console.log(chalk.gray(` ⚠️ Status line init skipped: ${statusLineError instanceof Error ? statusLineError.message : String(statusLineError)}`));
1410
- }
1411
- }
1412
- console.log('');
1413
- console.log(chalk.yellow(' 💡 TIP: Delete this increment and create your first real feature:'));
1414
- console.log(chalk.gray(' rm -rf .specweave/increments/0001-project-setup'));
1415
- console.log(chalk.gray(' /specweave:increment "my-feature"'));
1416
- console.log('');
397
+ console.log(chalk.green(' ✔ Created initial increment: ' + incrementId));
398
+ const statusLineUpdater = new StatusLineUpdater(targetDir);
399
+ await statusLineUpdater.update();
1417
400
  }
1418
- catch (error) {
401
+ catch {
1419
402
  console.log(chalk.yellow(' ⚠️ Could not create initial increment (non-critical)'));
1420
- if (process.env.DEBUG) {
1421
- console.log(chalk.gray(` Error: ${error instanceof Error ? error.message : String(error)}`));
1422
- }
1423
- console.log(chalk.gray(' → You can create your first increment manually with /specweave:increment'));
1424
- console.log('');
1425
403
  }
1426
404
  }
1427
405
  showNextSteps(finalProjectName, toolName, language, usedDotNotation, toolName === 'claude' ? autoInstallSucceeded : undefined);
1428
406
  }
1429
407
  catch (error) {
1430
408
  spinner.fail('Failed to create project');
1431
- console.error(chalk.red(`\n${locale.t('cli', 'init.genericError')}`), error);
409
+ console.error(chalk.red('\n' + locale.t('cli', 'init.genericError')), error);
1432
410
  process.exit(1);
1433
411
  }
1434
412
  }
1435
413
  /**
1436
- * Detect GitHub repository owner and name from git remote
1437
- * Parses .git/config to extract GitHub remote URL
414
+ * Install non-Claude adapter (Cursor, Generic)
1438
415
  */
1439
- function detectGitHubRemote(targetDir) {
1440
- try {
1441
- const gitConfigPath = path.join(targetDir, '.git', 'config');
1442
- if (!fs.existsSync(gitConfigPath)) {
1443
- return null;
1444
- }
1445
- const gitConfig = fs.readFileSync(gitConfigPath, 'utf-8');
1446
- // Match GitHub remote URLs (both HTTPS and SSH)
1447
- // HTTPS: https://github.com/owner/repo.git
1448
- // SSH: git@github.com:owner/repo.git
1449
- const httpsMatch = gitConfig.match(/https:\/\/github\.com\/([^/]+)\/([^/\s]+?)(?:\.git)?(?:\s|$)/);
1450
- const sshMatch = gitConfig.match(/git@github\.com:([^/]+)\/([^/\s]+?)(?:\.git)?(?:\s|$)/);
1451
- const match = httpsMatch || sshMatch;
1452
- if (match) {
1453
- return {
1454
- owner: match[1],
1455
- repo: match[2].replace(/\.git$/, '')
1456
- };
1457
- }
1458
- return null;
1459
- }
1460
- catch (error) {
1461
- return null;
1462
- }
1463
- }
1464
- /**
1465
- * Detect JIRA configuration from environment or .env file
1466
- */
1467
- function detectJiraConfig(targetDir) {
1468
- try {
1469
- // Check environment variables first
1470
- const envHost = process.env.JIRA_HOST;
1471
- const envEmail = process.env.JIRA_EMAIL;
1472
- const envToken = process.env.JIRA_API_TOKEN;
1473
- if (envHost && envEmail && envToken) {
1474
- return { host: envHost, email: envEmail, apiToken: envToken };
1475
- }
1476
- // Check .env file
1477
- const envPath = path.join(targetDir, '.env');
1478
- if (fs.existsSync(envPath)) {
1479
- const envContent = fs.readFileSync(envPath, 'utf-8');
1480
- const envVars = parseEnvFile(envContent);
1481
- const fileHost = envVars.JIRA_HOST;
1482
- const fileEmail = envVars.JIRA_EMAIL;
1483
- const fileToken = envVars.JIRA_API_TOKEN;
1484
- if (fileHost && fileEmail && fileToken) {
1485
- return { host: fileHost, email: fileEmail, apiToken: fileToken };
1486
- }
1487
- }
1488
- return null;
1489
- }
1490
- catch (error) {
1491
- return null;
1492
- }
1493
- }
1494
- /**
1495
- * Detect Azure DevOps configuration from environment or .env file
1496
- */
1497
- function detectADOConfig(targetDir) {
1498
- try {
1499
- // Check environment variables first
1500
- const envOrgUrl = process.env.ADO_ORG_URL;
1501
- const envProject = process.env.ADO_PROJECT;
1502
- const envPat = process.env.ADO_PAT || process.env.AZURE_DEVOPS_PAT;
1503
- if (envOrgUrl && envProject && envPat) {
1504
- return { orgUrl: envOrgUrl, project: envProject, pat: envPat };
1505
- }
1506
- // Check .env file
1507
- const envPath = path.join(targetDir, '.env');
1508
- if (fs.existsSync(envPath)) {
1509
- const envContent = fs.readFileSync(envPath, 'utf-8');
1510
- const envVars = parseEnvFile(envContent);
1511
- const fileOrgUrl = envVars.ADO_ORG_URL;
1512
- const fileProject = envVars.ADO_PROJECT;
1513
- const filePat = envVars.ADO_PAT || envVars.AZURE_DEVOPS_PAT;
1514
- if (fileOrgUrl && fileProject && filePat) {
1515
- return { orgUrl: fileOrgUrl, project: fileProject, pat: filePat };
1516
- }
1517
- }
1518
- return null;
1519
- }
1520
- catch (error) {
1521
- return null;
1522
- }
1523
- }
1524
- /**
1525
- * Prompt user and run external tool import
1526
- * Detects GitHub/JIRA/ADO configuration and imports work items
1527
- */
1528
- async function promptAndRunExternalImport(targetDir, isCI) {
1529
- // Load import configuration (T-027)
1530
- const importConfig = loadImportConfig(targetDir);
1531
- // Check if import is disabled via config
1532
- if (!importConfig.enabled) {
1533
- return {
1534
- results: [],
1535
- totalCount: 0,
1536
- allItems: [],
1537
- errors: {},
1538
- platforms: []
1539
- };
1540
- }
1541
- // Detect available external tools
1542
- const githubRemote = detectGitHubRemote(targetDir);
1543
- const jiraConfig = detectJiraConfig(targetDir);
1544
- const adoConfig = detectADOConfig(targetDir);
1545
- const availableTools = [];
1546
- if (githubRemote)
1547
- availableTools.push('GitHub');
1548
- if (jiraConfig)
1549
- availableTools.push('JIRA');
1550
- if (adoConfig)
1551
- availableTools.push('Azure DevOps');
1552
- // If no tools detected, skip import
1553
- if (availableTools.length === 0) {
1554
- return {
1555
- results: [],
1556
- totalCount: 0,
1557
- allItems: [],
1558
- errors: {},
1559
- platforms: []
1560
- };
1561
- }
1562
- console.log(chalk.blue('\n🔍 External Tool Detection'));
1563
- console.log(chalk.gray(` Found: ${availableTools.join(', ')}`));
1564
- console.log('');
1565
- // In CI mode, skip import without prompting
1566
- if (isCI) {
1567
- console.log(chalk.gray(' → CI mode: Skipping import (can run manually later)\n'));
1568
- return {
1569
- results: [],
1570
- totalCount: 0,
1571
- allItems: [],
1572
- errors: {},
1573
- platforms: []
1574
- };
1575
- }
1576
- // Prompt user to import
1577
- const { shouldImport } = await inquirer.prompt([
1578
- {
1579
- type: 'confirm',
1580
- name: 'shouldImport',
1581
- message: `Import existing work items from ${availableTools.join(', ')}?`,
1582
- default: false
1583
- }
1584
- ]);
1585
- if (!shouldImport) {
1586
- console.log(chalk.gray(' ✓ Skipping import\n'));
1587
- return {
1588
- results: [],
1589
- totalCount: 0,
1590
- allItems: [],
1591
- errors: {},
1592
- platforms: []
1593
- };
1594
- }
1595
- // US-011: Multi-Repo Import
1596
- // NOTE: This is a separate function scope, so we need our own repoSelectionConfig variable
1597
- let repoSelectionConfig = null;
1598
- if (githubRemote && process.env.GITHUB_TOKEN) {
1599
- try {
1600
- const { useMultiRepo } = await inquirer.prompt([
1601
- {
1602
- type: 'confirm',
1603
- name: 'useMultiRepo',
1604
- message: 'Do you want to import from multiple repositories?',
1605
- default: false
1606
- }
1607
- ]);
1608
- if (useMultiRepo) {
1609
- try {
1610
- const octokit = new Octokit({ auth: process.env.GITHUB_TOKEN });
1611
- repoSelectionConfig = await selectRepositories(octokit, process.env.GITHUB_TOKEN);
1612
- if (repoSelectionConfig) {
1613
- try {
1614
- const configPath = path.join(targetDir, '.specweave', 'config.json');
1615
- let config = {};
1616
- if (fs.existsSync(configPath)) {
1617
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
1618
- }
1619
- if (!config.github) {
1620
- config.github = {};
1621
- }
1622
- config.github.repositories = repoSelectionConfig.repositories;
1623
- config.github.selectionStrategy = repoSelectionConfig.selectionStrategy;
1624
- if (repoSelectionConfig.pattern) {
1625
- config.github.pattern = repoSelectionConfig.pattern;
1626
- }
1627
- if (repoSelectionConfig.organizationName) {
1628
- config.github.organizationName = repoSelectionConfig.organizationName;
1629
- }
1630
- fs.ensureDirSync(path.dirname(configPath));
1631
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2));
1632
- }
1633
- catch {
1634
- // Silent - config save is not critical
1635
- }
1636
- }
1637
- }
1638
- catch {
1639
- // Silent - continue with single repo
1640
- }
1641
- }
1642
- }
1643
- catch {
1644
- // Silent - skip multi-repo prompt
1645
- }
1646
- }
1647
- else if (repoSelectionConfig) {
1648
- // User already configured multi-repo in hosting section - reuse it!
1649
- console.log(chalk.gray(`✓ Using multi-repository configuration from hosting setup\n`));
1650
- }
1651
- // Map config timeRangeMonths to closest prompt option
1652
- let defaultTimeRange = 3; // Default to 3 months
1653
- if (importConfig.timeRangeMonths === 1)
1654
- defaultTimeRange = 1;
1655
- else if (importConfig.timeRangeMonths <= 3)
1656
- defaultTimeRange = 3;
1657
- else if (importConfig.timeRangeMonths <= 6)
1658
- defaultTimeRange = 6;
1659
- else
1660
- defaultTimeRange = 999;
1661
- // Prompt for time range (with config default)
1662
- const { timeRange } = await inquirer.prompt([
1663
- {
1664
- type: 'select',
1665
- name: 'timeRange',
1666
- message: 'How far back should we import?',
1667
- choices: [
1668
- { name: '1 month (recent items only)', value: 1 },
1669
- { name: '3 months (recommended)', value: 3 },
1670
- { name: '6 months (comprehensive)', value: 6 },
1671
- { name: 'All time (warning: may be slow)', value: 999 }
1672
- ],
1673
- default: defaultTimeRange
1674
- }
1675
- ]);
1676
- // Build coordinator configuration
1677
- const coordinatorConfig = {
1678
- importConfig: {
1679
- timeRangeMonths: timeRange,
1680
- includeClosed: false, // Only open/in-progress items
1681
- pageSize: importConfig.pageSize // Use config page size (T-027)
1682
- },
1683
- parallel: true
1684
- };
1685
- // Add GitHub config if available
1686
- if (githubRemote) {
1687
- coordinatorConfig.github = {
1688
- owner: githubRemote.owner,
1689
- repo: githubRemote.repo,
1690
- token: process.env.GITHUB_TOKEN
1691
- };
1692
- }
1693
- // Add JIRA config if available
1694
- if (jiraConfig) {
1695
- coordinatorConfig.jira = {
1696
- host: jiraConfig.host,
1697
- email: jiraConfig.email,
1698
- apiToken: jiraConfig.apiToken
1699
- };
1700
- }
1701
- // Add ADO config if available
1702
- if (adoConfig) {
1703
- coordinatorConfig.ado = {
1704
- orgUrl: adoConfig.orgUrl,
1705
- project: adoConfig.project,
1706
- pat: adoConfig.pat
1707
- };
1708
- }
1709
- // Run import with progress tracking
1710
- const spinner = ora('Importing items...').start();
1711
- let totalImported = 0;
1712
- coordinatorConfig.onProgress = (platform, count) => {
1713
- spinner.text = `Importing from ${platform}... (${count} items)`;
1714
- totalImported = count;
1715
- };
1716
- try {
1717
- const coordinator = new ImportCoordinator(coordinatorConfig);
1718
- const result = await coordinator.importAll();
1719
- spinner.succeed(`Imported ${result.totalCount} items`);
1720
- // Show breakdown by platform
1721
- if (result.results.length > 0) {
1722
- console.log('');
1723
- result.results.forEach(platformResult => {
1724
- console.log(chalk.gray(` ✓ ${platformResult.platform}: ${platformResult.count} items`));
1725
- });
1726
- }
1727
- // Show errors if any
1728
- if (Object.keys(result.errors).length > 0) {
1729
- console.log('');
1730
- console.log(chalk.yellow(' ⚠️ Some imports failed:'));
1731
- Object.entries(result.errors).forEach(([platform, errors]) => {
1732
- console.log(chalk.gray(` → ${platform}: ${errors.join(', ')}`));
1733
- });
1734
- }
1735
- // Warn if many items detected
1736
- if (result.totalCount > 100) {
1737
- console.log('');
1738
- console.log(chalk.yellow(` ⚠️ Imported ${result.totalCount} items (large dataset)`));
1739
- console.log(chalk.gray(' → Consider using time range filters for faster imports'));
1740
- }
1741
- // Convert imported items to living docs User Stories
1742
- // CRITICAL: This ONLY creates living docs, NOT increments
1743
- if (result.totalCount > 0) {
1744
- spinner.start('Converting to living docs...');
1745
- try {
1746
- const specsDir = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs');
1747
- const converter = new ItemConverter({ specsDir });
1748
- const convertedStories = await converter.convertItems(result.allItems);
1749
- spinner.succeed(`Converted ${convertedStories.length} User Stories to living docs`);
1750
- console.log(chalk.gray(` → Living docs created with E suffix (US-001E, US-002E, ...)`));
1751
- console.log(chalk.gray(` → Location: .specweave/docs/internal/specs/`));
1752
- console.log('');
1753
- // Validate that no increments were auto-created
1754
- try {
1755
- ItemConverter.validateNoIncrementsCreated(targetDir);
1756
- }
1757
- catch (validationError) {
1758
- spinner.fail('Import validation failed');
1759
- throw new Error(`CRITICAL ERROR: ${validationError.message}\n` +
1760
- `This is a bug in the import system. Please report it.`);
1761
- }
1762
- console.log(chalk.blue(' 💡 Next steps:'));
1763
- console.log(chalk.gray(' → Review imported User Stories in living docs'));
1764
- console.log(chalk.gray(' → Create increments manually when ready: /specweave:increment "feature"'));
1765
- console.log('');
1766
- }
1767
- catch (conversionError) {
1768
- spinner.fail('Conversion to living docs failed');
1769
- throw conversionError;
1770
- }
1771
- }
1772
- return result;
1773
- }
1774
- catch (error) {
1775
- spinner.fail('Import failed');
1776
- throw error;
1777
- }
1778
- }
1779
- function createDirectoryStructure(targetDir, adapterName) {
1780
- const directories = [
1781
- // Core increment structure
1782
- '.specweave/increments',
1783
- '.specweave/cache', // External tool cache (24-hour TTL)
1784
- // 6-pillar documentation structure
1785
- '.specweave/docs/internal/strategy', // Business specs (WHAT, WHY)
1786
- '.specweave/docs/internal/specs', // Feature specifications (detailed requirements)
1787
- '.specweave/docs/internal/architecture', // Technical design (HOW)
1788
- '.specweave/docs/internal/architecture/adr', // Architecture Decision Records
1789
- '.specweave/docs/internal/architecture/diagrams', // Architecture diagrams
1790
- '.specweave/docs/internal/delivery', // Roadmap, CI/CD, guides
1791
- '.specweave/docs/internal/operations', // Runbooks, SLOs
1792
- '.specweave/docs/internal/governance', // Security, compliance
1793
- '.specweave/docs/public', // Published documentation
1794
- ];
1795
- // NOTE: We do NOT create .claude/ folder anymore!
1796
- // Marketplace registration is GLOBAL via CLI, not per-project.
1797
- // Non-Claude adapters still use plugins/ folder (copied separately)
1798
- directories.forEach((dir) => {
1799
- fs.mkdirSync(path.join(targetDir, dir), { recursive: true });
1800
- });
1801
- }
1802
- async function copyTemplates(templatesDir, targetDir, projectName, language = 'en') {
1803
- const locale = getLocaleManager(language);
1804
- // Verify templates directory exists
1805
- if (!fs.existsSync(templatesDir)) {
1806
- console.error(chalk.red(`\n${locale.t('cli', 'init.errors.templatesNotFound', { path: templatesDir })}`));
1807
- const packageRoot = findPackageRoot(__dirname);
1808
- if (packageRoot) {
1809
- console.error(chalk.red(` ${locale.t('cli', 'init.errors.packageRoot', { root: packageRoot })}`));
1810
- console.error(chalk.red(` ${locale.t('cli', 'init.errors.tryingAlternate')}`));
1811
- // Try src/templates as fallback
1812
- const altPath = path.join(packageRoot, 'src', 'templates');
1813
- if (fs.existsSync(altPath)) {
1814
- console.error(chalk.yellow(` ${locale.t('cli', 'init.errors.foundTemplatesAt', { path: altPath })}`));
1815
- templatesDir = altPath;
1816
- }
1817
- else {
1818
- throw new Error('Failed to locate templates directory');
1819
- }
1820
- }
1821
- else {
1822
- throw new Error('Failed to locate templates directory and package root');
1823
- }
1824
- }
1825
- // Copy README.md
1826
- const readmeTemplate = path.join(templatesDir, 'README.md.template');
1827
- if (fs.existsSync(readmeTemplate)) {
1828
- let readme = fs.readFileSync(readmeTemplate, 'utf-8');
1829
- readme = readme.replace(/{{PROJECT_NAME}}/g, projectName);
1830
- fs.writeFileSync(path.join(targetDir, 'README.md'), readme);
1831
- }
1832
- // Generate CLAUDE.md - PRIMARY instruction file for Claude Code
1833
- // CRITICAL: Claude Code ONLY reads CLAUDE.md (NOT AGENTS.md!)
1834
- // This is the native/baseline experience - skills, agents, hooks, slash commands
1835
- const skillsDir = findSourceDir('skills');
1836
- const agentsDir = findSourceDir('agents');
1837
- const commandsDir = findSourceDir('commands');
1838
- const claudeMdTemplatePath = path.normalize(path.join(templatesDir, 'CLAUDE.md.template'));
1839
- const claudeGen = new ClaudeMdGenerator(skillsDir, agentsDir, commandsDir);
1840
- const claudeMd = await claudeGen.generate({
1841
- projectName,
416
+ async function installNonClaudeAdapter(adapterLoader, toolName, targetDir, projectName, options, spinner) {
417
+ spinner.text = 'Installing ' + toolName + ' adapter...';
418
+ const adapter = adapterLoader.getAdapter(toolName);
419
+ if (!adapter) {
420
+ throw new Error('Adapter not found: ' + toolName);
421
+ }
422
+ await adapterLoader.checkRequirements(toolName);
423
+ await adapter.install({
1842
424
  projectPath: targetDir,
1843
- templatePath: fs.existsSync(claudeMdTemplatePath) ? claudeMdTemplatePath : undefined
1844
- });
1845
- fs.writeFileSync(path.join(targetDir, 'CLAUDE.md'), claudeMd);
1846
- // Generate AGENTS.md - Universal file for ALL OTHER AI tools
1847
- // Following agents.md standard: https://agents.md/
1848
- // Used by: Cursor, Gemini CLI, Codex, GitHub Copilot, and ANY non-Claude tool
1849
- // NOTE: Claude Code does NOT read this file - it only reads CLAUDE.md above
1850
- // Replaces: .cursorrules, instructions.md, and other tool-specific files
1851
- const agentsMdTemplatePath = path.normalize(path.join(templatesDir, 'AGENTS.md.template'));
1852
- const agentsGen = new AgentsMdGenerator(skillsDir, agentsDir, commandsDir);
1853
- const agentsMd = await agentsGen.generate({
1854
425
  projectName,
1855
- projectPath: targetDir,
1856
- templatePath: fs.existsSync(agentsMdTemplatePath) ? agentsMdTemplatePath : undefined
426
+ techStack: options.techStack ? { language: options.techStack } : undefined,
427
+ docsApproach: 'incremental'
1857
428
  });
1858
- fs.writeFileSync(path.join(targetDir, 'AGENTS.md'), agentsMd);
1859
- // Copy .gitignore
1860
- const gitignoreTemplate = path.join(templatesDir, '.gitignore.template');
1861
- if (fs.existsSync(gitignoreTemplate)) {
1862
- fs.copyFileSync(gitignoreTemplate, path.join(targetDir, '.gitignore'));
1863
- }
1864
- // Copy .gitattributes (forces LF line endings on all platforms, prevents Windows CRLF warnings)
1865
- const gitattributesTemplate = path.join(templatesDir, '.gitattributes.template');
1866
- if (fs.existsSync(gitattributesTemplate)) {
1867
- fs.copyFileSync(gitattributesTemplate, path.join(targetDir, '.gitattributes'));
1868
- }
1869
- }
1870
- /**
1871
- * Detect ALL parent directories that contain .specweave/ folders
1872
- * SpecWeave ONLY supports root-level .specweave/ folders
1873
- * Nested .specweave/ folders are NOT supported
1874
- *
1875
- * @param targetDir - Directory where user wants to initialize
1876
- * @returns Array of paths to parent .specweave/ folders with depth info, or null if none found
1877
- */
1878
- function detectNestedSpecweave(targetDir) {
1879
- const foundFolders = [];
1880
- const homeDir = os.homedir();
1881
- // Start from parent of target directory
1882
- let currentDir = path.dirname(path.resolve(targetDir));
1883
- const root = path.parse(currentDir).root;
1884
- let depth = 1;
1885
- // Walk up the directory tree and find ALL .specweave/ folders
1886
- while (currentDir !== root) {
1887
- const specweavePath = path.join(currentDir, '.specweave');
1888
- // Check if .specweave/ exists at this level
1889
- if (fs.existsSync(specweavePath)) {
1890
- const isHomeDir = path.resolve(currentDir) === path.resolve(homeDir);
1891
- foundFolders.push({ path: currentDir, depth, isHomeDir });
1892
- }
1893
- // Move up one level
1894
- const parentDir = path.dirname(currentDir);
1895
- if (parentDir === currentDir)
1896
- break; // Reached root
1897
- currentDir = parentDir;
1898
- depth++;
1899
- }
1900
- return foundFolders.length > 0 ? foundFolders : null;
1901
- }
1902
- /**
1903
- * Find the package root by walking up the directory tree looking for package.json
1904
- * This works reliably on all platforms including Windows with UNC paths
1905
- */
1906
- function findPackageRoot(startDir) {
1907
- let currentDir = startDir;
1908
- const root = path.parse(currentDir).root;
1909
- while (currentDir !== root) {
1910
- const packageJsonPath = path.join(currentDir, 'package.json');
1911
- if (fs.existsSync(packageJsonPath)) {
1912
- try {
1913
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
1914
- // Verify this is the specweave package
1915
- if (packageJson.name === 'specweave') {
1916
- return currentDir;
1917
- }
1918
- }
1919
- catch (error) {
1920
- // Not a valid package.json, continue searching
429
+ // Copy plugins folder for non-Claude adapters
430
+ if (toolName !== 'claude') {
431
+ spinner.start('Copying plugins folder for command execution...');
432
+ const specweavePackageRoot = findPackageRoot(__dirname);
433
+ if (specweavePackageRoot) {
434
+ const sourcePluginsDir = path.join(specweavePackageRoot, 'plugins');
435
+ const targetPluginsDir = path.join(targetDir, 'plugins');
436
+ if (fs.existsSync(sourcePluginsDir)) {
437
+ fs.copySync(sourcePluginsDir, targetPluginsDir, {
438
+ overwrite: true,
439
+ filter: (src) => !path.basename(src).startsWith('.')
440
+ });
441
+ spinner.succeed('Plugins folder copied successfully');
1921
442
  }
1922
443
  }
1923
- const parentDir = path.dirname(currentDir);
1924
- if (parentDir === currentDir)
1925
- break; // Reached root
1926
- currentDir = parentDir;
1927
444
  }
1928
- return null;
1929
- }
1930
- /**
1931
- * Find the source directory, trying multiple possible locations
1932
- * Handles both development and installed package scenarios
1933
- * Windows-compatible with proper path normalization
1934
- */
1935
- function findSourceDir(relativePath) {
1936
- // First, try to find package root by walking up from __dirname
1937
- const packageRoot = findPackageRoot(__dirname);
1938
- if (packageRoot) {
1939
- // Try directly in package root FIRST (for plugins/, .claude-plugin/)
1940
- // This is critical because package.json includes these folders for npm publish
1941
- const rootPath = path.normalize(path.join(packageRoot, relativePath));
1942
- if (fs.existsSync(rootPath)) {
1943
- return rootPath;
1944
- }
1945
- // Try src/ directory (for templates/, utils/, etc.)
1946
- const srcPath = path.normalize(path.join(packageRoot, 'src', relativePath));
1947
- if (fs.existsSync(srcPath)) {
1948
- return srcPath;
1949
- }
1950
- // Try dist/ directory (fallback for compiled outputs)
1951
- const distPath = path.normalize(path.join(packageRoot, 'dist', relativePath));
1952
- if (fs.existsSync(distPath)) {
1953
- return distPath;
445
+ // Install core plugin
446
+ try {
447
+ spinner.start('Installing SpecWeave core plugin...');
448
+ const corePluginPath = findSourceDir('plugins/specweave', __dirname);
449
+ const { PluginLoader } = await import('../../core/plugin-loader.js');
450
+ const loader = new PluginLoader();
451
+ const corePlugin = await loader.loadFromDirectory(corePluginPath);
452
+ if (adapter.supportsPlugins()) {
453
+ await adapter.compilePlugin(corePlugin);
454
+ spinner.succeed('SpecWeave core plugin installed');
1954
455
  }
1955
456
  }
1956
- // Fallback: Try multiple possible locations relative to __dirname
1957
- const possiblePaths = [
1958
- // Development: dist/cli/commands -> src/
1959
- path.normalize(path.join(__dirname, '../../..', relativePath)),
1960
- // Installed: node_modules/specweave/dist/cli/commands -> node_modules/specweave/src/
1961
- path.normalize(path.join(__dirname, '../../../src', relativePath)),
1962
- // Alternative: go up from dist/ to package root, then to src/
1963
- path.normalize(path.join(__dirname, '../../..', 'src', relativePath)),
1964
- // Absolute from package root (for global installs)
1965
- path.resolve(__dirname, '../../../src', relativePath),
1966
- ];
1967
- for (const testPath of possiblePaths) {
1968
- if (fs.existsSync(testPath)) {
1969
- return testPath;
1970
- }
457
+ catch {
458
+ spinner.warn('Could not install core plugin');
1971
459
  }
1972
- // If nothing found, return the first path and let the caller handle the error
1973
- return possiblePaths[0];
1974
460
  }
1975
461
  /**
1976
- * Create .specweave/config.json with project settings
1977
- * Testing configuration is optional and can be added later via updateConfigWithTesting()
462
+ * Wrapper for issue tracker setup
1978
463
  */
1979
- function createConfigFile(targetDir, projectName, adapter, language, enableDocsPreview = true, testMode, coverageTarget) {
1980
- const configPath = path.join(targetDir, '.specweave', 'config.json');
1981
- const config = {
1982
- version: '2.0', // Config version for migration support
1983
- project: {
1984
- name: projectName,
1985
- version: '0.1.0',
1986
- },
1987
- adapters: {
1988
- default: adapter,
1989
- },
1990
- // Repository configuration (default: local)
1991
- repository: {
1992
- provider: 'local'
1993
- },
1994
- // Issue tracker configuration (default: none)
1995
- issueTracker: {
1996
- provider: 'none'
1997
- },
1998
- // Sync configuration with settings enabled
1999
- sync: {
2000
- enabled: false,
2001
- direction: 'bidirectional',
2002
- autoSync: false,
2003
- includeStatus: true,
2004
- autoApplyLabels: true,
2005
- settings: {
2006
- canUpsertInternalItems: true, // Allow updating living docs
2007
- canUpdateExternalItems: true, // Allow updating external trackers
2008
- canUpdateStatus: true, // Allow status updates
2009
- autoSyncOnCompletion: true // Auto-sync external tools on increment completion (v0.25.0+)
464
+ async function setupIssueTrackerWrapper(targetDir, language, isFrameworkRepo, repositoryHosting, isCI) {
465
+ try {
466
+ const { setupIssueTracker } = await import('../helpers/issue-tracker/index.js');
467
+ const configPath = path.join(targetDir, '.specweave', 'config.json');
468
+ let existingTracker = null;
469
+ if (fs.existsSync(configPath)) {
470
+ const config = await fs.readJson(configPath);
471
+ if (config.sync?.activeProfile && config.sync?.profiles) {
472
+ const activeProfile = config.sync.profiles[config.sync.activeProfile];
473
+ existingTracker = activeProfile?.provider || null;
474
+ }
475
+ }
476
+ if (existingTracker) {
477
+ console.log(chalk.blue('\n🔍 Existing Issue Tracker Configuration Detected'));
478
+ console.log(chalk.gray(' Current: ' + existingTracker.charAt(0).toUpperCase() + existingTracker.slice(1)));
479
+ if (isCI) {
480
+ console.log(chalk.gray(' → CI mode: Keeping existing configuration\n'));
481
+ return;
2010
482
  }
2011
- },
2012
- // Hook configuration (enable automatic living docs sync)
2013
- hooks: {
2014
- post_task_completion: {
2015
- sync_living_docs: true, // Auto-sync living docs after task completion
2016
- sync_tasks_md: true, // Auto-sync tasks.md
2017
- external_tracker_sync: true // Auto-sync with external trackers (Jira/GitHub/ADO)
2018
- },
2019
- post_increment_planning: {
2020
- auto_create_github_issue: false // Don't auto-create issues (user decides)
483
+ const reconfigure = await confirm({ message: 'Do you want to reconfigure your issue tracker?', default: false });
484
+ if (!reconfigure) {
485
+ console.log(chalk.gray(' ✓ Keeping existing configuration\n'));
486
+ return;
2021
487
  }
2022
- },
2023
- // Testing configuration (NEW - v0.18.0+) - only include if provided
2024
- ...(testMode && coverageTarget && {
2025
- testing: {
2026
- defaultTestMode: testMode,
2027
- defaultCoverageTarget: coverageTarget,
2028
- coverageTargets: {
2029
- unit: Math.min(coverageTarget + 5, 95), // Unit tests slightly higher
2030
- integration: coverageTarget, // Integration at default
2031
- e2e: Math.min(coverageTarget + 10, 100) // E2E tests highest (critical paths)
2032
- }
2033
- }
2034
- }),
2035
- // Documentation preview settings (for Claude Code only)
2036
- ...(adapter === 'claude' && {
2037
- documentation: {
2038
- preview: {
2039
- enabled: enableDocsPreview,
2040
- autoInstall: false, // Lazy install on first use
2041
- port: 3015, // Internal docs (avoid port 3000 - used by React/Next.js/Vite)
2042
- openBrowser: true,
2043
- theme: 'default',
2044
- excludeFolders: ['legacy', 'node_modules']
2045
- }
2046
- }
2047
- }),
2048
- // Only include language if non-English
2049
- ...(language !== 'en' && {
2050
- language,
2051
- translation: {
2052
- method: 'in-session',
2053
- autoTranslateLivingDocs: false,
2054
- keepFrameworkTerms: true,
2055
- keepTechnicalTerms: true,
2056
- translateCodeComments: true,
2057
- translateVariableNames: false,
2058
- },
2059
- }),
2060
- };
2061
- fs.writeJsonSync(configPath, config, { spaces: 2 });
2062
- }
2063
- /**
2064
- * Update config.json with testing configuration
2065
- * Called after user completes testing setup prompts
2066
- */
2067
- function updateConfigWithTesting(targetDir, testMode, coverageTarget) {
2068
- const configPath = path.join(targetDir, '.specweave', 'config.json');
2069
- if (!fs.existsSync(configPath)) {
2070
- console.error(chalk.red('⚠️ config.json not found, cannot update testing configuration'));
2071
- return;
2072
- }
2073
- const config = fs.readJsonSync(configPath);
2074
- config.testing = {
2075
- defaultTestMode: testMode,
2076
- defaultCoverageTarget: coverageTarget,
2077
- coverageTargets: {
2078
- unit: Math.min(coverageTarget + 5, 95),
2079
- integration: coverageTarget,
2080
- e2e: Math.min(coverageTarget + 10, 100)
2081
- }
2082
- };
2083
- fs.writeJsonSync(configPath, config, { spaces: 2 });
2084
- }
2085
- /**
2086
- * REMOVED: setupClaudePluginAutoRegistration()
2087
- *
2088
- * Previously created .claude/settings.json with extraKnownMarketplaces,
2089
- * but this is redundant because:
2090
- *
2091
- * 1. CLI marketplace registration is GLOBAL (persists across all projects)
2092
- * 2. settings.json is per-project and unnecessary for our use case
2093
- * 3. We use `claude plugin marketplace add` which registers globally
2094
- *
2095
- * Removed in favor of pure CLI approach (lines 687-883)
2096
- */
2097
- function showNextSteps(projectName, adapterName, language, usedDotNotation = false, pluginAutoInstalled = false) {
2098
- const locale = getLocaleManager(language);
2099
- console.log('');
2100
- console.log(chalk.cyan.bold(locale.t('cli', 'init.nextSteps.header')));
2101
- console.log('');
2102
- let stepNumber = 1;
2103
- // Only show "cd" step if we created a subdirectory
2104
- if (!usedDotNotation) {
2105
- console.log(` ${stepNumber}. ${chalk.white(locale.t('cli', 'init.nextSteps.cd', { projectName }))}`);
2106
- console.log('');
2107
- stepNumber++;
2108
- }
2109
- // Adapter-specific instructions
2110
- if (adapterName === 'claude') {
2111
- console.log(` ${stepNumber}. ${chalk.white(locale.t('cli', 'init.nextSteps.claude.step1'))}`);
2112
- console.log('');
2113
- stepNumber++;
2114
- // Only show manual install if auto-install failed
2115
- if (!pluginAutoInstalled) {
2116
- console.log(` ${stepNumber}. ${chalk.yellow.bold('⚠️ ' + locale.t('cli', 'init.nextSteps.claude.step2'))}`);
2117
- console.log(` ${chalk.cyan.bold(locale.t('cli', 'init.nextSteps.claude.installCore'))}`);
2118
- console.log(` ${chalk.gray('↑ Required for slash commands like /specweave:increment')}`);
2119
- console.log('');
2120
- stepNumber++;
2121
488
  }
2122
- console.log(` ${stepNumber}. ${chalk.white('All plugins are already installed!')}`);
2123
- console.log(` ${chalk.gray('✔ All 19+ SpecWeave plugins installed automatically')}`);
2124
- console.log(` ${chalk.gray('✔ No need to install additional plugins manually')}`);
2125
- console.log(` ${chalk.gray('✔ Full capabilities available immediately')}`);
2126
- console.log('');
2127
- stepNumber++;
2128
- console.log(` ${stepNumber}. ${chalk.white(locale.t('cli', 'init.nextSteps.claude.step4'))}`);
2129
- console.log(` ${chalk.cyan(locale.t('cli', 'init.nextSteps.claude.example'))}`);
2130
- console.log(` ${chalk.gray(locale.t('cli', 'init.nextSteps.claude.autoActivate'))}`);
2131
- }
2132
- else if (adapterName === 'cursor') {
2133
- console.log(` ${stepNumber}. ${chalk.white(locale.t('cli', 'init.nextSteps.cursor.step1'))}`);
2134
- console.log('');
2135
- console.log(` ${stepNumber + 1}. ${chalk.white(locale.t('cli', 'init.nextSteps.cursor.step2'))}`);
2136
- console.log(` ${locale.t('cli', 'init.nextSteps.cursor.guide')}`);
2137
- console.log('');
2138
- console.log(` ${stepNumber + 2}. ${chalk.white(locale.t('cli', 'init.nextSteps.cursor.step3'))}`);
2139
- console.log(` ${locale.t('cli', 'init.nextSteps.cursor.shortcuts')}`);
489
+ await setupIssueTracker({
490
+ projectPath: targetDir,
491
+ language,
492
+ maxRetries: 3,
493
+ isFrameworkRepo,
494
+ repositoryHosting
495
+ });
2140
496
  }
2141
- else if (adapterName === 'generic') {
2142
- console.log(` ${stepNumber}. ${chalk.white(locale.t('cli', 'init.nextSteps.generic.step1'))}`);
2143
- console.log('');
2144
- console.log(` ${stepNumber + 1}. ${chalk.white(locale.t('cli', 'init.nextSteps.generic.step2'))}`);
2145
- console.log(` ${locale.t('cli', 'init.nextSteps.generic.compatibility')}`);
497
+ catch {
498
+ console.log(chalk.yellow('\n⚠️ Issue tracker setup skipped (can configure later)'));
2146
499
  }
2147
- console.log('');
2148
- console.log(chalk.green.bold(locale.t('cli', 'init.nextSteps.footer')));
2149
- console.log('');
2150
- console.log(chalk.gray(locale.t('cli', 'init.nextSteps.docsLink')));
2151
- console.log(chalk.gray(locale.t('cli', 'init.nextSteps.githubLink')));
2152
- console.log('');
2153
500
  }
2154
501
  //# sourceMappingURL=init.js.map