specweave 1.0.418 ā 1.0.419
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/dist/src/cli/commands/init.d.ts +4 -11
- package/dist/src/cli/commands/init.d.ts.map +1 -1
- package/dist/src/cli/commands/init.js +126 -732
- package/dist/src/cli/commands/init.js.map +1 -1
- package/dist/src/cli/commands/resolve-structure.d.ts +4 -2
- package/dist/src/cli/commands/resolve-structure.d.ts.map +1 -1
- package/dist/src/cli/commands/resolve-structure.js +4 -2
- package/dist/src/cli/commands/resolve-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/ado-repo-cloning.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/ado-repo-cloning.js +14 -12
- package/dist/src/cli/helpers/init/ado-repo-cloning.js.map +1 -1
- package/dist/src/cli/helpers/init/bitbucket-repo-cloning.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/bitbucket-repo-cloning.js +14 -12
- package/dist/src/cli/helpers/init/bitbucket-repo-cloning.js.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.d.ts +2 -2
- package/dist/src/cli/helpers/init/directory-structure.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/directory-structure.js +5 -23
- package/dist/src/cli/helpers/init/directory-structure.js.map +1 -1
- package/dist/src/cli/helpers/init/github-repo-cloning.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/github-repo-cloning.js +14 -12
- package/dist/src/cli/helpers/init/github-repo-cloning.js.map +1 -1
- package/dist/src/cli/helpers/init/index.d.ts +5 -7
- package/dist/src/cli/helpers/init/index.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/index.js +6 -17
- package/dist/src/cli/helpers/init/index.js.map +1 -1
- package/dist/src/cli/helpers/init/next-steps.d.ts +6 -6
- package/dist/src/cli/helpers/init/next-steps.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/next-steps.js +49 -52
- package/dist/src/cli/helpers/init/next-steps.js.map +1 -1
- package/dist/src/cli/helpers/init/smart-defaults.js +1 -1
- package/dist/src/cli/helpers/init/smart-defaults.js.map +1 -1
- package/dist/src/cli/helpers/init/summary-banner.d.ts +2 -21
- package/dist/src/cli/helpers/init/summary-banner.d.ts.map +1 -1
- package/dist/src/cli/helpers/init/summary-banner.js +3 -49
- package/dist/src/cli/helpers/init/summary-banner.js.map +1 -1
- package/dist/src/config/types.d.ts +8 -8
- package/dist/src/core/background/job-launcher.d.ts +2 -0
- package/dist/src/core/background/job-launcher.d.ts.map +1 -1
- package/dist/src/core/background/job-launcher.js +7 -8
- package/dist/src/core/background/job-launcher.js.map +1 -1
- package/dist/src/init/architecture/types.d.ts +2 -2
- package/dist/src/init/compliance/types.d.ts +2 -2
- package/package.json +1 -1
- package/plugins/specweave/hooks/v2/guards/increment-existence-guard.sh +11 -80
- package/plugins/specweave/skills/team-build/SKILL.md +39 -120
- package/plugins/specweave/skills/team-lead/SKILL.md +232 -494
- package/src/templates/config.json.template +2 -77
- package/plugins/specweave/skills/team-lead/agents/brainstorm-advocate.md +0 -65
- package/plugins/specweave/skills/team-lead/agents/brainstorm-critic.md +0 -75
- package/plugins/specweave/skills/team-lead/agents/brainstorm-pragmatist.md +0 -83
- package/plugins/specweave/skills/team-lead/agents/reviewer-logic.md +0 -63
- package/plugins/specweave/skills/team-lead/agents/reviewer-performance.md +0 -63
- package/plugins/specweave/skills/team-lead/agents/reviewer-security.md +0 -62
|
@@ -1,43 +1,30 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* SpecWeave Init Command
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
* -
|
|
7
|
-
* - Plugin installation (Claude Code)
|
|
8
|
-
* - Issue tracker setup
|
|
9
|
-
* - Initial increment
|
|
10
|
-
*
|
|
11
|
-
* Refactored: Logic extracted to src/cli/helpers/init/
|
|
4
|
+
* Simplified (v1.0.415): Creates .specweave/ structure + config.json + instruction files.
|
|
5
|
+
* External tool setup moved to `specweave sync-setup`.
|
|
6
|
+
* Multi-repo setup moved to `specweave migrate-to-umbrella`.
|
|
12
7
|
*/
|
|
13
8
|
import * as fs from '../../utils/fs-native.js';
|
|
14
9
|
import * as path from 'path';
|
|
15
10
|
import * as os from 'os';
|
|
16
11
|
import chalk from 'chalk';
|
|
17
12
|
import ora from 'ora';
|
|
18
|
-
import { input, confirm
|
|
13
|
+
import { input, confirm } from '@inquirer/prompts';
|
|
19
14
|
import { execFileNoThrowSync } from '../../utils/execFileNoThrow.js';
|
|
20
15
|
import { AdapterLoader } from '../../adapters/adapter-loader.js';
|
|
21
16
|
import { getDirname } from '../../utils/esm-helpers.js';
|
|
22
17
|
import { isLanguageSupported, getSupportedLanguages } from '../../core/i18n/language-manager.js';
|
|
23
18
|
import { getLocaleManager } from '../../core/i18n/locale-manager.js';
|
|
24
|
-
import {
|
|
25
|
-
// Import helpers
|
|
26
|
-
import { findSourceDir, findPackageRoot, detectNestedSpecweave, detectUmbrellaParent, detectSuspiciousPath, detectGitHubRemote, promptSmartReinit, installAllPlugins, setupRepositoryHosting, promptLanguageSelection, getDefaultLanguageSelection, createDirectoryStructure, copyTemplates, createConfigFile, showNextSteps, installGitHooks, } from '../helpers/init/index.js';
|
|
27
|
-
import { triggerAdoRepoCloning } from '../helpers/init/ado-repo-cloning.js';
|
|
28
|
-
import { triggerGitHubRepoCloning } from '../helpers/init/github-repo-cloning.js';
|
|
29
|
-
import { triggerBitbucketRepoCloning } from '../helpers/init/bitbucket-repo-cloning.js';
|
|
30
|
-
import { createProjectFolders } from '../helpers/init/multi-project-folders.js';
|
|
19
|
+
import { findSourceDir, findPackageRoot, detectNestedSpecweave, detectUmbrellaParent, detectSuspiciousPath, detectProvider, promptSmartReinit, installAllPlugins, promptLanguageSelection, getDefaultLanguageSelection, createDirectoryStructure, copyTemplates, createConfigFile, showNextSteps, installGitHooks, } from '../helpers/init/index.js';
|
|
31
20
|
import { setupLspEnvVar } from '../helpers/init/shell-config.js';
|
|
32
|
-
import { getPluginScope, getScopeArgs } from '../../core/types/plugin-scope.js';
|
|
33
21
|
import { applySmartDefaults } from '../helpers/init/smart-defaults.js';
|
|
34
|
-
import { isGreenfield as isGreenfieldCheck } from '../helpers/init/greenfield-detection.js';
|
|
35
22
|
import { displaySummaryBanner } from '../helpers/init/summary-banner.js';
|
|
36
23
|
const __dirname = getDirname(import.meta.url);
|
|
24
|
+
const PROJECT_NAME_PATTERN = /^[a-z0-9-]+$/;
|
|
25
|
+
const PROJECT_NAME_VALIDATION_MSG = 'Must be lowercase letters, numbers, and hyphens only';
|
|
37
26
|
/**
|
|
38
27
|
* Unified CI/non-interactive detection.
|
|
39
|
-
* Single source of truth for determining if wizard should skip prompts.
|
|
40
|
-
* Covers all major CI platforms and non-TTY environments.
|
|
41
28
|
*/
|
|
42
29
|
export function isNonInteractive(options) {
|
|
43
30
|
return !!(options.quick ||
|
|
@@ -49,180 +36,21 @@ export function isNonInteractive(options) {
|
|
|
49
36
|
!process.stdin.isTTY);
|
|
50
37
|
}
|
|
51
38
|
/**
|
|
52
|
-
*
|
|
53
|
-
*/
|
|
54
|
-
async function isSpecWeaveFrameworkRepo(targetDir) {
|
|
55
|
-
try {
|
|
56
|
-
const packageJsonPath = path.join(targetDir, 'package.json');
|
|
57
|
-
if (!fs.existsSync(packageJsonPath)) {
|
|
58
|
-
return false;
|
|
59
|
-
}
|
|
60
|
-
const packageJson = await fs.readJson(packageJsonPath);
|
|
61
|
-
return packageJson.name === 'specweave';
|
|
62
|
-
}
|
|
63
|
-
catch {
|
|
64
|
-
return false;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
/**
|
|
68
|
-
* Create Multi-Project Folders based on Issue Tracker Configuration
|
|
69
|
-
*/
|
|
70
|
-
async function createMultiProjectFolders(targetDir) {
|
|
71
|
-
const envPath = path.join(targetDir, '.env');
|
|
72
|
-
const configPath = path.join(targetDir, '.specweave', 'config.json');
|
|
73
|
-
if (!fs.existsSync(envPath)) {
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
const envContent = readEnvFile(envPath);
|
|
77
|
-
const envVars = parseEnvFile(envContent);
|
|
78
|
-
const jiraProjects = envVars.JIRA_PROJECTS?.split(',').map((p) => p.trim()).filter(Boolean);
|
|
79
|
-
const jiraStrategy = envVars.JIRA_STRATEGY;
|
|
80
|
-
if (!jiraProjects?.length) {
|
|
81
|
-
return;
|
|
82
|
-
}
|
|
83
|
-
let config = {};
|
|
84
|
-
if (fs.existsSync(configPath)) {
|
|
85
|
-
config = await fs.readJson(configPath);
|
|
86
|
-
}
|
|
87
|
-
if (!config.sync) {
|
|
88
|
-
config.sync = {
|
|
89
|
-
enabled: true,
|
|
90
|
-
profiles: {},
|
|
91
|
-
defaultProfile: undefined,
|
|
92
|
-
settings: {
|
|
93
|
-
autoCreateIssue: true,
|
|
94
|
-
syncDirection: 'bidirectional'
|
|
95
|
-
}
|
|
96
|
-
};
|
|
97
|
-
}
|
|
98
|
-
if (jiraProjects?.length && jiraStrategy === 'project-per-team') {
|
|
99
|
-
const profileId = 'jira-default';
|
|
100
|
-
const syncConfig = config.sync;
|
|
101
|
-
const profiles = syncConfig.profiles;
|
|
102
|
-
if (!profiles[profileId]) {
|
|
103
|
-
const jiraProfile = {
|
|
104
|
-
provider: 'jira',
|
|
105
|
-
displayName: 'Jira Default',
|
|
106
|
-
config: {
|
|
107
|
-
domain: envVars.JIRA_DOMAIN || '',
|
|
108
|
-
projects: jiraProjects
|
|
109
|
-
},
|
|
110
|
-
timeRange: { default: '1M', max: '6M' },
|
|
111
|
-
rateLimits: { maxItemsPerSync: 500, warnThreshold: 100 }
|
|
112
|
-
};
|
|
113
|
-
profiles[profileId] = jiraProfile;
|
|
114
|
-
syncConfig.defaultProfile = profileId;
|
|
115
|
-
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
116
|
-
console.log(chalk.blue('\nš Creating Multi-Project Folders'));
|
|
117
|
-
console.log(chalk.gray(' Detected: ' + jiraProjects.length + ' Jira projects (' + jiraProjects.join(', ') + ')'));
|
|
118
|
-
for (const projectKey of jiraProjects) {
|
|
119
|
-
const projectId = projectKey.toLowerCase();
|
|
120
|
-
const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectId);
|
|
121
|
-
if (!fs.existsSync(specsPath)) {
|
|
122
|
-
fs.mkdirSync(specsPath, { recursive: true });
|
|
123
|
-
}
|
|
124
|
-
console.log(chalk.green(' ā Created project: ' + projectKey + ' (simplified structure)'));
|
|
125
|
-
}
|
|
126
|
-
console.log('');
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
// ADO Multi-Area Folder Creation (reads from config.json, not .env)
|
|
130
|
-
// CRITICAL FIX (2025-12-01): Iterate ALL ADO profiles, not just the first one
|
|
131
|
-
// Bug: .find() only returned first profile, causing multi-project folders to be skipped
|
|
132
|
-
const syncConfig = config.sync;
|
|
133
|
-
const profiles = (syncConfig?.profiles || {});
|
|
134
|
-
// Filter ALL ADO profiles (not .find() which only returns first!)
|
|
135
|
-
const adoProfiles = Object.values(profiles).filter(p => p.provider === 'ado');
|
|
136
|
-
if (adoProfiles.length > 0) {
|
|
137
|
-
console.log(chalk.blue('\nš Creating Azure DevOps Folders'));
|
|
138
|
-
for (const adoProfile of adoProfiles) {
|
|
139
|
-
if (!adoProfile?.config)
|
|
140
|
-
continue;
|
|
141
|
-
const { organization, project, areaPaths } = adoProfile.config;
|
|
142
|
-
if (organization && project) {
|
|
143
|
-
console.log(chalk.gray(` Project: ${project}`));
|
|
144
|
-
const projectFolder = project.replace(/\s+/g, '-').toLowerCase();
|
|
145
|
-
if (areaPaths?.length) {
|
|
146
|
-
// Create folder per area path
|
|
147
|
-
for (const areaPath of areaPaths) {
|
|
148
|
-
const areaName = areaPath.split('\\').pop() || areaPath;
|
|
149
|
-
const areaFolder = areaName.replace(/\s+/g, '-').toLowerCase();
|
|
150
|
-
const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectFolder, areaFolder);
|
|
151
|
-
if (!fs.existsSync(specsPath)) {
|
|
152
|
-
fs.mkdirSync(specsPath, { recursive: true });
|
|
153
|
-
}
|
|
154
|
-
console.log(chalk.green(` ā Created: specs/${projectFolder}/${areaFolder}/`));
|
|
155
|
-
}
|
|
156
|
-
}
|
|
157
|
-
else {
|
|
158
|
-
// Single project folder (no area paths)
|
|
159
|
-
const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectFolder);
|
|
160
|
-
if (!fs.existsSync(specsPath)) {
|
|
161
|
-
fs.mkdirSync(specsPath, { recursive: true });
|
|
162
|
-
}
|
|
163
|
-
console.log(chalk.green(` ā Created: specs/${projectFolder}/`));
|
|
164
|
-
}
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
console.log('');
|
|
168
|
-
}
|
|
169
|
-
// JIRA Multi-Board Folder Creation (reads from config.json, similar to ADO)
|
|
170
|
-
// CRITICAL FIX (2025-12-09): Creates folders from JIRA sync profiles
|
|
171
|
-
// Bug: JIRA folders were only created from legacy .env JIRA_PROJECTS, not from config.json profiles
|
|
172
|
-
const jiraProfiles = Object.values(profiles).filter(p => p.provider === 'jira');
|
|
173
|
-
if (jiraProfiles.length > 0) {
|
|
174
|
-
console.log(chalk.blue('\nš Creating JIRA Folders'));
|
|
175
|
-
for (const jiraProfile of jiraProfiles) {
|
|
176
|
-
if (!jiraProfile?.config?.projectKey)
|
|
177
|
-
continue;
|
|
178
|
-
const { projectKey, boards } = jiraProfile.config;
|
|
179
|
-
console.log(chalk.gray(` Project: ${projectKey}`));
|
|
180
|
-
const projectFolder = projectKey.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
181
|
-
if (boards?.length) {
|
|
182
|
-
// Create folder per board (2-level structure)
|
|
183
|
-
for (const board of boards) {
|
|
184
|
-
const boardName = board.name || `board-${board.id}`;
|
|
185
|
-
const boardFolder = boardName.toLowerCase().replace(/[^a-z0-9]/g, '-');
|
|
186
|
-
const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectFolder, boardFolder);
|
|
187
|
-
if (!fs.existsSync(specsPath)) {
|
|
188
|
-
fs.mkdirSync(specsPath, { recursive: true });
|
|
189
|
-
}
|
|
190
|
-
console.log(chalk.green(` ā Created: specs/${projectFolder}/${boardFolder}/`));
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
else {
|
|
194
|
-
// Single project folder (no boards)
|
|
195
|
-
const specsPath = path.join(targetDir, '.specweave', 'docs', 'internal', 'specs', projectFolder);
|
|
196
|
-
if (!fs.existsSync(specsPath)) {
|
|
197
|
-
fs.mkdirSync(specsPath, { recursive: true });
|
|
198
|
-
}
|
|
199
|
-
console.log(chalk.green(` ā Created: specs/${projectFolder}/`));
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
console.log('');
|
|
203
|
-
}
|
|
204
|
-
}
|
|
205
|
-
/**
|
|
206
|
-
* Main init command
|
|
39
|
+
* Main init command ā simplified to core scaffolding only.
|
|
207
40
|
*/
|
|
208
41
|
export async function initCommand(projectName, options = {}) {
|
|
209
|
-
// Detect CI/non-interactive environment or quick mode (unified, 0188)
|
|
210
42
|
const isCI = isNonInteractive(options);
|
|
211
|
-
// In quick mode, show a brief message
|
|
212
43
|
if (options.quick) {
|
|
213
|
-
console.log(chalk.cyan('\nā” Quick mode: Using sensible defaults
|
|
44
|
+
console.log(chalk.cyan('\nā” Quick mode: Using sensible defaults'));
|
|
214
45
|
}
|
|
215
|
-
// STEP 1:
|
|
216
|
-
// This must be asked before anything else so all prompts are in user's language
|
|
46
|
+
// STEP 1: Language selection (FIRST!)
|
|
217
47
|
let languageResult;
|
|
218
|
-
// Validate CLI language option if provided
|
|
219
48
|
const cliLanguage = options.language?.toLowerCase();
|
|
220
49
|
if (cliLanguage && !isLanguageSupported(cliLanguage)) {
|
|
221
50
|
console.error(chalk.red('\nā Invalid language: ' + options.language));
|
|
222
51
|
console.error(chalk.yellow('Supported languages: ' + getSupportedLanguages().join(', ') + '\n'));
|
|
223
52
|
process.exit(1);
|
|
224
53
|
}
|
|
225
|
-
// Ask for language (or use CLI option / CI default)
|
|
226
54
|
if (isCI) {
|
|
227
55
|
languageResult = getDefaultLanguageSelection(cliLanguage || 'en');
|
|
228
56
|
}
|
|
@@ -234,17 +62,15 @@ export async function initCommand(projectName, options = {}) {
|
|
|
234
62
|
}
|
|
235
63
|
const language = languageResult.language;
|
|
236
64
|
const locale = getLocaleManager(language);
|
|
237
|
-
// Now show welcome message in selected language
|
|
238
65
|
console.log(chalk.blue.bold('\n' + locale.t('cli', 'init.welcome') + '\n'));
|
|
66
|
+
// STEP 2: Path resolution
|
|
239
67
|
let targetDir = '';
|
|
240
68
|
let finalProjectName = '';
|
|
241
69
|
let usedDotNotation = false;
|
|
242
70
|
let continueExisting = false;
|
|
243
|
-
// Handle "." for current directory
|
|
244
71
|
if (projectName === '.') {
|
|
245
72
|
usedDotNotation = true;
|
|
246
73
|
targetDir = process.cwd();
|
|
247
|
-
// Safety: Prevent init in home directory
|
|
248
74
|
if (path.resolve(targetDir) === path.resolve(os.homedir())) {
|
|
249
75
|
console.log(chalk.red.bold('\nā DANGEROUS: Cannot initialize SpecWeave in home directory!\n'));
|
|
250
76
|
console.log(chalk.yellow(' Your home directory contains ALL your projects.'));
|
|
@@ -253,121 +79,85 @@ export async function initCommand(projectName, options = {}) {
|
|
|
253
79
|
process.exit(1);
|
|
254
80
|
}
|
|
255
81
|
const dirName = path.basename(targetDir);
|
|
256
|
-
|
|
257
|
-
if (!/^[a-z0-9-]+$/.test(dirName)) {
|
|
82
|
+
if (!PROJECT_NAME_PATTERN.test(dirName)) {
|
|
258
83
|
const suggestedName = dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-');
|
|
259
84
|
if (isCI) {
|
|
260
|
-
console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.invalidDirName', { dirName })));
|
|
261
|
-
console.log(chalk.gray(' ā CI mode: Auto-sanitizing to "' + suggestedName + '"'));
|
|
262
85
|
finalProjectName = suggestedName;
|
|
263
86
|
}
|
|
264
87
|
else {
|
|
265
|
-
console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.invalidDirName', { dirName })));
|
|
266
88
|
finalProjectName = await input({
|
|
267
89
|
message: 'Project name (for templates):',
|
|
268
90
|
default: suggestedName,
|
|
269
|
-
validate: (val) =>
|
|
91
|
+
validate: (val) => PROJECT_NAME_PATTERN.test(val) || PROJECT_NAME_VALIDATION_MSG,
|
|
270
92
|
});
|
|
271
93
|
}
|
|
272
94
|
}
|
|
273
95
|
else {
|
|
274
96
|
finalProjectName = dirName;
|
|
275
97
|
}
|
|
276
|
-
//
|
|
277
|
-
const existingFiles = fs.readdirSync(targetDir).filter(f => !f.startsWith('.'));
|
|
278
|
-
if (existingFiles.length > 0 && !options.force) {
|
|
279
|
-
if (isCI) {
|
|
280
|
-
console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })));
|
|
281
|
-
console.log(chalk.gray(' ā CI mode: Proceeding with initialization'));
|
|
282
|
-
}
|
|
283
|
-
else {
|
|
284
|
-
console.log(chalk.yellow('\n' + locale.t('cli', 'init.warnings.directoryNotEmpty', { count: existingFiles.length, plural: existingFiles.length === 1 ? '' : 's' })));
|
|
285
|
-
const proceed = await confirm({ message: 'Initialize SpecWeave in current directory?', default: false });
|
|
286
|
-
if (!proceed) {
|
|
287
|
-
console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
|
|
288
|
-
process.exit(0);
|
|
289
|
-
}
|
|
290
|
-
}
|
|
291
|
-
}
|
|
292
|
-
// Smart re-initialization
|
|
98
|
+
// Smart re-init
|
|
293
99
|
if (fs.existsSync(path.join(targetDir, '.specweave'))) {
|
|
294
100
|
const result = await promptSmartReinit({ targetDir, isCI, hasForce: !!options.force, language });
|
|
295
|
-
if (result.action === 'cancel')
|
|
101
|
+
if (result.action === 'cancel')
|
|
296
102
|
process.exit(0);
|
|
297
|
-
}
|
|
298
103
|
continueExisting = result.continueExisting;
|
|
299
104
|
}
|
|
105
|
+
else {
|
|
106
|
+
// Info: initializing in a non-empty directory
|
|
107
|
+
try {
|
|
108
|
+
const existingFiles = fs.readdirSync(targetDir).filter((f) => !f.startsWith('.'));
|
|
109
|
+
if (existingFiles.length > 0) {
|
|
110
|
+
console.log(chalk.gray(`\n ā¹ Directory contains ${existingFiles.length} file(s). Init is non-destructive ā only adds .specweave/.\n`));
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
catch { /* ignore read errors */ }
|
|
114
|
+
}
|
|
300
115
|
}
|
|
301
116
|
else {
|
|
302
|
-
// Create subdirectory OR use current directory in quick mode
|
|
303
117
|
if (!projectName) {
|
|
304
118
|
if (isCI) {
|
|
305
|
-
// CI/quick mode without project name: use current directory (like "." notation)
|
|
306
|
-
// This enables: specweave init --quick (without any args)
|
|
307
119
|
usedDotNotation = true;
|
|
308
120
|
targetDir = process.cwd();
|
|
309
121
|
const dirName = path.basename(targetDir);
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
console.log(chalk.gray(` ā Quick mode: Using current directory "${dirName}" as "${finalProjectName}"`));
|
|
314
|
-
}
|
|
315
|
-
else {
|
|
316
|
-
finalProjectName = dirName;
|
|
317
|
-
console.log(chalk.gray(` ā Quick mode: Using current directory "${finalProjectName}"`));
|
|
318
|
-
}
|
|
319
|
-
// Smart re-initialization for quick mode
|
|
122
|
+
finalProjectName = !PROJECT_NAME_PATTERN.test(dirName)
|
|
123
|
+
? dirName.toLowerCase().replace(/[^a-z0-9-]/g, '-')
|
|
124
|
+
: dirName;
|
|
320
125
|
if (fs.existsSync(path.join(targetDir, '.specweave'))) {
|
|
321
126
|
const result = await promptSmartReinit({ targetDir, isCI, hasForce: !!options.force, language });
|
|
322
|
-
if (result.action === 'cancel')
|
|
127
|
+
if (result.action === 'cancel')
|
|
323
128
|
process.exit(0);
|
|
324
|
-
}
|
|
325
129
|
continueExisting = result.continueExisting;
|
|
326
130
|
}
|
|
327
|
-
// Quick mode handled: targetDir and finalProjectName already set
|
|
328
|
-
// Skip to nested .specweave check below
|
|
329
131
|
}
|
|
330
132
|
else {
|
|
331
133
|
projectName = await input({
|
|
332
134
|
message: 'Project name:',
|
|
333
135
|
default: 'my-saas',
|
|
334
|
-
validate: (val) =>
|
|
136
|
+
validate: (val) => PROJECT_NAME_PATTERN.test(val) || PROJECT_NAME_VALIDATION_MSG,
|
|
335
137
|
});
|
|
336
138
|
}
|
|
337
139
|
}
|
|
338
|
-
// Only process subdirectory creation if projectName was provided/set
|
|
339
|
-
// (Quick mode without args already set targetDir and finalProjectName above)
|
|
340
140
|
if (projectName) {
|
|
341
141
|
targetDir = path.resolve(process.cwd(), projectName);
|
|
342
|
-
// CRITICAL FIX (v1.0.23): Normalize projectName to strip path prefixes like ./
|
|
343
|
-
// Bug: "specweave init ./my-project" stored "./my-project" in config.json
|
|
344
|
-
// which later caused split('/')[0] to return "." and fail validation
|
|
345
142
|
finalProjectName = path.basename(projectName);
|
|
346
143
|
if (fs.existsSync(targetDir)) {
|
|
347
144
|
const hasSpecweave = fs.existsSync(path.join(targetDir, '.specweave'));
|
|
348
145
|
if (hasSpecweave) {
|
|
349
146
|
const result = await promptSmartReinit({ targetDir, isCI, hasForce: !!options.force, language });
|
|
350
|
-
if (result.action === 'cancel')
|
|
147
|
+
if (result.action === 'cancel')
|
|
351
148
|
process.exit(0);
|
|
352
|
-
}
|
|
353
149
|
continueExisting = result.continueExisting;
|
|
354
150
|
}
|
|
355
151
|
else {
|
|
356
152
|
const existingFiles = fs.readdirSync(targetDir).filter(f => !f.startsWith('.'));
|
|
357
153
|
if (existingFiles.length > 0) {
|
|
358
|
-
|
|
359
|
-
if (isCI) {
|
|
360
|
-
// CI/quick mode: proceed without asking
|
|
361
|
-
console.log(chalk.gray(' ā CI/quick mode: Proceeding with initialization'));
|
|
362
|
-
}
|
|
363
|
-
else {
|
|
154
|
+
if (!isCI) {
|
|
364
155
|
const initExisting = await confirm({ message: 'Initialize SpecWeave in existing directory (non-destructive)?', default: false });
|
|
365
156
|
if (!initExisting) {
|
|
366
157
|
console.log(chalk.yellow(locale.t('cli', 'init.errors.cancelled')));
|
|
367
158
|
process.exit(0);
|
|
368
159
|
}
|
|
369
160
|
}
|
|
370
|
-
console.log(chalk.green(' ā
Initializing in existing directory (brownfield-safe)\n'));
|
|
371
161
|
}
|
|
372
162
|
}
|
|
373
163
|
}
|
|
@@ -376,88 +166,65 @@ export async function initCommand(projectName, options = {}) {
|
|
|
376
166
|
}
|
|
377
167
|
}
|
|
378
168
|
}
|
|
379
|
-
//
|
|
169
|
+
// STEP 3: Guard clauses
|
|
380
170
|
const umbrellaResult = detectUmbrellaParent(targetDir);
|
|
381
171
|
if (umbrellaResult) {
|
|
382
172
|
if (options.force) {
|
|
383
|
-
console.log(chalk.yellow(`\nā ļø Warning:
|
|
384
|
-
console.log(chalk.yellow(' Proceeding because --force was specified.\n'));
|
|
173
|
+
console.log(chalk.yellow(`\nā ļø Warning: Inside umbrella project at ${umbrellaResult.umbrellaRoot}. Proceeding with --force.\n`));
|
|
385
174
|
}
|
|
386
175
|
else {
|
|
387
|
-
console.log(chalk.red.bold('\nā Cannot initialize here:
|
|
176
|
+
console.log(chalk.red.bold('\nā Cannot initialize here: inside an umbrella project.\n'));
|
|
388
177
|
console.log(chalk.yellow(` Umbrella root: ${umbrellaResult.umbrellaRoot}`));
|
|
389
|
-
console.log(chalk.yellow(` Detected via: ${umbrellaResult.reason === 'config-umbrella-repo' ? 'config.json umbrellaRepo flag' : 'repositories/ directory'}`));
|
|
390
178
|
console.log(chalk.cyan('\nš” Run specweave init in the umbrella root instead, or use --force to override.\n'));
|
|
391
179
|
process.exit(1);
|
|
392
180
|
}
|
|
393
181
|
}
|
|
394
|
-
// Guard: Prevent init in suspicious paths (node_modules, dist, .git, etc.)
|
|
395
182
|
const suspiciousResult = detectSuspiciousPath(targetDir);
|
|
396
183
|
if (suspiciousResult) {
|
|
397
184
|
if (options.force) {
|
|
398
|
-
console.log(chalk.yellow(`\nā ļø Warning: Path contains suspicious segment "${suspiciousResult.segment}"`));
|
|
399
|
-
console.log(chalk.yellow(' Proceeding because --force was specified.\n'));
|
|
185
|
+
console.log(chalk.yellow(`\nā ļø Warning: Path contains suspicious segment "${suspiciousResult.segment}". Proceeding with --force.\n`));
|
|
400
186
|
}
|
|
401
187
|
else {
|
|
402
|
-
console.log(chalk.red.bold(`\nā Cannot initialize here: path contains "${suspiciousResult.segment}"
|
|
188
|
+
console.log(chalk.red.bold(`\nā Cannot initialize here: path contains "${suspiciousResult.segment}".\n`));
|
|
403
189
|
console.log(chalk.yellow(` Suggested project root: ${suspiciousResult.suggestedRoot}`));
|
|
404
|
-
console.log(chalk.cyan('\nš” Run specweave init in your project root instead, or use --force to override.\n'));
|
|
405
190
|
process.exit(1);
|
|
406
191
|
}
|
|
407
192
|
}
|
|
408
|
-
// Check for nested .specweave/
|
|
409
|
-
// EXCEPTION: User-level folders are VALID (e.g., ~/.specweave for global memory/state)
|
|
410
|
-
// EXCEPTION: Stale folders (no config.json) are NOT real projects and don't block init
|
|
411
193
|
const parentSpecweaveFolders = detectNestedSpecweave(targetDir);
|
|
412
194
|
if (parentSpecweaveFolders && parentSpecweaveFolders.length > 0) {
|
|
413
|
-
// Filter out user-level and stale folders
|
|
414
195
|
const problematicFolders = parentSpecweaveFolders.filter(f => !f.isUserLevel && !f.isStale);
|
|
415
196
|
const staleFolders = parentSpecweaveFolders.filter(f => f.isStale);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
console.log(chalk.yellow('\nā ļø Found stale .specweave/ folder(s) without config.json (not a real project):\n'));
|
|
197
|
+
if (staleFolders.length > 0 && !isCI) {
|
|
198
|
+
console.log(chalk.yellow('\nā ļø Found stale .specweave/ folder(s) without config.json:\n'));
|
|
419
199
|
for (const folder of staleFolders) {
|
|
420
200
|
console.log(chalk.gray(' ' + path.join(folder.path, '.specweave') + '/'));
|
|
421
201
|
}
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
try {
|
|
428
|
-
fs.rmSync(stalePath, { recursive: true, force: true });
|
|
429
|
-
console.log(chalk.green(' ā
Removed ' + stalePath));
|
|
430
|
-
}
|
|
431
|
-
catch {
|
|
432
|
-
console.log(chalk.yellow(' ā ļø Could not remove ' + stalePath));
|
|
433
|
-
}
|
|
202
|
+
const cleanup = await confirm({ message: 'Remove stale .specweave/ folder(s)?', default: true });
|
|
203
|
+
if (cleanup) {
|
|
204
|
+
for (const folder of staleFolders) {
|
|
205
|
+
try {
|
|
206
|
+
fs.rmSync(path.join(folder.path, '.specweave'), { recursive: true, force: true });
|
|
434
207
|
}
|
|
435
|
-
|
|
208
|
+
catch { /* skip */ }
|
|
436
209
|
}
|
|
437
210
|
}
|
|
438
|
-
else {
|
|
439
|
-
console.log(chalk.gray('\n ā Ignoring stale folders (no config.json)\n'));
|
|
440
|
-
}
|
|
441
211
|
}
|
|
442
212
|
if (problematicFolders.length > 0) {
|
|
443
213
|
console.log(chalk.red.bold('\n' + locale.t('cli', 'init.errors.nestedNotSupported') + '\n'));
|
|
444
214
|
for (const folder of problematicFolders) {
|
|
445
215
|
console.log(chalk.yellow(' Found .specweave/ at: ' + folder.path));
|
|
446
216
|
}
|
|
447
|
-
console.log(chalk.cyan.bold('\n š” SpecWeave doesn\'t support nested projects.'));
|
|
448
|
-
console.log(chalk.white(' Initialize in a different directory or remove the parent .specweave/ folder.\n'));
|
|
449
217
|
process.exit(1);
|
|
450
218
|
}
|
|
451
|
-
// User-level or stale folders found but no problematic ones - allow init to proceed
|
|
452
219
|
}
|
|
220
|
+
// STEP 4: Create project
|
|
453
221
|
const spinner = ora('Creating SpecWeave project...').start();
|
|
454
222
|
try {
|
|
455
|
-
//
|
|
223
|
+
// Adapter detection
|
|
456
224
|
const adapterLoader = new AdapterLoader();
|
|
457
225
|
let toolName;
|
|
458
226
|
if (options.adapter) {
|
|
459
227
|
toolName = options.adapter;
|
|
460
|
-
spinner.text = 'Using ' + toolName + '...';
|
|
461
228
|
}
|
|
462
229
|
else {
|
|
463
230
|
let existingAdapter = null;
|
|
@@ -465,104 +232,50 @@ export async function initCommand(projectName, options = {}) {
|
|
|
465
232
|
const existingConfigPath = path.join(targetDir, '.specweave', 'config.json');
|
|
466
233
|
if (fs.existsSync(existingConfigPath)) {
|
|
467
234
|
try {
|
|
468
|
-
|
|
469
|
-
existingAdapter = existingConfig?.adapters?.default || null;
|
|
235
|
+
existingAdapter = fs.readJsonSync(existingConfigPath)?.adapters?.default || null;
|
|
470
236
|
}
|
|
471
237
|
catch { /* ignore */ }
|
|
472
238
|
}
|
|
473
239
|
}
|
|
474
240
|
const detectedTool = await adapterLoader.detectTool();
|
|
475
241
|
spinner.stop();
|
|
476
|
-
console.log(chalk.cyan('\nš ' + locale.t('cli', 'init.toolDetection.header')));
|
|
477
|
-
if (existingAdapter) {
|
|
478
|
-
console.log(chalk.blue(' š Current adapter: ' + existingAdapter));
|
|
479
|
-
}
|
|
480
|
-
else {
|
|
481
|
-
console.log(chalk.gray(' ' + locale.t('cli', 'init.toolDetection.detected', { tool: detectedTool })));
|
|
482
|
-
}
|
|
483
|
-
console.log('');
|
|
484
242
|
if (isCI) {
|
|
485
|
-
console.log(chalk.gray(' ' + locale.t('cli', 'init.toolDetection.ciAutoConfirm', { tool: detectedTool })));
|
|
486
243
|
toolName = detectedTool;
|
|
487
244
|
}
|
|
488
245
|
else {
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
246
|
+
console.log(chalk.cyan('\nš ' + locale.t('cli', 'init.toolDetection.header')));
|
|
247
|
+
if (existingAdapter) {
|
|
248
|
+
console.log(chalk.blue(' š Current adapter: ' + existingAdapter));
|
|
249
|
+
}
|
|
250
|
+
else {
|
|
251
|
+
console.log(chalk.gray(' ' + locale.t('cli', 'init.toolDetection.detected', { tool: detectedTool })));
|
|
252
|
+
}
|
|
253
|
+
console.log('');
|
|
493
254
|
const confirmTool = await confirm({
|
|
494
255
|
message: locale.t('cli', 'init.toolDetection.confirmPrompt', { tool: detectedTool }),
|
|
495
256
|
default: true
|
|
496
257
|
});
|
|
258
|
+
toolName = confirmTool ? detectedTool : 'generic';
|
|
497
259
|
if (!confirmTool) {
|
|
498
|
-
// User declined Claude ā use generic adapter (AGENTS.md)
|
|
499
|
-
// AGENTS.md is the universal standard for all non-Claude AI tools
|
|
500
|
-
toolName = 'generic';
|
|
501
260
|
console.log(chalk.gray(' ā Using AGENTS.md (universal format for non-Claude tools)'));
|
|
502
261
|
}
|
|
503
|
-
else {
|
|
504
|
-
toolName = detectedTool;
|
|
505
|
-
}
|
|
506
262
|
}
|
|
507
263
|
spinner.start('Using ' + toolName + '...');
|
|
508
264
|
}
|
|
509
|
-
//
|
|
510
|
-
|
|
511
|
-
// Ask BEFORE repository hosting so greenfield users can defer structure.
|
|
512
|
-
// ========================================================================
|
|
513
|
-
spinner.stop();
|
|
514
|
-
let projectMaturity;
|
|
515
|
-
const autoDetectedGreenfield = isGreenfieldCheck(targetDir);
|
|
516
|
-
if (isCI) {
|
|
517
|
-
projectMaturity = autoDetectedGreenfield ? 'greenfield' : 'brownfield';
|
|
518
|
-
console.log(chalk.gray(` ā Auto-detected: ${projectMaturity} project\n`));
|
|
519
|
-
}
|
|
520
|
-
else {
|
|
521
|
-
console.log(chalk.cyan.bold('\nš± Project Type\n'));
|
|
522
|
-
projectMaturity = await select({
|
|
523
|
-
message: locale.t('cli', 'init.maturity.question'),
|
|
524
|
-
choices: [
|
|
525
|
-
{
|
|
526
|
-
name: `š ${locale.t('cli', 'init.maturity.greenfield')} ${chalk.gray('- ' + locale.t('cli', 'init.maturity.greenfieldDesc'))}`,
|
|
527
|
-
value: 'greenfield',
|
|
528
|
-
},
|
|
529
|
-
{
|
|
530
|
-
name: `š¦ ${locale.t('cli', 'init.maturity.brownfield')} ${chalk.gray('- ' + locale.t('cli', 'init.maturity.brownfieldDesc'))}`,
|
|
531
|
-
value: 'brownfield',
|
|
532
|
-
},
|
|
533
|
-
],
|
|
534
|
-
default: autoDetectedGreenfield ? 'greenfield' : 'brownfield',
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
spinner.start('Configuring project...');
|
|
538
|
-
// ========================================================================
|
|
539
|
-
// INTERACTIVE: Repository hosting + umbrella selection (BEFORE file creation)
|
|
540
|
-
// v1.0.286: Moved BEFORE directory structure creation so that:
|
|
541
|
-
// - Umbrella clone runs on a clean slate (no .specweave/ to conflict with)
|
|
542
|
-
// - No git init needed if umbrella provides .git
|
|
543
|
-
// - No file merging or .git replacement required
|
|
544
|
-
// ========================================================================
|
|
545
|
-
spinner.stop();
|
|
546
|
-
const gitHubRemote = detectGitHubRemote(targetDir);
|
|
547
|
-
const repoResult = await setupRepositoryHosting({ targetDir, isCI, gitHubRemote, language, projectMaturity });
|
|
548
|
-
spinner.start('Creating project files...');
|
|
265
|
+
// Provider auto-detection from .git/config (silent, no prompts)
|
|
266
|
+
const providerInfo = detectProvider(targetDir);
|
|
549
267
|
// Create directory structure
|
|
550
268
|
if (!continueExisting) {
|
|
551
269
|
await createDirectoryStructure(targetDir, toolName);
|
|
552
270
|
spinner.text = 'Directory structure created...';
|
|
553
271
|
}
|
|
554
|
-
else {
|
|
555
|
-
spinner.text = 'Using existing directory structure...';
|
|
556
|
-
}
|
|
557
|
-
// Note: Marketplace registration happens in installAllPlugins or via fallback
|
|
558
|
-
// No fake success message here - actual registration is done below
|
|
559
272
|
// Copy templates
|
|
560
273
|
const templatesDir = findSourceDir('templates', __dirname);
|
|
561
274
|
if (!continueExisting) {
|
|
562
275
|
await copyTemplates(templatesDir, targetDir, finalProjectName, language);
|
|
563
|
-
spinner.text = '
|
|
276
|
+
spinner.text = 'Templates copied...';
|
|
564
277
|
}
|
|
565
|
-
//
|
|
278
|
+
// Non-Claude adapter install
|
|
566
279
|
if (toolName === 'claude') {
|
|
567
280
|
spinner.text = 'Configuring for Claude Code...';
|
|
568
281
|
console.log('\n' + locale.t('cli', 'init.claudeNativeComplete'));
|
|
@@ -570,7 +283,7 @@ export async function initCommand(projectName, options = {}) {
|
|
|
570
283
|
else {
|
|
571
284
|
await installNonClaudeAdapter(adapterLoader, toolName, targetDir, finalProjectName, options, spinner);
|
|
572
285
|
}
|
|
573
|
-
//
|
|
286
|
+
// Git init
|
|
574
287
|
const gitDir = path.join(targetDir, '.git');
|
|
575
288
|
if (!fs.existsSync(gitDir)) {
|
|
576
289
|
const gitInitResult = execFileNoThrowSync('git', ['init'], { cwd: targetDir, shell: false });
|
|
@@ -593,13 +306,48 @@ export async function initCommand(projectName, options = {}) {
|
|
|
593
306
|
});
|
|
594
307
|
}
|
|
595
308
|
}
|
|
596
|
-
// Create config.json
|
|
597
|
-
createConfigFile(targetDir, finalProjectName, toolName, language, false
|
|
598
|
-
|
|
309
|
+
// Create config.json (simplified ā no maturity, no structureDeferred)
|
|
310
|
+
createConfigFile(targetDir, finalProjectName, toolName, language, false);
|
|
311
|
+
const configPath = path.join(targetDir, '.specweave', 'config.json');
|
|
312
|
+
const isGitRepo = fs.existsSync(path.join(targetDir, '.git'));
|
|
313
|
+
// Batch config updates: provider + smart defaults + LSP (single read-modify-write)
|
|
314
|
+
if (fs.existsSync(configPath)) {
|
|
315
|
+
try {
|
|
316
|
+
const config = fs.readJsonSync(configPath);
|
|
317
|
+
// Provider info from .git/config
|
|
318
|
+
if (providerInfo) {
|
|
319
|
+
const org = providerInfo.owner || providerInfo.organization;
|
|
320
|
+
config.repository = {
|
|
321
|
+
provider: providerInfo.provider,
|
|
322
|
+
...(org && { organization: org }),
|
|
323
|
+
...(providerInfo.repo && { repo: providerInfo.repo }),
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
// Smart defaults
|
|
327
|
+
applySmartDefaults(config, { adapter: toolName, language, isGitRepo });
|
|
328
|
+
if (languageResult.keepEnglishOriginals && config.translation) {
|
|
329
|
+
config.translation.keepEnglishOriginals = true;
|
|
330
|
+
}
|
|
331
|
+
// LSP auto-enable (Claude only)
|
|
332
|
+
if (toolName === 'claude') {
|
|
333
|
+
config.lsp = {
|
|
334
|
+
enabled: true,
|
|
335
|
+
autoInstallPlugins: true,
|
|
336
|
+
marketplace: 'boostvolt/claude-code-lsps',
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
fs.writeJsonSync(configPath, config, { spaces: 2 });
|
|
340
|
+
}
|
|
341
|
+
catch (err) {
|
|
342
|
+
console.log(chalk.yellow(' ā Could not update config defaults (non-critical)'));
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
// Plugin install (Claude only)
|
|
346
|
+
// CRITICAL FIX (v0.34.6): Skip plugin installation when continuing existing config.
|
|
347
|
+
// Previously, re-running `specweave init .` would deregister all marketplace plugins.
|
|
599
348
|
let autoInstallSucceeded = false;
|
|
600
349
|
let marketplaceOnly = false;
|
|
601
350
|
if (toolName === 'claude') {
|
|
602
|
-
// CRITICAL FIX (v0.34.6): Skip plugin installation in "continue existing" mode
|
|
603
351
|
if (continueExisting) {
|
|
604
352
|
console.log(chalk.green(' ā Keeping existing plugin configuration'));
|
|
605
353
|
autoInstallSucceeded = true;
|
|
@@ -608,313 +356,61 @@ export async function initCommand(projectName, options = {}) {
|
|
|
608
356
|
const result = await installAllPlugins({
|
|
609
357
|
dirname: __dirname,
|
|
610
358
|
forceRefresh: options.forceRefresh,
|
|
611
|
-
lazyMode: !options.fullInstall,
|
|
359
|
+
lazyMode: !options.fullInstall,
|
|
612
360
|
});
|
|
613
361
|
autoInstallSucceeded = result.success;
|
|
614
362
|
marketplaceOnly = result.marketplaceOnly || false;
|
|
615
|
-
// NOTE: sw@specweave is enabled at USER level by plugin-installer.ts
|
|
616
|
-
// Do NOT enable it at project level ā that would make the core framework
|
|
617
|
-
// plugin appear as "Project" scope instead of "User" scope in Claude Code UI.
|
|
618
363
|
}
|
|
619
|
-
// Enable agent teams env var
|
|
364
|
+
// Enable agent teams env var
|
|
620
365
|
try {
|
|
621
366
|
const { enableAgentTeamsEnvVar } = await import('../helpers/init/claude-settings-env.js');
|
|
622
367
|
enableAgentTeamsEnvVar(targetDir);
|
|
623
368
|
}
|
|
624
369
|
catch {
|
|
625
|
-
|
|
626
|
-
}
|
|
627
|
-
}
|
|
628
|
-
// Track background job IDs for living docs dependencies
|
|
629
|
-
const pendingJobIds = [];
|
|
630
|
-
// ADO Repository cloning (for multi-repo setups)
|
|
631
|
-
if (repoResult.adoProjectSelection && repoResult.adoClonePatternResult) {
|
|
632
|
-
try {
|
|
633
|
-
const cloneJobId = await triggerAdoRepoCloning(targetDir, repoResult.adoProjectSelection, repoResult.adoClonePatternResult);
|
|
634
|
-
if (cloneJobId) {
|
|
635
|
-
pendingJobIds.push(cloneJobId);
|
|
636
|
-
}
|
|
637
|
-
}
|
|
638
|
-
catch (cloneError) {
|
|
639
|
-
const msg = cloneError instanceof Error ? cloneError.message : String(cloneError);
|
|
640
|
-
console.log(chalk.yellow(`\n ā ļø Background clone failed to start: ${msg}`));
|
|
641
|
-
console.log(chalk.gray(' You can clone repositories later with /sw-ado:clone-repos\n'));
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
// GitHub Repository cloning (for multi-repo setups)
|
|
645
|
-
// Note: umbrella repo is cloned inside setupRepositoryHosting (right after user selects it)
|
|
646
|
-
// Exclude umbrella repo from nested cloning (it's already at the project root)
|
|
647
|
-
let githubClonedRepos = [];
|
|
648
|
-
if (repoResult.githubRepoSelection && repoResult.adoClonePatternResult) {
|
|
649
|
-
const excludeRepos = repoResult.umbrellaRepo ? [repoResult.umbrellaRepo] : [];
|
|
650
|
-
try {
|
|
651
|
-
const cloningResult = await triggerGitHubRepoCloning(targetDir, repoResult.githubRepoSelection, repoResult.adoClonePatternResult, repoResult.gitUrlFormat || 'https', excludeRepos);
|
|
652
|
-
if (cloningResult.jobId) {
|
|
653
|
-
pendingJobIds.push(cloningResult.jobId);
|
|
654
|
-
}
|
|
655
|
-
githubClonedRepos = cloningResult.clonedRepos;
|
|
656
|
-
}
|
|
657
|
-
catch (cloneError) {
|
|
658
|
-
const msg = cloneError instanceof Error ? cloneError.message : String(cloneError);
|
|
659
|
-
console.log(chalk.yellow(`\n ā ļø Background clone failed to start: ${msg}`));
|
|
660
|
-
console.log(chalk.gray(' You can clone repositories later with /sw-github:clone-repos\n'));
|
|
661
|
-
}
|
|
662
|
-
}
|
|
663
|
-
// Bitbucket Repository cloning (for multi-repo setups)
|
|
664
|
-
if (repoResult.bitbucketRepoSelection && repoResult.adoClonePatternResult) {
|
|
665
|
-
try {
|
|
666
|
-
const cloneJobId = await triggerBitbucketRepoCloning(targetDir, repoResult.bitbucketRepoSelection, repoResult.adoClonePatternResult);
|
|
667
|
-
if (cloneJobId) {
|
|
668
|
-
pendingJobIds.push(cloneJobId);
|
|
669
|
-
}
|
|
670
|
-
}
|
|
671
|
-
catch (cloneError) {
|
|
672
|
-
const msg = cloneError instanceof Error ? cloneError.message : String(cloneError);
|
|
673
|
-
console.log(chalk.yellow(`\n ā ļø Background clone failed to start: ${msg}`));
|
|
674
|
-
console.log(chalk.gray(' You can clone repositories later\n'));
|
|
675
|
-
}
|
|
676
|
-
}
|
|
677
|
-
// Update repository config based on hosting selection
|
|
678
|
-
// Bug fix: repository.provider was always "local" and organization was never set
|
|
679
|
-
{
|
|
680
|
-
const configPath = path.join(targetDir, '.specweave', 'config.json');
|
|
681
|
-
if (fs.existsSync(configPath)) {
|
|
682
|
-
try {
|
|
683
|
-
const config = await fs.readJson(configPath);
|
|
684
|
-
const hosting = repoResult.hosting;
|
|
685
|
-
if (hosting.startsWith('github')) {
|
|
686
|
-
const org = repoResult.githubRepoSelection?.org || gitHubRemote?.owner || config.repository?.organization;
|
|
687
|
-
config.repository = {
|
|
688
|
-
...config.repository,
|
|
689
|
-
provider: 'github',
|
|
690
|
-
...(org && { organization: org }),
|
|
691
|
-
...(repoResult.umbrellaRepo && { umbrellaRepo: repoResult.umbrellaRepo }),
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
else if (hosting.startsWith('ado')) {
|
|
695
|
-
const org = repoResult.adoProjectSelection?.org || config.repository?.organization;
|
|
696
|
-
config.repository = {
|
|
697
|
-
...config.repository,
|
|
698
|
-
provider: 'ado',
|
|
699
|
-
...(org && { organization: org }),
|
|
700
|
-
};
|
|
701
|
-
}
|
|
702
|
-
else if (hosting.startsWith('bitbucket')) {
|
|
703
|
-
const org = repoResult.bitbucketRepoSelection?.workspace || config.repository?.organization;
|
|
704
|
-
config.repository = {
|
|
705
|
-
...config.repository,
|
|
706
|
-
provider: 'bitbucket',
|
|
707
|
-
...(org && { organization: org }),
|
|
708
|
-
};
|
|
709
|
-
}
|
|
710
|
-
// When user selected an umbrella repo, enable umbrella mode
|
|
711
|
-
// and register cloned repos as child repos
|
|
712
|
-
if (repoResult.umbrellaRepo) {
|
|
713
|
-
const org = config.repository?.organization;
|
|
714
|
-
config.umbrella = {
|
|
715
|
-
enabled: true,
|
|
716
|
-
projectName: path.basename(targetDir),
|
|
717
|
-
childRepos: githubClonedRepos.map(repoName => ({
|
|
718
|
-
id: repoName,
|
|
719
|
-
name: repoName,
|
|
720
|
-
path: `repositories/${org ? org + '/' : ''}${repoName}`,
|
|
721
|
-
prefix: repoName.substring(0, 3).toUpperCase(),
|
|
722
|
-
...(org && {
|
|
723
|
-
sync: {
|
|
724
|
-
github: { owner: org, repo: repoName },
|
|
725
|
-
// Inherit global JIRA/ADO config so child repos don't need manual setup
|
|
726
|
-
...(config.sync?.jira?.projectKey && {
|
|
727
|
-
jira: { projectKey: config.sync.jira.projectKey },
|
|
728
|
-
}),
|
|
729
|
-
...(config.sync?.ado?.project && {
|
|
730
|
-
ado: { organization: config.sync.ado.organization, project: config.sync.ado.project },
|
|
731
|
-
}),
|
|
732
|
-
},
|
|
733
|
-
}),
|
|
734
|
-
})),
|
|
735
|
-
};
|
|
736
|
-
// Copy global sync config as umbrella.sync so umbrella-scoped
|
|
737
|
-
// increments route to the umbrella repo, not a child
|
|
738
|
-
if (config.sync?.github?.owner && config.sync?.github?.repo) {
|
|
739
|
-
config.umbrella.sync = {
|
|
740
|
-
github: { owner: config.sync.github.owner, repo: config.sync.github.repo },
|
|
741
|
-
};
|
|
742
|
-
if (config.sync?.jira?.projectKey) {
|
|
743
|
-
config.umbrella.sync.jira = { projectKey: config.sync.jira.projectKey };
|
|
744
|
-
}
|
|
745
|
-
if (config.sync?.ado?.project) {
|
|
746
|
-
config.umbrella.sync.ado = { project: config.sync.ado.project };
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
}
|
|
750
|
-
await fs.writeJson(configPath, config, { spaces: 2 });
|
|
751
|
-
}
|
|
752
|
-
catch {
|
|
753
|
-
// Non-critical ā config update failed but init can continue
|
|
754
|
-
}
|
|
755
|
-
}
|
|
756
|
-
}
|
|
757
|
-
// Create multi-project folders for GitHub repos (0188: provider symmetry)
|
|
758
|
-
if (githubClonedRepos.length > 0) {
|
|
759
|
-
console.log(chalk.blue('\nš Creating GitHub Project Folders'));
|
|
760
|
-
const created = await createProjectFolders(targetDir, githubClonedRepos);
|
|
761
|
-
for (const folder of created) {
|
|
762
|
-
console.log(chalk.green(` ā Created: specs/${folder}/`));
|
|
763
|
-
}
|
|
764
|
-
console.log('');
|
|
765
|
-
}
|
|
766
|
-
// Issue tracker setup
|
|
767
|
-
// Skip when greenfield user deferred structure ā they'll configure via /sw:sync-setup
|
|
768
|
-
let externalPluginInstalled;
|
|
769
|
-
if (repoResult.structureDeferred) {
|
|
770
|
-
console.log(chalk.gray('\nāļø Skipping issue tracker setup (deferred with repository structure)'));
|
|
771
|
-
console.log(chalk.gray(' Configure later via /sw:sync-setup\n'));
|
|
772
|
-
}
|
|
773
|
-
else {
|
|
774
|
-
const isFrameworkRepo = await isSpecWeaveFrameworkRepo(targetDir);
|
|
775
|
-
const githubRepoSelection = repoResult.githubRepoSelection
|
|
776
|
-
? {
|
|
777
|
-
org: repoResult.githubRepoSelection.org,
|
|
778
|
-
pat: repoResult.githubRepoSelection.pat,
|
|
779
|
-
clonedRepos: githubClonedRepos
|
|
780
|
-
}
|
|
781
|
-
: undefined;
|
|
782
|
-
await setupIssueTrackerWrapper(targetDir, language, isFrameworkRepo, repoResult.hosting, isCI, repoResult.adoProjectSelection, githubRepoSelection, repoResult.gitUrlFormat);
|
|
783
|
-
// SMART PLUGIN INSTALL (v1.0.122): Auto-install selected external tool plugin
|
|
784
|
-
if (toolName === 'claude' && autoInstallSucceeded) {
|
|
785
|
-
externalPluginInstalled = await autoInstallSelectedExternalPlugin(targetDir);
|
|
786
|
-
}
|
|
787
|
-
}
|
|
788
|
-
// Multi-project folders
|
|
789
|
-
await createMultiProjectFolders(targetDir);
|
|
790
|
-
// ========================================================================
|
|
791
|
-
// PHASE: Apply Smart Defaults (0200 ā replaces wizard loop)
|
|
792
|
-
// No more testing/interview/quality-gates/translation prompts.
|
|
793
|
-
// Everything is auto-provisioned and shown in the summary banner.
|
|
794
|
-
// ========================================================================
|
|
795
|
-
{
|
|
796
|
-
const smartDefaultsConfigPath = path.join(targetDir, '.specweave', 'config.json');
|
|
797
|
-
if (fs.existsSync(smartDefaultsConfigPath)) {
|
|
798
|
-
const config = fs.readJsonSync(smartDefaultsConfigPath);
|
|
799
|
-
const isGitRepo = fs.existsSync(path.join(targetDir, '.git'));
|
|
800
|
-
applySmartDefaults(config, { adapter: toolName, language, isGitRepo });
|
|
801
|
-
// Preserve keepEnglishOriginals from language selection
|
|
802
|
-
if (languageResult.keepEnglishOriginals && config.translation) {
|
|
803
|
-
config.translation.keepEnglishOriginals = true;
|
|
804
|
-
}
|
|
805
|
-
fs.writeJsonSync(smartDefaultsConfigPath, config, { spaces: 2 });
|
|
370
|
+
console.log(chalk.yellow(' ā Could not enable agent teams env var (non-critical)'));
|
|
806
371
|
}
|
|
372
|
+
setupLspEnvVar();
|
|
807
373
|
}
|
|
808
|
-
//
|
|
809
|
-
// PHASE: Auto-install Git Hooks (0200 ā no prompt)
|
|
810
|
-
// ========================================================================
|
|
811
|
-
const isGitRepo = fs.existsSync(path.join(targetDir, '.git'));
|
|
374
|
+
// Git hooks
|
|
812
375
|
if (isGitRepo && !continueExisting) {
|
|
813
376
|
installGitHooks(targetDir, templatesDir);
|
|
814
377
|
}
|
|
815
|
-
//
|
|
816
|
-
// PHASE: Auto-enable LSP for Claude (0200 ā no prompt)
|
|
817
|
-
// ========================================================================
|
|
818
|
-
if (toolName === 'claude') {
|
|
819
|
-
const lspConfigPath = path.join(targetDir, '.specweave', 'config.json');
|
|
820
|
-
if (fs.existsSync(lspConfigPath)) {
|
|
821
|
-
try {
|
|
822
|
-
const lspConfig = await fs.readJson(lspConfigPath);
|
|
823
|
-
lspConfig.lsp = {
|
|
824
|
-
enabled: true,
|
|
825
|
-
autoInstallPlugins: true,
|
|
826
|
-
marketplace: 'boostvolt/claude-code-lsps',
|
|
827
|
-
};
|
|
828
|
-
await fs.writeJson(lspConfigPath, lspConfig, { spaces: 2 });
|
|
829
|
-
}
|
|
830
|
-
catch { /* Non-critical */ }
|
|
831
|
-
}
|
|
832
|
-
setupLspEnvVar();
|
|
833
|
-
}
|
|
834
|
-
// ========================================================================
|
|
835
|
-
// PHASE: Greenfield Detection + Summary Banner (0200)
|
|
836
|
-
// ========================================================================
|
|
378
|
+
// Summary banner
|
|
837
379
|
{
|
|
838
|
-
const greenfieldStatus = isGreenfieldCheck(targetDir);
|
|
839
|
-
// Build provider info for banner
|
|
840
|
-
let bannerProvider;
|
|
841
|
-
if (repoResult.hosting.startsWith('github')) {
|
|
842
|
-
bannerProvider = { name: 'GitHub', owner: gitHubRemote?.owner, repo: gitHubRemote?.repo };
|
|
843
|
-
}
|
|
844
|
-
else if (repoResult.hosting.startsWith('ado')) {
|
|
845
|
-
bannerProvider = { name: 'Azure DevOps', organization: repoResult.adoProjectSelection?.org };
|
|
846
|
-
}
|
|
847
|
-
else if (repoResult.hosting.startsWith('bitbucket')) {
|
|
848
|
-
bannerProvider = { name: 'Bitbucket' };
|
|
849
|
-
}
|
|
850
|
-
else {
|
|
851
|
-
bannerProvider = { name: 'Local' };
|
|
852
|
-
}
|
|
853
|
-
// Single config read for banner data
|
|
854
|
-
const bannerConfigPath = path.join(targetDir, '.specweave', 'config.json');
|
|
855
380
|
let bannerConfig;
|
|
856
|
-
if (fs.existsSync(
|
|
381
|
+
if (fs.existsSync(configPath)) {
|
|
857
382
|
try {
|
|
858
|
-
bannerConfig = fs.readJsonSync(
|
|
383
|
+
bannerConfig = fs.readJsonSync(configPath);
|
|
859
384
|
}
|
|
860
385
|
catch { /* ignore */ }
|
|
861
386
|
}
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
else if (profile?.provider === 'jira')
|
|
871
|
-
bannerTracker = { name: 'Jira' };
|
|
872
|
-
else if (profile?.provider === 'ado')
|
|
873
|
-
bannerTracker = { name: 'Azure DevOps Boards' };
|
|
874
|
-
}
|
|
875
|
-
}
|
|
876
|
-
// Read sync permissions
|
|
877
|
-
let syncPermissions;
|
|
878
|
-
if (bannerConfig?.sync?.settings) {
|
|
879
|
-
const s = bannerConfig.sync.settings;
|
|
880
|
-
syncPermissions = {
|
|
881
|
-
canCreate: !!s.canUpsertInternalItems,
|
|
882
|
-
canUpdate: !!s.canUpdateExternalItems,
|
|
883
|
-
canUpdateStatus: !!s.canUpdateStatus,
|
|
387
|
+
let bannerProvider;
|
|
388
|
+
if (providerInfo) {
|
|
389
|
+
const providerNames = { github: 'GitHub', ado: 'Azure DevOps', bitbucket: 'Bitbucket' };
|
|
390
|
+
bannerProvider = {
|
|
391
|
+
name: providerNames[providerInfo.provider] || providerInfo.provider,
|
|
392
|
+
owner: providerInfo.owner,
|
|
393
|
+
repo: providerInfo.repo,
|
|
394
|
+
organization: providerInfo.organization,
|
|
884
395
|
};
|
|
885
396
|
}
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
397
|
+
else {
|
|
398
|
+
bannerProvider = { name: 'Local' };
|
|
399
|
+
}
|
|
400
|
+
const finalDefaults = {
|
|
401
|
+
testing: bannerConfig?.testing?.defaultTestMode || 'TDD',
|
|
402
|
+
qualityGates: bannerConfig?.qualityGates?.preset || 'standard',
|
|
403
|
+
lspEnabled: !!bannerConfig?.lsp?.enabled,
|
|
891
404
|
gitHooksInstalled: isGitRepo,
|
|
892
|
-
translationEnabled:
|
|
405
|
+
translationEnabled: !!bannerConfig?.translation?.enabled,
|
|
406
|
+
coverageTargets: bannerConfig?.testing?.coverageTargets,
|
|
893
407
|
};
|
|
894
|
-
if (bannerConfig) {
|
|
895
|
-
finalDefaults = {
|
|
896
|
-
testing: bannerConfig.testing?.defaultTestMode || 'TDD',
|
|
897
|
-
qualityGates: bannerConfig.qualityGates?.preset || 'standard',
|
|
898
|
-
lspEnabled: !!bannerConfig.lsp?.enabled,
|
|
899
|
-
gitHooksInstalled: isGitRepo,
|
|
900
|
-
translationEnabled: !!bannerConfig.translation?.enabled,
|
|
901
|
-
coverageTargets: bannerConfig.testing?.coverageTargets,
|
|
902
|
-
};
|
|
903
|
-
}
|
|
904
408
|
displaySummaryBanner({
|
|
905
409
|
projectName: finalProjectName,
|
|
906
410
|
provider: bannerProvider,
|
|
907
|
-
tracker: bannerTracker,
|
|
908
|
-
repoCount: 1 + githubClonedRepos.length,
|
|
909
|
-
isGreenfield: greenfieldStatus,
|
|
910
|
-
hasPendingClones: pendingJobIds.length > 0,
|
|
911
411
|
adapter: toolName,
|
|
912
412
|
language,
|
|
913
413
|
defaults: finalDefaults,
|
|
914
|
-
externalPluginInstalled,
|
|
915
|
-
syncPermissions,
|
|
916
|
-
projectMaturity,
|
|
917
|
-
structureDeferred: repoResult.structureDeferred,
|
|
918
414
|
});
|
|
919
415
|
}
|
|
920
416
|
showNextSteps(finalProjectName, toolName, language, usedDotNotation, toolName === 'claude' ? { pluginAutoInstalled: autoInstallSucceeded, marketplaceOnly } : undefined);
|
|
@@ -973,106 +469,4 @@ async function installNonClaudeAdapter(adapterLoader, toolName, targetDir, proje
|
|
|
973
469
|
spinner.warn('Could not install core plugin');
|
|
974
470
|
}
|
|
975
471
|
}
|
|
976
|
-
/**
|
|
977
|
-
* Wrapper for issue tracker setup
|
|
978
|
-
*/
|
|
979
|
-
async function setupIssueTrackerWrapper(targetDir, language, isFrameworkRepo, repositoryHosting, isCI, adoProjectSelection, githubCredentialsFromRepoSetup, gitUrlFormat) {
|
|
980
|
-
try {
|
|
981
|
-
const { setupIssueTracker } = await import('../helpers/issue-tracker/index.js');
|
|
982
|
-
const configPath = path.join(targetDir, '.specweave', 'config.json');
|
|
983
|
-
let existingTracker = null;
|
|
984
|
-
if (fs.existsSync(configPath)) {
|
|
985
|
-
const config = await fs.readJson(configPath);
|
|
986
|
-
if (config.sync?.defaultProfile && config.sync?.profiles) {
|
|
987
|
-
const defaultProfile = config.sync.profiles[config.sync.defaultProfile];
|
|
988
|
-
existingTracker = defaultProfile?.provider || null;
|
|
989
|
-
}
|
|
990
|
-
}
|
|
991
|
-
if (existingTracker) {
|
|
992
|
-
console.log(chalk.blue('\nš Existing Issue Tracker Configuration Detected'));
|
|
993
|
-
console.log(chalk.gray(' Current: ' + existingTracker.charAt(0).toUpperCase() + existingTracker.slice(1)));
|
|
994
|
-
if (isCI) {
|
|
995
|
-
console.log(chalk.gray(' ā CI mode: Keeping existing configuration\n'));
|
|
996
|
-
return;
|
|
997
|
-
}
|
|
998
|
-
const reconfigure = await confirm({ message: 'Do you want to reconfigure your issue tracker?', default: false });
|
|
999
|
-
if (!reconfigure) {
|
|
1000
|
-
console.log(chalk.gray(' ā Keeping existing configuration\n'));
|
|
1001
|
-
return;
|
|
1002
|
-
}
|
|
1003
|
-
}
|
|
1004
|
-
await setupIssueTracker({
|
|
1005
|
-
projectPath: targetDir,
|
|
1006
|
-
language,
|
|
1007
|
-
maxRetries: 3,
|
|
1008
|
-
isFrameworkRepo,
|
|
1009
|
-
repositoryHosting,
|
|
1010
|
-
adoCredentialsFromRepoSetup: adoProjectSelection,
|
|
1011
|
-
githubCredentialsFromRepoSetup,
|
|
1012
|
-
gitUrlFormat,
|
|
1013
|
-
isCI
|
|
1014
|
-
});
|
|
1015
|
-
}
|
|
1016
|
-
catch {
|
|
1017
|
-
console.log(chalk.yellow('\nā ļø Issue tracker setup skipped (can configure later)'));
|
|
1018
|
-
}
|
|
1019
|
-
}
|
|
1020
|
-
/**
|
|
1021
|
-
* Auto-install external tool plugin based on issue tracker selection
|
|
1022
|
-
*
|
|
1023
|
-
* NEW (v1.0.122): Smart plugin installation
|
|
1024
|
-
* Instead of loading all 24 plugins (~60K tokens), we:
|
|
1025
|
-
* 1. Load router skill by default (~500 tokens)
|
|
1026
|
-
* 2. Auto-load ONLY the selected external tool plugin (~5K tokens)
|
|
1027
|
-
* 3. Other plugins load on-demand via keywords
|
|
1028
|
-
*
|
|
1029
|
-
* Result: ~30K max instead of ~60K (50% token savings)
|
|
1030
|
-
*/
|
|
1031
|
-
async function autoInstallSelectedExternalPlugin(targetDir) {
|
|
1032
|
-
const configPath = path.join(targetDir, '.specweave', 'config.json');
|
|
1033
|
-
if (!fs.existsSync(configPath)) {
|
|
1034
|
-
return false;
|
|
1035
|
-
}
|
|
1036
|
-
try {
|
|
1037
|
-
const config = await fs.readJson(configPath);
|
|
1038
|
-
const syncConfig = config.sync;
|
|
1039
|
-
if (!syncConfig?.defaultProfile || !syncConfig?.profiles) {
|
|
1040
|
-
return false;
|
|
1041
|
-
}
|
|
1042
|
-
const defaultProfile = syncConfig.profiles[syncConfig.defaultProfile];
|
|
1043
|
-
if (!defaultProfile?.provider) {
|
|
1044
|
-
return false;
|
|
1045
|
-
}
|
|
1046
|
-
// Map provider to plugin name
|
|
1047
|
-
const providerToPlugin = {
|
|
1048
|
-
github: 'specweave-github',
|
|
1049
|
-
jira: 'specweave-jira',
|
|
1050
|
-
ado: 'specweave-ado',
|
|
1051
|
-
};
|
|
1052
|
-
const pluginToInstall = providerToPlugin[defaultProfile.provider];
|
|
1053
|
-
if (!pluginToInstall) {
|
|
1054
|
-
return false;
|
|
1055
|
-
}
|
|
1056
|
-
// Install plugin via Claude CLI directly (v1.0.210 - removed PluginCacheManager)
|
|
1057
|
-
console.log(chalk.cyan(`\nš¦ Auto-installing ${defaultProfile.provider.toUpperCase()} plugin...`));
|
|
1058
|
-
const scopeArgs = getScopeArgs(getPluginScope(pluginToInstall, 'specweave'));
|
|
1059
|
-
const cliResult = execFileNoThrowSync('claude', ['plugin', 'install', `${pluginToInstall}@specweave`, ...scopeArgs]);
|
|
1060
|
-
if (cliResult.success) {
|
|
1061
|
-
console.log(chalk.green(` ā ${pluginToInstall} installed (ready for sync commands)`));
|
|
1062
|
-
return true;
|
|
1063
|
-
}
|
|
1064
|
-
else {
|
|
1065
|
-
console.log(chalk.yellow(` ā Could not auto-install ${pluginToInstall}`));
|
|
1066
|
-
console.log(chalk.gray(` ā Install manually: claude plugin install ${pluginToInstall}@specweave`));
|
|
1067
|
-
return false;
|
|
1068
|
-
}
|
|
1069
|
-
}
|
|
1070
|
-
catch (error) {
|
|
1071
|
-
// Non-blocking - just log and continue
|
|
1072
|
-
if (process.env.DEBUG) {
|
|
1073
|
-
console.log(chalk.gray(` ā Auto-install skipped: ${error instanceof Error ? error.message : String(error)}`));
|
|
1074
|
-
}
|
|
1075
|
-
return false;
|
|
1076
|
-
}
|
|
1077
|
-
}
|
|
1078
472
|
//# sourceMappingURL=init.js.map
|