specweave 0.23.8 → 0.23.10

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 (40) hide show
  1. package/CLAUDE.md +118 -0
  2. package/dist/src/cli/commands/migrate-config.d.ts +22 -0
  3. package/dist/src/cli/commands/migrate-config.d.ts.map +1 -0
  4. package/dist/src/cli/commands/migrate-config.js +149 -0
  5. package/dist/src/cli/commands/migrate-config.js.map +1 -0
  6. package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
  7. package/dist/src/cli/helpers/issue-tracker/index.js +112 -60
  8. package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
  9. package/dist/src/cli/helpers/issue-tracker/jira.d.ts +24 -1
  10. package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
  11. package/dist/src/cli/helpers/issue-tracker/jira.js +111 -131
  12. package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
  13. package/dist/src/core/config/config-manager.d.ts +135 -0
  14. package/dist/src/core/config/config-manager.d.ts.map +1 -0
  15. package/dist/src/core/config/config-manager.js +341 -0
  16. package/dist/src/core/config/config-manager.js.map +1 -0
  17. package/dist/src/core/config/config-migrator.d.ts +102 -0
  18. package/dist/src/core/config/config-migrator.d.ts.map +1 -0
  19. package/dist/src/core/config/config-migrator.js +367 -0
  20. package/dist/src/core/config/config-migrator.js.map +1 -0
  21. package/dist/src/core/config/index.d.ts +10 -0
  22. package/dist/src/core/config/index.d.ts.map +1 -0
  23. package/dist/src/core/config/index.js +10 -0
  24. package/dist/src/core/config/index.js.map +1 -0
  25. package/dist/src/core/config/types.d.ts +216 -0
  26. package/dist/src/core/config/types.d.ts.map +1 -0
  27. package/dist/src/core/config/types.js +32 -0
  28. package/dist/src/core/config/types.js.map +1 -0
  29. package/dist/src/integrations/jira/jira-hierarchy-mapper.d.ts +104 -0
  30. package/dist/src/integrations/jira/jira-hierarchy-mapper.d.ts.map +1 -0
  31. package/dist/src/integrations/jira/jira-hierarchy-mapper.js +178 -0
  32. package/dist/src/integrations/jira/jira-hierarchy-mapper.js.map +1 -0
  33. package/package.json +1 -1
  34. package/plugins/specweave/commands/migrate-config.md +104 -0
  35. package/plugins/specweave/hooks/post-task-completion.sh +1 -1
  36. package/plugins/specweave/hooks/pre-task-completion.sh +1 -1
  37. package/plugins/specweave-ado/hooks/post-task-completion.sh +1 -1
  38. package/plugins/specweave-github/hooks/post-task-completion.sh +1 -1
  39. package/plugins/specweave-jira/hooks/post-task-completion.sh +1 -1
  40. package/plugins/specweave-release/hooks/.specweave/logs/dora-tracking.log +132 -0
package/CLAUDE.md CHANGED
@@ -1592,6 +1592,124 @@ vim .specweave/docs/internal/architecture/hld-system.md
1592
1592
  vim .specweave/docs/public/guides/user-guide.md
