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,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,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,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,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
|
+
}
|