@meltstudio/meltctl 2.0.3 โ†’ 2.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -70,6 +70,18 @@ Updates your project templates to the latest version. This command:
70
70
  - Verifies .melt/ workspace migration status
71
71
  - Handles version compatibility automatically
72
72
 
73
+ ### Clean Project
74
+
75
+ ```bash
76
+ meltctl project clean
77
+ ```
78
+
79
+ Safely removes all Melt-generated files from your project while preserving user-created content. This command:
80
+ - Removes the entire `.melt/` directory (all Melt-generated content)
81
+ - Selectively removes only Melt commands from `.cursor/commands/`
82
+ - Preserves user-created files in `.cursor/commands/`
83
+ - Provides interactive confirmation before deletion
84
+
73
85
  ## ๐Ÿ› ๏ธ Requirements
74
86
 
75
87
  - Node.js 22+ (works with Node.js 18+ but 22+ recommended)
@@ -120,12 +132,13 @@ This tool is part of the Melt Development Process. For issues or contributions:
120
132
  The CLI uses a bundled template system organized in:
121
133
 
122
134
  ```
123
- packages/cli/templates/
124
- โ”œโ”€โ”€ cursor-commands/ # All 8 Cursor AI command templates
125
- โ”œโ”€โ”€ melt-memory/ # Project context templates with dynamic timestamps
126
- โ””โ”€โ”€ melt-scripts/ # Utility scripts for sh and PowerShell
127
- โ”œโ”€โ”€ sh/ # Bash/zsh utility scripts
128
- โ””โ”€โ”€ ps/ # PowerShell utility scripts
135
+ packages/cli/
136
+ โ”œโ”€โ”€ memory/ # Project context and standards
137
+ โ”œโ”€โ”€ scripts/ # Utility scripts for sh and PowerShell
138
+ โ”‚ โ”œโ”€โ”€ sh/ # Bash/zsh utility scripts
139
+ โ”‚ โ””โ”€โ”€ ps/ # PowerShell utility scripts
140
+ โ””โ”€โ”€ templates/
141
+ โ””โ”€โ”€ cursor-commands/ # All 8 Cursor AI command templates
129
142
  ```
130
143
 
131
144
  Templates support dynamic content replacement (like timestamps) and are copied to your project during initialization.
