pulse-framework-cli 0.4.1
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/dist/commands/checkpoint.d.ts +2 -0
- package/dist/commands/checkpoint.js +129 -0
- package/dist/commands/correct.d.ts +2 -0
- package/dist/commands/correct.js +77 -0
- package/dist/commands/doctor.d.ts +2 -0
- package/dist/commands/doctor.js +183 -0
- package/dist/commands/escalate.d.ts +2 -0
- package/dist/commands/escalate.js +226 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.js +570 -0
- package/dist/commands/learn.d.ts +2 -0
- package/dist/commands/learn.js +137 -0
- package/dist/commands/profile.d.ts +2 -0
- package/dist/commands/profile.js +39 -0
- package/dist/commands/reset.d.ts +2 -0
- package/dist/commands/reset.js +130 -0
- package/dist/commands/review.d.ts +2 -0
- package/dist/commands/review.js +129 -0
- package/dist/commands/run.d.ts +2 -0
- package/dist/commands/run.js +272 -0
- package/dist/commands/start.d.ts +2 -0
- package/dist/commands/start.js +196 -0
- package/dist/commands/status.d.ts +2 -0
- package/dist/commands/status.js +239 -0
- package/dist/commands/watch.d.ts +2 -0
- package/dist/commands/watch.js +98 -0
- package/dist/hooks/install.d.ts +1 -0
- package/dist/hooks/install.js +89 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +40 -0
- package/dist/lib/artifacts.d.ts +7 -0
- package/dist/lib/artifacts.js +52 -0
- package/dist/lib/briefing.d.ts +77 -0
- package/dist/lib/briefing.js +231 -0
- package/dist/lib/clipboard.d.ts +9 -0
- package/dist/lib/clipboard.js +116 -0
- package/dist/lib/config.d.ts +14 -0
- package/dist/lib/config.js +167 -0
- package/dist/lib/context-export.d.ts +30 -0
- package/dist/lib/context-export.js +149 -0
- package/dist/lib/exec.d.ts +9 -0
- package/dist/lib/exec.js +23 -0
- package/dist/lib/git.d.ts +24 -0
- package/dist/lib/git.js +74 -0
- package/dist/lib/input.d.ts +15 -0
- package/dist/lib/input.js +80 -0
- package/dist/lib/notifications.d.ts +2 -0
- package/dist/lib/notifications.js +25 -0
- package/dist/lib/paths.d.ts +4 -0
- package/dist/lib/paths.js +39 -0
- package/dist/lib/prompts.d.ts +43 -0
- package/dist/lib/prompts.js +270 -0
- package/dist/lib/scanner.d.ts +37 -0
- package/dist/lib/scanner.js +413 -0
- package/dist/lib/types.d.ts +37 -0
- package/dist/lib/types.js +2 -0
- package/package.json +42 -0
- package/templates/.cursorrules +159 -0
- package/templates/AGENTS.md +198 -0
- package/templates/cursor/mcp.json +9 -0
- package/templates/cursor/pulse.mdc +144 -0
- package/templates/roles/architect.cursorrules +15 -0
- package/templates/roles/backend.cursorrules +12 -0
- package/templates/roles/frontend.cursorrules +12 -0
|
@@ -0,0 +1,570 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.registerInitCommand = registerInitCommand;
|
|
7
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_os_1 = __importDefault(require("node:os"));
|
|
10
|
+
const node_child_process_1 = require("node:child_process");
|
|
11
|
+
const artifacts_js_1 = require("../lib/artifacts.js");
|
|
12
|
+
const config_js_1 = require("../lib/config.js");
|
|
13
|
+
const paths_js_1 = require("../lib/paths.js");
|
|
14
|
+
const install_js_1 = require("../hooks/install.js");
|
|
15
|
+
const input_js_1 = require("../lib/input.js");
|
|
16
|
+
async function fileExists(p) {
|
|
17
|
+
try {
|
|
18
|
+
await promises_1.default.stat(p);
|
|
19
|
+
return true;
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function packageRoot() {
|
|
26
|
+
// dist/index.js -> dist/.. (package root)
|
|
27
|
+
return node_path_1.default.resolve(__dirname, "..", "..");
|
|
28
|
+
}
|
|
29
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
30
|
+
// Helper Functions for MCP Setup
|
|
31
|
+
// ════════════════════════════════════════════════════════════════════════════
|
|
32
|
+
/**
|
|
33
|
+
* Get absolute path to Node.js executable
|
|
34
|
+
*/
|
|
35
|
+
function getNodePath() {
|
|
36
|
+
try {
|
|
37
|
+
return (0, node_child_process_1.execSync)("which node", { encoding: "utf8" }).trim();
|
|
38
|
+
}
|
|
39
|
+
catch {
|
|
40
|
+
// Fallback for common locations
|
|
41
|
+
const fallbacks = [
|
|
42
|
+
"/usr/local/bin/node",
|
|
43
|
+
"/opt/homebrew/bin/node",
|
|
44
|
+
`${node_os_1.default.homedir()}/.nvm/current/bin/node`,
|
|
45
|
+
];
|
|
46
|
+
for (const p of fallbacks) {
|
|
47
|
+
try {
|
|
48
|
+
(0, node_child_process_1.execSync)(`${p} --version`, { encoding: "utf8" });
|
|
49
|
+
return p;
|
|
50
|
+
}
|
|
51
|
+
catch {
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
throw new Error("Node.js not found. Please ensure node is in your PATH.");
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Find pulse-mcp, install if not found
|
|
60
|
+
*/
|
|
61
|
+
async function ensurePulseMcpInstalled() {
|
|
62
|
+
// First check if already installed
|
|
63
|
+
try {
|
|
64
|
+
const mcpPath = (0, node_child_process_1.execSync)("which pulse-mcp", { encoding: "utf8" }).trim();
|
|
65
|
+
if (mcpPath)
|
|
66
|
+
return mcpPath;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
// Not in PATH, continue to check other locations
|
|
70
|
+
}
|
|
71
|
+
// Check common npm global locations
|
|
72
|
+
const globalLocations = [
|
|
73
|
+
`${node_os_1.default.homedir()}/.npm-global/bin/pulse-mcp`,
|
|
74
|
+
"/usr/local/bin/pulse-mcp",
|
|
75
|
+
`${node_os_1.default.homedir()}/.pulse/node_modules/.bin/pulse-mcp`,
|
|
76
|
+
];
|
|
77
|
+
for (const loc of globalLocations) {
|
|
78
|
+
if (await fileExists(loc)) {
|
|
79
|
+
return loc;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
// Check if we're in the PulseFramework monorepo (development)
|
|
83
|
+
const monorepoBin = node_path_1.default.resolve(__dirname, "..", "..", "..", "pulse-mcp", "dist", "index.js");
|
|
84
|
+
if (await fileExists(monorepoBin)) {
|
|
85
|
+
return monorepoBin;
|
|
86
|
+
}
|
|
87
|
+
// Not found - try to install
|
|
88
|
+
// eslint-disable-next-line no-console
|
|
89
|
+
console.log("\n⚠️ pulse-mcp not found. Attempting to install...\n");
|
|
90
|
+
try {
|
|
91
|
+
// Try npm link first (for development)
|
|
92
|
+
(0, node_child_process_1.execSync)("npm link @pulseframework/pulse-mcp 2>/dev/null || npm install -g @pulseframework/pulse-mcp", {
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
stdio: "inherit",
|
|
95
|
+
});
|
|
96
|
+
const newPath = (0, node_child_process_1.execSync)("which pulse-mcp", { encoding: "utf8" }).trim();
|
|
97
|
+
// eslint-disable-next-line no-console
|
|
98
|
+
console.log(`✅ pulse-mcp installed: ${newPath}\n`);
|
|
99
|
+
return newPath;
|
|
100
|
+
}
|
|
101
|
+
catch (installError) {
|
|
102
|
+
// eslint-disable-next-line no-console
|
|
103
|
+
console.log("\n⚠️ Could not auto-install pulse-mcp.");
|
|
104
|
+
// eslint-disable-next-line no-console
|
|
105
|
+
console.log(" Please run manually:");
|
|
106
|
+
// eslint-disable-next-line no-console
|
|
107
|
+
console.log(" npm install -g @pulseframework/pulse-mcp");
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log(" OR (from PulseFramework repo):");
|
|
110
|
+
// eslint-disable-next-line no-console
|
|
111
|
+
console.log(" npm link -w packages/pulse-mcp\n");
|
|
112
|
+
throw new Error("pulse-mcp installation failed");
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* Detect if we're in a subdirectory of a Cursor workspace
|
|
117
|
+
* Returns the workspace root if found, null otherwise
|
|
118
|
+
*/
|
|
119
|
+
async function detectCursorWorkspace(repoRoot) {
|
|
120
|
+
let current = node_path_1.default.dirname(repoRoot);
|
|
121
|
+
// Walk up the directory tree
|
|
122
|
+
while (current !== "/" && current !== node_path_1.default.dirname(current)) {
|
|
123
|
+
const cursorDir = node_path_1.default.join(current, ".cursor");
|
|
124
|
+
if (await fileExists(cursorDir)) {
|
|
125
|
+
return current;
|
|
126
|
+
}
|
|
127
|
+
current = node_path_1.default.dirname(current);
|
|
128
|
+
}
|
|
129
|
+
return null;
|
|
130
|
+
}
|
|
131
|
+
/**
|
|
132
|
+
* Get the global Cursor MCP config path
|
|
133
|
+
*/
|
|
134
|
+
function getGlobalMcpConfigPath() {
|
|
135
|
+
return node_path_1.default.join(node_os_1.default.homedir(), ".cursor", "mcp.json");
|
|
136
|
+
}
|
|
137
|
+
function registerInitCommand(program) {
|
|
138
|
+
program
|
|
139
|
+
.command("init")
|
|
140
|
+
.description("Initialize project for PULSE (.pulse/, Config, MCP, Cursor Rules)")
|
|
141
|
+
.option("-p, --path <path>", "Target path (default: cwd)")
|
|
142
|
+
.option("--hooks", "Install Git hooks (pre-commit, pre-push)")
|
|
143
|
+
.option("--preset <name>", "Preset: frontend, backend, fullstack, monorepo, custom")
|
|
144
|
+
.option("--mcp", "Install MCP + Cursor Rules")
|
|
145
|
+
.option("--global", "Install MCP config globally (~/.cursor/mcp.json)")
|
|
146
|
+
.option("--agents", "Create AGENTS.md (universal for all editors)")
|
|
147
|
+
.option("--no-interactive", "No interactive prompts")
|
|
148
|
+
.action(async (opts) => {
|
|
149
|
+
const start = node_path_1.default.resolve(opts.path ?? process.cwd());
|
|
150
|
+
const repoRoot = await (0, paths_js_1.findRepoRoot)(start);
|
|
151
|
+
if (!repoRoot) {
|
|
152
|
+
throw new Error(`Not in a git repository: ${start}`);
|
|
153
|
+
}
|
|
154
|
+
// eslint-disable-next-line no-console
|
|
155
|
+
console.log("\n🎯 PULSE Init\n");
|
|
156
|
+
await (0, artifacts_js_1.ensurePulseDirs)(repoRoot);
|
|
157
|
+
// eslint-disable-next-line no-console
|
|
158
|
+
console.log(`✅ .pulse/ directory created`);
|
|
159
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
160
|
+
// Preset Auswahl
|
|
161
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
162
|
+
let preset = "custom";
|
|
163
|
+
if (opts.preset && (0, config_js_1.getPresetNames)().includes(opts.preset)) {
|
|
164
|
+
preset = opts.preset;
|
|
165
|
+
}
|
|
166
|
+
else if (opts.interactive !== false) {
|
|
167
|
+
// eslint-disable-next-line no-console
|
|
168
|
+
console.log("\n📦 Choose a preset for your project:\n");
|
|
169
|
+
const choices = [
|
|
170
|
+
{ value: "frontend", label: "🎨 Frontend - React/Vue/Angular (stricter limits)" },
|
|
171
|
+
{ value: "backend", label: "⚙️ Backend - API/Services (moderate limits)" },
|
|
172
|
+
{ value: "fullstack", label: "🔄 Fullstack - Frontend + Backend" },
|
|
173
|
+
{ value: "monorepo", label: "📦 Monorepo - Multiple packages (looser limits)" },
|
|
174
|
+
{ value: "custom", label: "⚙️ Custom - Standard settings" },
|
|
175
|
+
];
|
|
176
|
+
preset = await (0, input_js_1.promptSelect)("Preset", choices, "fullstack");
|
|
177
|
+
}
|
|
178
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
179
|
+
// Create config
|
|
180
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
181
|
+
const cfgPath = (0, paths_js_1.configFile)(repoRoot);
|
|
182
|
+
if (!(await fileExists(cfgPath))) {
|
|
183
|
+
const presetConfig = config_js_1.PRESETS[preset];
|
|
184
|
+
const config = {
|
|
185
|
+
...config_js_1.DEFAULT_CONFIG,
|
|
186
|
+
preset,
|
|
187
|
+
thresholds: {
|
|
188
|
+
warnMaxFilesChanged: presetConfig.warnMaxFilesChanged,
|
|
189
|
+
warnMaxLinesChanged: presetConfig.warnMaxLinesChanged,
|
|
190
|
+
warnMaxDeletions: presetConfig.warnMaxDeletions,
|
|
191
|
+
},
|
|
192
|
+
checkpointReminderMinutes: presetConfig.checkpointReminderMinutes,
|
|
193
|
+
};
|
|
194
|
+
await promises_1.default.writeFile(cfgPath, JSON.stringify(config, null, 2) + "\n", "utf8");
|
|
195
|
+
// eslint-disable-next-line no-console
|
|
196
|
+
console.log(`✅ Config created: ${cfgPath} (Preset: ${preset})`);
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
// eslint-disable-next-line no-console
|
|
200
|
+
console.log(`ℹ️ Config exists: ${cfgPath}`);
|
|
201
|
+
}
|
|
202
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
203
|
+
// .cursorrules kopieren (Fallback ohne MCP)
|
|
204
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
205
|
+
const cursorrulesDst = node_path_1.default.join(repoRoot, ".cursorrules");
|
|
206
|
+
if (!(await fileExists(cursorrulesDst))) {
|
|
207
|
+
const src = node_path_1.default.join(packageRoot(), "templates", ".cursorrules");
|
|
208
|
+
if (await fileExists(src)) {
|
|
209
|
+
await promises_1.default.copyFile(src, cursorrulesDst);
|
|
210
|
+
// eslint-disable-next-line no-console
|
|
211
|
+
console.log(`✅ .cursorrules created (Fallback rules)`);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
// eslint-disable-next-line no-console
|
|
215
|
+
console.log(`⚠️ .cursorrules template not found: ${src}`);
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// eslint-disable-next-line no-console
|
|
220
|
+
console.log(`ℹ️ .cursorrules exists`);
|
|
221
|
+
}
|
|
222
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
223
|
+
// MCP + Cursor Rules (interactive or via flag)
|
|
224
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
225
|
+
let installMcp = opts.mcp === true || opts.global === true;
|
|
226
|
+
const useGlobalMcp = opts.global === true;
|
|
227
|
+
if (!installMcp && opts.interactive !== false) {
|
|
228
|
+
// eslint-disable-next-line no-console
|
|
229
|
+
console.log("");
|
|
230
|
+
installMcp = await (0, input_js_1.promptConfirm)("Install MCP + Cursor Rules? (recommended for Cursor IDE)", true);
|
|
231
|
+
}
|
|
232
|
+
if (installMcp) {
|
|
233
|
+
await installCursorIntegration(repoRoot, useGlobalMcp);
|
|
234
|
+
}
|
|
235
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
236
|
+
// AGENTS.md (Universal for other editors)
|
|
237
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
238
|
+
let installAgents = opts.agents === true;
|
|
239
|
+
if (!installAgents && !installMcp && opts.interactive !== false) {
|
|
240
|
+
// eslint-disable-next-line no-console
|
|
241
|
+
console.log("");
|
|
242
|
+
installAgents = await (0, input_js_1.promptConfirm)("Create AGENTS.md? (universal for Windsurf, Copilot, etc.)", true);
|
|
243
|
+
}
|
|
244
|
+
if (installAgents) {
|
|
245
|
+
await installAgentsMd(repoRoot);
|
|
246
|
+
}
|
|
247
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
248
|
+
// Role Templates kopieren
|
|
249
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
250
|
+
const rolesSrcDir = node_path_1.default.join(packageRoot(), "templates", "roles");
|
|
251
|
+
const rolesDstDir = node_path_1.default.join(repoRoot, ".pulse", "templates", "roles");
|
|
252
|
+
if (await fileExists(rolesSrcDir)) {
|
|
253
|
+
await promises_1.default.mkdir(rolesDstDir, { recursive: true });
|
|
254
|
+
const roleFiles = await promises_1.default.readdir(rolesSrcDir);
|
|
255
|
+
for (const f of roleFiles) {
|
|
256
|
+
if (!f.endsWith(".cursorrules"))
|
|
257
|
+
continue;
|
|
258
|
+
await promises_1.default.copyFile(node_path_1.default.join(rolesSrcDir, f), node_path_1.default.join(rolesDstDir, f));
|
|
259
|
+
}
|
|
260
|
+
// eslint-disable-next-line no-console
|
|
261
|
+
console.log(`✅ Role templates copied`);
|
|
262
|
+
}
|
|
263
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
264
|
+
// Git Hooks
|
|
265
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
266
|
+
if (opts.hooks) {
|
|
267
|
+
await (0, install_js_1.installHooks)(repoRoot);
|
|
268
|
+
// eslint-disable-next-line no-console
|
|
269
|
+
console.log(`✅ Git hooks installed`);
|
|
270
|
+
}
|
|
271
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
272
|
+
// Summary
|
|
273
|
+
// ════════════════════════════════════════════════════════════════════════
|
|
274
|
+
// eslint-disable-next-line no-console
|
|
275
|
+
console.log(`\n${"─".repeat(50)}`);
|
|
276
|
+
// eslint-disable-next-line no-console
|
|
277
|
+
console.log(`\n✨ PULSE initialized!\n`);
|
|
278
|
+
// eslint-disable-next-line no-console
|
|
279
|
+
console.log(`Preset: ${preset}`);
|
|
280
|
+
// eslint-disable-next-line no-console
|
|
281
|
+
console.log(`Max Lines: ${config_js_1.PRESETS[preset].warnMaxLinesChanged}`);
|
|
282
|
+
// eslint-disable-next-line no-console
|
|
283
|
+
console.log(`Checkpoint: ${config_js_1.PRESETS[preset].checkpointReminderMinutes} min`);
|
|
284
|
+
// eslint-disable-next-line no-console
|
|
285
|
+
console.log(`MCP: ${installMcp ? "✅ Installed" : "❌ Not installed"}`);
|
|
286
|
+
// eslint-disable-next-line no-console
|
|
287
|
+
console.log(`AGENTS.md: ${installAgents ? "✅ Created" : "❌ Not created"}\n`);
|
|
288
|
+
if (installMcp) {
|
|
289
|
+
// eslint-disable-next-line no-console
|
|
290
|
+
console.log(`📋 Next steps for MCP:`);
|
|
291
|
+
// eslint-disable-next-line no-console
|
|
292
|
+
console.log(` 1. Restart Cursor (MCP loads automatically)`);
|
|
293
|
+
// eslint-disable-next-line no-console
|
|
294
|
+
console.log(` 2. In Cursor: Settings > Features > Enable MCP`);
|
|
295
|
+
// eslint-disable-next-line no-console
|
|
296
|
+
console.log(` 3. Test: pulse status\n`);
|
|
297
|
+
}
|
|
298
|
+
else {
|
|
299
|
+
// eslint-disable-next-line no-console
|
|
300
|
+
console.log(`Next steps:`);
|
|
301
|
+
// eslint-disable-next-line no-console
|
|
302
|
+
console.log(` 1. pulse status - Check current state`);
|
|
303
|
+
// eslint-disable-next-line no-console
|
|
304
|
+
console.log(` 2. pulse run - Start workflow`);
|
|
305
|
+
// eslint-disable-next-line no-console
|
|
306
|
+
console.log(` 3. pulse s - Create single prompt\n`);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
/**
|
|
311
|
+
* Install Cursor MCP integration
|
|
312
|
+
* Creates .cursor/rules/pulse.mdc and .cursor/mcp.json (or global ~/.cursor/mcp.json)
|
|
313
|
+
*/
|
|
314
|
+
async function installCursorIntegration(repoRoot, useGlobal) {
|
|
315
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
316
|
+
// 0. Workspace Detection - check if Git root differs from Cursor workspace
|
|
317
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
318
|
+
const workspaceRoot = await detectCursorWorkspace(repoRoot);
|
|
319
|
+
const installLocations = [repoRoot];
|
|
320
|
+
if (workspaceRoot && workspaceRoot !== repoRoot) {
|
|
321
|
+
// eslint-disable-next-line no-console
|
|
322
|
+
console.log(`\n⚠️ Workspace mismatch detected:`);
|
|
323
|
+
// eslint-disable-next-line no-console
|
|
324
|
+
console.log(` Git root: ${repoRoot}`);
|
|
325
|
+
// eslint-disable-next-line no-console
|
|
326
|
+
console.log(` Cursor workspace: ${workspaceRoot}`);
|
|
327
|
+
// eslint-disable-next-line no-console
|
|
328
|
+
console.log(` → Installing rules in BOTH locations\n`);
|
|
329
|
+
installLocations.push(workspaceRoot);
|
|
330
|
+
}
|
|
331
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
332
|
+
// 1. Install Cursor Rules in all locations
|
|
333
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
334
|
+
for (const location of installLocations) {
|
|
335
|
+
const cursorDir = node_path_1.default.join(location, ".cursor");
|
|
336
|
+
const rulesDir = node_path_1.default.join(cursorDir, "rules");
|
|
337
|
+
await promises_1.default.mkdir(rulesDir, { recursive: true });
|
|
338
|
+
const rulesDst = node_path_1.default.join(rulesDir, "pulse.mdc");
|
|
339
|
+
const rulesSrc = node_path_1.default.join(packageRoot(), "templates", "cursor", "pulse.mdc");
|
|
340
|
+
if (await fileExists(rulesSrc)) {
|
|
341
|
+
await promises_1.default.copyFile(rulesSrc, rulesDst);
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
await promises_1.default.writeFile(rulesDst, generatePulseRules(), "utf8");
|
|
345
|
+
}
|
|
346
|
+
const relPath = node_path_1.default.relative(process.cwd(), rulesDst);
|
|
347
|
+
// eslint-disable-next-line no-console
|
|
348
|
+
console.log(`✅ Cursor rules: ${relPath}`);
|
|
349
|
+
}
|
|
350
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
351
|
+
// 2. MCP Config with absolute paths
|
|
352
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
353
|
+
let nodePath;
|
|
354
|
+
let pulseMcpPath;
|
|
355
|
+
try {
|
|
356
|
+
nodePath = getNodePath();
|
|
357
|
+
pulseMcpPath = await ensurePulseMcpInstalled();
|
|
358
|
+
}
|
|
359
|
+
catch (error) {
|
|
360
|
+
// eslint-disable-next-line no-console
|
|
361
|
+
console.log(`\n❌ MCP setup failed: ${error instanceof Error ? error.message : String(error)}`);
|
|
362
|
+
return;
|
|
363
|
+
}
|
|
364
|
+
// Determine MCP config location
|
|
365
|
+
const mcpDst = useGlobal
|
|
366
|
+
? getGlobalMcpConfigPath()
|
|
367
|
+
: node_path_1.default.join(repoRoot, ".cursor", "mcp.json");
|
|
368
|
+
// Ensure directory exists
|
|
369
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(mcpDst), { recursive: true });
|
|
370
|
+
// Check if we need a wrapper script (for workspace mismatch)
|
|
371
|
+
let mcpCommand = nodePath;
|
|
372
|
+
let mcpArgs = [pulseMcpPath];
|
|
373
|
+
if (!useGlobal && workspaceRoot && workspaceRoot !== repoRoot) {
|
|
374
|
+
// Create wrapper script that changes to the correct directory
|
|
375
|
+
const wrapperPath = node_path_1.default.join(repoRoot, ".pulse", "run-mcp.sh");
|
|
376
|
+
// Determine how to execute pulse-mcp:
|
|
377
|
+
// - If it's a .js file, use node to run it
|
|
378
|
+
// - If it's an executable (from npm global bin), run it directly
|
|
379
|
+
const isJsFile = pulseMcpPath.endsWith(".js");
|
|
380
|
+
const execCommand = isJsFile
|
|
381
|
+
? `exec "${nodePath}" "${pulseMcpPath}"`
|
|
382
|
+
: `exec "${pulseMcpPath}"`;
|
|
383
|
+
const wrapperContent = `#!/bin/bash
|
|
384
|
+
# PULSE MCP Wrapper - ensures correct working directory
|
|
385
|
+
cd "${repoRoot}"
|
|
386
|
+
${execCommand}
|
|
387
|
+
`;
|
|
388
|
+
await promises_1.default.writeFile(wrapperPath, wrapperContent, "utf8");
|
|
389
|
+
await promises_1.default.chmod(wrapperPath, 0o755);
|
|
390
|
+
mcpCommand = wrapperPath;
|
|
391
|
+
mcpArgs = [];
|
|
392
|
+
// eslint-disable-next-line no-console
|
|
393
|
+
console.log(`✅ Wrapper script: .pulse/run-mcp.sh (fixes workspace mismatch)`);
|
|
394
|
+
}
|
|
395
|
+
// Build MCP config
|
|
396
|
+
const mcpConfig = {
|
|
397
|
+
mcpServers: {
|
|
398
|
+
pulse: {
|
|
399
|
+
command: mcpCommand,
|
|
400
|
+
args: mcpArgs,
|
|
401
|
+
env: {
|
|
402
|
+
PULSE_PROJECT_ROOT: repoRoot,
|
|
403
|
+
},
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
};
|
|
407
|
+
// Merge with existing config if present
|
|
408
|
+
if (await fileExists(mcpDst)) {
|
|
409
|
+
try {
|
|
410
|
+
const existing = JSON.parse(await promises_1.default.readFile(mcpDst, "utf8"));
|
|
411
|
+
if (existing.mcpServers && typeof existing.mcpServers === "object") {
|
|
412
|
+
mcpConfig.mcpServers = {
|
|
413
|
+
...existing.mcpServers,
|
|
414
|
+
pulse: mcpConfig.mcpServers.pulse,
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
catch {
|
|
419
|
+
// Ignore parse errors, overwrite
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
await promises_1.default.writeFile(mcpDst, JSON.stringify(mcpConfig, null, 2) + "\n", "utf8");
|
|
423
|
+
const mcpLocation = useGlobal ? "~/.cursor/mcp.json (global)" : ".cursor/mcp.json (local)";
|
|
424
|
+
// eslint-disable-next-line no-console
|
|
425
|
+
console.log(`✅ MCP config: ${mcpLocation}`);
|
|
426
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
427
|
+
// 3. Post-Init Validation
|
|
428
|
+
// ────────────────────────────────────────────────────────────────────────────
|
|
429
|
+
await validateMcpSetup(nodePath, pulseMcpPath, mcpDst);
|
|
430
|
+
}
|
|
431
|
+
/**
|
|
432
|
+
* Validate MCP setup after installation
|
|
433
|
+
*/
|
|
434
|
+
async function validateMcpSetup(nodePath, pulseMcpPath, mcpConfigPath) {
|
|
435
|
+
// eslint-disable-next-line no-console
|
|
436
|
+
console.log(`\n🔍 Validating MCP setup...\n`);
|
|
437
|
+
let allOk = true;
|
|
438
|
+
// Check 1: Node executable
|
|
439
|
+
try {
|
|
440
|
+
(0, node_child_process_1.execSync)(`"${nodePath}" --version`, { encoding: "utf8" });
|
|
441
|
+
// eslint-disable-next-line no-console
|
|
442
|
+
console.log(` ✅ Node: ${nodePath}`);
|
|
443
|
+
}
|
|
444
|
+
catch {
|
|
445
|
+
// eslint-disable-next-line no-console
|
|
446
|
+
console.log(` ❌ Node not executable: ${nodePath}`);
|
|
447
|
+
allOk = false;
|
|
448
|
+
}
|
|
449
|
+
// Check 2: pulse-mcp executable
|
|
450
|
+
if (await fileExists(pulseMcpPath)) {
|
|
451
|
+
// eslint-disable-next-line no-console
|
|
452
|
+
console.log(` ✅ pulse-mcp: ${pulseMcpPath}`);
|
|
453
|
+
}
|
|
454
|
+
else {
|
|
455
|
+
// eslint-disable-next-line no-console
|
|
456
|
+
console.log(` ❌ pulse-mcp not found: ${pulseMcpPath}`);
|
|
457
|
+
allOk = false;
|
|
458
|
+
}
|
|
459
|
+
// Check 3: MCP config exists
|
|
460
|
+
if (await fileExists(mcpConfigPath)) {
|
|
461
|
+
// eslint-disable-next-line no-console
|
|
462
|
+
console.log(` ✅ MCP config: ${mcpConfigPath}`);
|
|
463
|
+
}
|
|
464
|
+
else {
|
|
465
|
+
// eslint-disable-next-line no-console
|
|
466
|
+
console.log(` ❌ MCP config missing: ${mcpConfigPath}`);
|
|
467
|
+
allOk = false;
|
|
468
|
+
}
|
|
469
|
+
if (allOk) {
|
|
470
|
+
// eslint-disable-next-line no-console
|
|
471
|
+
console.log(`\n ✨ All checks passed!`);
|
|
472
|
+
}
|
|
473
|
+
else {
|
|
474
|
+
// eslint-disable-next-line no-console
|
|
475
|
+
console.log(`\n ⚠️ Some checks failed. See above for details.`);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
/**
|
|
479
|
+
* Generate pulse.mdc rules content (fallback if template not found)
|
|
480
|
+
*/
|
|
481
|
+
function generatePulseRules() {
|
|
482
|
+
return `---
|
|
483
|
+
description: PULSE Framework - Automated safeguards for every message
|
|
484
|
+
globs: *
|
|
485
|
+
alwaysApply: true
|
|
486
|
+
---
|
|
487
|
+
|
|
488
|
+
# PULSE Framework Safeguards
|
|
489
|
+
|
|
490
|
+
## MANDATORY: Before every answer
|
|
491
|
+
|
|
492
|
+
**Before you answer, run the following MCP tools:**
|
|
493
|
+
|
|
494
|
+
1. **\`pulse_status\`**
|
|
495
|
+
- Shows: Profile, time since checkpoint, changes, findings
|
|
496
|
+
- If >15 min since checkpoint → Recommend checkpoint
|
|
497
|
+
|
|
498
|
+
2. **\`pulse_doctor\`** (if you changed code)
|
|
499
|
+
- Checks: Secrets, deletes, scope, loop signals
|
|
500
|
+
- Critical findings → STOP
|
|
501
|
+
|
|
502
|
+
## Safeguards (non-negotiable)
|
|
503
|
+
|
|
504
|
+
- ⏱️ **MAX 30 min autonomous** - Then STOP + ask user
|
|
505
|
+
- 🗑️ **NO DELETE** without explicit confirmation
|
|
506
|
+
- 📤 **NO GIT PUSH** without confirmation
|
|
507
|
+
- 🔐 **NO Secrets** in code
|
|
508
|
+
- 📋 **Git commit every 5-10 min** via \`pulse_checkpoint\`
|
|
509
|
+
|
|
510
|
+
## In case of problems
|
|
511
|
+
|
|
512
|
+
If you get stuck after 2-3 attempts:
|
|
513
|
+
1. **STOP** - No further changes
|
|
514
|
+
2. **\`pulse_escalate\`**
|
|
515
|
+
3. Wait for user instruction
|
|
516
|
+
|
|
517
|
+
## MCP Tools
|
|
518
|
+
|
|
519
|
+
| Tool | When |
|
|
520
|
+
|------|------|
|
|
521
|
+
| \`pulse_status\` | BEFORE EVERY ANSWER |
|
|
522
|
+
| \`pulse_checkpoint\` | After changes (5-10 min) |
|
|
523
|
+
| \`pulse_doctor\` | Before commits |
|
|
524
|
+
| \`pulse_escalate\` | When stuck |
|
|
525
|
+
`;
|
|
526
|
+
}
|
|
527
|
+
/**
|
|
528
|
+
* Install AGENTS.md (universal format for all AI editors)
|
|
529
|
+
*/
|
|
530
|
+
async function installAgentsMd(repoRoot) {
|
|
531
|
+
const agentsDst = node_path_1.default.join(repoRoot, "AGENTS.md");
|
|
532
|
+
const agentsSrc = node_path_1.default.join(packageRoot(), "templates", "AGENTS.md");
|
|
533
|
+
if (await fileExists(agentsDst)) {
|
|
534
|
+
// eslint-disable-next-line no-console
|
|
535
|
+
console.log(`ℹ️ AGENTS.md already exists`);
|
|
536
|
+
return;
|
|
537
|
+
}
|
|
538
|
+
if (await fileExists(agentsSrc)) {
|
|
539
|
+
await promises_1.default.copyFile(agentsSrc, agentsDst);
|
|
540
|
+
}
|
|
541
|
+
else {
|
|
542
|
+
// Fallback: Minimal inline version
|
|
543
|
+
const content = `# AI Agent Instructions
|
|
544
|
+
|
|
545
|
+
> Universal agent configuration for PULSE Framework.
|
|
546
|
+
|
|
547
|
+
## Critical Safeguards
|
|
548
|
+
|
|
549
|
+
1. 🗑️ **DELETE Guard** - Never delete without confirmation
|
|
550
|
+
2. 📤 **PUSH Guard** - Never push without confirmation
|
|
551
|
+
3. 🔐 **SECRETS Guard** - Never commit secrets
|
|
552
|
+
4. ⏱️ **30-Min Rule** - Stop after 30 min autonomous work
|
|
553
|
+
|
|
554
|
+
## Commands
|
|
555
|
+
|
|
556
|
+
| Command | When |
|
|
557
|
+
|---------|------|
|
|
558
|
+
| \`pulse status\` | Check state before work |
|
|
559
|
+
| \`pulse checkpoint\` | After changes (5-10 min) |
|
|
560
|
+
| \`pulse doctor\` | Before commits |
|
|
561
|
+
| \`pulse escalate\` | When stuck |
|
|
562
|
+
| \`pulse reset\` | To go back |
|
|
563
|
+
|
|
564
|
+
*Generated by PULSE Framework*
|
|
565
|
+
`;
|
|
566
|
+
await promises_1.default.writeFile(agentsDst, content, "utf8");
|
|
567
|
+
}
|
|
568
|
+
// eslint-disable-next-line no-console
|
|
569
|
+
console.log(`✅ AGENTS.md created (universal for all AI editors)`);
|
|
570
|
+
}
|