@specsafe/cli 0.4.0 → 0.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const applyCommand: Command;
3
+ //# sourceMappingURL=apply.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.d.ts","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAQpC,eAAO,MAAM,YAAY,SAqMrB,CAAC"}
@@ -0,0 +1,182 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { readFile, writeFile, readdir, copyFile, mkdir, unlink } from 'fs/promises';
5
+ import { join } from 'path';
6
+ import { DeltaParser, SemanticMerger } from '@specsafe/core';
7
+ import { confirm } from '@inquirer/prompts';
8
+ export const applyCommand = new Command('apply')
9
+ .description('Apply pending delta specs to a base spec')
10
+ .argument('<spec-id>', 'Base spec ID (e.g., SPEC-20260101-001)')
11
+ .option('--no-backup', 'Skip creating backup before applying')
12
+ .option('-f, --force', 'Apply even if conflicts exist')
13
+ .action(async (specId, options) => {
14
+ const spinner = ora('Loading delta specs...').start();
15
+ try {
16
+ // Validate specId to prevent path traversal
17
+ if (!/^[A-Za-z0-9_-]+$/.test(specId)) {
18
+ spinner.fail(chalk.red('Invalid spec ID. Use only alphanumeric characters, hyphens, and underscores.'));
19
+ process.exit(1);
20
+ }
21
+ // Load base spec
22
+ const baseSpecPath = join('specs/active', `${specId}.md`);
23
+ let baseContent;
24
+ try {
25
+ baseContent = await readFile(baseSpecPath, 'utf-8');
26
+ }
27
+ catch {
28
+ spinner.fail(chalk.red(`Base spec not found: ${specId}`));
29
+ console.log(chalk.gray('💡 Tip: Check that the spec exists in specs/active/'));
30
+ process.exit(1);
31
+ }
32
+ // Find all delta specs for this base spec
33
+ const deltasDir = 'specs/deltas';
34
+ let deltaFiles = [];
35
+ try {
36
+ const files = await readdir(deltasDir);
37
+ deltaFiles = files.filter(f => f.startsWith(`DELTA-${specId}-`) && f.endsWith('.md'));
38
+ }
39
+ catch {
40
+ spinner.fail(chalk.red('No deltas directory found'));
41
+ console.log(chalk.gray('💡 Tip: Create a delta spec first with: specsafe delta <spec-id>'));
42
+ process.exit(1);
43
+ }
44
+ if (deltaFiles.length === 0) {
45
+ spinner.fail(chalk.red(`No delta specs found for ${specId}`));
46
+ console.log(chalk.gray('💡 Tip: Create a delta spec first with: specsafe delta <spec-id>'));
47
+ process.exit(1);
48
+ }
49
+ spinner.text = `Found ${deltaFiles.length} delta spec(s)`;
50
+ // Parse all delta specs
51
+ const parser = new DeltaParser();
52
+ const deltaSpecs = [];
53
+ for (const file of deltaFiles) {
54
+ const deltaPath = join(deltasDir, file);
55
+ const deltaContent = await readFile(deltaPath, 'utf-8');
56
+ const deltaId = file.replace('.md', '');
57
+ try {
58
+ const deltaSpec = parser.parse(deltaContent, deltaId, specId);
59
+ const validation = parser.validate(deltaSpec);
60
+ if (!validation.valid) {
61
+ spinner.warn(chalk.yellow(`Validation errors in ${deltaId}:`));
62
+ for (const error of validation.errors) {
63
+ console.log(chalk.yellow(` - ${error}`));
64
+ }
65
+ if (!options.force) {
66
+ const proceed = await confirm({
67
+ message: 'Continue with other deltas?',
68
+ default: false
69
+ });
70
+ if (!proceed) {
71
+ process.exit(1);
72
+ }
73
+ spinner.start('Continuing with other deltas...');
74
+ continue;
75
+ }
76
+ spinner.start('Continuing...');
77
+ }
78
+ deltaSpecs.push(deltaSpec);
79
+ }
80
+ catch (err) {
81
+ spinner.warn(chalk.yellow(`Failed to parse ${deltaId}: ${err.message}`));
82
+ spinner.start('Continuing...');
83
+ continue;
84
+ }
85
+ }
86
+ if (deltaSpecs.length === 0) {
87
+ spinner.fail(chalk.red('No valid delta specs to apply'));
88
+ process.exit(1);
89
+ }
90
+ spinner.text = `Applying ${deltaSpecs.length} delta spec(s)...`;
91
+ // Apply all deltas in sequence
92
+ const merger = new SemanticMerger();
93
+ let currentContent = baseContent;
94
+ let totalStats = {
95
+ added: 0,
96
+ modified: 0,
97
+ removed: 0,
98
+ conflicts: 0
99
+ };
100
+ for (const deltaSpec of deltaSpecs) {
101
+ const result = merger.merge(currentContent, deltaSpec);
102
+ // Accumulate stats
103
+ totalStats.added += result.stats.added;
104
+ totalStats.modified += result.stats.modified;
105
+ totalStats.removed += result.stats.removed;
106
+ totalStats.conflicts += result.stats.conflicts;
107
+ if (result.conflicts.length > 0) {
108
+ spinner.warn(chalk.yellow(`Conflicts in ${deltaSpec.id}:`));
109
+ for (const conflict of result.conflicts) {
110
+ console.log(chalk.yellow(` - ${conflict.message}`));
111
+ }
112
+ if (!options.force) {
113
+ const proceed = await confirm({
114
+ message: 'Apply anyway (may result in incomplete merge)?',
115
+ default: false
116
+ });
117
+ if (!proceed) {
118
+ spinner.fail(chalk.red('Apply cancelled due to conflicts'));
119
+ process.exit(1);
120
+ }
121
+ spinner.start();
122
+ }
123
+ }
124
+ currentContent = result.content;
125
+ }
126
+ spinner.stop();
127
+ // Show preview of changes
128
+ console.log(chalk.blue('\n📊 Merge Summary:'));
129
+ console.log(chalk.green(` ✓ Added: ${totalStats.added} requirements`));
130
+ console.log(chalk.yellow(` ~ Modified: ${totalStats.modified} requirements`));
131
+ console.log(chalk.red(` - Removed: ${totalStats.removed} requirements`));
132
+ if (totalStats.conflicts > 0) {
133
+ console.log(chalk.red(` ⚠ Conflicts: ${totalStats.conflicts}`));
134
+ }
135
+ // Confirm before writing
136
+ const proceed = await confirm({
137
+ message: 'Apply these changes to the base spec?',
138
+ default: true
139
+ });
140
+ if (!proceed) {
141
+ console.log(chalk.yellow('\nApply cancelled.'));
142
+ process.exit(0);
143
+ }
144
+ spinner.start('Applying changes...');
145
+ // Create backup if requested
146
+ if (options.backup) {
147
+ const backupDir = join('specs', 'backups');
148
+ await mkdir(backupDir, { recursive: true });
149
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
150
+ const backupPath = join(backupDir, `${specId}-${timestamp}.md`);
151
+ await copyFile(baseSpecPath, backupPath);
152
+ spinner.text = `Created backup: ${backupPath}`;
153
+ }
154
+ // Write merged content
155
+ await writeFile(baseSpecPath, currentContent);
156
+ // Archive applied deltas (only those that were successfully applied)
157
+ const archiveDir = join('specs', 'deltas', 'applied');
158
+ await mkdir(archiveDir, { recursive: true });
159
+ for (const deltaSpec of deltaSpecs) {
160
+ const deltaFile = `${deltaSpec.id}.md`;
161
+ const deltaPath = join(deltasDir, deltaFile);
162
+ const archivePath = join(archiveDir, deltaFile);
163
+ await copyFile(deltaPath, archivePath);
164
+ await unlink(deltaPath); // Remove original after archiving
165
+ }
166
+ spinner.succeed(chalk.green(`Successfully applied ${deltaSpecs.length} delta spec(s) to ${specId}`));
167
+ console.log(chalk.blue(` Updated: ${baseSpecPath}`));
168
+ if (options.backup) {
169
+ console.log(chalk.blue(` Backup: specs/backups/`));
170
+ }
171
+ console.log(chalk.blue(` Archived deltas: specs/deltas/applied/`));
172
+ console.log(chalk.gray('\n Next steps:'));
173
+ console.log(chalk.gray(` 1. Review changes: git diff ${baseSpecPath}`));
174
+ console.log(chalk.gray(` 2. Update tests if needed`));
175
+ console.log(chalk.gray(` 3. Run: specsafe verify ${specId}`));
176
+ }
177
+ catch (error) {
178
+ spinner.fail(chalk.red(`Failed to apply deltas: ${error.message}`));
179
+ process.exit(1);
180
+ }
181
+ });
182
+ //# sourceMappingURL=apply.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"apply.js","sourceRoot":"","sources":["../../src/commands/apply.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const deltaCommand: Command;
3
+ //# sourceMappingURL=delta.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.d.ts","sourceRoot":"","sources":["../../src/commands/delta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,YAAY,SA6ErB,CAAC"}
@@ -0,0 +1,82 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { writeFile, mkdir, readFile } from 'fs/promises';
5
+ import { join } from 'path';
6
+ import { generateDeltaTemplate } from '@specsafe/core';
7
+ import { confirm } from '@inquirer/prompts';
8
+ import { execFileSync } from 'child_process';
9
+ export const deltaCommand = new Command('delta')
10
+ .description('Create a new delta spec for an existing spec (brownfield changes)')
11
+ .argument('<spec-id>', 'Base spec ID (e.g., SPEC-20260101-001)')
12
+ .option('-a, --author <author>', 'Author name', 'developer')
13
+ .option('--no-edit', 'Create without opening in editor')
14
+ .action(async (specId, options) => {
15
+ const spinner = ora('Creating delta spec...').start();
16
+ try {
17
+ // Validate specId to prevent path traversal
18
+ if (!/^[A-Za-z0-9_-]+$/.test(specId)) {
19
+ spinner.fail(chalk.red('Invalid spec ID. Use only alphanumeric characters, hyphens, and underscores.'));
20
+ process.exit(1);
21
+ }
22
+ // Validate base spec exists
23
+ const baseSpecPath = join('specs/active', `${specId}.md`);
24
+ try {
25
+ await readFile(baseSpecPath, 'utf-8');
26
+ }
27
+ catch {
28
+ spinner.fail(chalk.red(`Base spec not found: ${specId}`));
29
+ console.log(chalk.gray('💡 Tip: Check that the spec exists in specs/active/'));
30
+ process.exit(1);
31
+ }
32
+ // Generate delta spec ID
33
+ const date = new Date().toISOString().split('T')[0].replace(/-/g, '');
34
+ const deltaId = `DELTA-${specId}-${date}`;
35
+ // Create deltas directory if needed
36
+ await mkdir('specs/deltas', { recursive: true });
37
+ const deltaPath = join('specs/deltas', `${deltaId}.md`);
38
+ // Check if delta already exists
39
+ try {
40
+ await readFile(deltaPath, 'utf-8');
41
+ spinner.stop();
42
+ const overwrite = await confirm({
43
+ message: `Delta spec ${deltaId} already exists. Overwrite?`,
44
+ default: false
45
+ });
46
+ if (!overwrite) {
47
+ spinner.stop();
48
+ console.log(chalk.yellow('Delta creation cancelled.'));
49
+ process.exit(0);
50
+ }
51
+ }
52
+ catch {
53
+ // File doesn't exist, proceed
54
+ }
55
+ // Generate template
56
+ const template = generateDeltaTemplate(deltaId, specId, options.author);
57
+ await writeFile(deltaPath, template);
58
+ spinner.succeed(chalk.green(`Created delta spec: ${deltaId}`));
59
+ console.log(chalk.blue(` Location: ${deltaPath}`));
60
+ console.log(chalk.gray('\n Next steps:'));
61
+ console.log(chalk.gray(` 1. Edit ${deltaPath} to describe changes`));
62
+ console.log(chalk.gray(` 2. Run: specsafe diff ${specId}`));
63
+ console.log(chalk.gray(` 3. Run: specsafe apply ${specId}`));
64
+ // Open in editor if requested
65
+ if (options.edit) {
66
+ spinner.start('Opening in editor...');
67
+ try {
68
+ const editor = process.env.EDITOR || 'nano';
69
+ execFileSync(editor, [deltaPath], { stdio: 'inherit' });
70
+ spinner.stop();
71
+ }
72
+ catch (err) {
73
+ spinner.warn(chalk.yellow('Could not open editor. Edit the file manually.'));
74
+ }
75
+ }
76
+ }
77
+ catch (error) {
78
+ spinner.fail(chalk.red(`Failed to create delta spec: ${error.message}`));
79
+ process.exit(1);
80
+ }
81
+ });
82
+ //# sourceMappingURL=delta.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"delta.js","sourceRoot":"","sources":["../../src/commands/delta.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AACzD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AACvD,OAAO,EAAS,OAAO,EAAE,MAAM,mBAAmB,CAAC;AACnD,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAE7C,MAAM,CAAC,MAAM,YAAY,GAAG,IAAI,OAAO,CAAC,OAAO,CAAC;KAC7C,WAAW,CAAC,mEAAmE,CAAC;KAChF,QAAQ,CAAC,WAAW,EAAE,wCAAwC,CAAC;KAC/D,MAAM,CAAC,uBAAuB,EAAE,aAAa,EAAE,WAAW,CAAC;KAC3D,MAAM,CAAC,WAAW,EAAE,kCAAkC,CAAC;KACvD,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,OAA0C,EAAE,EAAE;IAC3E,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;IAEtD,IAAI,CAAC;QACH,4CAA4C;QAC5C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC,CAAC;YACxG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,4BAA4B;QAC5B,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC;QAC1D,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACxC,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,yBAAyB;QACzB,MAAM,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;QACtE,MAAM,OAAO,GAAG,SAAS,MAAM,IAAI,IAAI,EAAE,CAAC;QAE1C,oCAAoC;QACpC,MAAM,KAAK,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAEjD,MAAM,SAAS,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,OAAO,KAAK,CAAC,CAAC;QAExD,gCAAgC;QAChC,IAAI,CAAC;YACH,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACnC,OAAO,CAAC,IAAI,EAAE,CAAC;YACf,MAAM,SAAS,GAAG,MAAM,OAAO,CAAC;gBAC9B,OAAO,EAAE,cAAc,OAAO,6BAA6B;gBAC3D,OAAO,EAAE,KAAK;aACf,CAAC,CAAC;YACH,IAAI,CAAC,SAAS,EAAE,CAAC;gBACf,OAAO,CAAC,IAAI,EAAE,CAAC;gBACf,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,2BAA2B,CAAC,CAAC,CAAC;gBACvD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;YAClB,CAAC;QACH,CAAC;QAAC,MAAM,CAAC;YACP,8BAA8B;QAChC,CAAC;QAED,oBAAoB;QACpB,MAAM,QAAQ,GAAG,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC;QACxE,MAAM,SAAS,CAAC,SAAS,EAAE,QAAQ,CAAC,CAAC;QAErC,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,uBAAuB,OAAO,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,SAAS,EAAE,CAAC,CAAC,CAAC;QACpD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC,CAAC;QAC3C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,SAAS,sBAAsB,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC,CAAC;QAC/D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8BAA8B,MAAM,EAAE,CAAC,CAAC,CAAC;QAEhE,8BAA8B;QAC9B,IAAI,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;YACtC,IAAI,CAAC;gBACH,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,IAAI,MAAM,CAAC;gBAC5C,YAAY,CAAC,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC,CAAC;gBACxD,OAAO,CAAC,IAAI,EAAE,CAAC;YACjB,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,gDAAgD,CAAC,CAAC,CAAC;YAC/E,CAAC;QACH,CAAC;IACH,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,gCAAgC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACzE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const diffCommand: Command;
3
+ //# sourceMappingURL=diff.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.d.ts","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAOpC,eAAO,MAAM,WAAW,SA2GpB,CAAC"}
@@ -0,0 +1,102 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { readFile, readdir } from 'fs/promises';
5
+ import { join } from 'path';
6
+ import { DeltaParser, SemanticMerger } from '@specsafe/core';
7
+ export const diffCommand = new Command('diff')
8
+ .description('Show what would change when applying delta specs')
9
+ .argument('<spec-id>', 'Base spec ID (e.g., SPEC-20260101-001)')
10
+ .option('-v, --verbose', 'Show detailed diff')
11
+ .action(async (specId, options) => {
12
+ const spinner = ora('Loading delta specs...').start();
13
+ try {
14
+ // Validate specId to prevent path traversal
15
+ if (!/^[A-Za-z0-9_-]+$/.test(specId)) {
16
+ spinner.fail(chalk.red('Invalid spec ID. Use only alphanumeric characters, hyphens, and underscores.'));
17
+ process.exit(1);
18
+ }
19
+ // Load base spec
20
+ const baseSpecPath = join('specs/active', `${specId}.md`);
21
+ let baseContent;
22
+ try {
23
+ baseContent = await readFile(baseSpecPath, 'utf-8');
24
+ }
25
+ catch {
26
+ spinner.fail(chalk.red(`Base spec not found: ${specId}`));
27
+ console.log(chalk.gray('💡 Tip: Check that the spec exists in specs/active/'));
28
+ process.exit(1);
29
+ }
30
+ // Find all delta specs for this base spec
31
+ const deltasDir = 'specs/deltas';
32
+ let deltaFiles = [];
33
+ try {
34
+ const files = await readdir(deltasDir);
35
+ deltaFiles = files.filter(f => f.startsWith(`DELTA-${specId}-`) && f.endsWith('.md'));
36
+ }
37
+ catch {
38
+ spinner.fail(chalk.red('No deltas directory found'));
39
+ console.log(chalk.gray('💡 Tip: Create a delta spec first with: specsafe delta <spec-id>'));
40
+ process.exit(1);
41
+ }
42
+ if (deltaFiles.length === 0) {
43
+ spinner.fail(chalk.red(`No delta specs found for ${specId}`));
44
+ console.log(chalk.gray('💡 Tip: Create a delta spec first with: specsafe delta <spec-id>'));
45
+ process.exit(1);
46
+ }
47
+ spinner.succeed(chalk.green(`Found ${deltaFiles.length} delta spec(s)`));
48
+ // Parse and display each delta
49
+ const parser = new DeltaParser();
50
+ const merger = new SemanticMerger();
51
+ for (const file of deltaFiles) {
52
+ const deltaPath = join(deltasDir, file);
53
+ const deltaContent = await readFile(deltaPath, 'utf-8');
54
+ const deltaId = file.replace(/\.md$/, '');
55
+ console.log(chalk.blue(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━`));
56
+ console.log(chalk.blue.bold(`📄 ${deltaId}`));
57
+ console.log(chalk.blue(`━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`));
58
+ try {
59
+ const deltaSpec = parser.parse(deltaContent, deltaId, specId);
60
+ // Validate
61
+ const validation = parser.validate(deltaSpec);
62
+ if (!validation.valid) {
63
+ console.log(chalk.red('⚠️ Validation Errors:'));
64
+ for (const error of validation.errors) {
65
+ console.log(chalk.red(` • ${error}`));
66
+ }
67
+ console.log();
68
+ }
69
+ // Show diff preview
70
+ const diffPreview = merger.diff(baseContent, deltaSpec);
71
+ console.log(diffPreview);
72
+ // Show detailed changes if verbose
73
+ if (options.verbose) {
74
+ const mergeResult = merger.merge(baseContent, deltaSpec);
75
+ console.log(chalk.blue('\n📊 Merge Statistics:'));
76
+ console.log(chalk.green(` ✓ Added: ${mergeResult.stats.added}`));
77
+ console.log(chalk.yellow(` ~ Modified: ${mergeResult.stats.modified}`));
78
+ console.log(chalk.red(` - Removed: ${mergeResult.stats.removed}`));
79
+ if (mergeResult.conflicts.length > 0) {
80
+ console.log(chalk.red(`\n⚠️ Conflicts (${mergeResult.conflicts.length}):`));
81
+ for (const conflict of mergeResult.conflicts) {
82
+ console.log(chalk.red(` • [${conflict.type}] ${conflict.message}`));
83
+ }
84
+ }
85
+ }
86
+ }
87
+ catch (err) {
88
+ console.log(chalk.red(`❌ Failed to parse: ${err.message}`));
89
+ }
90
+ }
91
+ console.log(chalk.blue(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`));
92
+ console.log(chalk.gray(' Next steps:'));
93
+ console.log(chalk.gray(` • Review changes above`));
94
+ console.log(chalk.gray(` • Edit delta specs if needed`));
95
+ console.log(chalk.gray(` • Run: specsafe apply ${specId}`));
96
+ }
97
+ catch (error) {
98
+ spinner.fail(chalk.red(`Failed to generate diff: ${error.message}`));
99
+ process.exit(1);
100
+ }
101
+ });
102
+ //# sourceMappingURL=diff.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"diff.js","sourceRoot":"","sources":["../../src/commands/diff.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAC5B,OAAO,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE7D,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,kDAAkD,CAAC;KAC/D,QAAQ,CAAC,WAAW,EAAE,wCAAwC,CAAC;KAC/D,MAAM,CAAC,eAAe,EAAE,oBAAoB,CAAC;KAC7C,MAAM,CAAC,KAAK,EAAE,MAAc,EAAE,OAA6B,EAAE,EAAE;IAC9D,MAAM,OAAO,GAAG,GAAG,CAAC,wBAAwB,CAAC,CAAC,KAAK,EAAE,CAAC;IAEtD,IAAI,CAAC;QACH,4CAA4C;QAC5C,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,MAAM,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,8EAA8E,CAAC,CAAC,CAAC;YACxG,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,iBAAiB;QACjB,MAAM,YAAY,GAAG,IAAI,CAAC,cAAc,EAAE,GAAG,MAAM,KAAK,CAAC,CAAC;QAC1D,IAAI,WAAmB,CAAC;QACxB,IAAI,CAAC;YACH,WAAW,GAAG,MAAM,QAAQ,CAAC,YAAY,EAAE,OAAO,CAAC,CAAC;QACtD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,MAAM,EAAE,CAAC,CAAC,CAAC;YAC1D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qDAAqD,CAAC,CAAC,CAAC;YAC/E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,0CAA0C;QAC1C,MAAM,SAAS,GAAG,cAAc,CAAC;QACjC,IAAI,UAAU,GAAa,EAAE,CAAC;QAC9B,IAAI,CAAC;YACH,MAAM,KAAK,GAAG,MAAM,OAAO,CAAC,SAAS,CAAC,CAAC;YACvC,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,UAAU,CAAC,SAAS,MAAM,GAAG,CAAC,IAAI,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;QACxF,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,2BAA2B,CAAC,CAAC,CAAC;YACrD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC,CAAC;YAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,IAAI,UAAU,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC5B,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,MAAM,EAAE,CAAC,CAAC,CAAC;YAC9D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kEAAkE,CAAC,CAAC,CAAC;YAC5F,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,SAAS,UAAU,CAAC,MAAM,gBAAgB,CAAC,CAAC,CAAC;QAEzE,+BAA+B;QAC/B,MAAM,MAAM,GAAG,IAAI,WAAW,EAAE,CAAC;QACjC,MAAM,MAAM,GAAG,IAAI,cAAc,EAAE,CAAC;QAEpC,KAAK,MAAM,IAAI,IAAI,UAAU,EAAE,CAAC;YAC9B,MAAM,SAAS,GAAG,IAAI,CAAC,SAAS,EAAE,IAAI,CAAC,CAAC;YACxC,MAAM,YAAY,GAAG,MAAM,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC,CAAC;YACxD,MAAM,OAAO,GAAG,IAAI,CAAC,OAAO,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC;YAE1C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC,CAAC;YACtE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,MAAM,OAAO,EAAE,CAAC,CAAC,CAAC;YAC9C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4CAA4C,CAAC,CAAC,CAAC;YAEtE,IAAI,CAAC;gBACH,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,YAAY,EAAE,OAAO,EAAE,MAAM,CAAC,CAAC;gBAE9D,WAAW;gBACX,MAAM,UAAU,GAAG,MAAM,CAAC,QAAQ,CAAC,SAAS,CAAC,CAAC;gBAC9C,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;oBACtB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,wBAAwB,CAAC,CAAC,CAAC;oBACjD,KAAK,MAAM,KAAK,IAAI,UAAU,CAAC,MAAM,EAAE,CAAC;wBACtC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,QAAQ,KAAK,EAAE,CAAC,CAAC,CAAC;oBAC1C,CAAC;oBACD,OAAO,CAAC,GAAG,EAAE,CAAC;gBAChB,CAAC;gBAED,oBAAoB;gBACpB,MAAM,WAAW,GAAG,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;gBACxD,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC;gBAEzB,mCAAmC;gBACnC,IAAI,OAAO,CAAC,OAAO,EAAE,CAAC;oBACpB,MAAM,WAAW,GAAG,MAAM,CAAC,KAAK,CAAC,WAAW,EAAE,SAAS,CAAC,CAAC;oBAEzD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,wBAAwB,CAAC,CAAC,CAAC;oBAClD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,eAAe,WAAW,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;oBACnE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,kBAAkB,WAAW,CAAC,KAAK,CAAC,QAAQ,EAAE,CAAC,CAAC,CAAC;oBAC1E,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,iBAAiB,WAAW,CAAC,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;oBAErE,IAAI,WAAW,CAAC,SAAS,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;wBACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,oBAAoB,WAAW,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,CAAC,CAAC;wBAC7E,KAAK,MAAM,QAAQ,IAAI,WAAW,CAAC,SAAS,EAAE,CAAC;4BAC7C,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,SAAS,QAAQ,CAAC,IAAI,KAAK,QAAQ,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;wBACxE,CAAC;oBACH,CAAC;gBACH,CAAC;YAEH,CAAC;YAAC,OAAO,GAAQ,EAAE,CAAC;gBAClB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,sBAAsB,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;YAC9D,CAAC;QACH,CAAC;QAED,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,8CAA8C,CAAC,CAAC,CAAC;QACxE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,CAAC,CAAC;QACzC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,4BAA4B,CAAC,CAAC,CAAC;QACtD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,kCAAkC,CAAC,CAAC,CAAC;QAC5D,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,6BAA6B,MAAM,EAAE,CAAC,CAAC,CAAC;IAEjE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,4BAA4B,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QACrE,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Command } from 'commander';
2
+ export declare const initCommand: Command;
3
+ //# sourceMappingURL=init-old.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-old.d.ts","sourceRoot":"","sources":["../../src/commands/init-old.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,WAAW,SAyJpB,CAAC"}
@@ -0,0 +1,146 @@
1
+ import { Command } from 'commander';
2
+ import chalk from 'chalk';
3
+ import ora from 'ora';
4
+ import { writeFile, mkdir } from 'fs/promises';
5
+ import { ProjectTracker } from '@specsafe/core';
6
+ import { checkbox, select, confirm, input } from '@inquirer/prompts';
7
+ import { detectInstalledTools, availableTools } from '../utils/detectTools.js';
8
+ import { generateToolConfig, generateGitHooks } from '../utils/generateToolConfig.js';
9
+ export const initCommand = new Command('init')
10
+ .description('Initialize a new SpecSafe project')
11
+ .argument('[name]', 'Project name', 'my-project')
12
+ .action(async (defaultName) => {
13
+ const spinner = ora('Initializing SpecSafe project...').start();
14
+ try {
15
+ // Interactive prompts for project name
16
+ spinner.stop();
17
+ const name = await input({
18
+ message: 'Project name:',
19
+ default: defaultName,
20
+ });
21
+ // 1. Detect existing tools
22
+ const detectedTools = detectInstalledTools();
23
+ // 2. Prompt user to confirm/select tools
24
+ const toolChoices = availableTools.map((tool) => ({
25
+ name: tool.displayName + (detectedTools.includes(tool.name) ? ' (detected)' : ''),
26
+ value: tool.name,
27
+ checked: detectedTools.includes(tool.name),
28
+ }));
29
+ const selectedTools = await checkbox({
30
+ message: 'Which AI coding assistants do you use?',
31
+ choices: toolChoices,
32
+ });
33
+ // 3. Prompt for test framework
34
+ const testFramework = await select({
35
+ message: 'Select testing framework:',
36
+ choices: [
37
+ { name: 'Vitest (recommended)', value: 'vitest' },
38
+ { name: 'Jest', value: 'jest' },
39
+ { name: 'Playwright (E2E)', value: 'playwright' },
40
+ ],
41
+ default: 'vitest',
42
+ });
43
+ // 4. Prompt for git hooks
44
+ const useGitHooks = await confirm({
45
+ message: 'Enable git hooks for spec validation?',
46
+ default: true,
47
+ });
48
+ spinner.start('Creating project structure...');
49
+ // Create directory structure
50
+ await mkdir('specs/active', { recursive: true });
51
+ await mkdir('specs/completed', { recursive: true });
52
+ await mkdir('specs/archive', { recursive: true });
53
+ await mkdir('src', { recursive: true });
54
+ await mkdir('tests', { recursive: true });
55
+ // Create PROJECT_STATE.md
56
+ const tracker = new ProjectTracker(process.cwd());
57
+ await tracker.initialize(name);
58
+ // Create spec template
59
+ const template = `# Spec Template
60
+
61
+ ## Metadata
62
+ - **ID**: SPEC-{YYYYMMDD}-{NNN}
63
+ - **Status**: draft
64
+ - **Priority**: P1
65
+
66
+ ## 1. Purpose (WHY)
67
+
68
+ ## 2. Scope (WHAT)
69
+ ### In Scope
70
+ -
71
+
72
+ ### Out of Scope
73
+ -
74
+
75
+ ## 3. Requirements
76
+ | ID | Requirement | Priority | Acceptance Criteria |
77
+ |----|-------------|----------|---------------------|
78
+ | | | | |
79
+
80
+ ## 4. Technical Approach (HOW)
81
+
82
+ ## 5. Test Strategy
83
+
84
+ ## 6. Implementation Plan
85
+
86
+ ## 7. Success Criteria
87
+ - [ ]
88
+
89
+ ## 8. Risks & Mitigations
90
+
91
+ ## 9. Notes & References
92
+ `;
93
+ await writeFile('specs/template.md', template);
94
+ // Create config file with selected tools
95
+ const toolsConfig = {};
96
+ for (const tool of selectedTools) {
97
+ toolsConfig[tool] = { enabled: true };
98
+ }
99
+ const config = {
100
+ project: name,
101
+ version: '1.0.0',
102
+ stages: ['spec', 'test', 'code', 'qa', 'complete'],
103
+ testFramework,
104
+ language: 'typescript',
105
+ tools: toolsConfig,
106
+ gitHooks: {
107
+ enabled: useGitHooks,
108
+ },
109
+ };
110
+ await writeFile('specsafe.config.json', JSON.stringify(config, null, 2));
111
+ spinner.text = 'Generating tool configurations...';
112
+ // Generate tool configs for selected tools
113
+ for (const tool of selectedTools) {
114
+ await generateToolConfig(tool, '.');
115
+ }
116
+ // Generate git hooks if enabled
117
+ if (useGitHooks) {
118
+ await generateGitHooks('.');
119
+ }
120
+ spinner.succeed(chalk.green(`Initialized SpecSafe project: ${name}`));
121
+ console.log('\n' + chalk.blue('Generated configurations:'));
122
+ for (const tool of selectedTools) {
123
+ console.log(chalk.green(` ✓ ${tool} configuration`));
124
+ }
125
+ if (useGitHooks) {
126
+ console.log(chalk.green(' ✓ Git hooks enabled'));
127
+ }
128
+ console.log(chalk.green(` ✓ ${testFramework} configuration`));
129
+ console.log('\n' + chalk.blue('Next steps:'));
130
+ console.log(' 1. specsafe new <spec-name> - Create a new spec');
131
+ console.log(' 2. specsafe status - View project status');
132
+ console.log(' 3. Edit specs/active/ to define requirements');
133
+ }
134
+ catch (error) {
135
+ spinner.fail(chalk.red(`Failed to initialize: ${error.message}`));
136
+ if (error.message.includes('EEXIST')) {
137
+ console.log(chalk.gray("💡 Tip: Directory already exists. Use 'specsafe init <name>' to create elsewhere."));
138
+ }
139
+ if (error.message.includes('User force closed')) {
140
+ console.log(chalk.gray('💡 Tip: Init was cancelled by user.'));
141
+ process.exit(0);
142
+ }
143
+ process.exit(1);
144
+ }
145
+ });
146
+ //# sourceMappingURL=init-old.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"init-old.js","sourceRoot":"","sources":["../../src/commands/init-old.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,KAAK,MAAM,OAAO,CAAC;AAC1B,OAAO,GAAG,MAAM,KAAK,CAAC;AACtB,OAAO,EAAE,SAAS,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAChD,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,mBAAmB,CAAC;AACrE,OAAO,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,yBAAyB,CAAC;AAC/E,OAAO,EAAE,kBAAkB,EAAE,gBAAgB,EAAE,MAAM,gCAAgC,CAAC;AAEtF,MAAM,CAAC,MAAM,WAAW,GAAG,IAAI,OAAO,CAAC,MAAM,CAAC;KAC3C,WAAW,CAAC,mCAAmC,CAAC;KAChD,QAAQ,CAAC,QAAQ,EAAE,cAAc,EAAE,YAAY,CAAC;KAChD,MAAM,CAAC,KAAK,EAAE,WAAmB,EAAE,EAAE;IACpC,MAAM,OAAO,GAAG,GAAG,CAAC,kCAAkC,CAAC,CAAC,KAAK,EAAE,CAAC;IAEhE,IAAI,CAAC;QACH,uCAAuC;QACvC,OAAO,CAAC,IAAI,EAAE,CAAC;QACf,MAAM,IAAI,GAAG,MAAM,KAAK,CAAC;YACvB,OAAO,EAAE,eAAe;YACxB,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;QAEH,2BAA2B;QAC3B,MAAM,aAAa,GAAG,oBAAoB,EAAE,CAAC;QAE7C,yCAAyC;QACzC,MAAM,WAAW,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAAC;YAChD,IAAI,EAAE,IAAI,CAAC,WAAW,GAAG,CAAC,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC;YACjF,KAAK,EAAE,IAAI,CAAC,IAAI;YAChB,OAAO,EAAE,aAAa,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,CAAC;SAC3C,CAAC,CAAC,CAAC;QAEJ,MAAM,aAAa,GAAG,MAAM,QAAQ,CAAC;YACnC,OAAO,EAAE,wCAAwC;YACjD,OAAO,EAAE,WAAW;SACrB,CAAC,CAAC;QAEH,+BAA+B;QAC/B,MAAM,aAAa,GAAG,MAAM,MAAM,CAAC;YACjC,OAAO,EAAE,2BAA2B;YACpC,OAAO,EAAE;gBACP,EAAE,IAAI,EAAE,sBAAsB,EAAE,KAAK,EAAE,QAAQ,EAAE;gBACjD,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE;gBAC/B,EAAE,IAAI,EAAE,kBAAkB,EAAE,KAAK,EAAE,YAAY,EAAE;aAClD;YACD,OAAO,EAAE,QAAQ;SAClB,CAAC,CAAC;QAEH,0BAA0B;QAC1B,MAAM,WAAW,GAAG,MAAM,OAAO,CAAC;YAChC,OAAO,EAAE,uCAAuC;YAChD,OAAO,EAAE,IAAI;SACd,CAAC,CAAC;QAEH,OAAO,CAAC,KAAK,CAAC,+BAA+B,CAAC,CAAC;QAE/C,6BAA6B;QAC7B,MAAM,KAAK,CAAC,cAAc,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACjD,MAAM,KAAK,CAAC,iBAAiB,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACpD,MAAM,KAAK,CAAC,eAAe,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAClD,MAAM,KAAK,CAAC,KAAK,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QACxC,MAAM,KAAK,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAE1C,0BAA0B;QAC1B,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,OAAO,CAAC,GAAG,EAAE,CAAC,CAAC;QAClD,MAAM,OAAO,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC;QAE/B,uBAAuB;QACvB,MAAM,QAAQ,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAiCtB,CAAC;QACI,MAAM,SAAS,CAAC,mBAAmB,EAAE,QAAQ,CAAC,CAAC;QAE/C,yCAAyC;QACzC,MAAM,WAAW,GAAyC,EAAE,CAAC;QAC7D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,WAAW,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QACxC,CAAC;QAED,MAAM,MAAM,GAAG;YACb,OAAO,EAAE,IAAI;YACb,OAAO,EAAE,OAAO;YAChB,MAAM,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC;YAClD,aAAa;YACb,QAAQ,EAAE,YAAY;YACtB,KAAK,EAAE,WAAW;YAClB,QAAQ,EAAE;gBACR,OAAO,EAAE,WAAW;aACrB;SACF,CAAC;QACF,MAAM,SAAS,CAAC,sBAAsB,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;QAEzE,OAAO,CAAC,IAAI,GAAG,mCAAmC,CAAC;QAEnD,2CAA2C;QAC3C,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,MAAM,kBAAkB,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;QACtC,CAAC;QAED,gCAAgC;QAChC,IAAI,WAAW,EAAE,CAAC;YAChB,MAAM,gBAAgB,CAAC,GAAG,CAAC,CAAC;QAC9B,CAAC;QAED,OAAO,CAAC,OAAO,CAAC,KAAK,CAAC,KAAK,CAAC,iCAAiC,IAAI,EAAE,CAAC,CAAC,CAAC;QAEtE,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC,CAAC;QAC5D,KAAK,MAAM,IAAI,IAAI,aAAa,EAAE,CAAC;YACjC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,IAAI,gBAAgB,CAAC,CAAC,CAAC;QACxD,CAAC;QACD,IAAI,WAAW,EAAE,CAAC;YAChB,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC,CAAC;QACpD,CAAC;QACD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,OAAO,aAAa,gBAAgB,CAAC,CAAC,CAAC;QAE/D,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC,CAAC;QAC9C,OAAO,CAAC,GAAG,CAAC,oDAAoD,CAAC,CAAC;QAClE,OAAO,CAAC,GAAG,CAAC,wDAAwD,CAAC,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,gDAAgD,CAAC,CAAC;IAChE,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,yBAAyB,KAAK,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;QAClE,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAC,EAAE,CAAC;YACrC,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,mFAAmF,CAAC,CAAC,CAAC;QAC/G,CAAC;QACD,IAAI,KAAK,CAAC,OAAO,CAAC,QAAQ,CAAC,mBAAmB,CAAC,EAAE,CAAC;YAChD,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,qCAAqC,CAAC,CAAC,CAAC;YAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC,CAAC,CAAC"}
@@ -1 +1 @@
1
- {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AASpC,eAAO,MAAM,WAAW,SAyJpB,CAAC"}
1
+ {"version":3,"file":"init.d.ts","sourceRoot":"","sources":["../../src/commands/init.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAyFpC,eAAO,MAAM,WAAW,SA4PpB,CAAC"}