@onion-architect-ai/cli 4.1.0-beta.1

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/cli.js ADDED
@@ -0,0 +1,1952 @@
1
+ #!/usr/bin/env node
2
+ import fs2 from 'fs-extra';
3
+ import path8 from 'path';
4
+ import yaml5 from 'yaml';
5
+ import { Command } from 'commander';
6
+ import chalk from 'chalk';
7
+ import inquirer from 'inquirer';
8
+ import ora from 'ora';
9
+
10
+ var __getOwnPropNames = Object.getOwnPropertyNames;
11
+ var __esm = (fn, res) => function __init() {
12
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
13
+ };
14
+
15
+ // src/constants.ts
16
+ var ONION_VERSION, SUPPORTED_IDES;
17
+ var init_constants = __esm({
18
+ "src/constants.ts"() {
19
+ ONION_VERSION = "4.1.0-beta.1";
20
+ SUPPORTED_IDES = [
21
+ {
22
+ id: "cursor",
23
+ name: "Cursor",
24
+ detector: ".cursor",
25
+ loader: "cursor.js",
26
+ configFile: "settings.json"
27
+ },
28
+ {
29
+ id: "windsurf",
30
+ name: "Windsurf",
31
+ detector: ".windsurf",
32
+ loader: "windsurf.ts",
33
+ configFile: "settings.yml"
34
+ },
35
+ {
36
+ id: "claude-code",
37
+ name: "Claude Code",
38
+ detector: ".claude",
39
+ loader: "claude.py",
40
+ configFile: "config.json"
41
+ },
42
+ {
43
+ id: "vscode",
44
+ name: "VS Code (GitHub Copilot)",
45
+ detector: ".vscode",
46
+ loader: "vscode.js",
47
+ configFile: "settings.json"
48
+ },
49
+ {
50
+ id: "cline",
51
+ name: "Cline (VS Code)",
52
+ detector: ".vscode/extensions",
53
+ loader: "cline.js",
54
+ configFile: "cline-config.json"
55
+ }
56
+ ];
57
+ }
58
+ });
59
+
60
+ // src/core/validator.ts
61
+ function validateContextName(name) {
62
+ if (!name || typeof name !== "string") {
63
+ throw new Error("Nome do contexto \xE9 obrigat\xF3rio");
64
+ }
65
+ const trimmed = name.trim();
66
+ if (trimmed.length < 3 || trimmed.length > 20) {
67
+ throw new Error("Nome do contexto deve ter entre 3 e 20 caracteres");
68
+ }
69
+ if (!/^[a-z][a-z0-9-]*$/.test(trimmed)) {
70
+ throw new Error(
71
+ "Nome do contexto deve come\xE7ar com letra min\xFAscula e conter apenas letras, n\xFAmeros e h\xEDfens"
72
+ );
73
+ }
74
+ if (trimmed.endsWith("-")) {
75
+ throw new Error("Nome do contexto n\xE3o pode terminar com h\xEDfen");
76
+ }
77
+ const reserved = ["core", "ide", "contexts", "config", "help"];
78
+ if (reserved.includes(trimmed)) {
79
+ throw new Error(`"${trimmed}" \xE9 uma palavra reservada do sistema`);
80
+ }
81
+ return true;
82
+ }
83
+ function validateIDEName(ide) {
84
+ if (!ide || typeof ide !== "string") {
85
+ throw new Error("Nome do IDE \xE9 obrigat\xF3rio");
86
+ }
87
+ const supportedIds = SUPPORTED_IDES.map((i) => i.id);
88
+ if (!supportedIds.includes(ide.toLowerCase())) {
89
+ throw new Error(
90
+ `IDE "${ide}" n\xE3o \xE9 suportado. IDEs dispon\xEDveis: ${supportedIds.join(", ")}`
91
+ );
92
+ }
93
+ return true;
94
+ }
95
+ function validateConfig(config) {
96
+ if (!config || typeof config !== "object") {
97
+ throw new Error("Configura\xE7\xE3o inv\xE1lida");
98
+ }
99
+ const cfg = config;
100
+ const required = ["version", "contexts", "ides"];
101
+ for (const field of required) {
102
+ if (!cfg[field]) {
103
+ throw new Error(`Campo obrigat\xF3rio ausente no config: ${field}`);
104
+ }
105
+ }
106
+ if (!Array.isArray(cfg.contexts)) {
107
+ throw new Error('Campo "contexts" deve ser um array');
108
+ }
109
+ if (!Array.isArray(cfg.ides)) {
110
+ throw new Error('Campo "ides" deve ser um array');
111
+ }
112
+ for (const ctx of cfg.contexts) {
113
+ try {
114
+ validateContextName(ctx);
115
+ } catch (err) {
116
+ throw new Error(`Contexto inv\xE1lido "${ctx}": ${err.message}`);
117
+ }
118
+ }
119
+ for (const ide of cfg.ides) {
120
+ try {
121
+ validateIDEName(ide);
122
+ } catch (err) {
123
+ throw new Error(`IDE inv\xE1lido "${ide}": ${err.message}`);
124
+ }
125
+ }
126
+ return true;
127
+ }
128
+ var init_validator = __esm({
129
+ "src/core/validator.ts"() {
130
+ init_constants();
131
+ }
132
+ });
133
+ async function readConfig(projectRoot) {
134
+ const configPath = path8.join(projectRoot, CONFIG_FILENAME);
135
+ if (!await fs2.pathExists(configPath)) {
136
+ throw new Error(`Arquivo ${CONFIG_FILENAME} n\xE3o encontrado em ${projectRoot}`);
137
+ }
138
+ try {
139
+ const content = await fs2.readFile(configPath, "utf8");
140
+ const config = yaml5.parse(content);
141
+ validateConfig(config);
142
+ return config;
143
+ } catch (error) {
144
+ if (error.message.includes("n\xE3o encontrado")) {
145
+ throw error;
146
+ }
147
+ throw new Error(`Erro ao ler ${CONFIG_FILENAME}: ${error.message}`);
148
+ }
149
+ }
150
+ async function createConfig(projectRoot, data) {
151
+ const configPath = path8.join(projectRoot, CONFIG_FILENAME);
152
+ if (await fs2.pathExists(configPath)) {
153
+ throw new Error(`Arquivo ${CONFIG_FILENAME} j\xE1 existe. Use updateConfig() para modificar.`);
154
+ }
155
+ validateConfig(data);
156
+ try {
157
+ const yamlContent = yaml5.stringify(data, {
158
+ indent: 2,
159
+ lineWidth: 0
160
+ // Sem quebra de linha
161
+ });
162
+ await fs2.writeFile(configPath, yamlContent, "utf8");
163
+ } catch (error) {
164
+ throw new Error(`Erro ao criar ${CONFIG_FILENAME}: ${error.message}`);
165
+ }
166
+ }
167
+ async function updateConfig(projectRoot, updates) {
168
+ const current = await readConfig(projectRoot);
169
+ const merged = deepMerge(
170
+ current,
171
+ updates
172
+ );
173
+ validateConfig(merged);
174
+ const configPath = path8.join(projectRoot, CONFIG_FILENAME);
175
+ const yamlContent = yaml5.stringify(merged, {
176
+ indent: 2,
177
+ lineWidth: 0
178
+ });
179
+ await fs2.writeFile(configPath, yamlContent, "utf8");
180
+ return merged;
181
+ }
182
+ async function addContext(projectRoot, contextName) {
183
+ const config = await readConfig(projectRoot);
184
+ if (config.contexts.includes(contextName)) {
185
+ throw new Error(`Contexto "${contextName}" j\xE1 existe na configura\xE7\xE3o`);
186
+ }
187
+ return await updateConfig(projectRoot, {
188
+ contexts: [...config.contexts, contextName]
189
+ });
190
+ }
191
+ async function addIDE(projectRoot, ideName) {
192
+ const config = await readConfig(projectRoot);
193
+ if (config.ides.includes(ideName)) {
194
+ throw new Error(`IDE "${ideName}" j\xE1 existe na configura\xE7\xE3o`);
195
+ }
196
+ return await updateConfig(projectRoot, {
197
+ ides: [...config.ides, ideName]
198
+ });
199
+ }
200
+ function deepMerge(target, source) {
201
+ const result = { ...target };
202
+ for (const key in source) {
203
+ if (Object.prototype.hasOwnProperty.call(source, key)) {
204
+ const targetValue = result[key];
205
+ const sourceValue = source[key];
206
+ if (Array.isArray(targetValue) && Array.isArray(sourceValue)) {
207
+ result[key] = [.../* @__PURE__ */ new Set([...targetValue, ...sourceValue])];
208
+ } else if (typeof targetValue === "object" && targetValue !== null && typeof sourceValue === "object" && sourceValue !== null && !Array.isArray(targetValue) && !Array.isArray(sourceValue)) {
209
+ result[key] = deepMerge(
210
+ targetValue,
211
+ sourceValue
212
+ );
213
+ } else {
214
+ result[key] = sourceValue;
215
+ }
216
+ }
217
+ }
218
+ return result;
219
+ }
220
+ function createDefaultConfig(options = {}) {
221
+ return {
222
+ version: options.version || "4.0.0",
223
+ contexts: options.contexts || [],
224
+ ides: options.ides || ["cursor"],
225
+ integrations: options.integrations || {},
226
+ created: options.created || (/* @__PURE__ */ new Date()).toISOString(),
227
+ ...options
228
+ };
229
+ }
230
+ var CONFIG_FILENAME;
231
+ var init_config = __esm({
232
+ "src/core/config.ts"() {
233
+ init_validator();
234
+ CONFIG_FILENAME = ".onion-config.yml";
235
+ }
236
+ });
237
+ async function init(options = {}) {
238
+ try {
239
+ const projectRoot = process.cwd();
240
+ console.log("");
241
+ console.log(chalk.magenta.bold("\u{1F9C5} Initializing Onion System v4..."));
242
+ console.log("");
243
+ if (fs2.existsSync(path8.join(projectRoot, ".onion"))) {
244
+ console.log(chalk.yellow("\u26A0\uFE0F .onion/ already exists!"));
245
+ console.log(chalk.gray('Use "onion migrate" to upgrade from v3'));
246
+ process.exit(1);
247
+ }
248
+ const onionRoot = path8.resolve(import.meta.dirname, "../../../..");
249
+ const sourceOnion = path8.join(onionRoot, ".onion");
250
+ if (!fs2.existsSync(sourceOnion)) {
251
+ console.log(chalk.red("\u274C Could not find Onion source structure"));
252
+ console.log(chalk.gray(`Expected: ${sourceOnion}`));
253
+ process.exit(1);
254
+ }
255
+ console.log(chalk.cyan("\u{1F4C1} Creating .onion/ structure..."));
256
+ fs2.copySync(sourceOnion, path8.join(projectRoot, ".onion"), {
257
+ dereference: true
258
+ // Resolve symlinks
259
+ });
260
+ console.log(chalk.green("\u2713 Created .onion/"));
261
+ console.log(chalk.cyan("\u{1F3AF} Setting up Cursor IDE integration..."));
262
+ const cursorDir = path8.join(projectRoot, ".cursor");
263
+ fs2.ensureDirSync(cursorDir);
264
+ const sourceCursorCommands = path8.join(onionRoot, ".cursor/commands");
265
+ if (fs2.existsSync(sourceCursorCommands)) {
266
+ fs2.copySync(sourceCursorCommands, path8.join(cursorDir, "commands"), {
267
+ dereference: true
268
+ });
269
+ }
270
+ const sourceCursorAgents = path8.join(onionRoot, ".cursor/agents");
271
+ if (fs2.existsSync(sourceCursorAgents)) {
272
+ fs2.copySync(sourceCursorAgents, path8.join(cursorDir, "agents"), {
273
+ dereference: true
274
+ });
275
+ }
276
+ const sourceCursorRules = path8.join(onionRoot, ".cursor/rules");
277
+ if (fs2.existsSync(sourceCursorRules)) {
278
+ fs2.copySync(sourceCursorRules, path8.join(cursorDir, "rules"), {
279
+ dereference: true
280
+ });
281
+ }
282
+ console.log(chalk.green("\u2713 Created .cursor/"));
283
+ console.log(chalk.cyan("\u2699\uFE0F Creating configuration..."));
284
+ const config = `# Onion System v4 Configuration
285
+ version: 4.0.0
286
+ created: ${(/* @__PURE__ */ new Date()).toISOString()}
287
+ project_type: monorepo
288
+
289
+ contexts:
290
+ - name: business
291
+ enabled: true
292
+ description: Product specs, features, tasks
293
+ - name: technical
294
+ enabled: true
295
+ description: Development, architecture, PRs
296
+
297
+ ides:
298
+ - name: cursor
299
+ enabled: true
300
+ path: .cursor/
301
+
302
+ integrations:
303
+ task_manager:
304
+ provider: none
305
+ transcription:
306
+ provider: none
307
+ `;
308
+ fs2.writeFileSync(path8.join(projectRoot, ".onion-config.yml"), config, "utf8");
309
+ console.log(chalk.green("\u2713 Created .onion-config.yml"));
310
+ const readme = `# \u{1F9C5} Onion System v4
311
+
312
+ This project uses Onion System v4 for development.
313
+
314
+ ## Quick Start
315
+
316
+ ### Available Commands
317
+
318
+ **Business Context:**
319
+ \`\`\`
320
+ /business/help # Show all business commands
321
+ /business/spec # Create product spec
322
+ /business/task # Create task
323
+ /business/estimate # Estimate story points
324
+ \`\`\`
325
+
326
+ **Technical Context:**
327
+ \`\`\`
328
+ /technical/help # Show all technical commands
329
+ /technical/plan # Plan development
330
+ /technical/work # Start working on task
331
+ /technical/pr # Create pull request
332
+ \`\`\`
333
+
334
+ **Global:**
335
+ \`\`\`
336
+ /help # Show global help
337
+ \`\`\`
338
+
339
+ ## Documentation
340
+
341
+ - Full docs: https://github.com/your-org/onion-v4
342
+ - Configuration: .onion-config.yml
343
+ - Structure: .onion/
344
+
345
+ ## Learn More
346
+
347
+ - [Installation Guide](https://github.com/your-org/onion-v4/docs/onion/INSTALLATION.md)
348
+ - [Release Notes](https://github.com/your-org/onion-v4/docs/onion/RELEASE-NOTES-v4.0-beta.md)
349
+ `;
350
+ fs2.writeFileSync(path8.join(projectRoot, ".onion", "README.md"), readme, "utf8");
351
+ console.log("");
352
+ console.log(chalk.green.bold("\u2705 Onion System initialized successfully!"));
353
+ console.log("");
354
+ console.log(chalk.cyan("\u{1F4DA} Next steps:"));
355
+ console.log("");
356
+ console.log(" 1. Restart Cursor IDE");
357
+ console.log(" 2. Try a command:");
358
+ console.log(chalk.yellow(" /business/help"));
359
+ console.log(chalk.yellow(" /technical/help"));
360
+ console.log(" 3. Start developing:");
361
+ console.log(chalk.yellow(' /business/spec "my-feature"'));
362
+ console.log(chalk.yellow(" /technical/work"));
363
+ console.log("");
364
+ console.log(chalk.gray("Need help? Run: /help"));
365
+ console.log("");
366
+ } catch (error) {
367
+ console.log("");
368
+ console.log(chalk.red.bold("\u274C Initialization failed:"));
369
+ console.log(chalk.red(error instanceof Error ? error.message : String(error)));
370
+ console.log("");
371
+ if (options.debug) {
372
+ console.error(error);
373
+ }
374
+ process.exit(1);
375
+ }
376
+ }
377
+ var Logger = class {
378
+ spinner = null;
379
+ // Títulos
380
+ title(text) {
381
+ console.log("\n" + chalk.cyan.bold("\u2501".repeat(60)));
382
+ console.log(chalk.cyan.bold(` ${text}`));
383
+ console.log(chalk.cyan.bold("\u2501".repeat(60)));
384
+ }
385
+ // Seções
386
+ section(text) {
387
+ console.log("\n" + chalk.white.bold(text));
388
+ }
389
+ // Sucesso
390
+ success(text) {
391
+ console.log(chalk.green("\u2705 ") + text);
392
+ }
393
+ // Info
394
+ info(text) {
395
+ console.log(chalk.blue("\u2139\uFE0F ") + text);
396
+ }
397
+ // Warning
398
+ warn(text) {
399
+ console.log(chalk.yellow("\u26A0\uFE0F ") + text);
400
+ }
401
+ // Erro
402
+ error(text) {
403
+ console.log(chalk.red("\u274C ") + text);
404
+ }
405
+ // Spinner
406
+ startSpinner(text) {
407
+ this.spinner = ora(text).start();
408
+ }
409
+ stopSpinner(success = true, text = null) {
410
+ if (!this.spinner) return;
411
+ if (success) {
412
+ this.spinner.succeed(text ?? void 0);
413
+ } else {
414
+ this.spinner.fail(text ?? void 0);
415
+ }
416
+ this.spinner = null;
417
+ }
418
+ // Lista
419
+ list(items, prefix = " \u221F") {
420
+ items.forEach((item) => {
421
+ console.log(chalk.gray(prefix) + " " + item);
422
+ });
423
+ }
424
+ // Quebra
425
+ break() {
426
+ console.log();
427
+ }
428
+ };
429
+ var logger = new Logger();
430
+ async function detectOnionV4Structure(projectRoot) {
431
+ try {
432
+ const onionDir = path8.join(projectRoot, ".onion");
433
+ const configFile = path8.join(projectRoot, ".onion-config.yml");
434
+ if (!await fs2.pathExists(onionDir)) {
435
+ return null;
436
+ }
437
+ if (!await fs2.pathExists(configFile)) {
438
+ return null;
439
+ }
440
+ const contextsDir = path8.join(onionDir, "contexts");
441
+ if (!await fs2.pathExists(contextsDir)) {
442
+ return null;
443
+ }
444
+ const structure = {
445
+ version: "v4",
446
+ root: projectRoot,
447
+ onionDir,
448
+ configFile,
449
+ contextsDir,
450
+ coreDir: path8.join(onionDir, "core"),
451
+ ideDir: path8.join(onionDir, "ide"),
452
+ contexts: [],
453
+ ides: []
454
+ };
455
+ try {
456
+ const contextDirs = await fs2.readdir(contextsDir);
457
+ const validContexts = [];
458
+ for (const name of contextDirs) {
459
+ const stat = await fs2.stat(path8.join(contextsDir, name));
460
+ if (stat.isDirectory()) {
461
+ validContexts.push(name);
462
+ }
463
+ }
464
+ structure.contexts = validContexts;
465
+ } catch {
466
+ structure.contexts = [];
467
+ }
468
+ const ideDir = path8.join(onionDir, "ide");
469
+ if (await fs2.pathExists(ideDir)) {
470
+ try {
471
+ const ideDirs = await fs2.readdir(ideDir);
472
+ const validIdes = [];
473
+ for (const name of ideDirs) {
474
+ const stat = await fs2.stat(path8.join(ideDir, name));
475
+ if (stat.isDirectory()) {
476
+ validIdes.push(name);
477
+ }
478
+ }
479
+ structure.ides = validIdes;
480
+ } catch {
481
+ structure.ides = [];
482
+ }
483
+ }
484
+ return structure;
485
+ } catch {
486
+ return null;
487
+ }
488
+ }
489
+ async function detectOnionV3Structure(projectRoot) {
490
+ try {
491
+ const cursorDir = path8.join(projectRoot, ".cursor");
492
+ const commandsDir = path8.join(cursorDir, "commands");
493
+ const agentsDir = path8.join(cursorDir, "agents");
494
+ if (!await fs2.pathExists(cursorDir)) {
495
+ return null;
496
+ }
497
+ const hasCommands = await fs2.pathExists(commandsDir);
498
+ const hasAgents = await fs2.pathExists(agentsDir);
499
+ if (!hasCommands && !hasAgents) {
500
+ return null;
501
+ }
502
+ const onionDir = path8.join(projectRoot, ".onion");
503
+ if (await fs2.pathExists(onionDir)) {
504
+ return null;
505
+ }
506
+ const structure = {
507
+ version: "v3",
508
+ root: projectRoot,
509
+ cursorDir,
510
+ commandsDir,
511
+ agentsDir,
512
+ commands: {},
513
+ agents: {},
514
+ hasRules: false,
515
+ hasSessions: false
516
+ };
517
+ if (hasCommands) {
518
+ try {
519
+ const categories = await fs2.readdir(commandsDir);
520
+ for (const category of categories) {
521
+ const categoryPath = path8.join(commandsDir, category);
522
+ const stat = await fs2.stat(categoryPath);
523
+ if (stat.isDirectory()) {
524
+ const files = await fs2.readdir(categoryPath);
525
+ structure.commands[category] = files.filter((f) => f.endsWith(".md"));
526
+ }
527
+ }
528
+ } catch {
529
+ structure.commands = {};
530
+ }
531
+ }
532
+ if (hasAgents) {
533
+ try {
534
+ const categories = await fs2.readdir(agentsDir);
535
+ for (const category of categories) {
536
+ const categoryPath = path8.join(agentsDir, category);
537
+ const stat = await fs2.stat(categoryPath);
538
+ if (stat.isDirectory()) {
539
+ const files = await fs2.readdir(categoryPath);
540
+ structure.agents[category] = files.filter((f) => f.endsWith(".md"));
541
+ }
542
+ }
543
+ } catch {
544
+ structure.agents = {};
545
+ }
546
+ }
547
+ const rulesDir = path8.join(cursorDir, "rules");
548
+ structure.hasRules = await fs2.pathExists(rulesDir);
549
+ const sessionsDir = path8.join(cursorDir, "sessions");
550
+ structure.hasSessions = await fs2.pathExists(sessionsDir);
551
+ return structure;
552
+ } catch {
553
+ return null;
554
+ }
555
+ }
556
+ function validateMigrationEligibility(v3Structure) {
557
+ const issues = [];
558
+ if (!v3Structure || v3Structure.version !== "v3") {
559
+ return { canMigrate: false, issues: ["N\xE3o \xE9 um projeto Onion v3"] };
560
+ }
561
+ if (v3Structure.root) {
562
+ const onionPath = path8.join(v3Structure.root, ".onion");
563
+ if (fs2.existsSync(onionPath)) {
564
+ issues.push("Projeto j\xE1 tem estrutura .onion/ (poss\xEDvel v4)");
565
+ }
566
+ }
567
+ const hasCommands = Object.keys(v3Structure.commands || {}).length > 0;
568
+ const hasAgents = Object.keys(v3Structure.agents || {}).length > 0;
569
+ if (!hasCommands && !hasAgents) {
570
+ issues.push("Projeto n\xE3o tem comandos nem agentes para migrar");
571
+ }
572
+ const canMigrate = issues.length === 0;
573
+ return { canMigrate, issues };
574
+ }
575
+
576
+ // src/commands/add.ts
577
+ init_config();
578
+ init_validator();
579
+ async function generateCoreStructure(projectRoot) {
580
+ const onionRoot = path8.join(projectRoot, ".onion");
581
+ const corePaths = [
582
+ "core/knowbase/concepts",
583
+ "core/knowbase/frameworks",
584
+ "core/knowbase/tools",
585
+ "core/knowbase/learnings",
586
+ "core/agents",
587
+ "core/commands",
588
+ "core/rules",
589
+ "core/utils"
590
+ ];
591
+ for (const p of corePaths) {
592
+ await fs2.ensureDir(path8.join(onionRoot, p));
593
+ }
594
+ }
595
+ async function generateContextStructure(projectRoot, contextName, options = {}) {
596
+ const onionRoot = path8.join(projectRoot, ".onion");
597
+ const contextRoot = path8.join(onionRoot, "contexts", contextName);
598
+ const basePaths = [
599
+ "knowbase",
600
+ "agents",
601
+ "commands/starter",
602
+ "commands/intermediate",
603
+ "commands/advanced",
604
+ "sessions"
605
+ ];
606
+ for (const p of basePaths) {
607
+ await fs2.ensureDir(path8.join(contextRoot, p));
608
+ }
609
+ if (options.includeREADME !== false) {
610
+ await generateContextREADME(projectRoot, contextName);
611
+ }
612
+ if (options.includeConfig !== false) {
613
+ await generateContextConfig(projectRoot, contextName, { type: options.type });
614
+ }
615
+ }
616
+ async function generateContextREADME(projectRoot, contextName) {
617
+ const readmePath = path8.join(projectRoot, ".onion/contexts", contextName, "README.md");
618
+ const content = `# ${capitalizeFirst(contextName)} Context
619
+
620
+ > **Onion v4.0** | Multi-Context Development Orchestrator
621
+
622
+ ---
623
+
624
+ ## \u{1F3AF} Sobre Este Contexto
625
+
626
+ O contexto **${contextName}** \xE9 dedicado a [descrever prop\xF3sito].
627
+
628
+ ## \u{1F680} Quick Start
629
+
630
+ ### Comandos Starter (80% dos casos)
631
+
632
+ \`\`\`bash
633
+ /${contextName}/[comando-starter]
634
+ \`\`\`
635
+
636
+ ### Ver todos os comandos
637
+
638
+ \`\`\`bash
639
+ /${contextName}/help
640
+ \`\`\`
641
+
642
+ ---
643
+
644
+ **Vers\xE3o**: 4.0.0
645
+ **Criado**: ${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}
646
+ `;
647
+ await fs2.writeFile(readmePath, content, "utf-8");
648
+ }
649
+ async function generateContextConfig(projectRoot, contextName, config = {}) {
650
+ const configPath = path8.join(
651
+ projectRoot,
652
+ ".onion/contexts",
653
+ contextName,
654
+ ".context-config.yml"
655
+ );
656
+ const defaultConfig = {
657
+ context: {
658
+ name: contextName,
659
+ version: "4.0.0",
660
+ type: config.type || "custom"
661
+ },
662
+ integrations: config.integrations || {}
663
+ };
664
+ await fs2.writeFile(configPath, yaml5.stringify(defaultConfig), "utf-8");
665
+ }
666
+ async function generateStarterCommands(projectRoot, contextName, contextType) {
667
+ const starterPath = path8.join(
668
+ projectRoot,
669
+ ".onion/contexts",
670
+ contextName,
671
+ "commands/starter"
672
+ );
673
+ const starterCommands = [
674
+ {
675
+ name: "help",
676
+ description: `Show ${contextName} context help`,
677
+ content: generateHelpCommandContent(contextName, contextType)
678
+ },
679
+ {
680
+ name: "warm-up",
681
+ description: `Warm up ${contextName} context`,
682
+ content: generateWarmUpCommandContent(contextName)
683
+ }
684
+ ];
685
+ if (contextType === "business") {
686
+ starterCommands.push(
687
+ {
688
+ name: "spec",
689
+ description: "Create product specification",
690
+ content: generateBusinessSpecContent()
691
+ },
692
+ {
693
+ name: "task",
694
+ description: "Create task with story points",
695
+ content: generateBusinessTaskContent()
696
+ }
697
+ );
698
+ } else if (contextType === "technical") {
699
+ starterCommands.push(
700
+ {
701
+ name: "plan",
702
+ description: "Create development plan",
703
+ content: generateTechnicalPlanContent()
704
+ },
705
+ {
706
+ name: "work",
707
+ description: "Continue work on feature",
708
+ content: generateTechnicalWorkContent()
709
+ }
710
+ );
711
+ }
712
+ for (const cmd of starterCommands) {
713
+ const filePath = path8.join(starterPath, `${cmd.name}.md`);
714
+ await fs2.writeFile(filePath, cmd.content, "utf-8");
715
+ }
716
+ }
717
+ async function generateIDELoader(projectRoot, ideName, config = {}) {
718
+ const loaderPath = path8.join(projectRoot, ".onion/ide", ideName);
719
+ await fs2.ensureDir(loaderPath);
720
+ {
721
+ await generateCursorLoader(projectRoot, config);
722
+ }
723
+ }
724
+ async function generateDocsStructure(projectRoot, contexts = []) {
725
+ await fs2.ensureDir(path8.join(projectRoot, "docs/onion"));
726
+ for (const ctx of contexts) {
727
+ await fs2.ensureDir(path8.join(projectRoot, `docs/${ctx}-context`));
728
+ }
729
+ }
730
+ function capitalizeFirst(str) {
731
+ return str.charAt(0).toUpperCase() + str.slice(1);
732
+ }
733
+ function generateHelpCommandContent(contextName, contextType) {
734
+ return `---
735
+ name: help
736
+ description: Show ${contextName} context commands by level
737
+ model: sonnet
738
+ category: ${contextType}
739
+ tags: [help, onboarding]
740
+ version: "4.0.0"
741
+ updated: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
742
+ level: starter
743
+ context: ${contextName}
744
+ ---
745
+
746
+ # ${capitalizeFirst(contextName)} Context Help
747
+
748
+ List all commands in ${contextName} context organized by level.
749
+
750
+ Run: \`/${contextName}/help\` or \`/${contextName}/help --level=starter\`
751
+ `;
752
+ }
753
+ function generateWarmUpCommandContent(contextName) {
754
+ return `---
755
+ name: warm-up
756
+ description: Warm up ${contextName} context with project information
757
+ model: sonnet
758
+ category: ${contextName}
759
+ tags: [context, warm-up]
760
+ version: "4.0.0"
761
+ updated: "${(/* @__PURE__ */ new Date()).toISOString().split("T")[0]}"
762
+ level: starter
763
+ context: ${contextName}
764
+ ---
765
+
766
+ # Warm Up ${capitalizeFirst(contextName)} Context
767
+
768
+ Load project context and recent activity.
769
+ `;
770
+ }
771
+ function generateBusinessSpecContent() {
772
+ return `---
773
+ name: spec
774
+ description: Create product specification
775
+ model: sonnet
776
+ category: business
777
+ tags: [specification, product]
778
+ version: "4.0.0"
779
+ level: starter
780
+ context: business
781
+ ---
782
+
783
+ # Create Product Specification
784
+
785
+ Create detailed product specification for a feature.
786
+ `;
787
+ }
788
+ function generateBusinessTaskContent() {
789
+ return `---
790
+ name: task
791
+ description: Create task with story points
792
+ model: sonnet
793
+ category: business
794
+ tags: [task, story-points]
795
+ version: "4.0.0"
796
+ level: starter
797
+ context: business
798
+ ---
799
+
800
+ # Create Task
801
+
802
+ Create task in task manager with story points and acceptance criteria.
803
+ `;
804
+ }
805
+ function generateTechnicalPlanContent() {
806
+ return `---
807
+ name: plan
808
+ description: Create development plan
809
+ model: sonnet
810
+ category: technical
811
+ tags: [planning, development]
812
+ version: "4.0.0"
813
+ level: starter
814
+ context: technical
815
+ ---
816
+
817
+ # Create Development Plan
818
+
819
+ Create structured development plan with phases and tasks.
820
+ `;
821
+ }
822
+ function generateTechnicalWorkContent() {
823
+ return `---
824
+ name: work
825
+ description: Continue work on active feature
826
+ model: sonnet
827
+ category: technical
828
+ tags: [development, workflow]
829
+ version: "4.0.0"
830
+ level: starter
831
+ context: technical
832
+ ---
833
+
834
+ # Continue Work
835
+
836
+ Continue development on active feature, reading session and identifying next phase.
837
+ `;
838
+ }
839
+ async function generateCursorLoader(projectRoot, config) {
840
+ const cursorRoot = path8.join(projectRoot, ".cursor");
841
+ await fs2.ensureDir(cursorRoot);
842
+ for (const ctx of config.contexts || []) {
843
+ await fs2.ensureDir(path8.join(cursorRoot, "commands", ctx));
844
+ await fs2.ensureDir(path8.join(cursorRoot, "agents", ctx));
845
+ }
846
+ }
847
+ var LoadersGenerator = class {
848
+ projectRoot;
849
+ config;
850
+ constructor(projectRoot, config) {
851
+ this.projectRoot = projectRoot;
852
+ this.config = config;
853
+ }
854
+ async generate() {
855
+ for (const ide of this.config.ides) {
856
+ if (ide === "universal") {
857
+ await this.generateUniversalAgentsMd();
858
+ } else {
859
+ await this.generateIDELoader(ide);
860
+ await this.createIDERootStructure(ide);
861
+ }
862
+ }
863
+ }
864
+ async generateIDELoader(ide) {
865
+ logger.startSpinner(`Generating ${ide} loader...`);
866
+ const loaderPath = path8.join(this.projectRoot, ".onion", "ide", ide);
867
+ await fs2.ensureDir(loaderPath);
868
+ if (ide === "cursor") {
869
+ await this.generateCursorLoader(loaderPath);
870
+ } else if (ide === "windsurf") {
871
+ await this.generateWindsurfLoader(loaderPath);
872
+ } else if (ide === "claude-code" || ide === "claude") {
873
+ await this.generateClaudeLoader(loaderPath);
874
+ }
875
+ logger.stopSpinner(true, `Created ${ide} loader`);
876
+ }
877
+ async generateCursorLoader(loaderPath) {
878
+ const settings = {
879
+ onion: {
880
+ enabled: true,
881
+ root: ".onion",
882
+ loader: ".onion/ide/cursor/onion-loader.js",
883
+ version: "4.0.0-beta.1"
884
+ }
885
+ };
886
+ await fs2.writeJson(path8.join(loaderPath, "settings.json"), settings, { spaces: 2 });
887
+ const readme = `# Cursor IDE Loader
888
+
889
+ This loader syncs Onion resources to Cursor IDE.
890
+
891
+ ## Usage
892
+
893
+ The loader automatically syncs commands and agents from \`.onion/\` to \`.cursor/\`.
894
+
895
+ ## Manual Sync
896
+
897
+ Run: \`node .onion/ide/cursor/onion-loader.js\`
898
+ `;
899
+ await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
900
+ }
901
+ async generateWindsurfLoader(loaderPath) {
902
+ const windsurfConfig = {
903
+ onion: {
904
+ version: "4.0.0-beta.1",
905
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
906
+ contexts: this.config.contexts
907
+ }
908
+ };
909
+ const configPath = path8.join(this.projectRoot, "windsurf.config.yml");
910
+ await fs2.writeFile(configPath, yaml5.stringify(windsurfConfig), "utf-8");
911
+ const readme = `# Windsurf IDE Integration
912
+
913
+ This loader generates windsurf.config.yml for Windsurf IDE.
914
+
915
+ ## Files
916
+
917
+ - \`windsurf.config.yml\` - Configuration file (in project root)
918
+
919
+ ## Usage
920
+
921
+ The config is auto-generated during \`onion init\` or \`onion add windsurf\`.
922
+ `;
923
+ await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
924
+ }
925
+ async generateClaudeLoader(loaderPath) {
926
+ const claudeConfig = {
927
+ onion: {
928
+ version: "4.0.0-beta.1",
929
+ generated: (/* @__PURE__ */ new Date()).toISOString(),
930
+ contexts: this.config.contexts
931
+ }
932
+ };
933
+ const configPath = path8.join(this.projectRoot, "claude.config.json");
934
+ await fs2.writeJson(configPath, claudeConfig, { spaces: 2 });
935
+ const readme = `# Claude Code IDE Integration
936
+
937
+ This loader generates claude.config.json for Claude Code IDE.
938
+
939
+ ## Files
940
+
941
+ - \`claude.config.json\` - Configuration file (in project root)
942
+
943
+ ## Usage
944
+
945
+ The config is auto-generated during \`onion init\` or \`onion add claude-code\`.
946
+ `;
947
+ await fs2.writeFile(path8.join(loaderPath, "README.md"), readme, "utf-8");
948
+ }
949
+ async generateUniversalAgentsMd() {
950
+ logger.startSpinner("Generating universal AGENTS.md...");
951
+ const agentsMdContent = `# Sistema Onion - Universal Agents
952
+
953
+ Este arquivo serve como fallback para IDEs que n\xE3o possuem suporte nativo ao Sistema Onion.
954
+
955
+ ## Comandos Dispon\xEDveis
956
+
957
+ ### Core Commands
958
+ - \`/onion\` - Orquestrador principal
959
+ - \`/warm-up\` - Preparar contexto
960
+
961
+ ${this.config.contexts.map(
962
+ (ctx) => `
963
+ ### ${ctx.charAt(0).toUpperCase() + ctx.slice(1)} Context
964
+ - \`/${ctx}/spec\` - Criar especifica\xE7\xE3o
965
+ - \`/${ctx}/help\` - Ajuda do contexto
966
+ `
967
+ ).join("")}
968
+
969
+ ## Como Usar
970
+
971
+ Mencione os comandos acima no chat do seu IDE.
972
+
973
+ ## Configura\xE7\xE3o
974
+
975
+ Este projeto usa Sistema Onion v4.0. Para melhor experi\xEAncia, use:
976
+ - Cursor IDE
977
+ - Windsurf
978
+ - Claude Code
979
+
980
+ Ou acesse a documenta\xE7\xE3o em \`.onion/README.md\`
981
+ `;
982
+ const universalPath = path8.join(this.projectRoot, ".onion", "ide", "universal");
983
+ await fs2.ensureDir(universalPath);
984
+ await fs2.writeFile(path8.join(universalPath, "AGENTS.md"), agentsMdContent, "utf-8");
985
+ logger.stopSpinner(true, "Created universal AGENTS.md");
986
+ }
987
+ // Criar estrutura na raiz do projeto para o IDE reconhecer
988
+ async createIDERootStructure(ide) {
989
+ if (ide === "cursor") {
990
+ await this.createCursorRootStructure();
991
+ }
992
+ }
993
+ async createCursorRootStructure() {
994
+ logger.startSpinner("Creating .cursor/ structure...");
995
+ const cursorRoot = path8.join(this.projectRoot, ".cursor");
996
+ await fs2.ensureDir(cursorRoot);
997
+ const commandsPath = path8.join(cursorRoot, "commands");
998
+ const agentsPath = path8.join(cursorRoot, "agents");
999
+ await fs2.ensureDir(commandsPath);
1000
+ await fs2.ensureDir(agentsPath);
1001
+ for (const context of this.config.contexts) {
1002
+ await fs2.ensureDir(path8.join(commandsPath, context));
1003
+ await fs2.ensureDir(path8.join(agentsPath, context));
1004
+ await this.createStarterCommands(context, path8.join(commandsPath, context));
1005
+ }
1006
+ const cursorRulesContent = `# Sistema Onion v4.0
1007
+
1008
+ Este projeto usa o Sistema Onion - Multi-Context Development Orchestrator.
1009
+
1010
+ ## Estrutura
1011
+
1012
+ - **Comandos**: .cursor/commands/
1013
+ - **Agentes**: .cursor/agents/
1014
+ - **Fonte**: .onion/
1015
+
1016
+ ## Contextos Dispon\xEDveis
1017
+
1018
+ ${this.config.contexts.map((ctx) => `- **${ctx}**: /${ctx}/*`).join("\n")}
1019
+
1020
+ ## Documenta\xE7\xE3o
1021
+
1022
+ Consulte \`.onion/README.md\` para mais informa\xE7\xF5es.
1023
+
1024
+ ---
1025
+
1026
+ **Gerado por**: @onion/cli v4.0.0
1027
+ `;
1028
+ await fs2.writeFile(path8.join(cursorRoot, ".cursorrules"), cursorRulesContent, "utf-8");
1029
+ logger.stopSpinner(true, "Created .cursor/ structure");
1030
+ }
1031
+ async createStarterCommands(context, commandsPath) {
1032
+ const helpContent = `---
1033
+ name: help
1034
+ description: Ajuda do contexto ${context}
1035
+ category: ${context}
1036
+ version: "4.0.0"
1037
+ ---
1038
+
1039
+ # \u{1F4DA} ${context.charAt(0).toUpperCase() + context.slice(1)} Context - Help
1040
+
1041
+ Comandos dispon\xEDveis neste contexto.
1042
+
1043
+ ## \u{1F7E2} Starter Commands (essenciais)
1044
+
1045
+ - \`/${context}/help\` - Esta ajuda
1046
+
1047
+ ## \u{1F4D6} Documenta\xE7\xE3o
1048
+
1049
+ Veja documenta\xE7\xE3o completa em \`docs/${context}-context/\`
1050
+
1051
+ ---
1052
+
1053
+ **Fonte**: .onion/contexts/${context}/commands/starter/help.md
1054
+ **Auto-gerado por**: @onion/cli
1055
+ `;
1056
+ await fs2.writeFile(path8.join(commandsPath, "help.md"), helpContent, "utf-8");
1057
+ }
1058
+ };
1059
+
1060
+ // src/commands/add.ts
1061
+ async function add(options = {}) {
1062
+ try {
1063
+ const projectRoot = process.cwd();
1064
+ const project = await detectOnionV4Structure(projectRoot);
1065
+ if (!project) {
1066
+ logger.error("\u274C This is not an Onion v4 project");
1067
+ logger.info('Run "onion init" first to initialize the system');
1068
+ process.exit(1);
1069
+ }
1070
+ logger.title("\u{1F9C5} Add to Onion System");
1071
+ logger.break();
1072
+ const { type } = await inquirer.prompt([
1073
+ {
1074
+ type: "list",
1075
+ name: "type",
1076
+ message: "What would you like to add?",
1077
+ choices: [
1078
+ { name: "\u{1F4E6} New Context", value: "context" },
1079
+ { name: "\u{1F4BB} New IDE Integration", value: "ide" },
1080
+ { name: "\u274C Cancel", value: "cancel" }
1081
+ ]
1082
+ }
1083
+ ]);
1084
+ if (type === "cancel") {
1085
+ logger.info("Cancelled");
1086
+ return;
1087
+ }
1088
+ if (type === "context") {
1089
+ await addContext2(project);
1090
+ } else if (type === "ide") {
1091
+ await addIDE2(project);
1092
+ }
1093
+ } catch (error) {
1094
+ logger.break();
1095
+ logger.error("\u274C Add command failed:");
1096
+ logger.error(error.message);
1097
+ if (options.debug) {
1098
+ console.error(error);
1099
+ }
1100
+ process.exit(1);
1101
+ }
1102
+ }
1103
+ async function addContext2(project) {
1104
+ const { contextName } = await inquirer.prompt([
1105
+ {
1106
+ type: "input",
1107
+ name: "contextName",
1108
+ message: 'Context name (lowercase, e.g., "customer-success"):',
1109
+ validate: (input) => {
1110
+ try {
1111
+ validateContextName(input);
1112
+ return true;
1113
+ } catch (err) {
1114
+ return err.message;
1115
+ }
1116
+ }
1117
+ }
1118
+ ]);
1119
+ const config = await readConfig(project.root);
1120
+ if (config.contexts.includes(contextName)) {
1121
+ logger.error(`\u274C Context "${contextName}" already exists`);
1122
+ process.exit(1);
1123
+ }
1124
+ const { contextType } = await inquirer.prompt([
1125
+ {
1126
+ type: "list",
1127
+ name: "contextType",
1128
+ message: "Context type:",
1129
+ choices: [
1130
+ { name: "\u{1F4CA} Business/Product", value: "business" },
1131
+ { name: "\u2699\uFE0F Technical/Engineering", value: "technical" },
1132
+ { name: "\u{1F3A8} Custom", value: "custom" }
1133
+ ]
1134
+ }
1135
+ ]);
1136
+ logger.break();
1137
+ logger.startSpinner(`Creating context "${contextName}"...`);
1138
+ try {
1139
+ await generateContextStructure(project.root, contextName, {
1140
+ includeREADME: true,
1141
+ includeConfig: true
1142
+ });
1143
+ await generateStarterCommands(project.root, contextName, contextType);
1144
+ await addContext(project.root, contextName);
1145
+ for (const ide of config.ides) {
1146
+ const loaderConfig = {
1147
+ contexts: config.contexts,
1148
+ ides: [ide]
1149
+ };
1150
+ const loadersGen = new LoadersGenerator(project.root, loaderConfig);
1151
+ await loadersGen.generateIDELoader(ide);
1152
+ if (ide === "cursor") {
1153
+ try {
1154
+ const loaderPath = path8.join(project.root, ".onion/ide/cursor/onion-loader.js");
1155
+ if (fs2.existsSync(loaderPath)) {
1156
+ const loaderModule = await import(loaderPath);
1157
+ const loader = loaderModule.getLoader(project.root);
1158
+ loader.syncToCursor();
1159
+ }
1160
+ } catch {
1161
+ }
1162
+ }
1163
+ }
1164
+ logger.stopSpinner(true, `Context "${contextName}" created successfully`);
1165
+ logger.break();
1166
+ logger.success("Next steps:");
1167
+ logger.info(` 1. Explore: /${contextName}/help`);
1168
+ logger.info(` 2. Add commands: .onion/contexts/${contextName}/commands/`);
1169
+ logger.info(` 3. Add agents: .onion/contexts/${contextName}/agents/`);
1170
+ logger.break();
1171
+ } catch (error) {
1172
+ logger.stopSpinner(false, "Failed to create context");
1173
+ throw error;
1174
+ }
1175
+ }
1176
+ async function addIDE2(project) {
1177
+ const config = await readConfig(project.root);
1178
+ const availableIDEs = ["cursor", "windsurf", "claude"];
1179
+ const notConfigured = availableIDEs.filter((ide) => !config.ides.includes(ide));
1180
+ if (notConfigured.length === 0) {
1181
+ logger.warn("\u26A0\uFE0F All supported IDEs are already configured");
1182
+ logger.info("Configured IDEs: " + config.ides.join(", "));
1183
+ return;
1184
+ }
1185
+ const { ideName } = await inquirer.prompt([
1186
+ {
1187
+ type: "list",
1188
+ name: "ideName",
1189
+ message: "Which IDE would you like to add?",
1190
+ choices: notConfigured.map((ide) => ({
1191
+ name: capitalizeFirst2(ide),
1192
+ value: ide
1193
+ }))
1194
+ }
1195
+ ]);
1196
+ logger.break();
1197
+ logger.startSpinner(`Configuring ${ideName}...`);
1198
+ try {
1199
+ const loaderConfig = {
1200
+ contexts: config.contexts,
1201
+ ides: [ideName]
1202
+ };
1203
+ const loadersGen = new LoadersGenerator(project.root, loaderConfig);
1204
+ await loadersGen.generateIDELoader(ideName);
1205
+ await addIDE(project.root, ideName);
1206
+ if (ideName === "cursor") {
1207
+ try {
1208
+ const loaderPath = path8.join(project.root, ".onion/ide/cursor/onion-loader.js");
1209
+ if (fs2.existsSync(loaderPath)) {
1210
+ const loaderModule = await import(loaderPath);
1211
+ const loader = loaderModule.getLoader(project.root);
1212
+ const result = loader.syncToCursor();
1213
+ logger.info(` Synced ${result.commandsSynced} commands and ${result.agentsSynced} agents`);
1214
+ }
1215
+ } catch (err) {
1216
+ logger.warn(` Could not sync: ${err.message}`);
1217
+ }
1218
+ }
1219
+ logger.stopSpinner(true, `IDE "${ideName}" configured successfully`);
1220
+ logger.break();
1221
+ logger.success("Next steps:");
1222
+ if (ideName === "cursor") {
1223
+ logger.info(" 1. Restart Cursor to load commands");
1224
+ logger.info(" 2. Test: Run any command like /business/help");
1225
+ } else if (ideName === "windsurf") {
1226
+ logger.info(" 1. Restart Windsurf");
1227
+ logger.info(" 2. Commands will be available via Windsurf interface");
1228
+ } else if (ideName === "claude") {
1229
+ logger.info(" 1. Restart Claude Code");
1230
+ logger.info(" 2. Commands available via @onion agent");
1231
+ }
1232
+ logger.break();
1233
+ } catch (error) {
1234
+ logger.stopSpinner(false, "Failed to configure IDE");
1235
+ throw error;
1236
+ }
1237
+ }
1238
+ function capitalizeFirst2(str) {
1239
+ return str.charAt(0).toUpperCase() + str.slice(1);
1240
+ }
1241
+ init_config();
1242
+ async function detectV3Commands(projectRoot) {
1243
+ const v3 = await detectOnionV3Structure(projectRoot);
1244
+ if (!v3) {
1245
+ return [];
1246
+ }
1247
+ const commands = [];
1248
+ for (const [category, files] of Object.entries(v3.commands)) {
1249
+ const categoryPath = path8.join(v3.commandsDir, category);
1250
+ for (const file of files) {
1251
+ const filePath = path8.join(categoryPath, file);
1252
+ const metadata = await extractCommandMetadata(filePath);
1253
+ commands.push({
1254
+ name: path8.basename(file, ".md"),
1255
+ category,
1256
+ path: filePath,
1257
+ relativePath: path8.relative(projectRoot, filePath),
1258
+ metadata
1259
+ });
1260
+ }
1261
+ }
1262
+ return commands;
1263
+ }
1264
+ async function detectV3Agents(projectRoot) {
1265
+ const v3 = await detectOnionV3Structure(projectRoot);
1266
+ if (!v3) {
1267
+ return [];
1268
+ }
1269
+ const agents = [];
1270
+ for (const [category, files] of Object.entries(v3.agents)) {
1271
+ const categoryPath = path8.join(v3.agentsDir, category);
1272
+ for (const file of files) {
1273
+ const filePath = path8.join(categoryPath, file);
1274
+ const metadata = await extractAgentMetadata(filePath);
1275
+ agents.push({
1276
+ name: path8.basename(file, ".md"),
1277
+ category,
1278
+ path: filePath,
1279
+ relativePath: path8.relative(projectRoot, filePath),
1280
+ metadata
1281
+ });
1282
+ }
1283
+ }
1284
+ return agents;
1285
+ }
1286
+ async function detectV3CustomFiles(projectRoot) {
1287
+ const custom = [];
1288
+ const cursorDir = path8.join(projectRoot, ".cursor");
1289
+ if (!await fs2.pathExists(cursorDir)) {
1290
+ return custom;
1291
+ }
1292
+ const subdirs = ["commands", "agents", "rules", "utils"];
1293
+ for (const subdir of subdirs) {
1294
+ const dirPath = path8.join(cursorDir, subdir);
1295
+ if (await fs2.pathExists(dirPath)) {
1296
+ const files = await findCustomFiles(dirPath, projectRoot);
1297
+ custom.push(...files);
1298
+ }
1299
+ }
1300
+ return custom;
1301
+ }
1302
+ async function analyzeV3Structure(projectRoot) {
1303
+ const commands = await detectV3Commands(projectRoot);
1304
+ const agents = await detectV3Agents(projectRoot);
1305
+ const customFiles = await detectV3CustomFiles(projectRoot);
1306
+ const v3 = await detectOnionV3Structure(projectRoot);
1307
+ const stats = {
1308
+ totalCommands: commands.length,
1309
+ totalAgents: agents.length,
1310
+ totalCustomFiles: customFiles.length,
1311
+ commandsByCategory: {},
1312
+ agentsByCategory: {},
1313
+ hasRules: v3?.hasRules || false,
1314
+ hasSessions: v3?.hasSessions || false
1315
+ };
1316
+ for (const cmd of commands) {
1317
+ stats.commandsByCategory[cmd.category] = (stats.commandsByCategory[cmd.category] || 0) + 1;
1318
+ }
1319
+ for (const agent of agents) {
1320
+ stats.agentsByCategory[agent.category] = (stats.agentsByCategory[agent.category] || 0) + 1;
1321
+ }
1322
+ return {
1323
+ commands,
1324
+ agents,
1325
+ customFiles,
1326
+ stats,
1327
+ v3Structure: v3
1328
+ };
1329
+ }
1330
+ async function extractCommandMetadata(filePath) {
1331
+ try {
1332
+ const content = await fs2.readFile(filePath, "utf-8");
1333
+ const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
1334
+ if (!yamlMatch || !yamlMatch[1]) {
1335
+ return { raw: true };
1336
+ }
1337
+ const metadata = yaml5.parse(yamlMatch[1]);
1338
+ return {
1339
+ ...metadata,
1340
+ hasYAML: true,
1341
+ contentLength: content.length,
1342
+ linesCount: content.split("\n").length
1343
+ };
1344
+ } catch (error) {
1345
+ return {
1346
+ error: error.message,
1347
+ raw: true
1348
+ };
1349
+ }
1350
+ }
1351
+ async function extractAgentMetadata(filePath) {
1352
+ try {
1353
+ const content = await fs2.readFile(filePath, "utf-8");
1354
+ const yamlMatch = content.match(/^---\n([\s\S]*?)\n---/);
1355
+ if (!yamlMatch || !yamlMatch[1]) {
1356
+ return { raw: true };
1357
+ }
1358
+ const metadata = yaml5.parse(yamlMatch[1]);
1359
+ return {
1360
+ ...metadata,
1361
+ hasYAML: true,
1362
+ contentLength: content.length,
1363
+ linesCount: content.split("\n").length
1364
+ };
1365
+ } catch (error) {
1366
+ return {
1367
+ error: error.message,
1368
+ raw: true
1369
+ };
1370
+ }
1371
+ }
1372
+ async function findCustomFiles(dirPath, projectRoot) {
1373
+ const custom = [];
1374
+ try {
1375
+ const entries = await fs2.readdir(dirPath, { withFileTypes: true });
1376
+ for (const entry of entries) {
1377
+ const fullPath = path8.join(dirPath, entry.name);
1378
+ if (entry.isDirectory()) {
1379
+ const subFiles = await findCustomFiles(fullPath, projectRoot);
1380
+ custom.push(...subFiles);
1381
+ } else if (entry.isFile() && entry.name.endsWith(".md")) {
1382
+ const stats = await fs2.stat(fullPath);
1383
+ const content = await fs2.readFile(fullPath, "utf-8");
1384
+ if (content.length > 100) {
1385
+ custom.push({
1386
+ name: entry.name,
1387
+ path: fullPath,
1388
+ relativePath: path8.relative(projectRoot, fullPath),
1389
+ size: stats.size,
1390
+ modified: stats.mtime
1391
+ });
1392
+ }
1393
+ }
1394
+ }
1395
+ } catch {
1396
+ }
1397
+ return custom;
1398
+ }
1399
+
1400
+ // src/migrator/mapper.ts
1401
+ var CATEGORY_TO_CONTEXT = {
1402
+ // Commands
1403
+ product: "business",
1404
+ business: "business",
1405
+ engineer: "technical",
1406
+ git: "technical",
1407
+ docs: "technical",
1408
+ test: "technical",
1409
+ meta: "core",
1410
+ validate: "core",
1411
+ quick: "core",
1412
+ global: "core",
1413
+ // Agents
1414
+ development: "technical",
1415
+ compliance: "business",
1416
+ review: "technical",
1417
+ testing: "technical",
1418
+ research: "business"
1419
+ };
1420
+ var KNOWN_STARTER_COMMANDS = [
1421
+ "spec",
1422
+ "task",
1423
+ "estimate",
1424
+ "refine",
1425
+ "warm-up",
1426
+ "work",
1427
+ "plan",
1428
+ "pr",
1429
+ "docs",
1430
+ "sync",
1431
+ "init",
1432
+ "help"
1433
+ ];
1434
+ var KNOWN_ADVANCED_COMMANDS = [
1435
+ "bump",
1436
+ "hotfix",
1437
+ "e2e",
1438
+ "presentation",
1439
+ "branding",
1440
+ "analyze-pain-price",
1441
+ "transform-consolidated",
1442
+ "release-start",
1443
+ "release-finish",
1444
+ "feature-start",
1445
+ "feature-finish"
1446
+ ];
1447
+ function mapCommandToContext(command) {
1448
+ const context = CATEGORY_TO_CONTEXT[command.category] || "custom";
1449
+ const level = inferCommandLevel(command);
1450
+ const newPath = `.onion/contexts/${context}/commands/${level}/${command.name}.md`;
1451
+ return {
1452
+ context,
1453
+ level,
1454
+ newPath,
1455
+ oldPath: command.path,
1456
+ name: command.name,
1457
+ category: command.category
1458
+ };
1459
+ }
1460
+ function mapAgentToContext(agent) {
1461
+ const context = CATEGORY_TO_CONTEXT[agent.category] || "custom";
1462
+ if (agent.name.includes("onion") || agent.name.includes("metaspec")) {
1463
+ return {
1464
+ context: "core",
1465
+ newPath: `.onion/core/agents/${agent.name}.md`,
1466
+ oldPath: agent.path,
1467
+ name: agent.name,
1468
+ category: agent.category
1469
+ };
1470
+ }
1471
+ const newPath = `.onion/contexts/${context}/agents/${agent.name}.md`;
1472
+ return {
1473
+ context,
1474
+ newPath,
1475
+ oldPath: agent.path,
1476
+ name: agent.name,
1477
+ category: agent.category
1478
+ };
1479
+ }
1480
+ function inferCommandLevel(command) {
1481
+ const name = command.name.toLowerCase();
1482
+ if (KNOWN_STARTER_COMMANDS.includes(name)) {
1483
+ return "starter";
1484
+ }
1485
+ if (KNOWN_ADVANCED_COMMANDS.includes(name)) {
1486
+ return "advanced";
1487
+ }
1488
+ if (command.metadata) {
1489
+ if (command.metadata.level) {
1490
+ return command.metadata.level;
1491
+ }
1492
+ const tags = command.metadata.tags || [];
1493
+ const description = (command.metadata.description || "").toLowerCase();
1494
+ if (tags.includes("starter") || tags.includes("basic") || name.includes("help") || name.includes("warm-up")) {
1495
+ return "starter";
1496
+ }
1497
+ if (tags.includes("advanced") || tags.includes("release") || name.includes("release") || name.includes("hotfix") || description.includes("advanced") || description.includes("complex")) {
1498
+ return "advanced";
1499
+ }
1500
+ }
1501
+ return "intermediate";
1502
+ }
1503
+ function buildMigrationPlan(analysis) {
1504
+ const contextsSet = /* @__PURE__ */ new Set();
1505
+ const commands = [];
1506
+ const agents = [];
1507
+ const summary = {
1508
+ totalCommands: analysis.commands.length,
1509
+ totalAgents: analysis.agents.length,
1510
+ commandsByContext: {},
1511
+ agentsByContext: {},
1512
+ commandsByLevel: {
1513
+ starter: 0,
1514
+ intermediate: 0,
1515
+ advanced: 0
1516
+ }
1517
+ };
1518
+ for (const command of analysis.commands) {
1519
+ const mapping = mapCommandToContext(command);
1520
+ commands.push(mapping);
1521
+ contextsSet.add(mapping.context);
1522
+ summary.commandsByContext[mapping.context] = (summary.commandsByContext[mapping.context] || 0) + 1;
1523
+ const level = mapping.level;
1524
+ if (level in summary.commandsByLevel) {
1525
+ summary.commandsByLevel[level]++;
1526
+ }
1527
+ }
1528
+ for (const agent of analysis.agents) {
1529
+ const mapping = mapAgentToContext(agent);
1530
+ agents.push(mapping);
1531
+ contextsSet.add(mapping.context);
1532
+ summary.agentsByContext[mapping.context] = (summary.agentsByContext[mapping.context] || 0) + 1;
1533
+ }
1534
+ return {
1535
+ commands,
1536
+ agents,
1537
+ contexts: Array.from(contextsSet),
1538
+ summary
1539
+ };
1540
+ }
1541
+ function validateMigrationPlan(plan) {
1542
+ const issues = [];
1543
+ if (plan.contexts.length === 0) {
1544
+ issues.push("Nenhum contexto identificado");
1545
+ }
1546
+ if (plan.commands.length === 0 && plan.agents.length === 0) {
1547
+ issues.push("Nenhum comando ou agente para migrar");
1548
+ }
1549
+ const newPaths = /* @__PURE__ */ new Set();
1550
+ for (const cmd of plan.commands) {
1551
+ if (newPaths.has(cmd.newPath)) {
1552
+ issues.push(`Path duplicado: ${cmd.newPath}`);
1553
+ }
1554
+ newPaths.add(cmd.newPath);
1555
+ }
1556
+ for (const agent of plan.agents) {
1557
+ if (newPaths.has(agent.newPath)) {
1558
+ issues.push(`Path duplicado: ${agent.newPath}`);
1559
+ }
1560
+ newPaths.add(agent.newPath);
1561
+ }
1562
+ return {
1563
+ valid: issues.length === 0,
1564
+ issues
1565
+ };
1566
+ }
1567
+ function generateMigrationReport(plan) {
1568
+ const lines = [];
1569
+ lines.push("# Plano de Migra\xE7\xE3o Onion v3 \u2192 v4\n");
1570
+ lines.push("## \u{1F4CA} Resumo\n");
1571
+ lines.push(`- **Comandos**: ${plan.summary.totalCommands}`);
1572
+ lines.push(`- **Agentes**: ${plan.summary.totalAgents}`);
1573
+ lines.push(`- **Contextos**: ${plan.contexts.join(", ")}
1574
+ `);
1575
+ lines.push("## \u{1F4E6} Comandos por Contexto\n");
1576
+ for (const [context, count] of Object.entries(plan.summary.commandsByContext)) {
1577
+ lines.push(`- **${context}**: ${count} comandos`);
1578
+ }
1579
+ lines.push("");
1580
+ lines.push("## \u{1F4CA} Comandos por N\xEDvel\n");
1581
+ lines.push(`- **Starter**: ${plan.summary.commandsByLevel.starter}`);
1582
+ lines.push(`- **Intermediate**: ${plan.summary.commandsByLevel.intermediate}`);
1583
+ lines.push(`- **Advanced**: ${plan.summary.commandsByLevel.advanced}
1584
+ `);
1585
+ if (plan.summary.totalAgents > 0) {
1586
+ lines.push("## \u{1F916} Agentes por Contexto\n");
1587
+ for (const [context, count] of Object.entries(plan.summary.agentsByContext)) {
1588
+ lines.push(`- **${context}**: ${count} agentes`);
1589
+ }
1590
+ lines.push("");
1591
+ }
1592
+ return lines.join("\n");
1593
+ }
1594
+ async function transformCommandFile(sourceFilePath, targetFilePath, targetContext, targetLevel) {
1595
+ const content = await fs2.readFile(sourceFilePath, "utf-8");
1596
+ const yamlMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1597
+ if (!yamlMatch || !yamlMatch[1] || yamlMatch[2] === void 0) {
1598
+ const newContent2 = createCommandYAMLHeader(path8.basename(sourceFilePath, ".md"), targetContext, targetLevel) + "\n\n" + content;
1599
+ await fs2.ensureDir(path8.dirname(targetFilePath));
1600
+ await fs2.writeFile(targetFilePath, newContent2, "utf-8");
1601
+ return;
1602
+ }
1603
+ const existingYAML = yaml5.parse(yamlMatch[1]);
1604
+ const body = yamlMatch[2];
1605
+ const updatedYAML = {
1606
+ ...existingYAML,
1607
+ context: targetContext,
1608
+ level: targetLevel,
1609
+ version: existingYAML.version || "4.0.0",
1610
+ updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1611
+ };
1612
+ const newContent = "---\n" + yaml5.stringify(updatedYAML) + "---\n" + body;
1613
+ await fs2.ensureDir(path8.dirname(targetFilePath));
1614
+ await fs2.writeFile(targetFilePath, newContent, "utf-8");
1615
+ }
1616
+ async function transformAgentFile(sourceFilePath, targetFilePath, targetContext) {
1617
+ const content = await fs2.readFile(sourceFilePath, "utf-8");
1618
+ const yamlMatch = content.match(/^---\n([\s\S]*?)\n---\n([\s\S]*)$/);
1619
+ if (!yamlMatch || !yamlMatch[1] || yamlMatch[2] === void 0) {
1620
+ const newContent2 = createAgentYAMLHeader(path8.basename(sourceFilePath, ".md"), targetContext) + "\n\n" + content;
1621
+ await fs2.ensureDir(path8.dirname(targetFilePath));
1622
+ await fs2.writeFile(targetFilePath, newContent2, "utf-8");
1623
+ return;
1624
+ }
1625
+ const existingYAML = yaml5.parse(yamlMatch[1]);
1626
+ const body = yamlMatch[2];
1627
+ const updatedYAML = {
1628
+ ...existingYAML,
1629
+ context: targetContext,
1630
+ version: existingYAML.version || "4.0.0",
1631
+ updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0]
1632
+ };
1633
+ const newContent = "---\n" + yaml5.stringify(updatedYAML) + "---\n" + body;
1634
+ await fs2.ensureDir(path8.dirname(targetFilePath));
1635
+ await fs2.writeFile(targetFilePath, newContent, "utf-8");
1636
+ }
1637
+ async function createBackwardCompatSymlink(projectRoot, v3Path, v4Path) {
1638
+ const v3FullPath = path8.join(projectRoot, v3Path);
1639
+ const v4FullPath = path8.join(projectRoot, v4Path);
1640
+ await fs2.ensureDir(path8.dirname(v3FullPath));
1641
+ const relativePath = path8.relative(path8.dirname(v3FullPath), v4FullPath);
1642
+ try {
1643
+ if (await fs2.pathExists(v3FullPath)) {
1644
+ await fs2.remove(v3FullPath);
1645
+ }
1646
+ await fs2.symlink(relativePath, v3FullPath);
1647
+ } catch {
1648
+ console.warn(`Warning: Could not create symlink ${v3Path} \u2192 ${v4Path}`);
1649
+ }
1650
+ }
1651
+ async function createSymlinks(projectRoot, mappings) {
1652
+ let created = 0;
1653
+ for (const mapping of mappings) {
1654
+ try {
1655
+ await createBackwardCompatSymlink(projectRoot, mapping.oldPath, mapping.newPath);
1656
+ created++;
1657
+ } catch {
1658
+ }
1659
+ }
1660
+ return created;
1661
+ }
1662
+ async function copyRules(projectRoot) {
1663
+ const v3RulesPath = path8.join(projectRoot, ".cursor/rules");
1664
+ const v4RulesPath = path8.join(projectRoot, ".onion/core/rules");
1665
+ if (!await fs2.pathExists(v3RulesPath)) {
1666
+ return 0;
1667
+ }
1668
+ const files = await fs2.readdir(v3RulesPath);
1669
+ let copied = 0;
1670
+ for (const file of files) {
1671
+ if (file.endsWith(".mdc") || file.endsWith(".md")) {
1672
+ await fs2.copy(path8.join(v3RulesPath, file), path8.join(v4RulesPath, file));
1673
+ copied++;
1674
+ }
1675
+ }
1676
+ return copied;
1677
+ }
1678
+ async function copySessions(projectRoot, targetContext = "technical") {
1679
+ const v3SessionsPath = path8.join(projectRoot, ".cursor/sessions");
1680
+ const v4SessionsPath = path8.join(projectRoot, `.onion/contexts/${targetContext}/sessions`);
1681
+ if (!await fs2.pathExists(v3SessionsPath)) {
1682
+ return 0;
1683
+ }
1684
+ await fs2.copy(v3SessionsPath, v4SessionsPath);
1685
+ const dirs = await fs2.readdir(v3SessionsPath);
1686
+ let count = 0;
1687
+ for (const name of dirs) {
1688
+ const stat = await fs2.stat(path8.join(v3SessionsPath, name));
1689
+ if (stat.isDirectory()) {
1690
+ count++;
1691
+ }
1692
+ }
1693
+ return count;
1694
+ }
1695
+ function createCommandYAMLHeader(name, context, level) {
1696
+ const header = {
1697
+ name,
1698
+ description: `${name} command`,
1699
+ model: "sonnet",
1700
+ category: context,
1701
+ tags: [level],
1702
+ version: "4.0.0",
1703
+ updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1704
+ level,
1705
+ context
1706
+ };
1707
+ return "---\n" + yaml5.stringify(header) + "---";
1708
+ }
1709
+ function createAgentYAMLHeader(name, context) {
1710
+ const header = {
1711
+ name,
1712
+ description: `${name} agent`,
1713
+ model: "sonnet",
1714
+ category: context,
1715
+ tags: ["agent"],
1716
+ expertise: [],
1717
+ version: "4.0.0",
1718
+ updated: (/* @__PURE__ */ new Date()).toISOString().split("T")[0],
1719
+ context
1720
+ };
1721
+ return "---\n" + yaml5.stringify(header) + "---";
1722
+ }
1723
+
1724
+ // src/commands/migrate.ts
1725
+ async function migrate(options = {}) {
1726
+ try {
1727
+ const projectRoot = process.cwd();
1728
+ logger.title("\u{1F9C5} Onion Migration: v3 \u2192 v4");
1729
+ logger.break();
1730
+ const v3 = await detectOnionV3Structure(projectRoot);
1731
+ if (!v3) {
1732
+ logger.error("\u274C This is not an Onion v3 project");
1733
+ logger.info("Looking for .cursor/ structure with commands/ and agents/");
1734
+ logger.break();
1735
+ logger.info("To initialize a new Onion v4 project, run: onion init");
1736
+ process.exit(1);
1737
+ }
1738
+ logger.success("\u2705 Onion v3 project detected");
1739
+ logger.break();
1740
+ const eligibility = validateMigrationEligibility(v3);
1741
+ if (!eligibility.canMigrate) {
1742
+ logger.error("\u274C Cannot migrate this project:");
1743
+ for (const issue of eligibility.issues) {
1744
+ logger.error(` \u2022 ${issue}`);
1745
+ }
1746
+ process.exit(1);
1747
+ }
1748
+ logger.startSpinner("Analyzing v3 structure...");
1749
+ const analysis = await analyzeV3Structure(projectRoot);
1750
+ logger.stopSpinner(true, "Analysis complete");
1751
+ logger.break();
1752
+ logger.info("\u{1F4CA} Found:");
1753
+ logger.info(` \u2022 ${analysis.stats.totalCommands} commands`);
1754
+ logger.info(` \u2022 ${analysis.stats.totalAgents} agents`);
1755
+ if (analysis.stats.hasRules) logger.info(" \u2022 Rules directory");
1756
+ if (analysis.stats.hasSessions) logger.info(" \u2022 Sessions directory");
1757
+ logger.break();
1758
+ const plan = buildMigrationPlan(analysis);
1759
+ const planValidation = validateMigrationPlan(plan);
1760
+ if (!planValidation.valid) {
1761
+ logger.error("\u274C Migration plan has issues:");
1762
+ for (const issue of planValidation.issues) {
1763
+ logger.error(` \u2022 ${issue}`);
1764
+ }
1765
+ process.exit(1);
1766
+ }
1767
+ logger.title("\u{1F5FA}\uFE0F Migration Plan:");
1768
+ logger.break();
1769
+ logger.info(`\u{1F4E6} Contexts to create: ${plan.contexts.join(", ")}`);
1770
+ logger.break();
1771
+ logger.info("\u{1F4DD} Commands:");
1772
+ for (const [context, count] of Object.entries(plan.summary.commandsByContext)) {
1773
+ logger.info(` \u2022 ${context}: ${count} commands`);
1774
+ }
1775
+ logger.break();
1776
+ logger.info("\u{1F916} Agents:");
1777
+ for (const [context, count] of Object.entries(plan.summary.agentsByContext)) {
1778
+ logger.info(` \u2022 ${context}: ${count} agents`);
1779
+ }
1780
+ logger.break();
1781
+ const { confirm } = await inquirer.prompt([
1782
+ {
1783
+ type: "confirm",
1784
+ name: "confirm",
1785
+ message: "Proceed with migration?",
1786
+ default: false
1787
+ }
1788
+ ]);
1789
+ if (!confirm) {
1790
+ logger.warn("\u274C Migration cancelled");
1791
+ return;
1792
+ }
1793
+ logger.break();
1794
+ logger.title("\u{1F680} Starting migration...");
1795
+ logger.break();
1796
+ if (!options.noBackup) {
1797
+ logger.startSpinner("Creating backup...");
1798
+ const backupPath = path8.join(projectRoot, ".cursor-backup");
1799
+ await fs2.copy(path8.join(projectRoot, ".cursor"), backupPath);
1800
+ logger.stopSpinner(true, `Backup created: .cursor-backup/`);
1801
+ }
1802
+ logger.startSpinner("Creating v4 structure...");
1803
+ await generateCoreStructure(projectRoot);
1804
+ for (const contextName of plan.contexts) {
1805
+ await generateContextStructure(projectRoot, contextName, {
1806
+ includeREADME: true,
1807
+ includeConfig: true
1808
+ });
1809
+ }
1810
+ await generateIDELoader(projectRoot, "cursor", {
1811
+ contexts: plan.contexts
1812
+ });
1813
+ await generateDocsStructure(projectRoot, plan.contexts);
1814
+ logger.stopSpinner(true, "Structure created");
1815
+ logger.startSpinner(`Migrating ${plan.commands.length} commands...`);
1816
+ for (const mapping of plan.commands) {
1817
+ const targetPath = path8.join(projectRoot, mapping.newPath);
1818
+ await transformCommandFile(
1819
+ mapping.oldPath,
1820
+ targetPath,
1821
+ mapping.context,
1822
+ mapping.level
1823
+ );
1824
+ }
1825
+ logger.stopSpinner(true, `${plan.commands.length} commands migrated`);
1826
+ logger.startSpinner(`Migrating ${plan.agents.length} agents...`);
1827
+ for (const mapping of plan.agents) {
1828
+ const targetPath = path8.join(projectRoot, mapping.newPath);
1829
+ await transformAgentFile(mapping.oldPath, targetPath, mapping.context);
1830
+ }
1831
+ logger.stopSpinner(true, `${plan.agents.length} agents migrated`);
1832
+ if (analysis.stats.hasRules) {
1833
+ logger.startSpinner("Copying rules...");
1834
+ const copied = await copyRules(projectRoot);
1835
+ logger.stopSpinner(true, `${copied} rules copied`);
1836
+ }
1837
+ if (analysis.stats.hasSessions) {
1838
+ logger.startSpinner("Copying sessions...");
1839
+ await copySessions(projectRoot, "technical");
1840
+ logger.stopSpinner(true, `Sessions copied`);
1841
+ }
1842
+ logger.startSpinner("Creating symlinks for backward compatibility...");
1843
+ const symlinkMappings = [
1844
+ ...plan.commands.map((m) => ({
1845
+ oldPath: path8.relative(projectRoot, m.oldPath),
1846
+ newPath: m.newPath
1847
+ })),
1848
+ ...plan.agents.map((m) => ({
1849
+ oldPath: path8.relative(projectRoot, m.oldPath),
1850
+ newPath: m.newPath
1851
+ }))
1852
+ ];
1853
+ const symlinkCount = await createSymlinks(projectRoot, symlinkMappings);
1854
+ logger.stopSpinner(true, `${symlinkCount} symlinks created`);
1855
+ logger.startSpinner("Creating .onion-config.yml...");
1856
+ const configData = createDefaultConfig({
1857
+ version: "4.0.0",
1858
+ contexts: plan.contexts,
1859
+ ides: ["cursor"],
1860
+ migrated: true,
1861
+ migratedFrom: "v3",
1862
+ migratedAt: (/* @__PURE__ */ new Date()).toISOString(),
1863
+ migration: {
1864
+ commandsMigrated: plan.commands.length,
1865
+ agentsMigrated: plan.agents.length,
1866
+ backupPath: options.noBackup ? null : ".cursor-backup/"
1867
+ }
1868
+ });
1869
+ await createConfig(projectRoot, configData);
1870
+ logger.stopSpinner(true, "Config created");
1871
+ const reportPath = path8.join(projectRoot, "docs/onion/MIGRATION-REPORT.md");
1872
+ await fs2.ensureDir(path8.dirname(reportPath));
1873
+ await fs2.writeFile(reportPath, generateMigrationReport(plan), "utf-8");
1874
+ logger.break();
1875
+ logger.title("\u2705 Migration completed successfully!");
1876
+ logger.break();
1877
+ logger.success("Summary:");
1878
+ logger.info(` \u2022 ${plan.commands.length} commands migrated`);
1879
+ logger.info(` \u2022 ${plan.agents.length} agents migrated`);
1880
+ logger.info(` \u2022 ${plan.contexts.length} contexts created`);
1881
+ logger.info(` \u2022 ${symlinkCount} symlinks for backward compatibility`);
1882
+ if (!options.noBackup) {
1883
+ logger.info(" \u2022 Backup saved: .cursor-backup/");
1884
+ }
1885
+ logger.break();
1886
+ logger.success("Next steps:");
1887
+ logger.info(" 1. Review migration report: docs/onion/MIGRATION-REPORT.md");
1888
+ logger.info(" 2. Test your commands (they still work via symlinks)");
1889
+ logger.info(" 3. Explore new structure: .onion/contexts/");
1890
+ logger.info(" 4. Read system guide: docs/onion/levels-system.md");
1891
+ logger.info(" 5. Use new help commands: /business/help, /technical/help");
1892
+ logger.break();
1893
+ logger.warn("\u26A0\uFE0F Important:");
1894
+ logger.info(" \u2022 .cursor/ is now deprecated (kept for backward compatibility)");
1895
+ logger.info(" \u2022 New commands/agents should be added to .onion/contexts/");
1896
+ logger.info(" \u2022 Backup can be removed after validation: rm -rf .cursor-backup");
1897
+ logger.break();
1898
+ } catch (error) {
1899
+ logger.break();
1900
+ logger.error("\u274C Migration failed:");
1901
+ logger.error(error.message);
1902
+ if (options.debug) {
1903
+ console.error(error);
1904
+ }
1905
+ logger.break();
1906
+ logger.info("\u{1F4A1} Troubleshooting:");
1907
+ logger.info(" \u2022 Backup preserved: .cursor-backup/");
1908
+ logger.info(" \u2022 Review error above");
1909
+ logger.info(" \u2022 Run with --debug for more details");
1910
+ logger.info(" \u2022 Report issue: https://github.com/onion-system/onion/issues");
1911
+ logger.break();
1912
+ process.exit(1);
1913
+ }
1914
+ }
1915
+
1916
+ // src/commands/validate.ts
1917
+ async function validate(options = {}) {
1918
+ try {
1919
+ logger.info("Validating Onion structure...");
1920
+ logger.warn("Command not implemented yet - Coming soon!");
1921
+ } catch (error) {
1922
+ logger.error(`Validation failed: ${error.message}`);
1923
+ if (options.debug) {
1924
+ console.error(error);
1925
+ }
1926
+ process.exit(1);
1927
+ }
1928
+ }
1929
+
1930
+ // src/cli.ts
1931
+ init_constants();
1932
+ var program = new Command();
1933
+ console.log("");
1934
+ console.log(chalk.magenta("\u{1F9C5} Onion System CLI"));
1935
+ console.log(chalk.gray(`v${ONION_VERSION} - Multi-Context Development Orchestrator`));
1936
+ console.log("");
1937
+ program.name("onion").description("CLI for Onion System - Multi-Context Development Orchestrator").version(ONION_VERSION);
1938
+ program.command("init").description("Initialize new Onion project").option("-d, --debug", "Enable debug mode").action(async (options) => {
1939
+ await init(options);
1940
+ });
1941
+ program.command("add").description("Add context or IDE to existing project").option("-d, --debug", "Enable debug mode").action(async (options) => {
1942
+ await add(options);
1943
+ });
1944
+ program.command("migrate").description("Migrate from Onion v3 to v4").option("--no-backup", "Skip backup creation").option("-d, --debug", "Enable debug mode").action(async (options) => {
1945
+ await migrate(options);
1946
+ });
1947
+ program.command("validate").description("Validate Onion structure").option("-d, --debug", "Enable debug mode").action(async (options) => {
1948
+ await validate(options);
1949
+ });
1950
+ program.parse(process.argv);
1951
+ //# sourceMappingURL=cli.js.map
1952
+ //# sourceMappingURL=cli.js.map