specweave 0.22.4 → 0.22.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,6 +12,7 @@ import { AgentsMdGenerator } from '../../adapters/agents-md-generator.js';
12
12
  import { getDirname } from '../../utils/esm-helpers.js';
13
13
  import { LanguageManager, isLanguageSupported, getSupportedLanguages } from '../../core/i18n/language-manager.js';
14
14
  import { getLocaleManager } from '../../core/i18n/locale-manager.js';
15
+ import { consoleLogger } from '../../utils/logger.js';
15
16
  const __dirname = getDirname(import.meta.url);
16
17
  import { readEnvFile, parseEnvFile } from '../../utils/env-file.js';
17
18
  /**
@@ -135,6 +136,18 @@ async function createMultiProjectFolders(targetDir) {
135
136
  }
136
137
  }
137
138
  export async function initCommand(projectName, options = {}) {
139
+ // Initialize logger (injectable for testing)
140
+ const logger = options.logger ?? consoleLogger;
141
+ // NOTE: This CLI command is 99% user-facing output (console.log/console.error).
142
+ // All console.* calls in this function are legitimate user-facing exceptions
143
+ // as defined in CONTRIBUTING.md (colored messages, confirmations, formatted output).
144
+ // Logger is available for any internal debug logs if needed in future.
145
+ // Detect CI/non-interactive environment (use throughout function)
146
+ const isCI = process.env.CI === 'true' ||
147
+ process.env.GITHUB_ACTIONS === 'true' ||
148
+ process.env.GITLAB_CI === 'true' ||
149
+ process.env.CIRCLECI === 'true' ||
150
+ !process.stdin.isTTY;
138
151
  // Validate and normalize language option
139
152
  const language = options.language?.toLowerCase() || 'en';
140
153
  // Validate language if provided
@@ -182,22 +195,31 @@ export async function initCommand(projectName, options = {}) {
182
195
  const dirName = path.basename(targetDir);
183
196
  // Validate directory name is suitable for project name
184
197
  if (!/^[a-z0-9-]+$/.test(dirName)) {
185
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.invalidDirName', { dirName })}`));
186
198
  const suggestedName = dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
187
- const { name } = await inquirer.prompt([
188
- {
189
- type: 'input',
190
- name: 'name',
191
- message: 'Project name (for templates):',
192
- default: suggestedName,
193
- validate: (input) => {
194
- if (/^[a-z0-9-]+$/.test(input))
195
- return true;
196
- return 'Project name must be lowercase letters, numbers, and hyphens only';
199
+ if (isCI) {
200
+ // CI mode: auto-sanitize directory name without prompting
201
+ console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.invalidDirName', { dirName })}`));
202
+ console.log(chalk.gray(` → CI mode: Auto-sanitizing to "${suggestedName}"`));
203
+ finalProjectName = suggestedName;
204
+ }
205
+ else {
206
+ // Interactive mode: prompt user
207
+ console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.invalidDirName', { dirName })}`));
208
+ const { name } = await inquirer.prompt([
209
+ {
210
+ type: 'input',
211
+ name: 'name',
212
+ message: 'Project name (for templates):',
213
+ default: suggestedName,
214
+ validate: (input) => {
215
+ if (/^[a-z0-9-]+$/.test(input))
216
+ return true;
217
+ return 'Project name must be lowercase letters, numbers, and hyphens only';
218
+ },
197
219
  },
198
- },
199
- ]);
200
- finalProjectName = name;
220
+ ]);
221
+ finalProjectName = name;
222
+ }
201
223
  }
202
224
  else {
203
225
  finalProjectName = dirName;
@@ -206,18 +228,25 @@ export async function initCommand(projectName, options = {}) {
206
228
  const allFiles = fs.readdirSync(targetDir);
207
229
  const existingFiles = allFiles.filter(f => !f.startsWith('.')); // Ignore hidden files
208
230
  if (existingFiles.length > 0 && !options.force) {
209
- console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })}`));
210
- const { confirm } = await inquirer.prompt([
211
- {
212
- type: 'confirm',
213
- name: 'confirm',
214
- message: 'Initialize SpecWeave in current directory?',
215
- default: false,
216
- },
217
- ]);
218
- if (!confirm) {
219
- console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
220
- process.exit(0);
231
+ if (isCI) {
232
+ // CI mode: allow initialization in non-empty directory without prompting
233
+ console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })}`));
234
+ console.log(chalk.gray(` → CI mode: Proceeding with initialization`));
235
+ }
236
+ else {
237
+ console.log(chalk.yellow(`\n${locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })}`));
238
+ const { confirm } = await inquirer.prompt([
239
+ {
240
+ type: 'confirm',
241
+ name: 'confirm',
242
+ message: 'Initialize SpecWeave in current directory?',
243
+ default: false,
244
+ },
245
+ ]);
246
+ if (!confirm) {
247
+ console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
248
+ process.exit(0);
249
+ }
221
250
  }
222
251
  }
223
252
  // Check if .specweave already exists - SMART RE-INITIALIZATION
@@ -233,23 +262,35 @@ export async function initCommand(projectName, options = {}) {
233
262
  console.log(chalk.red(' • All documentation (.specweave/docs/)'));
234
263
  console.log(chalk.red(' • All configuration and history'));
235
264
  console.log(chalk.yellow('\n 💡 TIP: Use "specweave init ." (no --force) for safe updates\n'));
236
- // ALWAYS require confirmation, even in force mode (safety critical!)
237
- const { confirmDeletion } = await inquirer.prompt([
238
- {
239
- type: 'confirm',
240
- name: 'confirmDeletion',
241
- message: chalk.red('⚠️ Type "y" to PERMANENTLY DELETE all .specweave/ data:'),
242
- default: false,
243
- },
244
- ]);
245
- if (!confirmDeletion) {
246
- console.log(chalk.green('\n✅ Deletion cancelled. No data lost.'));
247
- console.log(chalk.gray(' Run "specweave init ." (without --force) for safe updates'));
248
- process.exit(0);
265
+ if (isCI) {
266
+ // CI mode: proceed with force deletion without prompting (test environment)
267
+ console.log(chalk.gray(' → CI mode: Proceeding with force deletion'));
268
+ action = 'fresh';
269
+ }
270
+ else {
271
+ // Interactive mode: ALWAYS require confirmation, even in force mode (safety critical!)
272
+ const { confirmDeletion } = await inquirer.prompt([
273
+ {
274
+ type: 'confirm',
275
+ name: 'confirmDeletion',
276
+ message: chalk.red('⚠️ Type "y" to PERMANENTLY DELETE all .specweave/ data:'),
277
+ default: false,
278
+ },
279
+ ]);
280
+ if (!confirmDeletion) {
281
+ console.log(chalk.green('\n✅ Deletion cancelled. No data lost.'));
282
+ console.log(chalk.gray(' → Run "specweave init ." (without --force) for safe updates'));
283
+ process.exit(0);
284
+ }
285
+ action = 'fresh';
249
286
  }
250
- action = 'fresh';
251
287
  console.log(chalk.yellow('\n 🔄 Force mode: Proceeding with fresh start...'));
252
288
  }
289
+ else if (isCI) {
290
+ // CI mode: auto-continue with existing project
291
+ console.log(chalk.gray(' → CI mode: Continuing with existing project'));
292
+ action = 'continue';
293
+ }
253
294
  else {
254
295
  // Interactive mode: Ask user what to do
255
296
  const result = await inquirer.prompt([
@@ -285,6 +326,12 @@ export async function initCommand(projectName, options = {}) {
285
326
  }
286
327
  if (action === 'fresh') {
287
328
  if (!options.force) {
329
+ if (isCI) {
330
+ // CI mode: NEVER allow fresh start without explicit --force flag (safety critical!)
331
+ console.log(chalk.red('\n⛔ ERROR: Cannot start fresh in CI mode without --force flag'));
332
+ console.log(chalk.gray(' → Use "specweave init . --force" if you really want to delete all data'));
333
+ process.exit(1);
334
+ }
288
335
  // Interactive mode: Ask for confirmation (force mode already confirmed above)
289
336
  console.log(chalk.yellow('\n⚠️ WARNING: This will DELETE all increments, docs, and configuration!'));
290
337
  const { confirmFresh } = await inquirer.prompt([
@@ -576,12 +623,7 @@ export async function initCommand(projectName, options = {}) {
576
623
  }
577
624
  }
578
625
  console.log('');
579
- // Check if running in CI/non-interactive environment
580
- const isCI = process.env.CI === 'true' ||
581
- process.env.GITHUB_ACTIONS === 'true' ||
582
- process.env.GITLAB_CI === 'true' ||
583
- process.env.CIRCLECI === 'true' ||
584
- !process.stdin.isTTY;
626
+ // Use function-level isCI (already defined at function start)
585
627
  let confirmTool = true; // Default to yes
586
628
  if (isCI) {
587
629
  // In CI, automatically use detected tool without prompting
@@ -798,102 +840,8 @@ export async function initCommand(projectName, options = {}) {
798
840
  });
799
841
  }
800
842
  }
801
- // 12. Prompt for testing configuration (NEW)
802
- let testMode = 'TDD';
803
- let coverageTarget = 80;
804
- // Only prompt if interactive (not CI)
805
- const isCI = process.env.CI === 'true' ||
806
- process.env.GITHUB_ACTIONS === 'true' ||
807
- process.env.GITLAB_CI === 'true' ||
808
- process.env.CIRCLECI === 'true' ||
809
- !process.stdin.isTTY;
810
- if (!isCI && !continueExisting) {
811
- console.log('');
812
- console.log(chalk.cyan.bold('🧪 Testing Configuration'));
813
- console.log(chalk.gray(' Configure your default testing approach and coverage targets'));
814
- console.log('');
815
- const { selectedTestMode } = await inquirer.prompt([
816
- {
817
- type: 'list',
818
- name: 'selectedTestMode',
819
- message: 'Select your testing approach:',
820
- choices: [
821
- {
822
- name: 'TDD (Test-Driven Development) - Write tests first',
823
- value: 'TDD',
824
- short: 'TDD'
825
- },
826
- {
827
- name: 'Test-After - Implement first, then write tests',
828
- value: 'test-after',
829
- short: 'Test-After'
830
- },
831
- {
832
- name: 'Manual Testing - No automated tests',
833
- value: 'manual',
834
- short: 'Manual'
835
- }
836
- ],
837
- default: 'TDD'
838
- }
839
- ]);
840
- testMode = selectedTestMode;
841
- const { selectedCoverageLevel } = await inquirer.prompt([
842
- {
843
- type: 'list',
844
- name: 'selectedCoverageLevel',
845
- message: 'Select your coverage target level:',
846
- choices: [
847
- {
848
- name: '70% - Acceptable (core paths covered)',
849
- value: 70,
850
- short: '70%'
851
- },
852
- {
853
- name: '80% - Good (recommended - most paths covered)',
854
- value: 80,
855
- short: '80%'
856
- },
857
- {
858
- name: '90% - Excellent (comprehensive coverage)',
859
- value: 90,
860
- short: '90%'
861
- },
862
- {
863
- name: 'Custom (enter your own value)',
864
- value: 'custom',
865
- short: 'Custom'
866
- }
867
- ],
868
- default: 80
869
- }
870
- ]);
871
- if (selectedCoverageLevel === 'custom') {
872
- const { customCoverage } = await inquirer.prompt([
873
- {
874
- type: 'number',
875
- name: 'customCoverage',
876
- message: 'Enter custom coverage target (70-95):',
877
- default: 80,
878
- validate: (input) => {
879
- if (input >= 70 && input <= 95)
880
- return true;
881
- return 'Coverage target must be between 70% and 95%';
882
- }
883
- }
884
- ]);
885
- coverageTarget = customCoverage;
886
- }
887
- else {
888
- coverageTarget = selectedCoverageLevel;
889
- }
890
- console.log('');
891
- console.log(chalk.green(` ✔ Testing: ${testMode}`));
892
- console.log(chalk.green(` ✔ Coverage Target: ${coverageTarget}%`));
893
- console.log('');
894
- }
895
- // 13. Create config.json with language and testing settings
896
- createConfigFile(targetDir, finalProjectName, toolName, language, false, testMode, coverageTarget);
843
+ // 12. Create config.json with basic settings (testing params added later)
844
+ createConfigFile(targetDir, finalProjectName, toolName, language, false);
897
845
  // 14. AUTO-INSTALL ALL PLUGINS via Claude CLI (Breaking Change: No selective loading)
898
846
  // NOTE: We do NOT create .claude/settings.json - marketplace registration via CLI is GLOBAL
899
847
  // and persists across all projects. settings.json would be redundant.
@@ -960,7 +908,8 @@ export async function initCommand(projectName, options = {}) {
960
908
  else {
961
909
  // Claude CLI available → install ALL plugins from marketplace
962
910
  try {
963
- // Step 1: FORCE marketplace refresh - remove and re-add from GitHub
911
+ // Step 1: Remove existing marketplace to force update (REQUIRED for updates!)
912
+ // This ensures users get the latest plugins when new ones are added
964
913
  spinner.start('Refreshing SpecWeave marketplace...');
965
914
  const listResult = execFileNoThrowSync('claude', [
966
915
  'plugin',
@@ -970,16 +919,15 @@ export async function initCommand(projectName, options = {}) {
970
919
  const marketplaceExists = listResult.success &&
971
920
  (listResult.stdout || '').toLowerCase().includes('specweave');
972
921
  if (marketplaceExists) {
973
- // Always remove existing marketplace to ensure fresh install
974
922
  execFileNoThrowSync('claude', [
975
923
  'plugin',
976
924
  'marketplace',
977
925
  'remove',
978
926
  'specweave'
979
927
  ]);
980
- console.log(chalk.blue(' 🔄 Removed existing marketplace'));
928
+ console.log(chalk.blue(' 🔄 Removed existing marketplace for update'));
981
929
  }
982
- // Add marketplace from GitHub (always fresh)
930
+ // Step 2: Add marketplace from GitHub (always fresh)
983
931
  const addResult = execFileNoThrowSync('claude', [
984
932
  'plugin',
985
933
  'marketplace',
@@ -990,7 +938,13 @@ export async function initCommand(projectName, options = {}) {
990
938
  throw new Error('Failed to add marketplace from GitHub');
991
939
  }
992
940
  console.log(chalk.green(' ✔ Marketplace registered from GitHub'));
993
- spinner.succeed('SpecWeave marketplace refreshed');
941
+ // CRITICAL: Wait for marketplace cache to fully initialize after remove+add
942
+ // The remove operation invalidates cache, add fetches from GitHub
943
+ // We need to wait for: fetch + parse + validate 25 plugins
944
+ // 2 seconds was too short, increasing to 5 seconds for reliability
945
+ spinner.text = 'Initializing marketplace cache (this takes a few seconds)...';
946
+ await new Promise(resolve => setTimeout(resolve, 5000)); // 5 second delay
947
+ spinner.succeed('SpecWeave marketplace ready');
994
948
  // Step 2: Load marketplace.json to get ALL available plugins
995
949
  spinner.start('Loading available plugins...');
996
950
  const marketplaceJsonPath = findSourceDir('.claude-plugin/marketplace.json');
@@ -1004,19 +958,35 @@ export async function initCommand(projectName, options = {}) {
1004
958
  }
1005
959
  console.log(chalk.blue(` 📦 Found ${allPlugins.length} plugins to install`));
1006
960
  spinner.succeed(`Found ${allPlugins.length} plugins`);
1007
- // Step 3: Install ALL plugins (no selective loading!)
961
+ // Step 3: Install ALL plugins with retry logic (handles remaining race conditions)
1008
962
  let successCount = 0;
1009
963
  let failCount = 0;
1010
964
  const failedPlugins = [];
1011
965
  for (const plugin of allPlugins) {
1012
966
  const pluginName = plugin.name;
1013
967
  spinner.start(`Installing ${pluginName}...`);
1014
- const installResult = execFileNoThrowSync('claude', [
1015
- 'plugin',
1016
- 'install',
1017
- pluginName
1018
- ]);
1019
- if (installResult.success) {
968
+ // Retry up to 3 times with exponential backoff
969
+ let installed = false;
970
+ for (let attempt = 1; attempt <= 3; attempt++) {
971
+ const installResult = execFileNoThrowSync('claude', [
972
+ 'plugin',
973
+ 'install',
974
+ pluginName
975
+ ]);
976
+ if (installResult.success) {
977
+ installed = true;
978
+ break;
979
+ }
980
+ // If "not found" error and not last attempt, wait and retry
981
+ if (installResult.stderr?.includes('not found') && attempt < 3) {
982
+ spinner.text = `Installing ${pluginName}... (retry ${attempt}/3)`;
983
+ await new Promise(resolve => setTimeout(resolve, 500 * attempt)); // 500ms, 1s, 1.5s
984
+ continue;
985
+ }
986
+ // Other errors or final attempt - stop retrying
987
+ break;
988
+ }
989
+ if (installed) {
1020
990
  successCount++;
1021
991
  spinner.succeed(`${pluginName} installed`);
1022
992
  }
@@ -1101,23 +1071,29 @@ export async function initCommand(projectName, options = {}) {
1101
1071
  console.log(chalk.blue('\n🔍 Existing Issue Tracker Configuration Detected'));
1102
1072
  console.log(chalk.gray(` Current: ${existingTracker.charAt(0).toUpperCase() + existingTracker.slice(1)}`));
1103
1073
  console.log('');
1104
- const { reconfigure } = await inquirer.prompt([{
1105
- type: 'confirm',
1106
- name: 'reconfigure',
1107
- message: 'Do you want to reconfigure your issue tracker?',
1108
- default: false
1109
- }]);
1110
- if (!reconfigure) {
1111
- console.log(chalk.gray(' ✓ Keeping existing configuration\n'));
1074
+ if (isCI) {
1075
+ // CI mode: keep existing configuration without prompting
1076
+ console.log(chalk.gray(' → CI mode: Keeping existing configuration\n'));
1112
1077
  }
1113
1078
  else {
1114
- // User wants to reconfigure - run setup
1115
- await setupIssueTracker({
1116
- projectPath: targetDir,
1117
- language: language,
1118
- maxRetries: 3,
1119
- isFrameworkRepo
1120
- });
1079
+ const { reconfigure } = await inquirer.prompt([{
1080
+ type: 'confirm',
1081
+ name: 'reconfigure',
1082
+ message: 'Do you want to reconfigure your issue tracker?',
1083
+ default: false
1084
+ }]);
1085
+ if (!reconfigure) {
1086
+ console.log(chalk.gray(' ✓ Keeping existing configuration\n'));
1087
+ }
1088
+ else {
1089
+ // User wants to reconfigure - run setup
1090
+ await setupIssueTracker({
1091
+ projectPath: targetDir,
1092
+ language: language,
1093
+ maxRetries: 3,
1094
+ isFrameworkRepo
1095
+ });
1096
+ }
1121
1097
  }
1122
1098
  }
1123
1099
  else {
@@ -1154,6 +1130,122 @@ export async function initCommand(projectName, options = {}) {
1154
1130
  }
1155
1131
  }
1156
1132
  }
1133
+ // 10.7 Testing Configuration (MOVED TO END - Better UX)
1134
+ // Prompt for testing approach and coverage targets after all setup is complete
1135
+ // This keeps the main flow fast and asks for preferences at the end
1136
+ let testMode = 'TDD';
1137
+ let coverageTarget = 80;
1138
+ // Only prompt if interactive (use function-level isCI)
1139
+ if (!isCI && !continueExisting) {
1140
+ console.log('');
1141
+ console.log(chalk.cyan.bold('🧪 Testing Configuration'));
1142
+ console.log(chalk.gray(' Configure your default testing approach and coverage targets'));
1143
+ console.log('');
1144
+ // Add guidance on which testing approach to choose
1145
+ console.log(chalk.white('💡 Which testing approach should you choose?'));
1146
+ console.log('');
1147
+ console.log(chalk.green(' ✓ TDD (Test-Driven Development)'));
1148
+ console.log(chalk.gray(' Best for: Complex business logic, critical features, refactoring'));
1149
+ console.log(chalk.gray(' Benefits: Better design, fewer bugs, confidence in changes'));
1150
+ console.log(chalk.gray(' Tradeoff: Slower initial development, requires discipline'));
1151
+ console.log('');
1152
+ console.log(chalk.blue(' ✓ Test-After'));
1153
+ console.log(chalk.gray(' Best for: Most projects, rapid prototyping, exploratory work'));
1154
+ console.log(chalk.gray(' Benefits: Fast iteration, flexible design, good coverage'));
1155
+ console.log(chalk.gray(' Tradeoff: May miss edge cases, harder to test after design'));
1156
+ console.log('');
1157
+ console.log(chalk.yellow(' ✓ Manual Testing'));
1158
+ console.log(chalk.gray(' Best for: Quick prototypes, proof-of-concepts, learning'));
1159
+ console.log(chalk.gray(' Benefits: Fastest development, no test maintenance'));
1160
+ console.log(chalk.gray(' Tradeoff: No safety net, regressions, hard to refactor'));
1161
+ console.log('');
1162
+ const { selectedTestMode } = await inquirer.prompt([
1163
+ {
1164
+ type: 'list',
1165
+ name: 'selectedTestMode',
1166
+ message: 'Select your testing approach:',
1167
+ choices: [
1168
+ {
1169
+ name: '🔴 TDD - Write tests first (recommended for production apps)',
1170
+ value: 'TDD',
1171
+ short: 'TDD'
1172
+ },
1173
+ {
1174
+ name: '🔵 Test-After - Implement first, test later (balanced approach)',
1175
+ value: 'test-after',
1176
+ short: 'Test-After'
1177
+ },
1178
+ {
1179
+ name: '🟡 Manual - No automated tests (prototypes only)',
1180
+ value: 'manual',
1181
+ short: 'Manual'
1182
+ }
1183
+ ],
1184
+ default: 'TDD'
1185
+ }
1186
+ ]);
1187
+ testMode = selectedTestMode;
1188
+ // Only ask for coverage if not manual testing
1189
+ if (testMode !== 'manual') {
1190
+ const { selectedCoverageLevel } = await inquirer.prompt([
1191
+ {
1192
+ type: 'list',
1193
+ name: 'selectedCoverageLevel',
1194
+ message: 'Select your coverage target level:',
1195
+ choices: [
1196
+ {
1197
+ name: '70% - Acceptable (core paths covered)',
1198
+ value: 70,
1199
+ short: '70%'
1200
+ },
1201
+ {
1202
+ name: '80% - Good (recommended - most paths covered)',
1203
+ value: 80,
1204
+ short: '80%'
1205
+ },
1206
+ {
1207
+ name: '90% - Excellent (comprehensive coverage)',
1208
+ value: 90,
1209
+ short: '90%'
1210
+ },
1211
+ {
1212
+ name: 'Custom (enter your own value)',
1213
+ value: 'custom',
1214
+ short: 'Custom'
1215
+ }
1216
+ ],
1217
+ default: 80
1218
+ }
1219
+ ]);
1220
+ if (selectedCoverageLevel === 'custom') {
1221
+ const { customCoverage } = await inquirer.prompt([
1222
+ {
1223
+ type: 'number',
1224
+ name: 'customCoverage',
1225
+ message: 'Enter custom coverage target (70-95):',
1226
+ default: 80,
1227
+ validate: (input) => {
1228
+ if (input >= 70 && input <= 95)
1229
+ return true;
1230
+ return 'Coverage target must be between 70% and 95%';
1231
+ }
1232
+ }
1233
+ ]);
1234
+ coverageTarget = customCoverage;
1235
+ }
1236
+ else {
1237
+ coverageTarget = selectedCoverageLevel;
1238
+ }
1239
+ }
1240
+ console.log('');
1241
+ console.log(chalk.green(` ✔ Testing: ${testMode}`));
1242
+ if (testMode !== 'manual') {
1243
+ console.log(chalk.green(` ✔ Coverage Target: ${coverageTarget}%`));
1244
+ }
1245
+ console.log('');
1246
+ // Update config.json with testing configuration
1247
+ updateConfigWithTesting(targetDir, testMode, coverageTarget);
1248
+ }
1157
1249
  showNextSteps(finalProjectName, toolName, language, usedDotNotation, toolName === 'claude' ? autoInstallSucceeded : undefined);
1158
1250
  }
1159
1251
  catch (error) {
@@ -1359,8 +1451,9 @@ function findSourceDir(relativePath) {
1359
1451
  }
1360
1452
  /**
1361
1453
  * Create .specweave/config.json with project settings
1454
+ * Testing configuration is optional and can be added later via updateConfigWithTesting()
1362
1455
  */
