mspec 0.0.7 → 0.0.8

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
@@ -19,7 +19,7 @@ It is designed to work seamlessly alongside your favorite AI coding agents: Clau
19
19
  We recommend running `mspec` directly via `npx` so you always get the freshest, most up-to-date prompts for your AI agents when initializing a new project.
20
20
 
21
21
  ```bash
22
- npx mspec init
22
+ npx mspec@latest init
23
23
  ```
24
24
 
25
25
  ---
@@ -31,11 +31,12 @@ The workflow follows a simple three-step loop: **Initialize -> Plan -> Implement
31
31
  ### Step 1: Initialize the Project
32
32
  Run this command in the root of your project:
33
33
  ```bash
34
- npx mspec init
34
+ npx mspec@latest init
35
35
  ```
36
36
  - It will prompt you for your preferred AI agent (Claude, Gemini, Cursor, etc.).
37
37
  - It will create the `.mspec/specs/` and `.mspec/tasks/` directories.
38
38
  - It will automatically inject custom commands into your project (e.g., `.gemini/commands/mspec.plan.toml` or `.cursor/rules/mspec.implement.mdc`) so your AI agent natively understands the framework and provides autocomplete commands like `/mspec.spec`.
39
+ - **Note:** If you already have `.mspec` in your project, running this command will **update** your local AI instructions to the latest version without overwriting your specs or tasks.
39
40
 
40
41
  *(Note: After running `init`, you may need to restart your AI agent session so it can detect the new slash commands).*
41
42
 
@@ -12,27 +12,44 @@ const templates_1 = require("../templates");
12
12
  const { prompt } = require('enquirer');
13
13
  async function initCommand() {
14
14
  const mspecDir = path_1.default.join(process.cwd(), '.mspec');
15
+ const configPath = path_1.default.join(mspecDir, 'mspec.json');
16
+ let existingAgent = '';
15
17
  if (fs_1.default.existsSync(mspecDir)) {
16
- console.log(chalk_1.default.yellow('.mspec directory already exists. Initialization skipped.'));
17
- return;
18
+ console.log(chalk_1.default.blue('Existing .mspec directory found. Updating integration files...'));
19
+ if (fs_1.default.existsSync(configPath)) {
20
+ try {
21
+ const config = JSON.parse(fs_1.default.readFileSync(configPath, 'utf-8'));
22
+ if (config && config.agent) {
23
+ existingAgent = config.agent;
24
+ }
25
+ }
26
+ catch (e) {
27
+ // ignore JSON parse errors
28
+ }
29
+ }
18
30
  }
19
- let response;
20
- try {
21
- response = await prompt({
22
- type: 'select',
23
- name: 'agent',
24
- message: 'Which AI agent are you using?',
25
- choices: ['claude', 'gemini', 'cursor', 'opencode', 'zed', 'generic'],
26
- });
31
+ let agent = existingAgent;
32
+ if (!agent) {
33
+ let response;
34
+ try {
35
+ response = await prompt({
36
+ type: 'select',
37
+ name: 'agent',
38
+ message: 'Which AI agent are you using?',
39
+ choices: ['claude', 'gemini', 'cursor', 'opencode', 'zed', 'generic'],
40
+ });
41
+ }
42
+ catch (error) {
43
+ console.log(chalk_1.default.yellow('\nInitialization cancelled.'));
44
+ return;
45
+ }
46
+ agent = response.agent;
27
47
  }
28
- catch (error) {
29
- console.log(chalk_1.default.yellow('\nInitialization cancelled.'));
30
- return;
48
+ // Create directories if they don't exist
49
+ if (!fs_1.default.existsSync(mspecDir)) {
50
+ fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'specs'), { recursive: true });
51
+ fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'tasks'), { recursive: true });
31
52
  }
32
- const agent = response.agent;
33
- // Create directories
34
- fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'specs'), { recursive: true });
35
- fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'tasks'), { recursive: true });
36
53
  // Write mspec.json config
