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.
- package/CLAUDE.md +73 -1
- package/README.md +245 -446
- package/dist/plugins/specweave-jira/lib/setup-wizard.d.ts.map +1 -1
- package/dist/plugins/specweave-jira/lib/setup-wizard.js +57 -78
- package/dist/plugins/specweave-jira/lib/setup-wizard.js.map +1 -1
- package/dist/src/cli/commands/import-docs.d.ts.map +1 -1
- package/dist/src/cli/commands/import-docs.js +23 -31
- package/dist/src/cli/commands/import-docs.js.map +1 -1
- package/dist/src/cli/commands/import-external.d.ts.map +1 -1
- package/dist/src/cli/commands/import-external.js +6 -10
- package/dist/src/cli/commands/import-external.js.map +1 -1
- package/dist/src/cli/commands/init-multiproject.d.ts.map +1 -1
- package/dist/src/cli/commands/init-multiproject.js +58 -73
- package/dist/src/cli/commands/init-multiproject.js.map +1 -1
- package/dist/src/cli/commands/init.d.ts +17 -11
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +221 -1874
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/install.d.ts.map +1 -1
- package/dist/src/cli/commands/install.js +14 -22
- package/dist/src/cli/commands/install.js.map +1 -1
- package/dist/src/cli/commands/migrate-config.d.ts.map +1 -1
- package/dist/src/cli/commands/migrate-config.js +6 -10
- package/dist/src/cli/commands/migrate-config.js.map +1 -1
- package/dist/src/cli/commands/switch-project.d.ts.map +1 -1
- package/dist/src/cli/commands/switch-project.js.map +1 -1
- package/dist/src/cli/helpers/ado-area-path-mapper.d.ts.map +1 -1
- package/dist/src/cli/helpers/ado-area-path-mapper.js +36 -49
- package/dist/src/cli/helpers/ado-area-path-mapper.js.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.d.ts.map +1 -1
- package/dist/src/cli/helpers/github/increment-profile-selector.js.map +1 -1
- package/dist/src/cli/helpers/github/profile-manager.d.ts.map +1 -1
- package/dist/src/cli/helpers/github/profile-manager.js +8 -11
- package/dist/src/cli/helpers/github/profile-manager.js.map +1 -1
- package/dist/src/cli/helpers/github-repo-selector.d.ts.map +1 -1
- package/dist/src/cli/helpers/github-repo-selector.js +26 -50
- package/dist/src/cli/helpers/github-repo-selector.js.map +1 -1
- package/dist/src/cli/helpers/import-strategy-prompter.d.ts.map +1 -1
- package/dist/src/cli/helpers/import-strategy-prompter.js +39 -52
- package/dist/src/cli/helpers/import-strategy-prompter.js.map +1 -1
- package/dist/src/cli/helpers/init/config-detection.d.ts +40 -0
- package/dist/src/cli/helpers/init/config-detection.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/config-detection.js +125 -0
- package/dist/src/cli/helpers/init/config-detection.js.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts +26 -0
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/directory-structure.js +190 -0
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -0
- package/dist/src/cli/helpers/init/external-import.d.ts +15 -0
- package/dist/src/cli/helpers/init/external-import.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/external-import.js +251 -0
- package/dist/src/cli/helpers/init/external-import.js.map +1 -0
- package/dist/src/cli/helpers/init/index.d.ts +15 -0
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/index.js +26 -0
- package/dist/src/cli/helpers/init/index.js.map +1 -0
- package/dist/src/cli/helpers/init/next-steps.d.ts +15 -0
- package/dist/src/cli/helpers/init/next-steps.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/next-steps.js +72 -0
- package/dist/src/cli/helpers/init/next-steps.js.map +1 -0
- package/dist/src/cli/helpers/init/path-utils.d.ts +41 -0
- package/dist/src/cli/helpers/init/path-utils.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/path-utils.js +146 -0
- package/dist/src/cli/helpers/init/path-utils.js.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.d.ts +28 -0
- package/dist/src/cli/helpers/init/plugin-installer.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/plugin-installer.js +238 -0
- package/dist/src/cli/helpers/init/plugin-installer.js.map +1 -0
- package/dist/src/cli/helpers/init/repository-setup.d.ts +28 -0
- package/dist/src/cli/helpers/init/repository-setup.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/repository-setup.js +78 -0
- package/dist/src/cli/helpers/init/repository-setup.js.map +1 -0
- package/dist/src/cli/helpers/init/smart-reinit.d.ts +30 -0
- package/dist/src/cli/helpers/init/smart-reinit.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/smart-reinit.js +140 -0
- package/dist/src/cli/helpers/init/smart-reinit.js.map +1 -0
- package/dist/src/cli/helpers/init/testing-config.d.ts +27 -0
- package/dist/src/cli/helpers/init/testing-config.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/testing-config.js +131 -0
- package/dist/src/cli/helpers/init/testing-config.js.map +1 -0
- package/dist/src/cli/helpers/init/types.d.ts +86 -0
- package/dist/src/cli/helpers/init/types.d.ts.map +1 -0
- package/dist/src/cli/helpers/init/types.js +5 -0
- package/dist/src/cli/helpers/init/types.js.map +1 -0
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js +10 -12
- package/dist/src/cli/helpers/issue-tracker/ado-auto-discover.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/ado.js +43 -60
- package/dist/src/cli/helpers/issue-tracker/ado.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js +193 -230
- package/dist/src/cli/helpers/issue-tracker/github-multi-repo.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/github.js +43 -54
- package/dist/src/cli/helpers/issue-tracker/github.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/index.js +27 -40
- package/dist/src/cli/helpers/issue-tracker/index.js.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.d.ts.map +1 -1
- package/dist/src/cli/helpers/issue-tracker/jira.js +54 -70
- package/dist/src/cli/helpers/issue-tracker/jira.js.map +1 -1
- package/dist/src/cli/helpers/smart-filter.d.ts.map +1 -1
- package/dist/src/cli/helpers/smart-filter.js +62 -85
- package/dist/src/cli/helpers/smart-filter.js.map +1 -1
- package/dist/src/core/increment/auto-transition-manager.d.ts +12 -0
- package/dist/src/core/increment/auto-transition-manager.d.ts.map +1 -1
- package/dist/src/core/increment/auto-transition-manager.js +45 -0
- package/dist/src/core/increment/auto-transition-manager.js.map +1 -1
- package/dist/src/core/increment/metadata-manager.d.ts.map +1 -1
- package/dist/src/core/increment/metadata-manager.js +46 -0
- package/dist/src/core/increment/metadata-manager.js.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.d.ts +12 -0
- package/dist/src/core/increment/status-change-sync-trigger.d.ts.map +1 -1
- package/dist/src/core/increment/status-change-sync-trigger.js +48 -2
- package/dist/src/core/increment/status-change-sync-trigger.js.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.d.ts +13 -0
- package/dist/src/core/living-docs/living-docs-sync.d.ts.map +1 -1
- package/dist/src/core/living-docs/living-docs-sync.js +40 -0
- package/dist/src/core/living-docs/living-docs-sync.js.map +1 -1
- package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts +5 -1
- package/dist/src/core/repo-structure/repo-bulk-discovery.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-bulk-discovery.js +68 -86
- package/dist/src/core/repo-structure/repo-bulk-discovery.js.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.d.ts.map +1 -1
- package/dist/src/core/repo-structure/repo-structure-manager.js +345 -425
- package/dist/src/core/repo-structure/repo-structure-manager.js.map +1 -1
- package/dist/src/core/sync/bidirectional-engine.d.ts.map +1 -1
- package/dist/src/core/sync/bidirectional-engine.js +21 -29
- package/dist/src/core/sync/bidirectional-engine.js.map +1 -1
- package/dist/src/init/InitFlow.js +15 -19
- package/dist/src/init/InitFlow.js.map +1 -1
- package/dist/src/init/repo/types.d.ts +1 -1
- package/dist/src/integrations/ado/area-path-mapper.d.ts.map +1 -1
- package/dist/src/integrations/ado/area-path-mapper.js +19 -23
- package/dist/src/integrations/ado/area-path-mapper.js.map +1 -1
- package/dist/src/utils/external-resource-validator.d.ts.map +1 -1
- package/dist/src/utils/external-resource-validator.js +41 -65
- package/dist/src/utils/external-resource-validator.js.map +1 -1
- package/dist/src/utils/project-detection.d.ts.map +1 -1
- package/dist/src/utils/project-detection.js +19 -21
- package/dist/src/utils/project-detection.js.map +1 -1
- package/dist/src/utils/project-validator.d.ts.map +1 -1
- package/dist/src/utils/project-validator.js +5 -7
- package/dist/src/utils/project-validator.js.map +1 -1
- package/package.json +2 -3
- package/plugins/specweave/agents/tech-lead/AGENT.md +9 -0
- package/plugins/specweave/hooks/post-edit-write-consolidated.sh +87 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.d.ts +12 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js +45 -0
- package/plugins/specweave/lib/vendor/core/increment/auto-transition-manager.js.map +1 -1
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js +46 -0
- package/plugins/specweave/lib/vendor/core/increment/metadata-manager.js.map +1 -1
- package/plugins/specweave/skills/increment-planner/SKILL.md +10 -5
- package/plugins/specweave/skills/specweave-framework/SKILL.md +6 -4
- package/plugins/specweave/templates/coding-standards.md.template +36 -0
- package/plugins/specweave-ado/lib/project-selector.js +56 -67
- package/plugins/specweave-ado/lib/project-selector.ts +72 -85
- package/plugins/specweave-github/lib/repo-selector.js +55 -66
- package/plugins/specweave-github/lib/repo-selector.ts +73 -84
- package/plugins/specweave-jira/commands/import-projects.js +3 -5
- package/plugins/specweave-jira/commands/import-projects.ts +3 -5
- package/plugins/specweave-jira/lib/project-selector.js +60 -71
- package/plugins/specweave-jira/lib/project-selector.ts +78 -91
- package/plugins/specweave-jira/lib/setup-wizard.js +51 -72
- package/plugins/specweave-jira/lib/setup-wizard.ts +56 -74
- 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
|
|
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 {
|
|
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
|
-
|
|
24
|
-
import {
|
|
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
|
|
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;
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
107
|
-
},
|
|
108
|
-
timeRange: {
|
|
109
|
-
default: '1M',
|
|
110
|
-
max: '6M'
|
|
86
|
+
projects: jiraProjects
|
|
111
87
|
},
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
warnThreshold: 100
|
|
115
|
-
}
|
|
88
|
+
timeRange: { default: '1M', max: '6M' },
|
|
89
|
+
rateLimits: { maxItemsPerSync: 500, warnThreshold: 100 }
|
|
116
90
|
};
|
|
117
|
-
|
|
118
|
-
|
|
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(
|
|
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();
|
|
128
|
-
const
|
|
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(
|
|
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
|
-
//
|
|
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
|
|
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');
|
|
163
|
-
console.error(chalk.red(
|
|
164
|
-
console.error(chalk.yellow(
|
|
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
|
-
|
|
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;
|
|
176
|
-
// Handle "." for current directory
|
|
131
|
+
let continueExisting = false;
|
|
132
|
+
// Handle "." for current directory
|
|
177
133
|
if (projectName === '.') {
|
|
178
134
|
usedDotNotation = true;
|
|
179
135
|
targetDir = process.cwd();
|
|
180
|
-
//
|
|
181
|
-
|
|
182
|
-
|
|
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.
|
|
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
|
|
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
|
-
|
|
208
|
-
console.log(chalk.
|
|
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
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
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
|
-
//
|
|
235
|
-
const
|
|
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
|
-
|
|
240
|
-
console.log(chalk.
|
|
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(
|
|
245
|
-
const
|
|
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
|
-
//
|
|
181
|
+
// Smart re-initialization
|
|
260
182
|
if (fs.existsSync(path.join(targetDir, '.specweave'))) {
|
|
261
|
-
|
|
262
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
410
|
-
// 1. Get project name if not provided
|
|
191
|
+
// Create subdirectory
|
|
411
192
|
if (!projectName) {
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
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 (
|
|
436
|
-
|
|
437
|
-
if (
|
|
438
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
//
|
|
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(
|
|
532
|
-
console.log('');
|
|
533
|
-
console.log(chalk.
|
|
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
|
-
//
|
|
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 =
|
|
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
|
|
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(
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
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:
|
|
661
|
-
{ name: 'Cursor (Partial - AGENTS.md compilation
|
|
662
|
-
{ name: 'Other (Copilot, ChatGPT
|
|
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
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
// User confirmed detected tool
|
|
670
|
-
toolName = detectedTool;
|
|
288
|
+
});
|
|
289
|
+
}
|
|
290
|
+
else {
|
|
291
|
+
toolName = detectedTool;
|
|
292
|
+
}
|
|
671
293
|
}
|
|
672
|
-
spinner.start(
|
|
294
|
+
spinner.start('Using ' + toolName + '...');
|
|
673
295
|
}
|
|
674
|
-
//
|
|
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
|
-
//
|
|
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
|
-
|
|
687
|
-
|
|
688
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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(
|
|
723
|
-
console.log(` ${locale.t('cli', 'init.claudeNativeBenefits')}`);
|
|
319
|
+
console.log('\n' + locale.t('cli', 'init.claudeNativeComplete'));
|
|
724
320
|
}
|
|
725
321
|
else {
|
|
726
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
//
|
|
347
|
+
// Create config.json
|
|
851
348
|
createConfigFile(targetDir, finalProjectName, toolName, language, false);
|
|
852
|
-
//
|
|
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
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
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
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
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(
|
|
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
|
|
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
|
-
//
|
|
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
|
-
|
|
1266
|
-
|
|
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
|
-
//
|
|
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
|
|
395
|
+
language
|
|
1396
396
|
});
|
|
1397
|
-
console.log(chalk.green(
|
|
1398
|
-
|
|
1399
|
-
|
|
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
|
|
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(
|
|
409
|
+
console.error(chalk.red('\n' + locale.t('cli', 'init.genericError')), error);
|
|
1432
410
|
process.exit(1);
|
|
1433
411
|
}
|
|
1434
412
|
}
|
|
1435
413
|
/**
|
|
1436
|
-
*
|
|
1437
|
-
* Parses .git/config to extract GitHub remote URL
|
|
414
|
+
* Install non-Claude adapter (Cursor, Generic)
|
|
1438
415
|
*/
|
|
1439
|
-
function
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
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
|
-
|
|
1856
|
-
|
|
426
|
+
techStack: options.techStack ? { language: options.techStack } : undefined,
|
|
427
|
+
docsApproach: 'incremental'
|
|
1857
428
|
});
|
|
1858
|
-
|
|
1859
|
-
|
|
1860
|
-
|
|
1861
|
-
|
|
1862
|
-
|
|
1863
|
-
|
|
1864
|
-
|
|
1865
|
-
|
|
1866
|
-
|
|
1867
|
-
|
|
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
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
1937
|
-
|
|
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
|
-
|
|
1957
|
-
|
|
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
|
-
*
|
|
1977
|
-
* Testing configuration is optional and can be added later via updateConfigWithTesting()
|
|
462
|
+
* Wrapper for issue tracker setup
|
|
1978
463
|
*/
|
|
1979
|
-
function
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
1988
|
-
|
|
1989
|
-
|
|
1990
|
-
|
|
1991
|
-
|
|
1992
|
-
|
|
1993
|
-
|
|
1994
|
-
|
|
1995
|
-
|
|
1996
|
-
|
|
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
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
2123
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
2128
|
-
|
|
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
|
-
|
|
2142
|
-
console.log(
|
|
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
|