1363
- function createConfigFile(targetDir, projectName, adapter, language, enableDocsPreview = true, testMode = 'TDD', coverageTarget = 80) {
1456
+ function createConfigFile(targetDir, projectName, adapter, language, enableDocsPreview = true, testMode, coverageTarget) {
1364
1457
  const configPath = path.join(targetDir, '.specweave', 'config.json');
1365
1458
  const config = {
1366
1459
  project: {
@@ -1370,16 +1463,18 @@ function createConfigFile(targetDir, projectName, adapter, language, enableDocsP
1370
1463
  adapters: {
1371
1464
  default: adapter,
1372
1465
  },
1373
- // Testing configuration (NEW - v0.18.0+)
1374
- testing: {
1375
- defaultTestMode: testMode,
1376
- defaultCoverageTarget: coverageTarget,
1377
- coverageTargets: {
1378
- unit: Math.min(coverageTarget + 5, 95), // Unit tests slightly higher
1379
- integration: coverageTarget, // Integration at default
1380
- e2e: Math.min(coverageTarget + 10, 100) // E2E tests highest (critical paths)
1466
+ // Testing configuration (NEW - v0.18.0+) - only include if provided
1467
+ ...(testMode && coverageTarget && {
1468
+ testing: {
1469
+ defaultTestMode: testMode,
1470
+ defaultCoverageTarget: coverageTarget,
1471
+ coverageTargets: {
1472
+ unit: Math.min(coverageTarget + 5, 95), // Unit tests slightly higher
1473
+ integration: coverageTarget, // Integration at default
1474
+ e2e: Math.min(coverageTarget + 10, 100) // E2E tests highest (critical paths)
1475
+ }
1381
1476
  }
1382
- },
1477
+ }),
1383
1478
  // Documentation preview settings (for Claude Code only)
1384
1479
  ...(adapter === 'claude' && {
1385
1480
  documentation: {
@@ -1408,6 +1503,28 @@ function createConfigFile(targetDir, projectName, adapter, language, enableDocsP
1408
1503
  };
1409
1504
  fs.writeJsonSync(configPath, config, { spaces: 2 });
1410
1505
  }
1506
+ /**
1507
+ * Update config.json with testing configuration
1508
+ * Called after user completes testing setup prompts
1509
+ */
1510
+ function updateConfigWithTesting(targetDir, testMode, coverageTarget) {
1511
+ const configPath = path.join(targetDir, '.specweave', 'config.json');
1512
+ if (!fs.existsSync(configPath)) {
1513
+ console.error(chalk.red('⚠️ config.json not found, cannot update testing configuration'));
1514
+ return;
1515
+ }
1516
+ const config = fs.readJsonSync(configPath);
1517
+ config.testing = {
1518
+ defaultTestMode: testMode,
1519
+ defaultCoverageTarget: coverageTarget,
1520
+ coverageTargets: {
1521
+ unit: Math.min(coverageTarget + 5, 95),
1522
+ integration: coverageTarget,
1523
+ e2e: Math.min(coverageTarget + 10, 100)
1524
+ }
1525
+ };
1526
+ fs.writeJsonSync(configPath, config, { spaces: 2 });
1527
+ }
1411
1528
  /**
1412
1529
  * REMOVED: setupClaudePluginAutoRegistration()
1413
1530
  *