37
54
  const mspecConfig = {
38
55
  agent,
@@ -41,7 +58,7 @@ async function initCommand() {
41
58
  tasks: '.mspec/tasks'
42
59
  }
43
60
  };
44
- fs_1.default.writeFileSync(path_1.default.join(mspecDir, 'mspec.json'), JSON.stringify(mspecConfig, null, 2));
61
+ fs_1.default.writeFileSync(configPath, JSON.stringify(mspecConfig, null, 2));
45
62
  // Write agent integration files
46
63
  const agentTemplates = (0, templates_1.getTemplates)(agent);
47
64
  if (agentTemplates.length > 0) {
@@ -49,11 +66,11 @@ async function initCommand() {
49
66
  const targetDir = path_1.default.join(process.cwd(), template.dir);
50
67
  fs_1.default.mkdirSync(targetDir, { recursive: true });
51
68
  fs_1.default.writeFileSync(path_1.default.join(targetDir, template.file), template.content);
52
- console.log(chalk_1.default.green(`Created integration file for ${agent} at ${path_1.default.join(template.dir, template.file)}`));
69
+ console.log(chalk_1.default.green(`Updated integration file for ${agent} at ${path_1.default.join(template.dir, template.file)}`));
53
70
  }
54
71
  }
55
72
  else {
56
73
  console.log(chalk_1.default.yellow(`No specific integration template found for ${agent}. Setup completed with generic settings.`));
57
74
  }
58
- console.log(chalk_1.default.green('mspec initialized successfully!'));
75
+ console.log(chalk_1.default.green('mspec initialized/updated successfully!'));
59
76
  }
@@ -53,13 +53,26 @@ describe('initCommand', () => {
53
53
  }
54
54
  });
55
55
  });
56
- it('should skip initialization if .mspec directory already exists', async () => {
56
+ it('should update integration files if .mspec directory already exists', async () => {
57
57
  const mspecDir = path_1.default.join(tmpDir, '.mspec');
58
58
  fs_1.default.mkdirSync(mspecDir);
59
+ fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'specs'));
60
+ fs_1.default.mkdirSync(path_1.default.join(mspecDir, 'tasks'));
61
+ fs_1.default.writeFileSync(path_1.default.join(mspecDir, 'mspec.json'), JSON.stringify({ agent: 'cursor' }));
59
62
  const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
63
+ mockPrompt.mockRejectedValueOnce(new Error('Should not prompt'));
60
64
  await (0, init_1.initCommand)();
61
- expect(fs_1.default.existsSync(path_1.default.join(mspecDir, 'specs'))).toBe(false);
62
- expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Initialization skipped.'));
65
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Existing .mspec directory found. Updating integration files...'));
66
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.cursor/rules/mspec.spec.mdc'))).toBe(true);
67
+ });
68
+ it('should prompt if .mspec directory exists but mspec.json is missing or invalid', async () => {
69
+ const mspecDir = path_1.default.join(tmpDir, '.mspec');
70
+ fs_1.default.mkdirSync(mspecDir);
71
+ mockPrompt.mockResolvedValueOnce({ agent: 'gemini' });
72
+ const consoleSpy = jest.spyOn(console, 'log').mockImplementation();
73
+ await (0, init_1.initCommand)();
74
+ expect(consoleSpy).toHaveBeenCalledWith(expect.stringContaining('Existing .mspec directory found. Updating integration files...'));
75
+ expect(fs_1.default.existsSync(path_1.default.join(tmpDir, '.gemini/commands/mspec.spec.toml'))).toBe(true);
63
76
  });
64
77
  it('should handle prompt cancellation gracefully', async () => {
65
78
  mockPrompt.mockRejectedValueOnce(new Error('User cancelled'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mspec",
3
- "version": "0.0.7",
3
+ "version": "0.0.8",
4
4
  "description": "A minimalist Spec-Driven Development (SDD) toolkit for solo developers and AI agents",
5
5
  "main": "index.js",
6
6
  "scripts": {