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,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerCheckpointCommand(program: Command): void;
@@ -0,0 +1,129 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCheckpointCommand = registerCheckpointCommand;
4
+ const artifacts_js_1 = require("../lib/artifacts.js");
5
+ const config_js_1 = require("../lib/config.js");
6
+ const exec_js_1 = require("../lib/exec.js");
7
+ const paths_js_1 = require("../lib/paths.js");
8
+ const git_js_1 = require("../lib/git.js");
9
+ const scanner_js_1 = require("../lib/scanner.js");
10
+ function registerCheckpointCommand(program) {
11
+ program
12
+ .command("checkpoint")
13
+ .alias("c") // Kurzform: pulse c
14
+ .description("Git Checkpoint: Check status, detect red flags, optional tests/commit")
15
+ .option("--staged", "Use staged diff")
16
+ .option("--inspect-latest", "Inspect the latest commit diff (useful if Cursor auto-committed)")
17
+ .option("--run-tests", "Run configured test command")
18
+ .option("-m, --message <msg>", "If provided, run git commit -am/-m with this message (staged only)")
19
+ .action(async (opts) => {
20
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
21
+ if (!repoRoot)
22
+ throw new Error("Not inside a git repository.");
23
+ const [config, status, log] = await Promise.all([
24
+ (0, config_js_1.loadConfig)(repoRoot),
25
+ (0, git_js_1.gitStatusPorcelain)(repoRoot),
26
+ (0, git_js_1.gitLogOneline)(repoRoot, 5),
27
+ ]);
28
+ let diffText = "";
29
+ let diffStat = "";
30
+ let diffNumstat = "";
31
+ let diffNameStatus = "";
32
+ if (opts.inspectLatest) {
33
+ // last commit diff
34
+ const d = await (0, exec_js_1.exec)("git", ["show", "--format=", "--unified=0"], { cwd: repoRoot });
35
+ diffText = d.stdout;
36
+ const s = await (0, exec_js_1.exec)("git", ["show", "--format=", "--stat"], { cwd: repoRoot });
37
+ diffStat = s.stdout.trimEnd();
38
+ const n = await (0, exec_js_1.exec)("git", ["show", "--format=", "--numstat"], { cwd: repoRoot });
39
+ diffNumstat = n.stdout.trimEnd();
40
+ const ns = await (0, exec_js_1.exec)("git", ["show", "--format=", "--name-status"], { cwd: repoRoot });
41
+ diffNameStatus = ns.stdout.trimEnd();
42
+ }
43
+ else {
44
+ const staged = Boolean(opts.staged);
45
+ [diffText, diffStat, diffNumstat] = await Promise.all([
46
+ (0, git_js_1.gitDiffText)(repoRoot, { staged }),
47
+ (0, git_js_1.gitDiffStat)(repoRoot, { staged }),
48
+ (0, git_js_1.gitDiffNumstat)(repoRoot, { staged }),
49
+ ]);
50
+ const ns = await (0, exec_js_1.exec)("git", ["diff", "--name-status", ...(staged ? ["--staged"] : [])], { cwd: repoRoot });
51
+ diffNameStatus = ns.stdout.trimEnd();
52
+ }
53
+ const scan = (0, scanner_js_1.scanDiff)(config, { diffText, diffStat, diffNumstat, diffNameStatus });
54
+ const ts = (0, artifacts_js_1.timestampId)();
55
+ const artifact = [
56
+ `# Checkpoint (${ts})`,
57
+ ``,
58
+ `## Git status`,
59
+ "```",
60
+ status || "(clean)",
61
+ "```",
62
+ ``,
63
+ `## Recent commits`,
64
+ "```",
65
+ log || "(none)",
66
+ "```",
67
+ ``,
68
+ `## Diff stat`,
69
+ "```",
70
+ diffStat || "(no changes)",
71
+ "```",
72
+ ``,
73
+ `## Findings`,
74
+ ...formatFindings(scan.findings),
75
+ ``,
76
+ ].join("\n");
77
+ const p = await (0, artifacts_js_1.writeArtifact)(repoRoot, "worklogs", `${ts}-checkpoint.md`, artifact);
78
+ // eslint-disable-next-line no-console
79
+ console.log(`✅ Wrote ${p}`);
80
+ if (scan.findings.length) {
81
+ // eslint-disable-next-line no-console
82
+ console.log("\nFindings:");
83
+ for (const f of scan.findings) {
84
+ // eslint-disable-next-line no-console
85
+ console.log(`- [${f.severity.toUpperCase()}] ${f.code}: ${f.message}`);
86
+ }
87
+ }
88
+ // Optionally run tests
89
+ const shouldRunTests = Boolean(opts.runTests) && Boolean(config.commands.test?.trim());
90
+ if (shouldRunTests) {
91
+ // eslint-disable-next-line no-console
92
+ console.log(`\nRunning tests: ${config.commands.test}`);
93
+ const res = await execShell(config.commands.test, repoRoot);
94
+ if (res.exitCode !== 0) {
95
+ // eslint-disable-next-line no-console
96
+ console.error(res.stderr || res.stdout);
97
+ process.exit(res.exitCode);
98
+ }
99
+ }
100
+ // Optional commit (staged only)
101
+ if (opts.message) {
102
+ // eslint-disable-next-line no-console
103
+ console.log(`\nCreating commit: ${opts.message}`);
104
+ const res = await (0, exec_js_1.exec)("git", ["commit", "-m", opts.message], { cwd: repoRoot });
105
+ if (res.exitCode !== 0) {
106
+ // eslint-disable-next-line no-console
107
+ console.error(res.stderr || res.stdout);
108
+ process.exit(res.exitCode);
109
+ }
110
+ }
111
+ const state = await (0, artifacts_js_1.loadState)(repoRoot);
112
+ state.lastCheckpointAt = new Date().toISOString();
113
+ await (0, artifacts_js_1.saveState)(repoRoot, state);
114
+ });
115
+ }
116
+ function formatFindings(findings) {
117
+ if (!findings.length)
118
+ return ["- ✅ No findings."];
119
+ return findings.map((f) => {
120
+ const details = f.details ? `\n - Details:\n\n\`\`\`\n${f.details}\n\`\`\`` : "";
121
+ return `- **${f.severity.toUpperCase()} ${f.code}**: ${f.message}${details}`;
122
+ });
123
+ }
124
+ async function execShell(cmd, cwd) {
125
+ // minimal shell wrapper to run configured commands
126
+ const shell = process.platform === "win32" ? "cmd.exe" : "/bin/sh";
127
+ const args = process.platform === "win32" ? ["/c", cmd] : ["-lc", cmd];
128
+ return await (0, exec_js_1.exec)(shell, args, { cwd });
129
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerCorrectCommand(program: Command): void;
@@ -0,0 +1,77 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerCorrectCommand = registerCorrectCommand;
4
+ const artifacts_js_1 = require("../lib/artifacts.js");
5
+ const input_js_1 = require("../lib/input.js");
6
+ const paths_js_1 = require("../lib/paths.js");
7
+ const clipboard_js_1 = require("../lib/clipboard.js");
8
+ function registerCorrectCommand(program) {
9
+ program
10
+ .command("correct")
11
+ .description("Create correction prompt when agent goes off track")
12
+ .option("--feedback <text>", "Correction feedback to the agent")
13
+ .option("--mode <mode>", "explain | narrow | milestone", "narrow")
14
+ .option("-C, --clipboard", "Copy prompt to clipboard")
15
+ .action(async (opts) => {
16
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
17
+ if (!repoRoot)
18
+ throw new Error("Not in a git repository.");
19
+ const state = await (0, artifacts_js_1.loadState)(repoRoot);
20
+ const feedback = opts.feedback ?? (await (0, input_js_1.promptText)("What is going wrong? (Correction feedback)", ""));
21
+ const mode = (opts.mode ?? "narrow").toLowerCase();
22
+ // eslint-disable-next-line no-console
23
+ console.log("\n🔄 PULSE Correction\n");
24
+ const modePrompts = {
25
+ explain: `STOP. Explain what you understood:
26
+
27
+ 1. **AS-IS**: What is the current state?
28
+ 2. **TO-BE**: What should be the state?
29
+ 3. **Attempts**: What have you tried so far?
30
+ 4. **Theory**: Why does it not work?
31
+
32
+ Then suggest a MINIMAL next step.`,
33
+ narrow: `CORRECTION: Apply the following change with MINIMAL scope.
34
+ Change ONLY what is necessary. NO refactoring of unrelated code.`,
35
+ milestone: `STOP. The task is too big.
36
+
37
+ Split it into small milestones (1 change per milestone).
38
+ Suggest only Milestone 1, implement it, and then STOP.`,
39
+ };
40
+ const modePrompt = modePrompts[mode] ?? modePrompts.narrow ?? "";
41
+ const fullPrompt = feedback.trim()
42
+ ? `${modePrompt}\n\n**Feedback:**\n${feedback.trim()}`
43
+ : modePrompt;
44
+ const ts = (0, artifacts_js_1.timestampId)();
45
+ const filename = `${ts}-correct.md`;
46
+ const content = [
47
+ `# Correction Pulse (${ts})`,
48
+ ``,
49
+ `- Layer: **${state.profile}**`,
50
+ `- Mode: **${mode}**`,
51
+ ``,
52
+ `## Prompt`,
53
+ ``,
54
+ "```",
55
+ fullPrompt,
56
+ "```",
57
+ ``,
58
+ ].join("\n");
59
+ const p = await (0, artifacts_js_1.writeArtifact)(repoRoot, "pulses", filename, content);
60
+ // eslint-disable-next-line no-console
61
+ console.log(`✅ Saved: ${p}`);
62
+ // Clipboard
63
+ if (opts.clipboard) {
64
+ const clipboardMsg = await (0, clipboard_js_1.copyAndNotify)(fullPrompt);
65
+ // eslint-disable-next-line no-console
66
+ console.log(clipboardMsg);
67
+ }
68
+ // eslint-disable-next-line no-console
69
+ console.log(`\n${"─".repeat(60)}`);
70
+ // eslint-disable-next-line no-console
71
+ console.log(`\n📋 CORRECTION PROMPT${opts.clipboard ? " (copied)" : ""}:\n`);
72
+ // eslint-disable-next-line no-console
73
+ console.log(fullPrompt);
74
+ // eslint-disable-next-line no-console
75
+ console.log(`\n${"─".repeat(60)}\n`);
76
+ });
77
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerDoctorCommand(program: Command): void;
@@ -0,0 +1,183 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.registerDoctorCommand = registerDoctorCommand;
4
+ const config_js_1 = require("../lib/config.js");
5
+ const artifacts_js_1 = require("../lib/artifacts.js");
6
+ const exec_js_1 = require("../lib/exec.js");
7
+ const paths_js_1 = require("../lib/paths.js");
8
+ const git_js_1 = require("../lib/git.js");
9
+ const scanner_js_1 = require("../lib/scanner.js");
10
+ const briefing_js_1 = require("../lib/briefing.js");
11
+ function registerDoctorCommand(program) {
12
+ program
13
+ .command("doctor")
14
+ .alias("d")
15
+ .description("Check safeguards + red flags (secrets, deletes, loops, scope)")
16
+ .option("--staged", "Scan staged diff")
17
+ .option("--ci", "CI mode: quieter output + exit codes")
18
+ .option("--hook <name>", "Hook mode: pre-commit | pre-push", "none")
19
+ .option("--loop", "Include loop-detection hints (heuristics)")
20
+ .option("--confirm-delete", "Explicitly confirm deletes (for this run)")
21
+ .option("--allow-push", "Explicitly allow push (for this run)")
22
+ .action(async (opts) => {
23
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
24
+ if (!repoRoot)
25
+ throw new Error("Not in a git repository.");
26
+ const [config, state] = await Promise.all([(0, config_js_1.loadConfig)(repoRoot), (0, artifacts_js_1.loadState)(repoRoot)]);
27
+ const hook = (opts.hook ?? "none");
28
+ // Safeguard: Push gate (hook only)
29
+ if (hook === "pre-push") {
30
+ const allowed = opts.allowPush || process.env.PULSE_ALLOW_PUSH === "1";
31
+ if (!allowed) {
32
+ print(opts.ci, `PULSE safeguard: push blocked. Set PULSE_ALLOW_PUSH=1 for an explicit push.`, "");
33
+ process.exit(2);
34
+ }
35
+ }
36
+ const staged = Boolean(opts.staged) || hook === "pre-commit";
37
+ const [diffText, diffStat, diffNumstat, diffNameStatus] = await Promise.all([
38
+ (0, git_js_1.gitDiffText)(repoRoot, { staged }),
39
+ (0, git_js_1.gitDiffStat)(repoRoot, { staged }),
40
+ (0, git_js_1.gitDiffNumstat)(repoRoot, { staged }),
41
+ (0, git_js_1.gitDiffNameStatus)(repoRoot, { staged }),
42
+ ]);
43
+ const scan = (0, scanner_js_1.scanDiff)(config, { diffText, diffStat, diffNumstat, diffNameStatus });
44
+ // Mixed enforcement: deletes are critical only if not explicitly confirmed
45
+ const hasDeleteFinding = scan.findings.some((f) => f.code === "MASS_DELETE" && f.message.includes("File deletion"));
46
+ const deleteConfirmed = Boolean(opts.confirmDelete) || process.env.PULSE_CONFIRM_DELETE === "1";
47
+ if (hasDeleteFinding && deleteConfirmed) {
48
+ scan.findings = scan.findings.map((f) => f.code === "MASS_DELETE" && f.message.includes("File deletion")
49
+ ? { ...f, severity: "warn", message: `${f.message} (confirmed)` }
50
+ : f);
51
+ }
52
+ // Optional loop heuristics
53
+ if (opts.loop) {
54
+ const log = await (0, git_js_1.gitLogOneline)(repoRoot, 15);
55
+ const logWithFiles = await (0, exec_js_1.exec)("git", ["log", "--name-only", "--oneline", "-15"], {
56
+ cwd: repoRoot,
57
+ });
58
+ const loopSignals = (0, scanner_js_1.detectLoopSignals)(log, logWithFiles.stdout);
59
+ for (const signal of loopSignals) {
60
+ scan.findings.push({
61
+ severity: signal.severity,
62
+ code: "LOOP_SIGNAL",
63
+ message: signal.message,
64
+ details: signal.details,
65
+ });
66
+ }
67
+ }
68
+ // ════════════════════════════════════════════════════════════════════════
69
+ // Preset-Verletzungen explizit hinzufügen
70
+ // ════════════════════════════════════════════════════════════════════════
71
+ const scope = (0, briefing_js_1.calculateScopeCheck)(config, scan.stats);
72
+ if (scope.exceeded) {
73
+ const presetName = config.preset ?? "custom";
74
+ for (const field of scope.exceededFields) {
75
+ const current = field === "files"
76
+ ? scope.files.current
77
+ : field === "lines"
78
+ ? scope.lines.current
79
+ : scope.deletes.current;
80
+ const max = field === "files"
81
+ ? scope.files.max
82
+ : field === "lines"
83
+ ? scope.lines.max
84
+ : scope.deletes.max;
85
+ scan.findings.push({
86
+ severity: "warn",
87
+ code: "BIG_CHANGESET",
88
+ message: `Preset limit exceeded (${presetName}): ${field} ${current}/${max}`,
89
+ });
90
+ }
91
+ }
92
+ // Output
93
+ const critical = scan.findings.filter((f) => f.severity === "critical");
94
+ const warnings = scan.findings.filter((f) => f.severity === "warn");
95
+ // Calculate recommendation
96
+ const risk = (0, briefing_js_1.calculateRiskSummary)(scan);
97
+ const time = (0, briefing_js_1.calculateTimeSummary)(state.lastCheckpointAt, config.checkpointReminderMinutes ?? 30);
98
+ const recommendation = (0, briefing_js_1.generateRecommendation)(scope, risk, time);
99
+ if (!opts.ci) {
100
+ const presetProfile = config.preset
101
+ ? `${config.preset}/${state.profile}`
102
+ : state.profile;
103
+ // eslint-disable-next-line no-console
104
+ console.log(`\n🔍 Pulse Doctor (${staged ? "staged" : "working tree"})\n`);
105
+ // eslint-disable-next-line no-console
106
+ console.log(`Profile: ${presetProfile}`);
107
+ // eslint-disable-next-line no-console
108
+ console.log(`Scope: ${scan.stats.filesChanged} files | +${scan.stats.linesAdded} -${scan.stats.linesDeleted} lines`);
109
+ // Scope progress
110
+ if (scan.stats.filesChanged > 0) {
111
+ // eslint-disable-next-line no-console
112
+ console.log(`Limits (${config.preset ?? "custom"}): Files ${scope.files.percent}%, Lines ${scope.lines.percent}%`);
113
+ }
114
+ // eslint-disable-next-line no-console
115
+ console.log(`\nDiff stat:\n${diffStat || "(no changes)"}`);
116
+ // eslint-disable-next-line no-console
117
+ console.log("");
118
+ printFindings(scan.findings);
119
+ // eslint-disable-next-line no-console
120
+ console.log("");
121
+ // ══════════════════════════════════════════════════════════════════════
122
+ // Show recommendation
123
+ // ══════════════════════════════════════════════════════════════════════
124
+ const actionEmoji = {
125
+ approve: "✅",
126
+ checkpoint: "⏱️",
127
+ escalate: "🚨",
128
+ stop: "🛑",
129
+ }[recommendation.action];
130
+ // eslint-disable-next-line no-console
131
+ console.log(`${actionEmoji} RECOMMENDATION: ${recommendation.action.toUpperCase()}`);
132
+ // eslint-disable-next-line no-console
133
+ console.log(` → ${recommendation.reason}`);
134
+ if (recommendation.command) {
135
+ // eslint-disable-next-line no-console
136
+ console.log(` → ${recommendation.command}`);
137
+ }
138
+ // eslint-disable-next-line no-console
139
+ console.log("");
140
+ }
141
+ else {
142
+ printFindings(scan.findings);
143
+ }
144
+ if (critical.length)
145
+ process.exit(2);
146
+ if (warnings.length)
147
+ process.exit(1);
148
+ process.exit(0);
149
+ });
150
+ }
151
+ function print(ci, msg, details) {
152
+ if (!ci) {
153
+ // eslint-disable-next-line no-console
154
+ console.error(msg);
155
+ if (details) {
156
+ // eslint-disable-next-line no-console
157
+ console.error(details);
158
+ }
159
+ }
160
+ else {
161
+ // eslint-disable-next-line no-console
162
+ console.log(msg);
163
+ }
164
+ }
165
+ function printFindings(findings) {
166
+ if (!findings.length) {
167
+ // eslint-disable-next-line no-console
168
+ console.log("✅ No findings");
169
+ return;
170
+ }
171
+ for (const f of findings) {
172
+ const emoji = f.severity === "critical" ? "🚨" : "⚠️";
173
+ // eslint-disable-next-line no-console
174
+ console.log(`${emoji} ${f.code}: ${f.message}`);
175
+ if (f.details) {
176
+ // eslint-disable-next-line no-console
177
+ console.log(f.details
178
+ .split("\n")
179
+ .map((l) => ` ${l}`)
180
+ .join("\n"));
181
+ }
182
+ }
183
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerEscalateCommand(program: Command): void;
@@ -0,0 +1,226 @@
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.registerEscalateCommand = registerEscalateCommand;
7
+ const promises_1 = __importDefault(require("node:fs/promises"));
8
+ const artifacts_js_1 = require("../lib/artifacts.js");
9
+ const input_js_1 = require("../lib/input.js");
10
+ const paths_js_1 = require("../lib/paths.js");
11
+ const git_js_1 = require("../lib/git.js");
12
+ const prompts_js_1 = require("../lib/prompts.js");
13
+ const clipboard_js_1 = require("../lib/clipboard.js");
14
+ const context_export_js_1 = require("../lib/context-export.js");
15
+ async function readOptionalFile(p) {
16
+ if (!p)
17
+ return "";
18
+ try {
19
+ return await promises_1.default.readFile(p, "utf8");
20
+ }
21
+ catch {
22
+ return "";
23
+ }
24
+ }
25
+ function registerEscalateCommand(program) {
26
+ program
27
+ .command("escalate")
28
+ .alias("e")
29
+ .description("Create escalation: Prepare problem for external model (GPT-5/Claude/Opus)")
30
+ .option("--problem <text>", "What is the problem?")
31
+ .option("--tried <text>", "What has Cursor tried already?")
32
+ .option("--error <text>", "Error message / logs")
33
+ .option("--error-file <path>", "Path to log file")
34
+ .option("--code <text>", "Relevant code")
35
+ .option("--code-file <path>", "Path to code file")
36
+ .option("--question <text>", "Your specific question")
37
+ .option("--detailed", "Include full diff (not just summary)")
38
+ .option("-C, --clipboard", "Copy prompt to clipboard")
39
+ .option("--include <patterns...>", "Include files (glob patterns)")
40
+ .option("--auto-include", "Automatically include relevant files from git diff")
41
+ .action(async (opts) => {
42
+ const repoRoot = await (0, paths_js_1.findRepoRoot)(process.cwd());
43
+ if (!repoRoot)
44
+ throw new Error("Not in a git repository.");
45
+ // eslint-disable-next-line no-console
46
+ console.log("\n🚨 PULSE Escalation\n");
47
+ // eslint-disable-next-line no-console
48
+ console.log("Creating escalation package for external reasoning model (GPT-5/Claude/Opus)\n");
49
+ // ══════════════════════════════════════════════════════════════════════
50
+ // Collect information (interactive if not via flag)
51
+ // ══════════════════════════════════════════════════════════════════════
52
+ const cursorExplanation = opts.problem ??
53
+ opts.tried ??
54
+ (await (0, input_js_1.promptText)("What is the problem? (What did Cursor try, where is it stuck?)", ""));
55
+ // Versuche sammeln
56
+ const attempts = [];
57
+ if (opts.tried) {
58
+ attempts.push(opts.tried);
59
+ }
60
+ else {
61
+ // eslint-disable-next-line no-console
62
+ console.log("\nWhat has Cursor tried already? (Enter to finish)\n");
63
+ for (let i = 1; i <= 5; i++) {
64
+ const attempt = await (0, input_js_1.promptText)(` Attempt ${i}`, "");
65
+ if (!attempt.trim())
66
+ break;
67
+ attempts.push(attempt);
68
+ }
69
+ }
70
+ // Error
71
+ const errorText = opts.error ??
72
+ (opts.errorFile
73
+ ? await readOptionalFile(opts.errorFile)
74
+ : await (0, input_js_1.promptText)("Error message / logs (optional)", ""));
75
+ // Code (legacy option)
76
+ let codeSnippets = opts.code ??
77
+ (opts.codeFile ? await readOptionalFile(opts.codeFile) : "");
78
+ // Frage
79
+ const question = opts.question ??
80
+ (await (0, input_js_1.promptText)("Your specific question", "What is the root cause and how do I solve it?"));
81
+ // ══════════════════════════════════════════════════════════════════════
82
+ // Git-Kontext sammeln
83
+ // ══════════════════════════════════════════════════════════════════════
84
+ // eslint-disable-next-line no-console
85
+ console.log("\n📊 Collecting Git context...");
86
+ const [log, stat, diff, nameStatus] = await Promise.all([
87
+ (0, git_js_1.gitLogOneline)(repoRoot, 10),
88
+ (0, git_js_1.gitDiffStat)(repoRoot),
89
+ opts.detailed ? (0, git_js_1.gitDiffText)(repoRoot, { maxLines: 300 }) : Promise.resolve(""),
90
+ (0, git_js_1.gitDiffNameStatus)(repoRoot),
91
+ ]);
92
+ // ══════════════════════════════════════════════════════════════════════
93
+ // Kontext-Export (Dateien inkludieren)
94
+ // ══════════════════════════════════════════════════════════════════════
95
+ let contextExportXml = "";
96
+ if (opts.include && opts.include.length > 0) {
97
+ // eslint-disable-next-line no-console
98
+ console.log(`📁 Exporting files: ${opts.include.join(", ")}`);
99
+ const ctx = await (0, context_export_js_1.exportFiles)(repoRoot, opts.include);
100
+ contextExportXml = (0, context_export_js_1.renderContextExportXml)(ctx);
101
+ // eslint-disable-next-line no-console
102
+ console.log(` → ${ctx.totalFiles} files, ~${ctx.totalLines} lines`);
103
+ if (ctx.truncated) {
104
+ // eslint-disable-next-line no-console
105
+ console.log(` ⚠️ Truncated (limit reached)`);
106
+ }
107
+ }
108
+ else if (opts.autoInclude) {
109
+ // eslint-disable-next-line no-console
110
+ console.log("📁 Auto-detecting relevant files...");
111
+ const files = await (0, context_export_js_1.autoDetectFiles)(repoRoot, nameStatus);
112
+ if (files.length > 0) {
113
+ // eslint-disable-next-line no-console
114
+ console.log(` Found: ${files.join(", ")}`);
115
+ const doInclude = await (0, input_js_1.promptConfirm)(`Include these ${files.length} files?`, true);
116
+ if (doInclude) {
117
+ const ctx = await (0, context_export_js_1.exportFiles)(repoRoot, files);
118
+ contextExportXml = (0, context_export_js_1.renderContextExportXml)(ctx);
119
+ // eslint-disable-next-line no-console
120
+ console.log(` → ${ctx.totalFiles} files, ~${ctx.totalLines} lines`);
121
+ }
122
+ }
123
+ else {
124
+ // eslint-disable-next-line no-console
125
+ console.log(" No relevant files found.");
126
+ }
127
+ }
128
+ else if (!codeSnippets && !opts.code && !opts.codeFile) {
129
+ // Ask if user wants to include files
130
+ const wantInclude = await (0, input_js_1.promptConfirm)("Do you want to include files?", false);
131
+ if (wantInclude) {
132
+ const files = await (0, context_export_js_1.autoDetectFiles)(repoRoot, nameStatus);
133
+ if (files.length > 0) {
134
+ // eslint-disable-next-line no-console
135
+ console.log(` Auto-detected: ${files.join(", ")}`);
136
+ const useAuto = await (0, input_js_1.promptConfirm)("Use these files?", true);
137
+ if (useAuto) {
138
+ const ctx = await (0, context_export_js_1.exportFiles)(repoRoot, files);
139
+ contextExportXml = (0, context_export_js_1.renderContextExportXml)(ctx);
140
+ // eslint-disable-next-line no-console
141
+ console.log(` → ${ctx.totalFiles} files, ~${ctx.totalLines} lines`);
142
+ }
143
+ else {
144
+ const pattern = await (0, input_js_1.promptText)("Enter glob pattern (e.g. src/**/*.ts)", "");
145
+ if (pattern) {
146
+ const ctx = await (0, context_export_js_1.exportFiles)(repoRoot, [pattern]);
147
+ contextExportXml = (0, context_export_js_1.renderContextExportXml)(ctx);
148
+ // eslint-disable-next-line no-console
149
+ console.log(` → ${ctx.totalFiles} files, ~${ctx.totalLines} lines`);
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ // Merge context export with code snippets
156
+ if (contextExportXml) {
157
+ codeSnippets = contextExportXml + (codeSnippets ? `\n\n${codeSnippets}` : "");
158
+ }
159
+ // ══════════════════════════════════════════════════════════════════════
160
+ // Eskalations-Prompt generieren
161
+ // ══════════════════════════════════════════════════════════════════════
162
+ const prompt = (0, prompts_js_1.renderEscalationPrompt)({
163
+ cursorExplanation,
164
+ errorText,
165
+ gitLog: log,
166
+ gitDiff: opts.detailed ? diff : stat,
167
+ question,
168
+ codeSnippets: codeSnippets || undefined,
169
+ attempts: attempts.length > 0 ? attempts : undefined,
170
+ });
171
+ // ══════════════════════════════════════════════════════════════════════
172
+ // Save
173
+ // ══════════════════════════════════════════════════════════════════════
174
+ const ts = (0, artifacts_js_1.timestampId)();
175
+ const filename = `${ts}-escalate.md`;
176
+ const content = [
177
+ `# Escalation (${ts})`,
178
+ ``,
179
+ `## Instructions`,
180
+ ``,
181
+ `1. **Copy** the prompt below`,
182
+ `2. **Paste** into ChatGPT, Claude, GPT-5 or Opus`,
183
+ `3. **Read** the analysis and step-by-step instructions`,
184
+ `4. **Pass** instructions to Cursor (DO NOT blindly copy code!)`,
185
+ ``,
186
+ `---`,
187
+ ``,
188
+ `## Prompt`,
189
+ ``,
190
+ "```",
191
+ prompt,
192
+ "```",
193
+ ``,
194
+ `---`,
195
+ ``,
196
+ `## Metadata`,
197
+ `- Created: ${new Date().toISOString()}`,
198
+ `- Attempts documented: ${attempts.length}`,
199
+ `- Git context: ${log ? "✅" : "❌"}`,
200
+ `- Files included: ${contextExportXml ? "✅" : "❌"}`,
201
+ ``,
202
+ ].join("\n");
203
+ const p = await (0, artifacts_js_1.writeArtifact)(repoRoot, "escalations", filename, content);
204
+ // ══════════════════════════════════════════════════════════════════════
205
+ // Output
206
+ // ══════════════════════════════════════════════════════════════════════
207
+ // eslint-disable-next-line no-console
208
+ console.log(`\n✅ Saved: ${p}`);
209
+ // Clipboard
210
+ if (opts.clipboard) {
211
+ const clipboardMsg = await (0, clipboard_js_1.copyAndNotify)(prompt);
212
+ // eslint-disable-next-line no-console
213
+ console.log(clipboardMsg);
214
+ }
215
+ // eslint-disable-next-line no-console
216
+ console.log(`\n${"═".repeat(60)}`);
217
+ // eslint-disable-next-line no-console
218
+ console.log(`\n📋 ESCALATION PROMPT${opts.clipboard ? " (copied)" : ""}:\n`);
219
+ // eslint-disable-next-line no-console
220
+ console.log(prompt);
221
+ // eslint-disable-next-line no-console
222
+ console.log(`\n${"═".repeat(60)}`);
223
+ // eslint-disable-next-line no-console
224
+ console.log(`\n💡 Tip: The answer contains step-by-step instructions for Cursor.\n`);
225
+ });
226
+ }
@@ -0,0 +1,2 @@
1
+ import type { Command } from "commander";
2
+ export declare function registerInitCommand(program: Command): void;