1593
1593
  ```
1594
1594
 
1595
+ ### Configuration Management
1596
+
1597
+ **CRITICAL**: SpecWeave separates secrets from configuration (v0.24.0+)
1598
+
1599
+ **Architecture**:
1600
+ - **Secrets** (tokens, passwords) → `.env` (gitignored, NEVER commit)
1601
+ - **Configuration** (domains, strategies, settings) → `.specweave/config.json` (committed to git)
1602
+
1603
+ **Why this matters**:
1604
+ - ✅ Team shares configuration via git
1605
+ - ✅ Secrets stay local (12-Factor App pattern)
1606
+ - ✅ Onboarding: `cp .env.example .env` (fill in your tokens)
1607
+ - ✅ Type-safe configuration with validation
1608
+
1609
+ **Using ConfigManager**:
1610
+
1611
+ ```typescript
1612
+ import { getConfigManager } from '../core/config/index.js';
1613
+
1614
+ // Read configuration
1615
+ const configManager = getConfigManager(projectRoot);
1616
+ const config = await configManager.read();
1617
+
1618
+ // Get specific value
1619
+ const domain = await configManager.get('issueTracker.domain');
1620
+
1621
+ // Update configuration
1622
+ await configManager.update({
1623
+ issueTracker: {
1624
+ provider: 'jira',
1625
+ domain: 'example.atlassian.net',
1626
+ strategy: 'project-per-team'
1627
+ }
1628
+ });
1629
+
1630
+ // Write entire config (validates before writing)
1631
+ await configManager.write(newConfig);
1632
+
1633
+ // Set specific value
1634
+ await configManager.set('sync.enabled', true);
1635
+ ```
1636
+
1637
+ **Init flow separation** (Jira example):
1638
+
1639
+ ```typescript
1640
+ // Step 1: Save secrets to .env (gitignored)
1641
+ const secretEnvVars = getJiraEnvVars(credentials); // ONLY tokens/emails
1642
+ saveCredentialsToEnv(projectPath, 'jira', secretEnvVars);
1643
+
1644
+ // Step 2: Save config to .specweave/config.json (committed)
1645
+ const configManager = getConfigManager(projectPath);
1646
+ const jiraConfig = getJiraConfig(credentials); // domain, strategy, projects
1647
+ await configManager.update(jiraConfig);
1648
+
1649
+ // Step 3: Generate .env.example for team onboarding
1650
+ generateEnvExample(projectPath, 'jira');
1651
+ ```
1652
+
1653
+ **What goes where**:
1654
+
1655
+ | Data Type | Location | Example | Committed? |
1656
+ |-----------|----------|---------|------------|
1657
+ | API Tokens | `.env` | `JIRA_API_TOKEN=xyz` | ❌ Never |
1658
+ | Emails | `.env` | `JIRA_EMAIL=user@example.com` | ❌ Never |
1659
+ | Domains | `config.json` | `"domain": "example.atlassian.net"` | ✅ Yes |
1660
+ | Strategies | `config.json` | `"strategy": "project-per-team"` | ✅ Yes |
1661
+ | Project Keys | `config.json` | `"projects": [{"key": "PROJ1"}]` | ✅ Yes |
1662
+ | Sync Settings | `config.json` | `"includeStatus": true` | ✅ Yes |
1663
+
1664
+ **Validation**:
1665
+
1666
+ ConfigManager validates configuration before writing:
1667
+
1668
+ ```typescript
1669
+ const result = configManager.validate(config);
1670
+ if (!result.valid) {
1671
+ console.error('Validation errors:', result.errors);
1672
+ // Example error:
1673
+ // {
1674
+ // path: 'issueTracker.provider',
1675
+ // message: 'Invalid provider. Must be one of: none, jira, github, ado',
1676
+ // value: 'invalid-provider'
1677
+ // }
1678
+ }
1679
+ ```
1680
+
1681
+ **Migration Tool** (for existing projects):
1682
+
1683
+ If you have an existing project using the old .env-only format, use the migration tool:
1684
+
1685
+ ```bash
1686
+ # Preview what will be migrated (dry run)
1687
+ node -e "require('./dist/src/cli/commands/migrate-config.js').migrateConfig({ dryRun: true })"
1688
+
1689
+ # Perform migration
1690
+ node -e "require('./dist/src/cli/commands/migrate-config.js').migrateConfig()"
1691
+
1692
+ # Force migration even if not needed
1693
+ node -e "require('./dist/src/cli/commands/migrate-config.js').migrateConfig({ force: true })"
1694
+ ```
1695
+
1696
+ **What the migration tool does**:
1697
+ 1. ✅ Analyzes `.env` file and classifies variables
1698
+ 2. ✅ Creates backup: `.env.backup.TIMESTAMP`
1699
+ 3. ✅ Updates `.env` (keeps only secrets)
1700
+ 4. ✅ Creates/updates `.specweave/config.json` (adds config)
1701
+ 5. ✅ Generates `.env.example` for team onboarding
1702
+
1703
+ **Classification logic**:
1704
+ - **Secrets** (stay in .env): Variables containing `token`, `api_token`, `pat`, `secret`, `key`, `password`, `credential`, `auth`, or email addresses
1705
+ - **Config** (move to config.json): Everything else (domains, strategies, project keys, etc.)
1706
+
1707
+ **See also**:
1708
+ - ADR-0050: Secrets vs Configuration Separation
1709
+ - `src/core/config/config-manager.ts` - ConfigManager implementation
1710
+ - `src/core/config/config-migrator.ts` - Migration tool implementation
1711
+ - `tests/unit/core/config/config-migrator.test.ts` - Usage examples
1712
+
1595
1713
  ---
1596
1714
 
1597
1715
  ## Troubleshooting
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Migrate Config Command
3
+ *
4
+ * Migrates old .env-only configuration to split secrets/config format
5
+ *
6
+ * @module cli/commands/migrate-config
7
+ */
8
+ /**
9
+ * Command options
10
+ */
11
+ export interface MigrateConfigOptions {
12
+ dryRun?: boolean;
13
+ force?: boolean;
14
+ yes?: boolean;
15
+ }
16
+ /**
17
+ * Migrate configuration from .env-only to split format
18
+ *
19
+ * @param options - Command options
20
+ */
21
+ export declare function migrateConfig(options?: MigrateConfigOptions): Promise<void>;
22
+ //# sourceMappingURL=migrate-config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-config.d.ts","sourceRoot":"","sources":["../../../../src/cli/commands/migrate-config.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAQH;;GAEG;AACH,MAAM,WAAW,oBAAoB;IACnC,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE,OAAO,CAAC;IAChB,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,OAAO,GAAE,oBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2IrF"}
@@ -0,0 +1,149 @@
1
+ /**
2
+ * Migrate Config Command
3
+ *
4
+ * Migrates old .env-only configuration to split secrets/config format
5
+ *
6
+ * @module cli/commands/migrate-config
7
+ */
8
+ import chalk from 'chalk';
9
+ import inquirer from 'inquirer';
10
+ import ora from 'ora';
11
+ import { ConfigMigrator } from '../../core/config/config-migrator.js';
12
+ /**
13
+ * Migrate configuration from .env-only to split format
14
+ *
15
+ * @param options - Command options
16
+ */
17
+ export async function migrateConfig(options = {}) {
18
+ const { dryRun = false, force = false, yes = false } = options;
19
+ console.log(chalk.bold.cyan('\n🔄 SpecWeave Configuration Migration\n'));
20
+ console.log(chalk.gray('Migrating from .env-only to split secrets/config format\n'));
21
+ const migrator = new ConfigMigrator(process.cwd());
22
+ // Check if migration is needed
23
+ if (!force) {
24
+ const spinner = ora('Checking if migration is needed...').start();
25
+ const needed = await migrator.needsMigration();
26
+ if (!needed) {
27
+ spinner.succeed('Migration not needed');
28
+ console.log(chalk.green('\n✅ Your project already uses the split format!'));
29
+ console.log(chalk.gray(' Secrets are in .env, config is in .specweave/config.json\n'));
30
+ return;
31
+ }
32
+ spinner.succeed('Migration needed - old format detected');
33
+ }
34
+ // Preview migration
35
+ console.log(chalk.bold('\n📋 Migration Preview\n'));
36
+ const spinner = ora('Analyzing .env file...').start();
37
+ let preview;
38
+ try {
39
+ preview = await migrator.preview();
40
+ spinner.succeed('Analysis complete');
41
+ }
42
+ catch (error) {
43
+ spinner.fail('Analysis failed');
44
+ console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
45
+ return;
46
+ }
47
+ // Display classification results
48
+ console.log(chalk.bold('\nClassification Results:'));
49
+ console.log(chalk.green(` Secrets: ${preview.secretsCount} variables`));
50
+ console.log(chalk.blue(` Config: ${preview.configCount} variables`));
51
+ if (preview.warnings.length > 0) {
52
+ console.log(chalk.yellow('\n⚠️ Warnings:'));
53
+ preview.warnings.forEach(w => console.log(chalk.yellow(` - ${w}`)));
54
+ }
55
+ // Show detailed breakdown
56
+ console.log(chalk.bold('\n📊 Detailed Breakdown:\n'));
57
+ const secretVars = preview.classified.filter(c => c.type === 'secret');
58
+ const configVars = preview.classified.filter(c => c.type === 'config');
59
+ if (secretVars.length > 0) {
60
+ console.log(chalk.green.bold(' Secrets (will stay in .env):'));
61
+ secretVars.forEach(v => {
62
+ const maskedValue = maskSecret(v.value);
63
+ console.log(chalk.gray(` ${v.key}=${maskedValue}`));
64
+ console.log(chalk.gray(` └─ ${v.reason}\n`));
65
+ });
66
+ }
67
+ if (configVars.length > 0) {
68
+ console.log(chalk.blue.bold(' Configuration (will move to config.json):'));
69
+ configVars.forEach(v => {
70
+ console.log(chalk.gray(` ${v.key}=${v.value}`));
71
+ console.log(chalk.gray(` └─ ${v.reason}\n`));
72
+ });
73
+ }
74
+ // Dry run mode - stop here
75
+ if (dryRun) {
76
+ console.log(chalk.yellow('\n🔍 Dry run mode - no changes made\n'));
77
+ console.log(chalk.gray('Run without --dry-run to perform migration\n'));
78
+ return;
79
+ }
80
+ // Confirm migration
81
+ if (!yes) {
82
+ console.log(chalk.bold('\n❓ Migration Actions:\n'));
83
+ console.log(chalk.gray(' 1. Backup .env → .env.backup.TIMESTAMP'));
84
+ console.log(chalk.gray(' 2. Update .env (keep only secrets)'));
85
+ console.log(chalk.gray(' 3. Create/update .specweave/config.json (add config)'));
86
+ console.log(chalk.gray(' 4. Generate .env.example (team template)\n'));
87
+ const { confirm } = await inquirer.prompt([
88
+ {
89
+ type: 'confirm',
90
+ name: 'confirm',
91
+ message: 'Proceed with migration?',
92
+ default: false
93
+ }
94
+ ]);
95
+ if (!confirm) {
96
+ console.log(chalk.yellow('\n⚠️ Migration cancelled\n'));
97
+ return;
98
+ }
99
+ }
100
+ // Perform migration
101
+ console.log(chalk.bold('\n🚀 Performing Migration...\n'));
102
+ const migrationSpinner = ora('Migrating configuration...').start();
103
+ let result;
104
+ try {
105
+ result = await migrator.migrate({ backup: true, force });
106
+ migrationSpinner.succeed('Migration complete');
107
+ }
108
+ catch (error) {
109
+ migrationSpinner.fail('Migration failed');
110
+ console.error(chalk.red(`\n❌ Error: ${error.message}\n`));
111
+ return;
112
+ }
113
+ // Display results
114
+ console.log(chalk.bold.green('\n✅ Migration Successful!\n'));
115
+ console.log(chalk.bold('Summary:'));
116
+ console.log(chalk.green(` ✓ ${result.secretsCount} secrets kept in .env`));
117
+ console.log(chalk.blue(` ✓ ${result.configCount} config items moved to config.json`));
118
+ if (result.backupPath) {
119
+ console.log(chalk.gray(` ✓ Backup created: ${result.backupPath}`));
120
+ }
121
+ console.log(chalk.gray(` ✓ .env.example generated\n`));
122
+ // Show next steps
123
+ console.log(chalk.bold('📝 Next Steps:\n'));
124
+ console.log(chalk.cyan(' 1. Review .specweave/config.json (commit to git)'));
125
+ console.log(chalk.cyan(' 2. Share .env.example with team (commit to git)'));
126
+ console.log(chalk.cyan(' 3. Team members: cp .env.example .env (fill in tokens)'));
127
+ console.log(chalk.cyan(' 4. Verify: specweave init . (should detect existing config)\n'));
128
+ console.log(chalk.bold('🎯 Benefits:\n'));
129
+ console.log(chalk.green(' ✓ Team shares configuration via git'));
130
+ console.log(chalk.green(' ✓ Secrets stay local (never committed)'));
131
+ console.log(chalk.green(' ✓ Type-safe configuration with validation'));
132
+ console.log(chalk.green(' ✓ Easy onboarding for new team members\n'));
133
+ }
134
+ /**
135
+ * Mask secret value for display
136
+ *
137
+ * @param value - Secret value
138
+ * @returns Masked value
139
+ */
140
+ function maskSecret(value) {
141
+ if (value.length <= 8) {
142
+ return '***';
143
+ }
144
+ const visible = 4;
145
+ const start = value.substring(0, visible);
146
+ const end = value.substring(value.length - visible);
147
+ return `${start}${'*'.repeat(value.length - visible * 2)}${end}`;
148
+ }
149
+ //# sourceMappingURL=migrate-config.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"migrate-config.js","sourceRoot":"","sources":["../../../../src/cli/commands/migrate-config.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/helpers/issue-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EAAoC,YAAY,EAAE,MAAM,YAAY,CAAC;AAyCjF;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CAmS/E;AAsdD,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/cli/helpers/issue-tracker/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAQH,OAAO,KAAK,EAAoC,YAAY,EAAE,MAAM,YAAY,CAAC;AA2CjF;;;;;;;;GAQG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAAC,CA+R/E;AAihBD,cAAc,YAAY,CAAC;AAC3B,cAAc,YAAY,CAAC"}
@@ -8,17 +8,17 @@
8
8
  */
9
9
  import chalk from 'chalk';
10
10
  import inquirer from 'inquirer';
11
- import ora from 'ora';
12
11
  import fs from 'fs';
13
12
  import path from 'path';
14
13
  import { execSync } from 'child_process';
15
- import { detectDefaultTracker, getTrackerDisplayName, installTrackerPlugin, isClaudeCliAvailable } from './utils.js';
14
+ import { detectDefaultTracker, getTrackerDisplayName } from './utils.js';
16
15
  import { readEnvFile, writeEnvFile, createEnvFromTemplate, updateEnvVars, ensureEnvGitignored } from '../../../utils/env-file.js';
17
16
  import { getLocaleManager } from '../../../core/i18n/locale-manager.js';
17
+ import { getConfigManager } from '../../../core/config/index.js';
18
18
  // GitHub
19
19
  import { checkExistingGitHubCredentials, promptGitHubCredentials, validateGitHubConnection, getGitHubEnvVars, showGitHubSetupComplete, showGitHubSetupSkipped } from './github.js';
20
20
  // Jira
21
- import { checkExistingJiraCredentials, promptJiraCredentials, validateJiraConnection, getJiraEnvVars, showJiraSetupComplete, showJiraSetupSkipped } from './jira.js';
21
+ import { checkExistingJiraCredentials, promptJiraCredentials, validateJiraConnection, getJiraEnvVars, getJiraConfig, showJiraSetupComplete, showJiraSetupSkipped } from './jira.js';
22
22
  // Azure DevOps
23
23
  import { checkExistingAzureDevOpsCredentials, promptAzureDevOpsCredentials, validateAzureDevOpsConnection, getAzureDevOpsEnvVars, showAzureDevOpsSetupComplete, showAzureDevOpsSetupSkipped } from './ado.js';
24
24
  /**
@@ -183,8 +183,7 @@ export async function setupIssueTracker(options) {
183
183
  if (tracker === 'jira') {
184
184
  await validateResources(tracker, credentials, projectPath);
185
185
  }
186
- // Install plugin
187
- await installPlugin(tracker, language);
186
+ // Show setup complete message (plugins managed via marketplace)
188
187
  showSetupComplete(tracker, language);
189
188
  return true;
190
189
  }
@@ -273,9 +272,7 @@ export async function setupIssueTracker(options) {
273
272
  if (tracker === 'jira' || tracker === 'ado') {
274
273
  await validateResources(tracker, credentials, projectPath);
275
274
  }
276
- // Step 6: Install plugin
277
- await installPlugin(tracker, language);
278
- // Step 7: Show success message
275
+ // Step 6: Show success message (plugins managed via marketplace)
279
276
  showSetupComplete(tracker, language);
280
277
  return true;
281
278
  }
@@ -373,7 +370,85 @@ async function validateResources(tracker, credentials, projectPath) {
373
370
  }
374
371
  }
375
372
  /**
376
- * Save credentials to .env file
373
+ * Generate .env.example template for team onboarding
374
+ *
375
+ * Creates a template file showing required environment variables
376
+ * with placeholder values and setup instructions
377
+ */
378
+ function generateEnvExample(projectPath, tracker) {
379
+ const examplePath = path.join(projectPath, '.env.example');
380
+ let content = `# SpecWeave Environment Variables
381
+ #
382
+ # IMPORTANT: Copy this file to .env and replace placeholder values with your actual credentials
383
+ # NEVER commit .env to git! It's already in .gitignore
384
+ #
385
+ # Setup Instructions:
386
+ # 1. Copy: cp .env.example .env
387
+ # 2. Replace placeholder values below with your actual credentials
388
+ # 3. Run: specweave init . (to validate credentials)
389
+
390
+ `;
391
+ if (tracker === 'jira') {
392
+ content += `# ==========================================
393
+ # Jira Credentials (REQUIRED)
394
+ # ==========================================
395
+ # Get your Jira API token from:
396
+ # Cloud: https://id.atlassian.com/manage-profile/security/api-tokens
397
+ # Server: Your Jira instance → Settings → Personal Access Tokens
398
+
399
+ JIRA_API_TOKEN=your_jira_api_token_here
400
+ JIRA_EMAIL=your_email@company.com
401
+
402
+ # NOTE: Domain, strategy, and projects are now in .specweave/config.json
403
+ # (This makes them shareable across the team via git)
404
+ `;
405
+ }
406
+ else if (tracker === 'github') {
407
+ content += `# ==========================================
408
+ # GitHub Credentials (REQUIRED)
409
+ # ==========================================
410
+ # Get your GitHub token from:
411
+ # https://github.com/settings/tokens
412
+ # Required scopes: repo, read:org
413
+
414
+ GITHUB_TOKEN=your_github_token_here
415
+ `;
416
+ }
417
+ else if (tracker === 'ado') {
418
+ content += `# ==========================================
419
+ # Azure DevOps Credentials (REQUIRED)
420
+ # ==========================================
421
+ # Get your PAT from:
422
+ # https://dev.azure.com/YOUR_ORG/_usersSettings/tokens
423
+
424
+ ADO_PAT=your_ado_pat_here
425
+ `;
426
+ }
427
+ content += `
428
+ # ==========================================
429
+ # Optional: Additional Integrations
430
+ # ==========================================
431
+ # Uncomment and configure if you use multiple tools:
432
+
433
+ # GitHub (if using with Jira)
434
+ # GITHUB_TOKEN=your_github_token_here
435
+
436
+ # Bitbucket
437
+ # BITBUCKET_TOKEN=your_bitbucket_token_here
438
+
439
+ # GitLab
440
+ # GITLAB_TOKEN=your_gitlab_token_here
441
+ `;
442
+ fs.writeFileSync(examplePath, content, 'utf-8');
443
+ console.log(chalk.green('✓ Generated .env.example (commit this to git!)'));
444
+ console.log(chalk.gray(' Team members can copy it to .env and add their credentials'));
445
+ }
446
+ /**
447
+ * Save credentials to .env file AND configuration to config.json
448
+ *
449
+ * NEW (v0.23.0): Implements ADR-0050 secrets/config separation
450
+ * - Secrets (tokens, emails) → .env (gitignored)
451
+ * - Configuration (domains, strategies, projects) → config.json (committed)
377
452
  */
378
453
  async function saveCredentials(projectPath, tracker, credentials) {
379
454
  // Read existing .env or create from template
@@ -381,14 +456,14 @@ async function saveCredentials(projectPath, tracker, credentials) {
381
456
  if (!envContent) {
382
457
  envContent = createEnvFromTemplate(projectPath);
383
458
  }
384
- // Get tracker-specific env vars
459
+ // Get tracker-specific env vars (ONLY secrets)
385
460
  let envVars = [];
386
461
  switch (tracker) {
387
462
  case 'github':
388
463
  envVars = getGitHubEnvVars(credentials);
389
464
  break;
390
465
  case 'jira':
391
- envVars = getJiraEnvVars(credentials);
466
+ envVars = getJiraEnvVars(credentials); // NOW returns ONLY secrets
392
467
  break;
393
468
  case 'ado':
394
469
  envVars = getAzureDevOpsEnvVars(credentials);
@@ -401,6 +476,18 @@ async function saveCredentials(projectPath, tracker, credentials) {
401
476
  // Ensure .env is gitignored
402
477
  ensureEnvGitignored(projectPath);
403
478
  console.log(chalk.green('✓ Credentials saved to .env (gitignored)'));
479
+ // Save non-sensitive configuration to config.json
480
+ if (tracker === 'jira') {
481
+ const configManager = getConfigManager(projectPath);
482
+ const jiraConfig = getJiraConfig(credentials);
483
+ await configManager.update(jiraConfig);
484
+ console.log(chalk.green('✓ Configuration saved to .specweave/config.json'));
485
+ console.log(chalk.gray(` Domain: ${jiraConfig.issueTracker.domain}`));
486
+ console.log(chalk.gray(` Strategy: ${jiraConfig.issueTracker.strategy}`));
487
+ console.log(chalk.gray(` Projects: ${jiraConfig.issueTracker.projects?.map(p => p.key).join(', ')}`));
488
+ }
489
+ // Generate .env.example for team onboarding
490
+ generateEnvExample(projectPath, tracker);
404
491
  }
405
492
  /**
406
493
  * Write sync config to .specweave/config.json
@@ -411,20 +498,9 @@ async function saveCredentials(projectPath, tracker, credentials) {
411
498
  * NEW (v0.21.0): Includes sync settings (status sync, task checkboxes, auto-labels)
412
499
  */
413
500
  async function writeSyncConfig(projectPath, tracker, credentials, syncSettings, syncPermissions, repositoryProfiles, monorepoProjects) {
414
- const configPath = path.join(projectPath, '.specweave', 'config.json');
415
- // Read existing config or create new one
416
- let config = {
417
- project: {
418
- name: path.basename(projectPath),
419
- version: '0.1.0'
420
- },
421
- adapters: {
422
- default: 'claude'
423
- }
424
- };
425
- if (fs.existsSync(configPath)) {
426
- config = JSON.parse(fs.readFileSync(configPath, 'utf-8'));
427
- }
501
+ const configManager = getConfigManager(projectPath);
502
+ // Read existing config
503
+ const config = await configManager.read();
428
504
  // Add hooks configuration (enables auto-sync!)
429
505
  config.hooks = {
430
506
  post_task_completion: {
@@ -516,6 +592,8 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
516
592
  const defaultProfile = repositoryProfiles.find(p => p.isDefault) || repositoryProfiles[0];
517
593
  config.sync = {
518
594
  enabled: true,
595
+ direction: 'bidirectional',
596
+ autoSync: false,
519
597
  provider: tracker, // NEW: Exclusive provider
520
598
  includeStatus: syncSettings.includeStatus, // NEW: Status sync toggle
521
599
  autoApplyLabels: syncSettings.autoApplyLabels, // NEW: Auto-labeling
@@ -548,6 +626,8 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
548
626
  };
549
627
  config.sync = {
550
628
  enabled: true,
629
+ direction: 'bidirectional',
630
+ autoSync: false,
551
631
  provider: tracker, // NEW: Exclusive provider
552
632
  includeStatus: syncSettings.includeStatus, // NEW: Status sync toggle
553
633
  autoApplyLabels: syncSettings.autoApplyLabels, // NEW: Auto-labeling
@@ -579,6 +659,8 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
579
659
  };
580
660
  config.sync = {
581
661
  enabled: true,
662
+ direction: 'bidirectional',
663
+ autoSync: false,
582
664
  provider: tracker, // NEW: Exclusive provider
583
665
  includeStatus: syncSettings.includeStatus, // NEW: Status sync toggle
584
666
  autoApplyLabels: syncSettings.autoApplyLabels, // NEW: Auto-labeling
@@ -606,6 +688,8 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
606
688
  };
607
689
  config.sync = {
608
690
  enabled: true,
691
+ direction: 'bidirectional',
692
+ autoSync: false,
609
693
  provider: tracker, // NEW: Exclusive provider
610
694
  includeStatus: syncSettings.includeStatus, // NEW: Status sync toggle
611
695
  autoApplyLabels: syncSettings.autoApplyLabels, // NEW: Auto-labeling
@@ -618,8 +702,8 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
618
702
  profiles
619
703
  };
620
704
  }
621
- // Write config
622
- fs.writeFileSync(configPath, JSON.stringify(config, null, 2) + '\n');
705
+ // Write config using ConfigManager
706
+ await configManager.write(config);
623
707
  console.log(chalk.green(`✓ Sync config written to .specweave/config.json`));
624
708
  console.log(chalk.gray(` Provider: ${tracker}`));
625
709
  console.log(chalk.gray(` Auto-sync: enabled`));
@@ -627,39 +711,7 @@ async function writeSyncConfig(projectPath, tracker, credentials, syncSettings,
627
711
  console.log(chalk.gray(` Auto-labeling: ${syncSettings.autoApplyLabels ? 'enabled' : 'disabled'}`));
628
712
  console.log(chalk.gray(` Hooks: post_task_completion, post_increment_planning`));
629
713
  }
630
- /**
631
- * Install tracker plugin
632
- */
633
- async function installPlugin(tracker, language) {
634
- const spinner = ora(`Installing ${getTrackerDisplayName(tracker)} plugin...`).start();
635
- // Check if Claude CLI is available
636
- if (!isClaudeCliAvailable()) {
637
- spinner.warn('Claude CLI not found');
638
- console.log(chalk.yellow('\n📦 Manual plugin installation required:'));
639
- console.log(chalk.white(` /plugin install specweave-${tracker}\n`));
640
- return;
641
- }
642
- // Install plugin
643
- const result = installTrackerPlugin(tracker);
644
- if (result.success) {
645
- if (result.alreadyInstalled) {
646
- spinner.succeed(`${getTrackerDisplayName(tracker)} plugin already installed`);
647
- }
648
- else {
649
- spinner.succeed(`${getTrackerDisplayName(tracker)} plugin installed`);
650
- }
651
- }
652
- else {
653
- spinner.fail(`Could not auto-install plugin`);
654
- console.log(chalk.yellow('\n📦 Manual plugin installation:'));
655
- console.log(chalk.white(` /plugin install specweave-${tracker}`));
656
- // Show error details in DEBUG mode
657
- if (process.env.DEBUG && result.error) {
658
- console.log(chalk.gray(` Error: ${result.error}`));
659
- }
660
- console.log('');
661
- }
662
- }
714
+ // Removed installPlugin() function - plugins are managed via marketplace, not during init
663
715
  /**
664
716
  * Show setup complete message
665
717
  */