@ryuenn3123/agentic-senior-core 1.9.1 → 1.9.2

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.
@@ -0,0 +1,339 @@
1
+ /**
2
+ * Init Command — Interactive project initialization.
3
+ * Depends on: constants, utils, profile-packs, skill-selector, detector, compiler
4
+ */
5
+ import { createInterface } from 'node:readline/promises';
6
+ import { stdin, stdout } from 'node:process';
7
+ import path from 'node:path';
8
+
9
+ import {
10
+ CLI_VERSION,
11
+ AGENT_CONTEXT_DIR,
12
+ INIT_PRESETS,
13
+ PROFILE_PRESETS,
14
+ BLUEPRINT_RECOMMENDATIONS,
15
+ } from '../constants.mjs';
16
+
17
+ import {
18
+ ensureDirectory,
19
+ askChoice,
20
+ askYesNo,
21
+ toTitleCase,
22
+ normalizeChoiceInput,
23
+ matchFileNameFromInput,
24
+ matchProfileNameFromInput,
25
+ collectFileNames,
26
+ formatBlockingSeverities,
27
+ formatDuration,
28
+ copyGovernanceAssetsToTarget,
29
+ } from '../utils.mjs';
30
+
31
+ import { collectProfilePacks, findProfilePackByInput } from '../profile-packs.mjs';
32
+ import { inferSkillDomainNamesFromSelection } from '../skill-selector.mjs';
33
+ import { detectProjectContext, buildDetectionSummary, formatDetectionCandidates } from '../detector.mjs';
34
+ import { compileDynamicContext, writeSelectedPolicy, writeOnboardingReport } from '../compiler.mjs';
35
+
36
+ export { REPO_ROOT } from '../constants.mjs';
37
+
38
+ export function parseInitArguments(commandArguments) {
39
+ const parsedInitOptions = {
40
+ targetDirectory: '.',
41
+ preset: undefined,
42
+ profile: undefined,
43
+ profilePack: undefined,
44
+ stack: undefined,
45
+ blueprint: undefined,
46
+ ci: undefined,
47
+ newbie: false,
48
+ };
49
+
50
+ for (let argumentIndex = 0; argumentIndex < commandArguments.length; argumentIndex++) {
51
+ const currentArgument = commandArguments[argumentIndex];
52
+
53
+ if (!currentArgument.startsWith('--')) {
54
+ parsedInitOptions.targetDirectory = currentArgument;
55
+ continue;
56
+ }
57
+
58
+ if (currentArgument === '--profile') {
59
+ parsedInitOptions.profile = matchProfileNameFromInput(commandArguments[argumentIndex + 1] || '');
60
+ argumentIndex += 1;
61
+ continue;
62
+ }
63
+
64
+ if (currentArgument === '--preset') {
65
+ parsedInitOptions.preset = normalizeChoiceInput(commandArguments[argumentIndex + 1] || '');
66
+ argumentIndex += 1;
67
+ continue;
68
+ }
69
+
70
+ if (currentArgument.startsWith('--preset=')) {
71
+ parsedInitOptions.preset = normalizeChoiceInput(currentArgument.split('=')[1]);
72
+ continue;
73
+ }
74
+
75
+ if (currentArgument.startsWith('--profile=')) {
76
+ parsedInitOptions.profile = matchProfileNameFromInput(currentArgument.split('=')[1]);
77
+ continue;
78
+ }
79
+
80
+ if (currentArgument === '--profile-pack') {
81
+ parsedInitOptions.profilePack = commandArguments[argumentIndex + 1];
82
+ argumentIndex += 1;
83
+ continue;
84
+ }
85
+
86
+ if (currentArgument.startsWith('--profile-pack=')) {
87
+ parsedInitOptions.profilePack = currentArgument.split('=')[1];
88
+ continue;
89
+ }
90
+
91
+ if (currentArgument === '--stack') {
92
+ parsedInitOptions.stack = commandArguments[argumentIndex + 1];
93
+ argumentIndex += 1;
94
+ continue;
95
+ }
96
+
97
+ if (currentArgument.startsWith('--stack=')) {
98
+ parsedInitOptions.stack = currentArgument.split('=')[1];
99
+ continue;
100
+ }
101
+
102
+ if (currentArgument === '--blueprint') {
103
+ parsedInitOptions.blueprint = commandArguments[argumentIndex + 1];
104
+ argumentIndex += 1;
105
+ continue;
106
+ }
107
+
108
+ if (currentArgument.startsWith('--blueprint=')) {
109
+ parsedInitOptions.blueprint = currentArgument.split('=')[1];
110
+ continue;
111
+ }
112
+
113
+ if (currentArgument === '--ci') {
114
+ const ciRawValue = commandArguments[argumentIndex + 1];
115
+ parsedInitOptions.ci = ciRawValue?.toLowerCase() === 'true';
116
+ argumentIndex += 1;
117
+ continue;
118
+ }
119
+
120
+ if (currentArgument.startsWith('--ci=')) {
121
+ parsedInitOptions.ci = currentArgument.split('=')[1]?.toLowerCase() === 'true';
122
+ continue;
123
+ }
124
+
125
+ if (currentArgument === '--newbie') {
126
+ parsedInitOptions.newbie = true;
127
+ continue;
128
+ }
129
+
130
+ throw new Error(`Unknown option: ${currentArgument}`);
131
+ }
132
+
133
+ if (parsedInitOptions.newbie && parsedInitOptions.profile && parsedInitOptions.profile !== 'beginner') {
134
+ throw new Error('--newbie can only be combined with --profile beginner');
135
+ }
136
+
137
+ return parsedInitOptions;
138
+ }
139
+
140
+ export async function runInitCommand(targetDirectoryArgument, initOptions = {}) {
141
+ const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument || '.');
142
+ const setupStartedAt = Date.now();
143
+ await ensureDirectory(resolvedTargetDirectoryPath);
144
+
145
+ const userInterface = createInterface({ input: stdin, output: stdout });
146
+
147
+ try {
148
+ const stackFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, 'stacks'));
149
+ const blueprintFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, 'blueprints'));
150
+ const profilePackDefinitions = await collectProfilePacks(path.dirname(AGENT_CONTEXT_DIR));
151
+ const selectedPreset = initOptions.preset ? INIT_PRESETS[initOptions.preset] || null : null;
152
+
153
+ const selectedStackFileNameFromOption = initOptions.stack
154
+ ? matchFileNameFromInput(initOptions.stack, stackFileNames)
155
+ : null;
156
+ const selectedBlueprintFileNameFromOption = initOptions.blueprint
157
+ ? matchFileNameFromInput(initOptions.blueprint, blueprintFileNames)
158
+ : null;
159
+ const selectedProfilePack = initOptions.profilePack
160
+ ? findProfilePackByInput(initOptions.profilePack, profilePackDefinitions)
161
+ : null;
162
+
163
+ if (initOptions.stack && !selectedStackFileNameFromOption) {
164
+ throw new Error(`Unknown stack: ${initOptions.stack}`);
165
+ }
166
+
167
+ if (initOptions.blueprint && !selectedBlueprintFileNameFromOption) {
168
+ throw new Error(`Unknown blueprint: ${initOptions.blueprint}`);
169
+ }
170
+
171
+ if (initOptions.profilePack && !selectedProfilePack) {
172
+ throw new Error(`Unknown profile pack: ${initOptions.profilePack}`);
173
+ }
174
+
175
+ if (initOptions.preset && !selectedPreset) {
176
+ throw new Error(`Unknown preset: ${initOptions.preset}`);
177
+ }
178
+
179
+ if (selectedProfilePack && !stackFileNames.includes(selectedProfilePack.defaultStackFileName)) {
180
+ throw new Error(
181
+ `Profile pack ${selectedProfilePack.fileName} references unknown stack file: ${selectedProfilePack.defaultStackFileName}`
182
+ );
183
+ }
184
+
185
+ if (selectedProfilePack && !blueprintFileNames.includes(selectedProfilePack.defaultBlueprintFileName)) {
186
+ throw new Error(
187
+ `Profile pack ${selectedProfilePack.fileName} references unknown blueprint file: ${selectedProfilePack.defaultBlueprintFileName}`
188
+ );
189
+ }
190
+
191
+ console.log(`\nAgentic-Senior-Core CLI v${CLI_VERSION}`);
192
+ console.log('I will copy governance files into your target folder and compile a single rulebook for your AI tools.');
193
+
194
+ if (selectedPreset) {
195
+ console.log(`Using preset: ${initOptions.preset} (${selectedPreset.description}).`);
196
+ }
197
+
198
+ const projectDetection = await detectProjectContext(resolvedTargetDirectoryPath);
199
+ if (projectDetection.hasExistingProjectFiles) {
200
+ console.log('I found files in the target directory, so I checked whether this already looks like an existing project.');
201
+ console.log(buildDetectionSummary(projectDetection));
202
+ console.log('Detection reasoning:');
203
+ console.log(projectDetection.detectionReasoning);
204
+ console.log('Top candidates:');
205
+ console.log(formatDetectionCandidates(projectDetection.rankedCandidates));
206
+ } else {
207
+ console.log('The target directory is empty, so I will guide you through a fresh setup.');
208
+ }
209
+
210
+ const selectedProfileName = initOptions.profile
211
+ ? initOptions.profile
212
+ : selectedPreset?.profile
213
+ ? selectedPreset.profile
214
+ : initOptions.newbie
215
+ ? 'beginner'
216
+ : selectedProfilePack?.defaultProfileName
217
+ ? selectedProfilePack.defaultProfileName
218
+ : normalizeChoiceInput(await askChoice(
219
+ 'How much guidance do you want?',
220
+ Object.values(PROFILE_PRESETS).map((profilePreset) => `${profilePreset.displayName} — ${profilePreset.description}`),
221
+ userInterface
222
+ )).split('-')[0];
223
+
224
+ const selectedProfile = PROFILE_PRESETS[selectedProfileName];
225
+ if (!selectedProfile) {
226
+ throw new Error(`Unknown profile: ${selectedProfileName}`);
227
+ }
228
+
229
+ console.log(`\nSelected profile: ${selectedProfile.displayName}`);
230
+ console.log(`This profile will block these review severities in CI: ${formatBlockingSeverities(selectedProfile.blockingSeverities)}.`);
231
+
232
+ if (selectedProfilePack) {
233
+ console.log(`Applying team profile pack: ${selectedProfilePack.displayName}.`);
234
+ console.log(`Pack defaults: stack ${toTitleCase(selectedProfilePack.defaultStackFileName)}, blueprint ${toTitleCase(selectedProfilePack.defaultBlueprintFileName)}.`);
235
+ }
236
+
237
+ const shouldApplyDetectedStack = projectDetection.recommendedStackFileName && !selectedStackFileNameFromOption
238
+ ? await askYesNo(
239
+ `Use the detected stack recommendation (${toTitleCase(projectDetection.recommendedStackFileName)})?`,
240
+ userInterface,
241
+ projectDetection.confidenceLabel === 'high'
242
+ )
243
+ : false;
244
+
245
+ const stackDisplayChoices = stackFileNames.map((stackFileName) => toTitleCase(stackFileName));
246
+ const blueprintDisplayChoices = blueprintFileNames.map((blueprintFileName) => toTitleCase(blueprintFileName));
247
+
248
+ const selectedResolvedStackFileName = selectedStackFileNameFromOption
249
+ || selectedPreset?.stack
250
+ || (shouldApplyDetectedStack ? projectDetection.recommendedStackFileName : null)
251
+ || selectedProfilePack?.defaultStackFileName
252
+ || selectedProfile.defaultStackFileName
253
+ || stackFileNames[
254
+ stackDisplayChoices.indexOf(
255
+ await askChoice('Which stack should this governance pack target?', stackDisplayChoices, userInterface)
256
+ )
257
+ ];
258
+
259
+ const recommendedBlueprintFileName = shouldApplyDetectedStack
260
+ ? projectDetection.recommendedBlueprintFileName
261
+ : BLUEPRINT_RECOMMENDATIONS[selectedResolvedStackFileName] || null;
262
+
263
+ if (!recommendedBlueprintFileName && !selectedBlueprintFileNameFromOption && !selectedProfile.defaultBlueprintFileName) {
264
+ console.log('\nI could not map that stack to a first-party blueprint automatically, so I will ask you to choose one.');
265
+ }
266
+
267
+ const selectedResolvedBlueprintFileName = selectedBlueprintFileNameFromOption
268
+ || selectedPreset?.blueprint
269
+ || recommendedBlueprintFileName
270
+ || selectedProfilePack?.defaultBlueprintFileName
271
+ || selectedProfile.defaultBlueprintFileName
272
+ || blueprintFileNames[
273
+ blueprintDisplayChoices.indexOf(
274
+ await askChoice('Which blueprint should I scaffold into the compiled rulebook?', blueprintDisplayChoices, userInterface)
275
+ )
276
+ ];
277
+
278
+ const includeCiGuardrails = typeof initOptions.ci === 'boolean'
279
+ ? initOptions.ci
280
+ : typeof selectedPreset?.ci === 'boolean'
281
+ ? selectedPreset.ci
282
+ : selectedProfilePack?.lockCi
283
+ ? selectedProfilePack.defaultCi
284
+ : typeof selectedProfilePack?.defaultCi === 'boolean'
285
+ ? selectedProfilePack.defaultCi
286
+ : selectedProfile.lockCi
287
+ ? selectedProfile.defaultCi
288
+ : await askYesNo('Enable CI/CD guardrails and the LLM Judge policy?', userInterface, selectedProfile.defaultCi);
289
+
290
+ await copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath);
291
+
292
+ await compileDynamicContext({
293
+ targetDirectoryPath: resolvedTargetDirectoryPath,
294
+ selectedProfileName,
295
+ selectedProfilePack,
296
+ selectedStackFileName: selectedResolvedStackFileName,
297
+ selectedBlueprintFileName: selectedResolvedBlueprintFileName,
298
+ includeCiGuardrails,
299
+ });
300
+
301
+ await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
302
+
303
+ const setupDurationMs = Date.now() - setupStartedAt;
304
+ await writeOnboardingReport({
305
+ targetDirectoryPath: resolvedTargetDirectoryPath,
306
+ selectedProfileName,
307
+ selectedProfilePack,
308
+ selectedPreset: initOptions.preset || null,
309
+ selectedStackFileName: selectedResolvedStackFileName,
310
+ selectedBlueprintFileName: selectedResolvedBlueprintFileName,
311
+ includeCiGuardrails,
312
+ setupDurationMs,
313
+ projectDetection,
314
+ selectedSkillDomains: inferSkillDomainNamesFromSelection(selectedResolvedStackFileName, selectedResolvedBlueprintFileName),
315
+ operationMode: 'init',
316
+ });
317
+
318
+ console.log('\nInitialization complete.');
319
+ console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
320
+ console.log(`- Profile: ${selectedProfile.displayName}`);
321
+ if (initOptions.preset) {
322
+ console.log(`- Preset: ${initOptions.preset}`);
323
+ }
324
+ if (selectedProfilePack) {
325
+ console.log(`- Team profile pack: ${selectedProfilePack.displayName}`);
326
+ }
327
+ console.log(`- Stack: ${toTitleCase(selectedResolvedStackFileName)}`);
328
+ console.log(`- Blueprint: ${toTitleCase(selectedResolvedBlueprintFileName)}`);
329
+ console.log(`- CI/CD guardrails: ${includeCiGuardrails ? 'enabled' : 'disabled'}`);
330
+ console.log(`- Blocking severities: ${formatBlockingSeverities(selectedProfile.blockingSeverities)}`);
331
+ console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
332
+ console.log('- Generated files: .cursorrules, .windsurfrules, and .agent-context/state/onboarding-report.json');
333
+ console.log('\nPlain-language summary:');
334
+ console.log(`I prepared a ${selectedProfile.displayName.toLowerCase()} governance pack for a ${toTitleCase(selectedResolvedStackFileName)} project using the ${toTitleCase(selectedResolvedBlueprintFileName)} blueprint.`);
335
+ console.log('Your AI tools will now receive one compiled rulebook plus the original source rules, and your review threshold is stored in .agent-context/policies/llm-judge-threshold.json.');
336
+ } finally {
337
+ userInterface.close();
338
+ }
339
+ }
@@ -0,0 +1,81 @@
1
+ /**
2
+ * Launch Command — Numbered interactive launcher.
3
+ * Depends on: constants, utils, init command, skill-selector
4
+ */
5
+ import { createInterface } from 'node:readline/promises';
6
+ import { stdin, stdout } from 'node:process';
7
+
8
+ import { CLI_VERSION, INIT_PRESETS } from '../constants.mjs';
9
+ import { askChoice, normalizeChoiceInput } from '../utils.mjs';
10
+ import { runInitCommand } from './init.mjs';
11
+ import { runSkillCommand } from '../skill-selector.mjs';
12
+
13
+ export async function runLaunchCommand() {
14
+ const userInterface = createInterface({ input: stdin, output: stdout });
15
+
16
+ try {
17
+ console.log(`\nAgentic-Senior-Core CLI v${CLI_VERSION}`);
18
+ console.log('Start with a numbered choice. You can still use commands later if you want direct control.');
19
+
20
+ const launchChoice = await askChoice(
21
+ 'How do you want to start?',
22
+ [
23
+ 'GitHub template (zero install)',
24
+ 'npm / npx path',
25
+ 'Bootstrap scripts',
26
+ 'Preset starter',
27
+ 'Interactive init wizard',
28
+ 'Skill selector',
29
+ 'Exit',
30
+ ],
31
+ userInterface
32
+ );
33
+
34
+ if (launchChoice === 'GitHub template (zero install)') {
35
+ console.log('\nOpen the GitHub template here:');
36
+ console.log('https://github.com/fatidaprilian/Agentic-Senior-Core/generate');
37
+ return;
38
+ }
39
+
40
+ if (launchChoice === 'npm / npx path') {
41
+ console.log('\nChoose one of these package paths:');
42
+ console.log('npm exec --yes @ryuenn3123/agentic-senior-core init');
43
+ console.log('npx @ryuenn3123/agentic-senior-core init');
44
+ console.log('npm install -g @ryuenn3123/agentic-senior-core && agentic-senior-core init');
45
+ return;
46
+ }
47
+
48
+ if (launchChoice === 'Bootstrap scripts') {
49
+ console.log('\nUse the repository bootstrap scripts:');
50
+ console.log('Windows: powershell -ExecutionPolicy Bypass -File .\\scripts\\init-project.ps1 -TargetDirectory .');
51
+ console.log('Linux/macOS: bash ./scripts/init-project.sh .');
52
+ return;
53
+ }
54
+
55
+ if (launchChoice === 'Preset starter') {
56
+ const presetNames = Object.keys(INIT_PRESETS);
57
+ const selectedPresetName = await askChoice(
58
+ 'Choose a starter preset:',
59
+ presetNames.map((presetName) => `${presetName} - ${INIT_PRESETS[presetName].description}`),
60
+ userInterface
61
+ );
62
+
63
+ await runInitCommand('.', { preset: normalizeChoiceInput(selectedPresetName.split(' - ')[0]) });
64
+ return;
65
+ }
66
+
67
+ if (launchChoice === 'Interactive init wizard') {
68
+ await runInitCommand('.', {});
69
+ return;
70
+ }
71
+
72
+ if (launchChoice === 'Skill selector') {
73
+ await runSkillCommand([]);
74
+ return;
75
+ }
76
+
77
+ console.log('Exit selected.');
78
+ } finally {
79
+ userInterface.close();
80
+ }
81
+ }
@@ -0,0 +1,165 @@
1
+ /**
2
+ * Upgrade Command — Governance upgrade assistant.
3
+ * Depends on: constants, utils, detector, compiler
4
+ */
5
+ import { createInterface } from 'node:readline/promises';
6
+ import { stdin, stdout } from 'node:process';
7
+ import fs from 'node:fs/promises';
8
+ import path from 'node:path';
9
+
10
+ import {
11
+ CLI_VERSION,
12
+ AGENT_CONTEXT_DIR,
13
+ PROFILE_PRESETS,
14
+ BLUEPRINT_RECOMMENDATIONS,
15
+ } from '../constants.mjs';
16
+
17
+ import {
18
+ ensureDirectory,
19
+ askYesNo,
20
+ toTitleCase,
21
+ collectFileNames,
22
+ formatDuration,
23
+ pathExists,
24
+ copyGovernanceAssetsToTarget,
25
+ } from '../utils.mjs';
26
+
27
+ import { detectProjectContext } from '../detector.mjs';
28
+ import {
29
+ buildCompiledRulesContent,
30
+ writeSelectedPolicy,
31
+ writeOnboardingReport,
32
+ loadOnboardingReportIfExists,
33
+ } from '../compiler.mjs';
34
+
35
+ export function parseUpgradeArguments(commandArguments) {
36
+ const parsedUpgradeOptions = {
37
+ targetDirectory: '.',
38
+ dryRun: false,
39
+ skipConfirmation: false,
40
+ };
41
+
42
+ for (let argumentIndex = 0; argumentIndex < commandArguments.length; argumentIndex++) {
43
+ const currentArgument = commandArguments[argumentIndex];
44
+
45
+ if (!currentArgument.startsWith('--')) {
46
+ parsedUpgradeOptions.targetDirectory = currentArgument;
47
+ continue;
48
+ }
49
+
50
+ if (currentArgument === '--dry-run') {
51
+ parsedUpgradeOptions.dryRun = true;
52
+ continue;
53
+ }
54
+
55
+ if (currentArgument === '--yes') {
56
+ parsedUpgradeOptions.skipConfirmation = true;
57
+ continue;
58
+ }
59
+
60
+ throw new Error(`Unknown option: ${currentArgument}`);
61
+ }
62
+
63
+ return parsedUpgradeOptions;
64
+ }
65
+
66
+ export async function runUpgradeCommand(targetDirectoryArgument, upgradeOptions = {}) {
67
+ const resolvedTargetDirectoryPath = path.resolve(targetDirectoryArgument || '.');
68
+ const setupStartedAt = Date.now();
69
+ await ensureDirectory(resolvedTargetDirectoryPath);
70
+
71
+ const userInterface = createInterface({ input: stdin, output: stdout });
72
+
73
+ try {
74
+ console.log(`\nAgentic-Senior-Core CLI v${CLI_VERSION}`);
75
+ console.log('Running upgrade assistant for an existing repository.');
76
+
77
+ await copyGovernanceAssetsToTarget(resolvedTargetDirectoryPath);
78
+
79
+ const stackFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, 'stacks'));
80
+ const blueprintFileNames = await collectFileNames(path.join(AGENT_CONTEXT_DIR, 'blueprints'));
81
+ const existingOnboardingReport = await loadOnboardingReportIfExists(resolvedTargetDirectoryPath);
82
+ const projectDetection = await detectProjectContext(resolvedTargetDirectoryPath);
83
+
84
+ const selectedProfileName = PROFILE_PRESETS[existingOnboardingReport?.selectedProfile]
85
+ ? existingOnboardingReport.selectedProfile
86
+ : 'balanced';
87
+
88
+ const selectedStackFileName = stackFileNames.includes(existingOnboardingReport?.selectedStack)
89
+ ? existingOnboardingReport.selectedStack
90
+ : projectDetection.recommendedStackFileName || 'typescript.md';
91
+
92
+ const selectedBlueprintFileName = blueprintFileNames.includes(existingOnboardingReport?.selectedBlueprint)
93
+ ? existingOnboardingReport.selectedBlueprint
94
+ : BLUEPRINT_RECOMMENDATIONS[selectedStackFileName] || 'api-nextjs.md';
95
+
96
+ const includeCiGuardrails = typeof existingOnboardingReport?.ciGuardrailsEnabled === 'boolean'
97
+ ? existingOnboardingReport.ciGuardrailsEnabled
98
+ : true;
99
+
100
+ const currentRulesPath = path.join(resolvedTargetDirectoryPath, '.cursorrules');
101
+ const currentRulesContent = await pathExists(currentRulesPath)
102
+ ? await fs.readFile(currentRulesPath, 'utf8')
103
+ : '';
104
+
105
+ const plannedRulesContent = await buildCompiledRulesContent({
106
+ targetDirectoryPath: resolvedTargetDirectoryPath,
107
+ selectedProfileName,
108
+ selectedStackFileName,
109
+ selectedBlueprintFileName,
110
+ includeCiGuardrails,
111
+ });
112
+
113
+ const isRulesContentChanged = currentRulesContent !== plannedRulesContent;
114
+ const currentRuleLineCount = currentRulesContent ? currentRulesContent.split(/\r?\n/).length : 0;
115
+ const plannedRuleLineCount = plannedRulesContent.split(/\r?\n/).length;
116
+
117
+ console.log('\nUpgrade preview:');
118
+ console.log(`- Target directory: ${resolvedTargetDirectoryPath}`);
119
+ console.log(`- Profile: ${toTitleCase(selectedProfileName)}`);
120
+ console.log(`- Stack: ${toTitleCase(selectedStackFileName)}`);
121
+ console.log(`- Blueprint: ${toTitleCase(selectedBlueprintFileName)}`);
122
+ console.log(`- CI/CD guardrails: ${includeCiGuardrails ? 'enabled' : 'disabled'}`);
123
+ console.log(`- Existing rules lines: ${currentRuleLineCount}`);
124
+ console.log(`- Planned rules lines: ${plannedRuleLineCount}`);
125
+ console.log(`- Rules changed: ${isRulesContentChanged ? 'yes' : 'no'}`);
126
+
127
+ if (upgradeOptions.dryRun) {
128
+ console.log('\nDry run enabled. No files were modified.');
129
+ return;
130
+ }
131
+
132
+ const shouldApplyUpgrade = upgradeOptions.skipConfirmation
133
+ ? true
134
+ : await askYesNo('Apply upgrade and write migrated files?', userInterface, true);
135
+
136
+ if (!shouldApplyUpgrade) {
137
+ console.log('Upgrade cancelled by user.');
138
+ return;
139
+ }
140
+
141
+ await fs.writeFile(currentRulesPath, plannedRulesContent, 'utf8');
142
+ await fs.writeFile(path.join(resolvedTargetDirectoryPath, '.windsurfrules'), plannedRulesContent, 'utf8');
143
+ await writeSelectedPolicy(resolvedTargetDirectoryPath, selectedProfileName);
144
+
145
+ const setupDurationMs = Date.now() - setupStartedAt;
146
+ await writeOnboardingReport({
147
+ targetDirectoryPath: resolvedTargetDirectoryPath,
148
+ selectedProfileName,
149
+ selectedProfilePack: existingOnboardingReport?.selectedProfilePack || null,
150
+ selectedStackFileName,
151
+ selectedBlueprintFileName,
152
+ includeCiGuardrails,
153
+ setupDurationMs,
154
+ projectDetection,
155
+ operationMode: 'upgrade',
156
+ });
157
+
158
+ console.log('\nUpgrade complete.');
159
+ console.log(`- Rules rewritten: ${isRulesContentChanged ? 'yes' : 'no (metadata refreshed)'}`);
160
+ console.log(`- Setup time: ${formatDuration(setupDurationMs)}`);
161
+ console.log('- Updated files: .cursorrules, .windsurfrules, .agent-context/state/onboarding-report.json');
162
+ } finally {
163
+ userInterface.close();
164
+ }
165
+ }