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 +3 -2
- package/dist/commands/init.js +37 -20
- package/dist/commands/init.test.js +16 -3
- package/package.json +1 -1
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
|
|
package/dist/commands/init.js
CHANGED
|
@@ -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.
|
|
17
|
-
|
|
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
|
|
20
|
-
|
|
21
|
-
response
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
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
|
-
|
|
29
|
-
|
|
30
|
-
|
|
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(
|
|
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(`
|
|
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
|
|
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(
|
|
62
|
-
expect(
|
|
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'));
|