@keepgoingdev/mcp-server 0.8.0 → 0.9.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/index.js +143 -16
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -339,7 +339,8 @@ function generateEnrichedBriefing(opts) {
|
|
|
339
339
|
timestamp: s.timestamp,
|
|
340
340
|
summary: s.summary || "",
|
|
341
341
|
nextStep: s.nextStep || "",
|
|
342
|
-
branch: s.gitBranch
|
|
342
|
+
branch: s.gitBranch,
|
|
343
|
+
sessionPhase: s.sessionPhase
|
|
343
344
|
}));
|
|
344
345
|
}
|
|
345
346
|
if (opts.recentCommits && opts.recentCommits.length > 0) {
|
|
@@ -372,10 +373,13 @@ function buildCurrentFocus(lastSession, projectState, gitBranch) {
|
|
|
372
373
|
function buildRecentActivity(lastSession, recentSessions, recentCommitMessages) {
|
|
373
374
|
const parts = [];
|
|
374
375
|
const sessionCount = recentSessions.length;
|
|
376
|
+
const planCount = recentSessions.filter((s) => s.sessionPhase === "planning").length;
|
|
375
377
|
if (sessionCount > 1) {
|
|
376
|
-
|
|
378
|
+
const planSuffix = planCount > 0 ? ` (${planCount} plan-only)` : "";
|
|
379
|
+
parts.push(`${sessionCount} recent sessions${planSuffix}`);
|
|
377
380
|
} else if (sessionCount === 1) {
|
|
378
|
-
|
|
381
|
+
const planSuffix = planCount === 1 ? " (plan-only)" : "";
|
|
382
|
+
parts.push(`1 recent session${planSuffix}`);
|
|
379
383
|
}
|
|
380
384
|
if (lastSession.summary) {
|
|
381
385
|
const brief = lastSession.summary.length > 120 ? lastSession.summary.slice(0, 117) + "..." : lastSession.summary;
|
|
@@ -574,7 +578,8 @@ function formatEnrichedBriefing(briefing) {
|
|
|
574
578
|
for (const s of briefing.sessionHistory) {
|
|
575
579
|
const relTime = formatRelativeTime(s.timestamp);
|
|
576
580
|
const branch = s.branch ? ` (${s.branch})` : "";
|
|
577
|
-
|
|
581
|
+
const planTag = s.sessionPhase === "planning" ? " (plan)" : "";
|
|
582
|
+
lines.push(`- **${relTime}${branch}${planTag}:** ${s.summary || "No summary"}. Next: ${s.nextStep || "Not specified"}`);
|
|
578
583
|
}
|
|
579
584
|
}
|
|
580
585
|
if (briefing.recentCommits && briefing.recentCommits.length > 0) {
|
|
@@ -2099,6 +2104,15 @@ var POST_TOOL_USE_HOOK = {
|
|
|
2099
2104
|
}
|
|
2100
2105
|
]
|
|
2101
2106
|
};
|
|
2107
|
+
var PLAN_MODE_HOOK = {
|
|
2108
|
+
matcher: "Read|Grep|Glob|Bash|WebSearch",
|
|
2109
|
+
hooks: [
|
|
2110
|
+
{
|
|
2111
|
+
type: "command",
|
|
2112
|
+
command: "npx -y @keepgoingdev/mcp-server --heartbeat"
|
|
2113
|
+
}
|
|
2114
|
+
]
|
|
2115
|
+
};
|
|
2102
2116
|
var SESSION_END_HOOK = {
|
|
2103
2117
|
matcher: "",
|
|
2104
2118
|
hooks: [
|
|
@@ -2108,7 +2122,7 @@ var SESSION_END_HOOK = {
|
|
|
2108
2122
|
}
|
|
2109
2123
|
]
|
|
2110
2124
|
};
|
|
2111
|
-
var KEEPGOING_RULES_VERSION =
|
|
2125
|
+
var KEEPGOING_RULES_VERSION = 3;
|
|
2112
2126
|
var KEEPGOING_RULES_CONTENT = `<!-- @keepgoingdev/mcp-server v${KEEPGOING_RULES_VERSION} -->
|
|
2113
2127
|
## KeepGoing
|
|
2114
2128
|
|
|
@@ -2118,6 +2132,8 @@ After completing a task or meaningful piece of work, call the \`save_checkpoint\
|
|
|
2118
2132
|
- \`summary\`: 1-2 sentences. What changed and why, no file paths, no implementation details (those are captured from git).
|
|
2119
2133
|
- \`nextStep\`: What to do next
|
|
2120
2134
|
- \`blocker\`: Any blocker (if applicable)
|
|
2135
|
+
|
|
2136
|
+
When working in plan mode (investigating, designing, iterating on an approach before any edits), call \`save_checkpoint\` when you reach a significant milestone or conclusion. Use the summary to capture what was investigated and decided. This preserves planning context for future sessions.
|
|
2121
2137
|
`;
|
|
2122
2138
|
function getRulesFileVersion(content) {
|
|
2123
2139
|
const match = content.match(/<!-- @keepgoingdev\/mcp-server v(\d+) -->/);
|
|
@@ -2178,6 +2194,13 @@ function writeHooksToSettings(settings) {
|
|
|
2178
2194
|
settings.hooks.PostToolUse.push(POST_TOOL_USE_HOOK);
|
|
2179
2195
|
changed = true;
|
|
2180
2196
|
}
|
|
2197
|
+
const hasHeartbeat = settings.hooks.PostToolUse.some(
|
|
2198
|
+
(entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes("--heartbeat"))
|
|
2199
|
+
);
|
|
2200
|
+
if (!hasHeartbeat) {
|
|
2201
|
+
settings.hooks.PostToolUse.push(PLAN_MODE_HOOK);
|
|
2202
|
+
changed = true;
|
|
2203
|
+
}
|
|
2181
2204
|
if (!Array.isArray(settings.hooks.SessionEnd)) {
|
|
2182
2205
|
settings.hooks.SessionEnd = [];
|
|
2183
2206
|
}
|
|
@@ -2741,7 +2764,11 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
|
2741
2764
|
const touchedFiles = getTouchedFiles(workspacePath);
|
|
2742
2765
|
const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);
|
|
2743
2766
|
const projectName = path10.basename(resolveStorageRoot(workspacePath));
|
|
2767
|
+
const writer = new KeepGoingWriter(workspacePath);
|
|
2744
2768
|
const sessionId = generateSessionId({ workspaceRoot: workspacePath, branch: gitBranch ?? void 0, worktreePath: workspacePath });
|
|
2769
|
+
const existingTasks = writer.readCurrentTasks();
|
|
2770
|
+
const existingSession = existingTasks.find((t) => t.sessionId === sessionId);
|
|
2771
|
+
const sessionPhase = existingSession?.sessionPhase;
|
|
2745
2772
|
const checkpoint = createCheckpoint({
|
|
2746
2773
|
summary,
|
|
2747
2774
|
nextStep: nextStep || "",
|
|
@@ -2751,10 +2778,19 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
|
2751
2778
|
commitHashes,
|
|
2752
2779
|
workspaceRoot: workspacePath,
|
|
2753
2780
|
source: "manual",
|
|
2754
|
-
sessionId
|
|
2781
|
+
sessionId,
|
|
2782
|
+
...sessionPhase ? { sessionPhase } : {},
|
|
2783
|
+
...sessionPhase === "planning" ? { tags: ["plan"] } : {}
|
|
2755
2784
|
});
|
|
2756
|
-
const writer = new KeepGoingWriter(workspacePath);
|
|
2757
2785
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
2786
|
+
writer.upsertSession({
|
|
2787
|
+
sessionId,
|
|
2788
|
+
sessionActive: true,
|
|
2789
|
+
branch: gitBranch ?? void 0,
|
|
2790
|
+
updatedAt: checkpoint.timestamp,
|
|
2791
|
+
taskSummary: summary,
|
|
2792
|
+
nextStep: nextStep || void 0
|
|
2793
|
+
});
|
|
2758
2794
|
const lines = [
|
|
2759
2795
|
`Checkpoint saved.`,
|
|
2760
2796
|
`- **ID:** ${checkpoint.id}`,
|
|
@@ -2907,8 +2943,9 @@ function registerGetCurrentTask(server, reader) {
|
|
|
2907
2943
|
lines.push("");
|
|
2908
2944
|
}
|
|
2909
2945
|
for (const task of [...activeTasks, ...finishedTasks]) {
|
|
2946
|
+
const phaseLabel = task.sessionPhase === "planning" ? "Planning" : void 0;
|
|
2910
2947
|
const statusIcon = task.sessionActive ? "\u{1F7E2}" : "\u2705";
|
|
2911
|
-
const statusLabel = task.sessionActive ? "Active" : "Finished";
|
|
2948
|
+
const statusLabel = task.sessionActive ? phaseLabel || "Active" : "Finished";
|
|
2912
2949
|
const sessionLabel = task.sessionLabel || task.agentLabel || task.sessionId || "Session";
|
|
2913
2950
|
lines.push(`### ${statusIcon} ${sessionLabel} (${statusLabel})`);
|
|
2914
2951
|
lines.push(`- **Updated:** ${formatRelativeTime(task.updatedAt)}`);
|
|
@@ -3470,6 +3507,7 @@ import path12 from "path";
|
|
|
3470
3507
|
async function handleSaveCheckpoint() {
|
|
3471
3508
|
const wsPath = resolveWsPath();
|
|
3472
3509
|
const reader = new KeepGoingReader(wsPath);
|
|
3510
|
+
const writer = new KeepGoingWriter(wsPath);
|
|
3473
3511
|
const { session: lastSession } = reader.getScopedLastSession();
|
|
3474
3512
|
if (lastSession?.timestamp) {
|
|
3475
3513
|
const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();
|
|
@@ -3479,10 +3517,44 @@ async function handleSaveCheckpoint() {
|
|
|
3479
3517
|
}
|
|
3480
3518
|
const touchedFiles = getTouchedFiles(wsPath);
|
|
3481
3519
|
const commitHashes = getCommitsSince(wsPath, lastSession?.timestamp);
|
|
3482
|
-
|
|
3520
|
+
const gitBranch = getCurrentBranch(wsPath);
|
|
3521
|
+
const existingTasks = writer.readCurrentTasks();
|
|
3522
|
+
const existingSession = existingTasks.find(
|
|
3523
|
+
(t) => t.sessionActive && t.worktreePath === wsPath && t.branch === (gitBranch ?? void 0)
|
|
3524
|
+
) ?? existingTasks.find(
|
|
3525
|
+
(t) => t.sessionActive && t.worktreePath === wsPath
|
|
3526
|
+
);
|
|
3527
|
+
const sessionId = existingSession?.sessionId ?? generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
3528
|
+
const isPlanning = existingSession?.sessionPhase === "planning";
|
|
3529
|
+
if (touchedFiles.length === 0 && commitHashes.length === 0 && !isPlanning) {
|
|
3530
|
+
process.exit(0);
|
|
3531
|
+
}
|
|
3532
|
+
const projectName = path12.basename(resolveStorageRoot(wsPath));
|
|
3533
|
+
if (touchedFiles.length === 0 && commitHashes.length === 0 && isPlanning) {
|
|
3534
|
+
const summary2 = existingSession?.sessionLabel || existingSession?.taskSummary || "Planning session";
|
|
3535
|
+
const checkpoint2 = createCheckpoint({
|
|
3536
|
+
summary: summary2,
|
|
3537
|
+
nextStep: existingSession?.nextStep || "",
|
|
3538
|
+
gitBranch,
|
|
3539
|
+
touchedFiles: [],
|
|
3540
|
+
commitHashes: [],
|
|
3541
|
+
workspaceRoot: wsPath,
|
|
3542
|
+
source: "auto",
|
|
3543
|
+
sessionId,
|
|
3544
|
+
sessionPhase: "planning",
|
|
3545
|
+
tags: ["plan"]
|
|
3546
|
+
});
|
|
3547
|
+
writer.saveCheckpoint(checkpoint2, projectName);
|
|
3548
|
+
writer.upsertSession({
|
|
3549
|
+
sessionId,
|
|
3550
|
+
sessionActive: false,
|
|
3551
|
+
nextStep: checkpoint2.nextStep || void 0,
|
|
3552
|
+
branch: gitBranch ?? void 0,
|
|
3553
|
+
updatedAt: checkpoint2.timestamp
|
|
3554
|
+
});
|
|
3555
|
+
console.log(`[KeepGoing] Plan checkpoint saved: ${summary2}`);
|
|
3483
3556
|
process.exit(0);
|
|
3484
3557
|
}
|
|
3485
|
-
const gitBranch = getCurrentBranch(wsPath);
|
|
3486
3558
|
const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);
|
|
3487
3559
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3488
3560
|
const events = buildSessionEvents({
|
|
@@ -3496,8 +3568,7 @@ async function handleSaveCheckpoint() {
|
|
|
3496
3568
|
});
|
|
3497
3569
|
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path12.basename(f)).join(", ")}`;
|
|
3498
3570
|
const nextStep = buildSmartNextStep(events);
|
|
3499
|
-
const
|
|
3500
|
-
const sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
3571
|
+
const sessionPhase = existingSession?.sessionPhase;
|
|
3501
3572
|
const checkpoint = createCheckpoint({
|
|
3502
3573
|
summary,
|
|
3503
3574
|
nextStep,
|
|
@@ -3506,9 +3577,10 @@ async function handleSaveCheckpoint() {
|
|
|
3506
3577
|
commitHashes,
|
|
3507
3578
|
workspaceRoot: wsPath,
|
|
3508
3579
|
source: "auto",
|
|
3509
|
-
sessionId
|
|
3580
|
+
sessionId,
|
|
3581
|
+
...sessionPhase ? { sessionPhase } : {},
|
|
3582
|
+
...sessionPhase === "planning" ? { tags: ["plan"] } : {}
|
|
3510
3583
|
});
|
|
3511
|
-
const writer = new KeepGoingWriter(wsPath);
|
|
3512
3584
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
3513
3585
|
writer.upsertSession({
|
|
3514
3586
|
sessionId,
|
|
@@ -3776,7 +3848,8 @@ async function handleUpdateTaskFromHook() {
|
|
|
3776
3848
|
branch,
|
|
3777
3849
|
worktreePath: wsPath,
|
|
3778
3850
|
sessionActive: true,
|
|
3779
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3851
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3852
|
+
sessionPhase: "active"
|
|
3780
3853
|
};
|
|
3781
3854
|
const sessionId = hookData.session_id || generateSessionId({ ...task, workspaceRoot: wsPath });
|
|
3782
3855
|
task.sessionId = sessionId;
|
|
@@ -3923,6 +3996,59 @@ async function handleDetectDecisions() {
|
|
|
3923
3996
|
process.exit(0);
|
|
3924
3997
|
}
|
|
3925
3998
|
|
|
3999
|
+
// src/cli/heartbeat.ts
|
|
4000
|
+
var STDIN_TIMEOUT_MS3 = 3e3;
|
|
4001
|
+
var THROTTLE_MS = 3e4;
|
|
4002
|
+
async function handleHeartbeat() {
|
|
4003
|
+
const wsPath = resolveWsPath();
|
|
4004
|
+
const chunks = [];
|
|
4005
|
+
const timeout = setTimeout(() => process.exit(0), STDIN_TIMEOUT_MS3);
|
|
4006
|
+
process.stdin.on("error", () => {
|
|
4007
|
+
clearTimeout(timeout);
|
|
4008
|
+
process.exit(0);
|
|
4009
|
+
});
|
|
4010
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
4011
|
+
process.stdin.on("end", () => {
|
|
4012
|
+
clearTimeout(timeout);
|
|
4013
|
+
try {
|
|
4014
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4015
|
+
if (!raw) {
|
|
4016
|
+
process.exit(0);
|
|
4017
|
+
}
|
|
4018
|
+
const hookData = JSON.parse(raw);
|
|
4019
|
+
const writer = new KeepGoingWriter(wsPath);
|
|
4020
|
+
const existing = writer.readCurrentTasks();
|
|
4021
|
+
const sessionIdFromHook = hookData.session_id;
|
|
4022
|
+
const sessionId = sessionIdFromHook || generateSessionId({ workspaceRoot: wsPath, worktreePath: wsPath, branch: getCurrentBranch(wsPath) ?? void 0 });
|
|
4023
|
+
const existingSession = existing.find((t) => t.sessionId === sessionId);
|
|
4024
|
+
if (existingSession?.updatedAt) {
|
|
4025
|
+
const ageMs = Date.now() - new Date(existingSession.updatedAt).getTime();
|
|
4026
|
+
if (ageMs < THROTTLE_MS) {
|
|
4027
|
+
process.exit(0);
|
|
4028
|
+
}
|
|
4029
|
+
}
|
|
4030
|
+
const sessionPhase = existingSession?.sessionPhase === "active" ? "active" : "planning";
|
|
4031
|
+
const branch = existingSession?.branch ?? getCurrentBranch(wsPath) ?? void 0;
|
|
4032
|
+
const task = {
|
|
4033
|
+
sessionId,
|
|
4034
|
+
sessionActive: true,
|
|
4035
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4036
|
+
sessionPhase,
|
|
4037
|
+
worktreePath: wsPath,
|
|
4038
|
+
branch
|
|
4039
|
+
};
|
|
4040
|
+
if (!existingSession?.sessionLabel && hookData.transcript_path) {
|
|
4041
|
+
const label = extractSessionLabel(hookData.transcript_path);
|
|
4042
|
+
if (label) task.sessionLabel = label;
|
|
4043
|
+
}
|
|
4044
|
+
writer.upsertSession(task);
|
|
4045
|
+
} catch {
|
|
4046
|
+
}
|
|
4047
|
+
process.exit(0);
|
|
4048
|
+
});
|
|
4049
|
+
process.stdin.resume();
|
|
4050
|
+
}
|
|
4051
|
+
|
|
3926
4052
|
// src/index.ts
|
|
3927
4053
|
var CLI_HANDLERS = {
|
|
3928
4054
|
"--print-momentum": handlePrintMomentum,
|
|
@@ -3932,7 +4058,8 @@ var CLI_HANDLERS = {
|
|
|
3932
4058
|
"--print-current": handlePrintCurrent,
|
|
3933
4059
|
"--statusline": handleStatusline,
|
|
3934
4060
|
"--continue-on": handleContinueOn,
|
|
3935
|
-
"--detect-decisions": handleDetectDecisions
|
|
4061
|
+
"--detect-decisions": handleDetectDecisions,
|
|
4062
|
+
"--heartbeat": handleHeartbeat
|
|
3936
4063
|
};
|
|
3937
4064
|
var flag = process.argv.slice(2).find((a) => a in CLI_HANDLERS);
|
|
3938
4065
|
if (flag) {
|