@@ -0,0 +1,5 @@
1
+ interface CleanOptions {
2
+ placeholder?: never;
3
+ }
4
+ export declare function cleanCommand(_options?: CleanOptions): Promise<void>;
5
+ export {};
@@ -0,0 +1,239 @@
1
+ import { intro, outro, confirm, spinner } from '@clack/prompts';
2
+ import chalk from 'chalk';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+ // List of Melt-generated command files to remove
6
+ const MELT_COMMAND_FILES = [
7
+ 'melt-plan.md',
8
+ 'melt-test-plan.md',
9
+ 'melt-docs.md',
10
+ 'melt-implement.md',
11
+ 'melt-pr.md',
12
+ 'melt-review.md',
13
+ 'melt-complete.md',
14
+ 'melt-debug.md',
15
+ ];
16
+ export async function cleanCommand(_options = {}) {
17
+ intro(chalk.blue('๐Ÿงน Melt Project - Clean'));
18
+ const currentDir = process.cwd();
19
+ const meltDir = path.join(currentDir, '.melt');
20
+ const cursorCommandsDir = path.join(currentDir, '.cursor', 'commands');
21
+ // Check if this is a Melt workspace
22
+ const hasMeltWorkspace = await fs.pathExists(meltDir);
23
+ const hasCursorCommands = await fs.pathExists(cursorCommandsDir);
24
+ if (!hasMeltWorkspace && !hasCursorCommands) {
25
+ console.log(chalk.yellow('โš ๏ธ No Melt workspace found in this directory.'));
26
+ console.log("This project doesn't appear to have Melt tools installed.");
27
+ outro(chalk.gray('Nothing to clean.'));
28
+ return;
29
+ }
30
+ // Analyze what will be cleaned
31
+ const analysisResult = await analyzeCleanupTarget(currentDir);
32
+ if (analysisResult.removedFiles.length === 0 && analysisResult.removedDirs.length === 0) {
33
+ console.log(chalk.yellow('โš ๏ธ No Melt files found to clean.'));
34
+ outro(chalk.gray('Nothing to clean.'));
35
+ return;
36
+ }
37
+ // Show what will be cleaned
38
+ displayCleanupPlan(analysisResult);
39
+ // Get confirmation
40
+ const shouldProceed = await confirm({
41
+ message: 'Do you want to proceed with cleaning? This action cannot be undone.',
42
+ });
43
+ if (!shouldProceed) {
44
+ outro(chalk.gray('Clean operation cancelled.'));
45
+ return;
46
+ }
47
+ // Perform the cleanup
48
+ const s = spinner();
49
+ s.start('Cleaning Melt workspace...');
50
+ try {
51
+ const cleanResult = await performCleanup(currentDir);
52
+ s.stop('โœ… Cleanup completed!');
53
+ displayCleanupResults(cleanResult);
54
+ outro(chalk.green('๐ŸŽ‰ Melt workspace cleaned successfully!'));
55
+ }
56
+ catch (error) {
57
+ s.stop('โŒ Cleanup failed');
58
+ console.error(chalk.red('Error during cleanup:'), error);
59
+ process.exit(1);
60
+ }
61
+ }
62
+ async function analyzeCleanupTarget(baseDir) {
63
+ const result = {
64
+ removedFiles: [],
65
+ removedDirs: [],
66
+ preservedFiles: [],
67
+ errors: [],
68
+ };
69
+ const meltDir = path.join(baseDir, '.melt');
70
+ const cursorCommandsDir = path.join(baseDir, '.cursor', 'commands');
71
+ try {
72
+ // Check .melt directory (remove entirely)
73
+ if (await fs.pathExists(meltDir)) {
74
+ result.removedDirs.push('.melt/');
75
+ }
76
+ // Check .cursor/commands directory (selective removal)
77
+ if (await fs.pathExists(cursorCommandsDir)) {
78
+ const commandFiles = await fs.readdir(cursorCommandsDir);
79
+ for (const file of commandFiles) {
80
+ const filePath = path.join(cursorCommandsDir, file);
81
+ const stat = await fs.stat(filePath);
82
+ if (stat.isFile()) {
83
+ if (MELT_COMMAND_FILES.includes(file)) {
84
+ result.removedFiles.push(path.join('.cursor/commands', file));
85
+ }
86
+ else {
87
+ result.preservedFiles.push(path.join('.cursor/commands', file));
88
+ }
89
+ }
90
+ }
91
+ // Check if .cursor/commands will be empty after cleaning
92
+ const meltFiles = commandFiles.filter(file => MELT_COMMAND_FILES.includes(file));
93
+ const nonMeltFiles = commandFiles.filter(file => !MELT_COMMAND_FILES.includes(file));
94
+ if (meltFiles.length > 0 && nonMeltFiles.length === 0) {
95
+ result.removedDirs.push('.cursor/commands/');
96
+ // Also check if .cursor itself would be empty
97
+ const cursorDir = path.join(baseDir, '.cursor');
98
+ const cursorContents = await fs.readdir(cursorDir);
99
+ if (cursorContents.length === 1 && cursorContents[0] === 'commands') {
100
+ result.removedDirs.push('.cursor/');
101
+ }
102
+ }
103
+ }
104
+ }
105
+ catch (error) {
106
+ result.errors.push(`Failed to analyze cleanup target: ${error instanceof Error ? error.message : String(error)}`);
107
+ }
108
+ return result;
109
+ }
110
+ async function performCleanup(baseDir) {
111
+ const result = {
112
+ removedFiles: [],
113
+ removedDirs: [],
114
+ preservedFiles: [],
115
+ errors: [],
116
+ };
117
+ const meltDir = path.join(baseDir, '.melt');
118
+ const cursorCommandsDir = path.join(baseDir, '.cursor', 'commands');
119
+ try {
120
+ // Remove .melt directory entirely
121
+ if (await fs.pathExists(meltDir)) {
122
+ await fs.remove(meltDir);
123
+ result.removedDirs.push('.melt/');
124
+ }
125
+ // Remove Melt command files from .cursor/commands
126
+ if (await fs.pathExists(cursorCommandsDir)) {
127
+ const commandFiles = await fs.readdir(cursorCommandsDir);
128
+ let hasNonMeltFiles = false;
129
+ for (const file of commandFiles) {
130
+ const filePath = path.join(cursorCommandsDir, file);
131
+ const stat = await fs.stat(filePath);
132
+ if (stat.isFile()) {
133
+ if (MELT_COMMAND_FILES.includes(file)) {
134
+ try {
135
+ await fs.remove(filePath);
136
+ result.removedFiles.push(path.join('.cursor/commands', file));
137
+ }
138
+ catch (error) {
139
+ result.errors.push(`Failed to remove ${file}: ${error instanceof Error ? error.message : String(error)}`);
140
+ }
141
+ }
142
+ else {
143
+ hasNonMeltFiles = true;
144
+ result.preservedFiles.push(path.join('.cursor/commands', file));
145
+ }
146
+ }
147
+ }
148
+ // Remove empty directories if no user files remain
149
+ if (!hasNonMeltFiles) {
150
+ try {
151
+ await fs.remove(cursorCommandsDir);
152
+ result.removedDirs.push('.cursor/commands/');
153
+ // Check if .cursor directory is now empty
154
+ const cursorDir = path.join(baseDir, '.cursor');
155
+ if (await fs.pathExists(cursorDir)) {
156
+ const cursorContents = await fs.readdir(cursorDir);
157
+ if (cursorContents.length === 0) {
158
+ await fs.remove(cursorDir);
159
+ result.removedDirs.push('.cursor/');
160
+ }
161
+ }
162
+ }
163
+ catch (error) {
164
+ result.errors.push(`Failed to remove empty directories: ${error instanceof Error ? error.message : String(error)}`);
165
+ }
166
+ }
167
+ }
168
+ }
169
+ catch (error) {
170
+ result.errors.push(`Cleanup failed: ${error instanceof Error ? error.message : String(error)}`);
171
+ }
172
+ return result;
173
+ }
174
+ function displayCleanupPlan(result) {
175
+ console.log();
176
+ console.log(chalk.cyan('๐Ÿ“‹ Cleanup Plan:'));
177
+ console.log();
178
+ if (result.removedDirs.length > 0) {
179
+ console.log(chalk.yellow('Directories to be removed:'));
180
+ result.removedDirs.forEach(dir => {
181
+ console.log(` โ€ข ${chalk.red(dir)}`);
182
+ });
183
+ console.log();
184
+ }
185
+ if (result.removedFiles.length > 0) {
186
+ console.log(chalk.yellow('Files to be removed:'));
187
+ result.removedFiles.forEach(file => {
188
+ console.log(` โ€ข ${chalk.red(file)}`);
189
+ });
190
+ console.log();
191
+ }
192
+ if (result.preservedFiles.length > 0) {
193
+ console.log(chalk.green('Files to be preserved:'));
194
+ result.preservedFiles.forEach(file => {
195
+ console.log(` โ€ข ${chalk.cyan(file)}`);
196
+ });
197
+ console.log();
198
+ }
199
+ if (result.errors.length > 0) {
200
+ console.log(chalk.red('Analysis errors:'));
201
+ result.errors.forEach(error => {
202
+ console.log(` โš ๏ธ ${error}`);
203
+ });
204
+ console.log();
205
+ }
206
+ }
207
+ function displayCleanupResults(result) {
208
+ console.log();
209
+ console.log(chalk.green('๐ŸŽฏ Cleanup Summary:'));
210
+ console.log();
211
+ if (result.removedDirs.length > 0) {
212
+ console.log(chalk.green(`โœ… Removed ${result.removedDirs.length} directories:`));
213
+ result.removedDirs.forEach(dir => {
214
+ console.log(` โ€ข ${dir}`);
215
+ });
216
+ console.log();
217
+ }
218
+ if (result.removedFiles.length > 0) {
219
+ console.log(chalk.green(`โœ… Removed ${result.removedFiles.length} files:`));
220
+ result.removedFiles.forEach(file => {
221
+ console.log(` โ€ข ${file}`);
222
+ });
223
+ console.log();
224
+ }
225
+ if (result.preservedFiles.length > 0) {
226
+ console.log(chalk.cyan(`๐Ÿ”’ Preserved ${result.preservedFiles.length} user files:`));
227
+ result.preservedFiles.forEach(file => {
228
+ console.log(` โ€ข ${file}`);
229
+ });
230
+ console.log();
231
+ }
232
+ if (result.errors.length > 0) {
233
+ console.log(chalk.red(`โŒ Errors encountered (${result.errors.length}):`));
234
+ result.errors.forEach(error => {
235
+ console.log(` โ€ข ${error}`);
236
+ });
237
+ console.log();
238
+ }
239
+ }
@@ -119,22 +119,21 @@ async function copyTemplates(baseDir, shell) {
119
119
  if (existsSync(cursorCommandsTemplateDir)) {
120
120
  fs.copySync(cursorCommandsTemplateDir, cursorCommandsDestDir);
121
121
  }
122
- // Create initial context file from template
123
- const contextTemplatePath = join(templatesDir, 'melt-memory', 'context.md');
122
+ // Create initial context file from memory
123
+ const contextSourcePath = join(__dirname, '../../../memory', 'context.md');
124
124
  const contextDestPath = join(baseDir, '.melt', 'memory', 'context.md');
125
- if (existsSync(contextTemplatePath)) {
126
- let contextContent = readFileSync(contextTemplatePath, 'utf8');
125
+ if (existsSync(contextSourcePath)) {
126
+ let contextContent = readFileSync(contextSourcePath, 'utf8');
127
127
  // Replace timestamp placeholders
128
128
  const timestamp = new Date().toISOString();
129
129
  contextContent = contextContent.replace(/{{timestamp}}/g, timestamp);
130
130
  fs.writeFileSync(contextDestPath, contextContent, 'utf8');
131
131
  }
132
- // Copy shell script from template
133
- const scriptExt = shell === 'ps' ? 'ps1' : 'sh';
134
- const scriptTemplatePath = join(templatesDir, 'melt-scripts', shell, `common.${scriptExt}`);
135
- const scriptDestPath = join(baseDir, '.melt', 'scripts', shell, `common.${scriptExt}`);
136
- if (existsSync(scriptTemplatePath)) {
137
- fs.copySync(scriptTemplatePath, scriptDestPath);
132
+ // Copy shell scripts from CLI package
133
+ const scriptsSourceDir = join(__dirname, '../../../scripts', shell);
134
+ const scriptsDestDir = join(baseDir, '.melt', 'scripts', shell);
135
+ if (existsSync(scriptsSourceDir)) {
136
+ fs.copySync(scriptsSourceDir, scriptsDestDir);
138
137
  }
139
138
  }
140
139
  function createVersionFile(meltDir) {
package/dist/index.js CHANGED
@@ -5,6 +5,7 @@ import { join, dirname } from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import { initCommand } from './commands/project/init.js';
7
7
  import { updateCommand } from './commands/project/update.js';
8
+ import { cleanCommand } from './commands/project/clean.js';
8
9
  // Read version from package.json
9
10
  const __filename = fileURLToPath(import.meta.url);
10
11
  const __dirname = dirname(__filename);
@@ -34,4 +35,10 @@ projectCommand
34
35
  .command('update')
35
36
  .description('Update project configurations to latest version')
36
37
  .action(updateCommand);
38
+ projectCommand
39
+ .command('clean')
40
+ .description('Remove all Melt-generated files from the project')
41
+ .action(() => {
42
+ return cleanCommand();
43
+ });
37
44
  program.parse(process.argv);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@meltstudio/meltctl",
3
- "version": "2.0.3",
3
+ "version": "2.2.0",
4
4
  "description": "CLI tool for Melt development process automation - initialize and update project configurations",
5
5
  "main": "dist/index.js",
6
6
  "type": "module",
@@ -1,15 +1,25 @@
1
1
  ---
2
- description: Create implementation plan following Melt's domain-driven architecture standards
2
+ description: Create implementation plan with User Story and RFC validation following Melt's domain-driven architecture standards
3
+ scripts:
4
+ sh: .melt/scripts/sh/get-plan-context.sh --json
5
+ ps: .melt/scripts/ps/get-plan-context.ps1 -Json
3
6
  ---
4
7
 
5
8
  Create a comprehensive implementation plan for the requested feature. Follow these steps:
6
9
 
7
- 1. **Analyze Requirements**:
8
- - Read `.melt/memory/context.md` for project context and constraints
10
+ 1. **Collect Requirements**:
11
+ - Run `{SCRIPT}` from the project root and parse JSON for USER_STORY, RFC, ISSUE_ID, PLAN_FILE, and PROJECT_ROOT
12
+ - If script fails, prompt manually for User Story and RFC
13
+ - Validate that both User Story and RFC are complete and actionable
14
+
15
+ 2. **Analyze Requirements**:
16
+ - Analyze the provided User Story for clear acceptance criteria and user value
17
+ - Review the RFC for technical decisions, architecture, and constraints
18
+ - Read `.melt/memory/context.md` for additional project context
9
19
  - Identify the target domain(s) this feature belongs to
10
20
  - Determine integration points with existing domains
11
21
 
12
- 2. **Architecture Planning**:
22
+ 3. **Architecture Planning**:
13
23
  - Design domain structure following our patterns:
14
24
  ```
15
25
  src/domains/[domain]/
@@ -23,27 +33,28 @@ Create a comprehensive implementation plan for the requested feature. Follow the
23
33
  - Plan component hierarchy and state management
24
34
  - Design data flow and API integration points
25
35
 
26
- 3. **Technical Decisions**:
36
+ 4. **Technical Decisions**:
27
37
  - Choose appropriate React patterns (server vs client components)
28
38
  - Plan Zod schemas for data validation
29
39
  - Design TypeScript interfaces and types
30
40
  - Plan testing strategy (unit, integration, e2e)
31
41
 
32
- 4. **Implementation Strategy**:
42
+ 5. **Implementation Strategy**:
33
43
  - Break down into atomic, testable units
34
44
  - Identify dependencies and execution order
35
45
  - Plan for error handling and edge cases
36
46
  - Consider accessibility and performance requirements
37
47
 
38
- 5. **Create Detailed Plan**:
48
+ 6. **Create Detailed Plan**:
39
49
  - List specific files to create/modify
40
50
  - Define component interfaces and props
41
51
  - Specify API endpoints and data contracts
42
52
  - Plan test cases and coverage targets
43
53
 
44
- 6. **Save Planning Output**:
45
- - Save comprehensive plan to `.melt/outputs/plans/[timestamp]-plan.md`
46
- - Update `.melt/memory/context.md` with planning decisions
47
- - Create task breakdown for implementation
54
+ 7. **Save Planning Output**:
55
+ - Save comprehensive plan to the PLAN_FILE path provided by the script
56
+ - Include User Story and RFC as context sections in the plan
57
+ - Update `.melt/memory/context.md` with planning decisions and issue reference
58
+ - Create task breakdown for implementation with clear dependencies
48
59
 
49
60
  Focus on maintainable, testable code that follows our domain-driven architecture principles.
@@ -1,23 +0,0 @@
1
- # Project Context
2
-
3
- ## Project Overview
4
- This project uses Melt's development standards with domain-driven architecture.
5
-
6
- ## Architecture Principles
7
- - **Domain-Driven Design**: Code organized by business domains
8
- - **React 2025 Standards**: Functional components, hooks, TypeScript strict
9
- - **Type Safety**: Zod schemas for runtime validation
10
- - **Testing**: 80% coverage minimum with comprehensive test strategies
11
-
12
- ## Development Workflow
13
- 1. **Plan**: Create implementation plans in `.melt/outputs/plans/`
14
- 2. **Implement**: Follow domain architecture patterns
15
- 3. **Review**: Ensure quality and compliance
16
- 4. **Debug**: Systematic problem-solving approach
17
-
18
- ## Current Status
19
- - Initialized: {{timestamp}}
20
- - Last Updated: {{timestamp}}
21
-
22
- ## Notes
23
- Update this file as the project evolves to maintain context for AI assistants and team members.
@@ -1,48 +0,0 @@
1
- #!/usr/bin/env pwsh
2
- # Common utilities for Melt development workflow
3
-
4
- function Get-ProjectRoot {
5
- try {
6
- git rev-parse --show-toplevel
7
- }
8
- catch {
9
- Get-Location
10
- }
11
- }
12
-
13
- function Get-Timestamp {
14
- Get-Date -Format "yyyyMMdd-HHmmss"
15
- }
16
-
17
- function Test-MeltWorkspace {
18
- if (-not (Test-Path ".melt" -PathType Container)) {
19
- Write-Error "ERROR: .melt workspace not found. Run 'meltctl project init' first."
20
- exit 1
21
- }
22
- }
23
-
24
- function New-OutputFile {
25
- param(
26
- [string]$Category,
27
- [string]$Name
28
- )
29
-
30
- $timestamp = Get-Timestamp
31
- $filename = "$timestamp-$Name.md"
32
- $filepath = ".melt/outputs/$Category/$filename"
33
-
34
- New-Item -Path ".melt/outputs/$Category" -ItemType Directory -Force | Out-Null
35
- return $filepath
36
- }
37
-
38
- function Update-Context {
39
- param([string]$Message)
40
-
41
- $timestamp = Get-Date -Format "yyyy-MM-ddTHH:mm:ssK"
42
-
43
- Add-Content -Path ".melt/memory/context.md" -Value "## Update: $timestamp"
44
- Add-Content -Path ".melt/memory/context.md" -Value $Message
45
- Add-Content -Path ".melt/memory/context.md" -Value ""
46
- }
47
-
48
- Write-Output "Melt development utilities loaded."
@@ -1,44 +0,0 @@
1
- #!/usr/bin/env bash
2
- # Common utilities for Melt development workflow
3
-
4
- # Get project root directory
5
- get_project_root() {
6
- git rev-parse --show-toplevel 2>/dev/null || pwd
7
- }
8
-
9
- # Get current timestamp for file naming
10
- get_timestamp() {
11
- date '+%Y%m%d-%H%M%S'
12
- }
13
-
14
- # Check if .melt directory exists
15
- check_melt_workspace() {
16
- if [[ ! -d ".melt" ]]; then
17
- echo "ERROR: .melt workspace not found. Run 'meltctl project init' first."
18
- exit 1
19
- fi
20
- }
21
-
22
- # Create output file with timestamp
23
- create_output_file() {
24
- local category="$1"
25
- local name="$2"
26
- local timestamp=$(get_timestamp)
27
- local filename="${timestamp}-${name}.md"
28
- local filepath=".melt/outputs/${category}/${filename}"
29
-
30
- mkdir -p ".melt/outputs/${category}"
31
- echo "$filepath"
32
- }
33
-
34
- # Update context file with new information
35
- update_context() {
36
- local message="$1"
37
- local timestamp=$(date -Iseconds)
38
-
39
- echo "## Update: $timestamp" >> .melt/memory/context.md
40
- echo "$message" >> .melt/memory/context.md
41
- echo "" >> .melt/memory/context.md
42
- }
43
-
44
- echo "Melt development utilities loaded."