@keepgoingdev/mcp-server 0.8.0 → 0.9.0
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 +139 -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,40 @@ 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 sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
3522
|
+
const existingTasks = writer.readCurrentTasks();
|
|
3523
|
+
const existingSession = existingTasks.find((t) => t.sessionId === sessionId);
|
|
3524
|
+
const isPlanning = existingSession?.sessionPhase === "planning";
|
|
3525
|
+
if (touchedFiles.length === 0 && commitHashes.length === 0 && !isPlanning) {
|
|
3526
|
+
process.exit(0);
|
|
3527
|
+
}
|
|
3528
|
+
const projectName = path12.basename(resolveStorageRoot(wsPath));
|
|
3529
|
+
if (touchedFiles.length === 0 && commitHashes.length === 0 && isPlanning) {
|
|
3530
|
+
const summary2 = existingSession?.sessionLabel || existingSession?.taskSummary || "Planning session";
|
|
3531
|
+
const checkpoint2 = createCheckpoint({
|
|
3532
|
+
summary: summary2,
|
|
3533
|
+
nextStep: existingSession?.nextStep || "",
|
|
3534
|
+
gitBranch,
|
|
3535
|
+
touchedFiles: [],
|
|
3536
|
+
commitHashes: [],
|
|
3537
|
+
workspaceRoot: wsPath,
|
|
3538
|
+
source: "auto",
|
|
3539
|
+
sessionId,
|
|
3540
|
+
sessionPhase: "planning",
|
|
3541
|
+
tags: ["plan"]
|
|
3542
|
+
});
|
|
3543
|
+
writer.saveCheckpoint(checkpoint2, projectName);
|
|
3544
|
+
writer.upsertSession({
|
|
3545
|
+
sessionId,
|
|
3546
|
+
sessionActive: false,
|
|
3547
|
+
nextStep: checkpoint2.nextStep || void 0,
|
|
3548
|
+
branch: gitBranch ?? void 0,
|
|
3549
|
+
updatedAt: checkpoint2.timestamp
|
|
3550
|
+
});
|
|
3551
|
+
console.log(`[KeepGoing] Plan checkpoint saved: ${summary2}`);
|
|
3483
3552
|
process.exit(0);
|
|
3484
3553
|
}
|
|
3485
|
-
const gitBranch = getCurrentBranch(wsPath);
|
|
3486
3554
|
const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);
|
|
3487
3555
|
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
3488
3556
|
const events = buildSessionEvents({
|
|
@@ -3496,8 +3564,7 @@ async function handleSaveCheckpoint() {
|
|
|
3496
3564
|
});
|
|
3497
3565
|
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path12.basename(f)).join(", ")}`;
|
|
3498
3566
|
const nextStep = buildSmartNextStep(events);
|
|
3499
|
-
const
|
|
3500
|
-
const sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
3567
|
+
const sessionPhase = existingSession?.sessionPhase;
|
|
3501
3568
|
const checkpoint = createCheckpoint({
|
|
3502
3569
|
summary,
|
|
3503
3570
|
nextStep,
|
|
@@ -3506,9 +3573,10 @@ async function handleSaveCheckpoint() {
|
|
|
3506
3573
|
commitHashes,
|
|
3507
3574
|
workspaceRoot: wsPath,
|
|
3508
3575
|
source: "auto",
|
|
3509
|
-
sessionId
|
|
3576
|
+
sessionId,
|
|
3577
|
+
...sessionPhase ? { sessionPhase } : {},
|
|
3578
|
+
...sessionPhase === "planning" ? { tags: ["plan"] } : {}
|
|
3510
3579
|
});
|
|
3511
|
-
const writer = new KeepGoingWriter(wsPath);
|
|
3512
3580
|
writer.saveCheckpoint(checkpoint, projectName);
|
|
3513
3581
|
writer.upsertSession({
|
|
3514
3582
|
sessionId,
|
|
@@ -3776,7 +3844,8 @@ async function handleUpdateTaskFromHook() {
|
|
|
3776
3844
|
branch,
|
|
3777
3845
|
worktreePath: wsPath,
|
|
3778
3846
|
sessionActive: true,
|
|
3779
|
-
updatedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
3847
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3848
|
+
sessionPhase: "active"
|
|
3780
3849
|
};
|
|
3781
3850
|
const sessionId = hookData.session_id || generateSessionId({ ...task, workspaceRoot: wsPath });
|
|
3782
3851
|
task.sessionId = sessionId;
|
|
@@ -3923,6 +3992,59 @@ async function handleDetectDecisions() {
|
|
|
3923
3992
|
process.exit(0);
|
|
3924
3993
|
}
|
|
3925
3994
|
|
|
3995
|
+
// src/cli/heartbeat.ts
|
|
3996
|
+
var STDIN_TIMEOUT_MS3 = 3e3;
|
|
3997
|
+
var THROTTLE_MS = 3e4;
|
|
3998
|
+
async function handleHeartbeat() {
|
|
3999
|
+
const wsPath = resolveWsPath();
|
|
4000
|
+
const chunks = [];
|
|
4001
|
+
const timeout = setTimeout(() => process.exit(0), STDIN_TIMEOUT_MS3);
|
|
4002
|
+
process.stdin.on("error", () => {
|
|
4003
|
+
clearTimeout(timeout);
|
|
4004
|
+
process.exit(0);
|
|
4005
|
+
});
|
|
4006
|
+
process.stdin.on("data", (chunk) => chunks.push(chunk));
|
|
4007
|
+
process.stdin.on("end", () => {
|
|
4008
|
+
clearTimeout(timeout);
|
|
4009
|
+
try {
|
|
4010
|
+
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
4011
|
+
if (!raw) {
|
|
4012
|
+
process.exit(0);
|
|
4013
|
+
}
|
|
4014
|
+
const hookData = JSON.parse(raw);
|
|
4015
|
+
const writer = new KeepGoingWriter(wsPath);
|
|
4016
|
+
const existing = writer.readCurrentTasks();
|
|
4017
|
+
const sessionIdFromHook = hookData.session_id;
|
|
4018
|
+
const sessionId = sessionIdFromHook || generateSessionId({ workspaceRoot: wsPath, worktreePath: wsPath, branch: getCurrentBranch(wsPath) ?? void 0 });
|
|
4019
|
+
const existingSession = existing.find((t) => t.sessionId === sessionId);
|
|
4020
|
+
if (existingSession?.updatedAt) {
|
|
4021
|
+
const ageMs = Date.now() - new Date(existingSession.updatedAt).getTime();
|
|
4022
|
+
if (ageMs < THROTTLE_MS) {
|
|
4023
|
+
process.exit(0);
|
|
4024
|
+
}
|
|
4025
|
+
}
|
|
4026
|
+
const sessionPhase = existingSession?.sessionPhase === "active" ? "active" : "planning";
|
|
4027
|
+
const branch = existingSession?.branch ?? getCurrentBranch(wsPath) ?? void 0;
|
|
4028
|
+
const task = {
|
|
4029
|
+
sessionId,
|
|
4030
|
+
sessionActive: true,
|
|
4031
|
+
updatedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
4032
|
+
sessionPhase,
|
|
4033
|
+
worktreePath: wsPath,
|
|
4034
|
+
branch
|
|
4035
|
+
};
|
|
4036
|
+
if (!existingSession?.sessionLabel && hookData.transcript_path) {
|
|
4037
|
+
const label = extractSessionLabel(hookData.transcript_path);
|
|
4038
|
+
if (label) task.sessionLabel = label;
|
|
4039
|
+
}
|
|
4040
|
+
writer.upsertSession(task);
|
|
4041
|
+
} catch {
|
|
4042
|
+
}
|
|
4043
|
+
process.exit(0);
|
|
4044
|
+
});
|
|
4045
|
+
process.stdin.resume();
|
|
4046
|
+
}
|
|
4047
|
+
|
|
3926
4048
|
// src/index.ts
|
|
3927
4049
|
var CLI_HANDLERS = {
|
|
3928
4050
|
"--print-momentum": handlePrintMomentum,
|
|
@@ -3932,7 +4054,8 @@ var CLI_HANDLERS = {
|
|
|
3932
4054
|
"--print-current": handlePrintCurrent,
|
|
3933
4055
|
"--statusline": handleStatusline,
|
|
3934
4056
|
"--continue-on": handleContinueOn,
|
|
3935
|
-
"--detect-decisions": handleDetectDecisions
|
|
4057
|
+
"--detect-decisions": handleDetectDecisions,
|
|
4058
|
+
"--heartbeat": handleHeartbeat
|
|
3936
4059
|
};
|
|
3937
4060
|
var flag = process.argv.slice(2).find((a) => a in CLI_HANDLERS);
|
|
3938
4061
|
if (flag) {
|