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.
Files changed (64) hide show
  1. package/dist/commands/checkpoint.d.ts +2 -0
  2. package/dist/commands/checkpoint.js +129 -0
  3. package/dist/commands/correct.d.ts +2 -0
  4. package/dist/commands/correct.js +77 -0
  5. package/dist/commands/doctor.d.ts +2 -0
  6. package/dist/commands/doctor.js +183 -0
  7. package/dist/commands/escalate.d.ts +2 -0
  8. package/dist/commands/escalate.js +226 -0
  9. package/dist/commands/init.d.ts +2 -0
  10. package/dist/commands/init.js +570 -0
  11. package/dist/commands/learn.d.ts +2 -0
  12. package/dist/commands/learn.js +137 -0
  13. package/dist/commands/profile.d.ts +2 -0
  14. package/dist/commands/profile.js +39 -0
  15. package/dist/commands/reset.d.ts +2 -0
  16. package/dist/commands/reset.js +130 -0
  17. package/dist/commands/review.d.ts +2 -0
  18. package/dist/commands/review.js +129 -0
  19. package/dist/commands/run.d.ts +2 -0
  20. package/dist/commands/run.js +272 -0
  21. package/dist/commands/start.d.ts +2 -0
  22. package/dist/commands/start.js +196 -0
  23. package/dist/commands/status.d.ts +2 -0
  24. package/dist/commands/status.js +239 -0
  25. package/dist/commands/watch.d.ts +2 -0
  26. package/dist/commands/watch.js +98 -0
  27. package/dist/hooks/install.d.ts +1 -0
  28. package/dist/hooks/install.js +89 -0
  29. package/dist/index.d.ts +2 -0
  30. package/dist/index.js +40 -0
  31. package/dist/lib/artifacts.d.ts +7 -0
  32. package/dist/lib/artifacts.js +52 -0
  33. package/dist/lib/briefing.d.ts +77 -0
  34. package/dist/lib/briefing.js +231 -0
  35. package/dist/lib/clipboard.d.ts +9 -0
  36. package/dist/lib/clipboard.js +116 -0
  37. package/dist/lib/config.d.ts +14 -0
  38. package/dist/lib/config.js +167 -0
  39. package/dist/lib/context-export.d.ts +30 -0
  40. package/dist/lib/context-export.js +149 -0
  41. package/dist/lib/exec.d.ts +9 -0
  42. package/dist/lib/exec.js +23 -0
  43. package/dist/lib/git.d.ts +24 -0
  44. package/dist/lib/git.js +74 -0
  45. package/dist/lib/input.d.ts +15 -0
  46. package/dist/lib/input.js +80 -0
  47. package/dist/lib/notifications.d.ts +2 -0
  48. package/dist/lib/notifications.js +25 -0
  49. package/dist/lib/paths.d.ts +4 -0
  50. package/dist/lib/paths.js +39 -0
  51. package/dist/lib/prompts.d.ts +43 -0
  52. package/dist/lib/prompts.js +270 -0
  53. package/dist/lib/scanner.d.ts +37 -0
  54. package/dist/lib/scanner.js +413 -0
  55. package/dist/lib/types.d.ts +37 -0
  56. package/dist/lib/types.js +2 -0
  57. package/package.json +42 -0
  58. package/templates/.cursorrules +159 -0
  59. package/templates/AGENTS.md +198 -0
  60. package/templates/cursor/mcp.json +9 -0
  61. package/templates/cursor/pulse.mdc +144 -0
  62. package/templates/roles/architect.cursorrules +15 -0
  63. package/templates/roles/backend.cursorrules +12 -0
  64. 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
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerLearnCommand(program: Command): void;