-
-
Notifications
You must be signed in to change notification settings - Fork 5.9k
Expand file tree
/
Copy pathcursor-plugin.test.js
More file actions
111 lines (98 loc) · 4.43 KB
/
Copy pathcursor-plugin.test.js
File metadata and controls
111 lines (98 loc) · 4.43 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
#!/usr/bin/env node
// Smoke test for the Cursor plugin adapter: manifest, hooks, rules, command and
// skills wiring, plus the Cursor-specific hook output shape (Cursor parses hook
// stdout as JSON and only sessionStart accepts injected context).
const test = require('node:test');
const assert = require('node:assert/strict');
const fs = require('fs');
const os = require('os');
const path = require('path');
const { spawnSync } = require('child_process');
const root = path.join(__dirname, '..');
const SKILL_DIRS = [
'ponytail',
'ponytail-review',
'ponytail-audit',
'ponytail-debt',
'ponytail-gain',
'ponytail-help',
];
function readJSON(relPath) {
return JSON.parse(fs.readFileSync(path.join(root, relPath), 'utf8'));
}
test('cursor plugin manifest points at files that ship', () => {
const manifest = readJSON('.cursor-plugin/plugin.json');
assert.equal(manifest.name, 'ponytail');
assert.ok(manifest.version, 'manifest must declare a version');
assert.ok(manifest.description, 'manifest must declare a description');
assert.equal(manifest.license, 'MIT');
assert.equal(manifest.rules, './.cursor/rules/');
assert.equal(manifest.skills, './skills/');
assert.equal(manifest.commands, './.cursor/commands/');
assert.equal(manifest.hooks, './hooks/cursor-hooks.json');
for (const rel of [manifest.rules, manifest.skills, manifest.commands, manifest.hooks, manifest.logo]) {
assert.ok(fs.existsSync(path.join(root, rel)), `manifest points at missing path: ${rel}`);
}
for (const skill of SKILL_DIRS) {
assert.ok(
fs.existsSync(path.join(root, 'skills', skill, 'SKILL.md')),
`missing skill: skills/${skill}/SKILL.md`,
);
}
});
test('cursor hooks use camelCase events and resolve via CURSOR_PLUGIN_ROOT', () => {
const config = readJSON('hooks/cursor-hooks.json');
assert.equal(config.version, 1);
assert.match(config.hooks.sessionStart[0].command, /ponytail-activate\.js/);
assert.match(config.hooks.beforeSubmitPrompt[0].command, /ponytail-mode-tracker\.js/);
for (const event of Object.keys(config.hooks)) {
assert.match(
config.hooks[event][0].command,
/\$\{CURSOR_PLUGIN_ROOT\}/,
`${event} command must resolve through CURSOR_PLUGIN_ROOT`,
);
}
});
test('cursor rule and /ponytail command exist', () => {
const rule = fs.readFileSync(path.join(root, '.cursor', 'rules', 'ponytail.mdc'), 'utf8');
assert.match(rule, /alwaysApply:\s*true/, 'the rule must be always-on');
assert.match(rule, /lazy senior developer/);
const command = fs.readFileSync(path.join(root, '.cursor', 'commands', 'ponytail.md'), 'utf8');
assert.match(command, /^---\n[\s\S]*description:/, 'command needs frontmatter with a description');
assert.match(command, /lite, full, ultra, or off/);
});
test('cursor hook output: sessionStart injects, beforeSubmitPrompt only confirms', () => {
const temp = fs.mkdtempSync(path.join(os.tmpdir(), 'ponytail-cursor-'));
const state = path.join(temp, '.cursor', '.ponytail-active');
const env = {
...process.env,
HOME: temp,
USERPROFILE: temp,
CURSOR_PLUGIN_ROOT: root,
PONYTAIL_DEFAULT_MODE: 'full',
};
delete env.PLUGIN_DATA;
delete env.COPILOT_PLUGIN_DATA;
delete env.QODER_SESSION_ID;
const run = (script, input = '') => spawnSync(
process.execPath,
[path.join(root, 'hooks', script)],
{ env, input, encoding: 'utf8' },
);
let result = run('ponytail-activate.js');
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.readFileSync(state, 'utf8'), 'full');
let output = JSON.parse(result.stdout);
assert.match(output.additional_context, /PONYTAIL MODE ACTIVE — level: full/);
assert.ok(!/STATUSLINE SETUP NEEDED/.test(output.additional_context), 'Cursor has no statusline to nudge about');
result = run('ponytail-mode-tracker.js', JSON.stringify({ prompt: '/ponytail ultra' }));
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.readFileSync(state, 'utf8'), 'ultra', 'switch must persist to ~/.cursor');
output = JSON.parse(result.stdout);
assert.equal(output.continue, true, 'beforeSubmitPrompt must never block the prompt');
assert.match(output.user_message, /PONYTAIL MODE CHANGED — level: ultra/);
result = run('ponytail-mode-tracker.js', JSON.stringify({ prompt: 'stop ponytail' }));
assert.equal(result.status, 0, result.stderr);
assert.equal(fs.existsSync(state), false, 'flag must be cleared after stop ponytail');
fs.rmSync(temp, { recursive: true, force: true });
});