@keepgoingdev/mcp-server 0.1.2 → 0.2.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 +308 -9
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,12 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
// src/index.ts
|
|
4
|
+
import path5 from "path";
|
|
4
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
5
6
|
import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
|
|
6
7
|
|
|
7
8
|
// src/storage.ts
|
|
8
|
-
import
|
|
9
|
-
import
|
|
9
|
+
import fs2 from "fs";
|
|
10
|
+
import path2 from "path";
|
|
11
|
+
|
|
12
|
+
// ../../packages/shared/src/session.ts
|
|
13
|
+
import { randomUUID } from "crypto";
|
|
14
|
+
function generateCheckpointId() {
|
|
15
|
+
return randomUUID();
|
|
16
|
+
}
|
|
17
|
+
function createCheckpoint(fields) {
|
|
18
|
+
return {
|
|
19
|
+
id: generateCheckpointId(),
|
|
20
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
21
|
+
...fields
|
|
22
|
+
};
|
|
23
|
+
}
|
|
10
24
|
|
|
11
25
|
// ../../packages/shared/src/timeUtils.ts
|
|
12
26
|
function formatRelativeTime(timestamp) {
|
|
@@ -81,9 +95,27 @@ function getGitLogSince(workspacePath2, format, sinceTimestamp) {
|
|
|
81
95
|
return [];
|
|
82
96
|
}
|
|
83
97
|
}
|
|
98
|
+
function getCommitsSince(workspacePath2, sinceTimestamp) {
|
|
99
|
+
return getGitLogSince(workspacePath2, "%H", sinceTimestamp);
|
|
100
|
+
}
|
|
84
101
|
function getCommitMessagesSince(workspacePath2, sinceTimestamp) {
|
|
85
102
|
return getGitLogSince(workspacePath2, "%s", sinceTimestamp);
|
|
86
103
|
}
|
|
104
|
+
function getTouchedFiles(workspacePath2) {
|
|
105
|
+
try {
|
|
106
|
+
const result = execFileSync("git", ["status", "--porcelain"], {
|
|
107
|
+
cwd: workspacePath2,
|
|
108
|
+
encoding: "utf-8",
|
|
109
|
+
timeout: 5e3
|
|
110
|
+
});
|
|
111
|
+
if (!result.trim()) {
|
|
112
|
+
return [];
|
|
113
|
+
}
|
|
114
|
+
return result.trim().split("\n").map((line) => line.substring(3).trim()).filter((file) => file.length > 0 && !file.endsWith("/"));
|
|
115
|
+
} catch {
|
|
116
|
+
return [];
|
|
117
|
+
}
|
|
118
|
+
}
|
|
87
119
|
|
|
88
120
|
// ../../packages/shared/src/reentry.ts
|
|
89
121
|
var RECENT_SESSION_COUNT = 5;
|
|
@@ -285,25 +317,102 @@ function inferFocusFromFiles(files) {
|
|
|
285
317
|
return names.join(", ");
|
|
286
318
|
}
|
|
287
319
|
|
|
288
|
-
// src/storage.ts
|
|
320
|
+
// ../../packages/shared/src/storage.ts
|
|
321
|
+
import fs from "fs";
|
|
322
|
+
import path from "path";
|
|
323
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
289
324
|
var STORAGE_DIR = ".keepgoing";
|
|
290
325
|
var META_FILE = "meta.json";
|
|
291
326
|
var SESSIONS_FILE = "sessions.json";
|
|
292
327
|
var STATE_FILE = "state.json";
|
|
293
|
-
var
|
|
328
|
+
var KeepGoingWriter = class {
|
|
294
329
|
storagePath;
|
|
295
|
-
metaFilePath;
|
|
296
330
|
sessionsFilePath;
|
|
297
331
|
stateFilePath;
|
|
332
|
+
metaFilePath;
|
|
298
333
|
constructor(workspacePath2) {
|
|
299
334
|
this.storagePath = path.join(workspacePath2, STORAGE_DIR);
|
|
300
|
-
this.metaFilePath = path.join(this.storagePath, META_FILE);
|
|
301
335
|
this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);
|
|
302
336
|
this.stateFilePath = path.join(this.storagePath, STATE_FILE);
|
|
337
|
+
this.metaFilePath = path.join(this.storagePath, META_FILE);
|
|
338
|
+
}
|
|
339
|
+
ensureDir() {
|
|
340
|
+
if (!fs.existsSync(this.storagePath)) {
|
|
341
|
+
fs.mkdirSync(this.storagePath, { recursive: true });
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
saveCheckpoint(checkpoint, projectName) {
|
|
345
|
+
this.ensureDir();
|
|
346
|
+
let sessionsData;
|
|
347
|
+
try {
|
|
348
|
+
if (fs.existsSync(this.sessionsFilePath)) {
|
|
349
|
+
const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, "utf-8"));
|
|
350
|
+
if (Array.isArray(raw)) {
|
|
351
|
+
sessionsData = { version: 1, project: projectName, sessions: raw };
|
|
352
|
+
} else {
|
|
353
|
+
sessionsData = raw;
|
|
354
|
+
}
|
|
355
|
+
} else {
|
|
356
|
+
sessionsData = { version: 1, project: projectName, sessions: [] };
|
|
357
|
+
}
|
|
358
|
+
} catch {
|
|
359
|
+
sessionsData = { version: 1, project: projectName, sessions: [] };
|
|
360
|
+
}
|
|
361
|
+
sessionsData.sessions.push(checkpoint);
|
|
362
|
+
sessionsData.lastSessionId = checkpoint.id;
|
|
363
|
+
const MAX_SESSIONS = 200;
|
|
364
|
+
if (sessionsData.sessions.length > MAX_SESSIONS) {
|
|
365
|
+
sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);
|
|
366
|
+
}
|
|
367
|
+
fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
|
|
368
|
+
const state = {
|
|
369
|
+
lastSessionId: checkpoint.id,
|
|
370
|
+
lastKnownBranch: checkpoint.gitBranch,
|
|
371
|
+
lastActivityAt: checkpoint.timestamp
|
|
372
|
+
};
|
|
373
|
+
fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
374
|
+
let meta;
|
|
375
|
+
try {
|
|
376
|
+
if (fs.existsSync(this.metaFilePath)) {
|
|
377
|
+
meta = JSON.parse(fs.readFileSync(this.metaFilePath, "utf-8"));
|
|
378
|
+
meta.lastUpdated = checkpoint.timestamp;
|
|
379
|
+
} else {
|
|
380
|
+
meta = {
|
|
381
|
+
projectId: randomUUID2(),
|
|
382
|
+
createdAt: checkpoint.timestamp,
|
|
383
|
+
lastUpdated: checkpoint.timestamp
|
|
384
|
+
};
|
|
385
|
+
}
|
|
386
|
+
} catch {
|
|
387
|
+
meta = {
|
|
388
|
+
projectId: randomUUID2(),
|
|
389
|
+
createdAt: checkpoint.timestamp,
|
|
390
|
+
lastUpdated: checkpoint.timestamp
|
|
391
|
+
};
|
|
392
|
+
}
|
|
393
|
+
fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
|
|
394
|
+
}
|
|
395
|
+
};
|
|
396
|
+
|
|
397
|
+
// src/storage.ts
|
|
398
|
+
var STORAGE_DIR2 = ".keepgoing";
|
|
399
|
+
var META_FILE2 = "meta.json";
|
|
400
|
+
var SESSIONS_FILE2 = "sessions.json";
|
|
401
|
+
var STATE_FILE2 = "state.json";
|
|
402
|
+
var KeepGoingReader = class {
|
|
403
|
+
storagePath;
|
|
404
|
+
metaFilePath;
|
|
405
|
+
sessionsFilePath;
|
|
406
|
+
stateFilePath;
|
|
407
|
+
constructor(workspacePath2) {
|
|
408
|
+
this.storagePath = path2.join(workspacePath2, STORAGE_DIR2);
|
|
409
|
+
this.metaFilePath = path2.join(this.storagePath, META_FILE2);
|
|
410
|
+
this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE2);
|
|
411
|
+
this.stateFilePath = path2.join(this.storagePath, STATE_FILE2);
|
|
303
412
|
}
|
|
304
413
|
/** Check if .keepgoing/ directory exists. */
|
|
305
414
|
exists() {
|
|
306
|
-
return
|
|
415
|
+
return fs2.existsSync(this.storagePath);
|
|
307
416
|
}
|
|
308
417
|
/** Read state.json, returns undefined if missing or corrupt. */
|
|
309
418
|
getState() {
|
|
@@ -370,10 +479,10 @@ var KeepGoingReader = class {
|
|
|
370
479
|
}
|
|
371
480
|
readJsonFile(filePath) {
|
|
372
481
|
try {
|
|
373
|
-
if (!
|
|
482
|
+
if (!fs2.existsSync(filePath)) {
|
|
374
483
|
return void 0;
|
|
375
484
|
}
|
|
376
|
-
const raw =
|
|
485
|
+
const raw = fs2.readFileSync(filePath, "utf-8");
|
|
377
486
|
return JSON.parse(raw);
|
|
378
487
|
} catch {
|
|
379
488
|
return void 0;
|
|
@@ -569,6 +678,152 @@ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
|
|
|
569
678
|
);
|
|
570
679
|
}
|
|
571
680
|
|
|
681
|
+
// src/tools/saveCheckpoint.ts
|
|
682
|
+
import path3 from "path";
|
|
683
|
+
import { z as z2 } from "zod";
|
|
684
|
+
function registerSaveCheckpoint(server2, reader2, workspacePath2) {
|
|
685
|
+
server2.tool(
|
|
686
|
+
"save_checkpoint",
|
|
687
|
+
"Save a development checkpoint. Call this after completing a task or meaningful piece of work, not just at end of session. Each checkpoint helps the next session (or developer) pick up exactly where you left off.",
|
|
688
|
+
{
|
|
689
|
+
summary: z2.string().describe("What was accomplished in this session"),
|
|
690
|
+
nextStep: z2.string().optional().describe("What to do next"),
|
|
691
|
+
blocker: z2.string().optional().describe("Any blocker preventing progress")
|
|
692
|
+
},
|
|
693
|
+
async ({ summary, nextStep, blocker }) => {
|
|
694
|
+
const lastSession = reader2.getLastSession();
|
|
695
|
+
const gitBranch = getCurrentBranch(workspacePath2);
|
|
696
|
+
const touchedFiles = getTouchedFiles(workspacePath2);
|
|
697
|
+
const commitHashes = getCommitsSince(workspacePath2, lastSession?.timestamp);
|
|
698
|
+
const projectName = path3.basename(workspacePath2);
|
|
699
|
+
const checkpoint = createCheckpoint({
|
|
700
|
+
summary,
|
|
701
|
+
nextStep: nextStep || "",
|
|
702
|
+
blocker,
|
|
703
|
+
gitBranch,
|
|
704
|
+
touchedFiles,
|
|
705
|
+
commitHashes,
|
|
706
|
+
workspaceRoot: workspacePath2,
|
|
707
|
+
source: "manual"
|
|
708
|
+
});
|
|
709
|
+
const writer = new KeepGoingWriter(workspacePath2);
|
|
710
|
+
writer.saveCheckpoint(checkpoint, projectName);
|
|
711
|
+
const lines = [
|
|
712
|
+
`Checkpoint saved.`,
|
|
713
|
+
`- **ID:** ${checkpoint.id}`,
|
|
714
|
+
`- **Branch:** ${gitBranch || "unknown"}`,
|
|
715
|
+
`- **Files tracked:** ${touchedFiles.length}`,
|
|
716
|
+
`- **Commits captured:** ${commitHashes.length}`
|
|
717
|
+
];
|
|
718
|
+
return {
|
|
719
|
+
content: [{ type: "text", text: lines.join("\n") }]
|
|
720
|
+
};
|
|
721
|
+
}
|
|
722
|
+
);
|
|
723
|
+
}
|
|
724
|
+
|
|
725
|
+
// src/tools/setupProject.ts
|
|
726
|
+
import fs3 from "fs";
|
|
727
|
+
import path4 from "path";
|
|
728
|
+
import { z as z3 } from "zod";
|
|
729
|
+
var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
|
|
730
|
+
var SESSION_START_HOOK = {
|
|
731
|
+
matcher: "",
|
|
732
|
+
hooks: [
|
|
733
|
+
{
|
|
734
|
+
type: "command",
|
|
735
|
+
command: "npx -y @keepgoingdev/mcp-server --print-momentum"
|
|
736
|
+
}
|
|
737
|
+
]
|
|
738
|
+
};
|
|
739
|
+
var STOP_HOOK = {
|
|
740
|
+
matcher: "",
|
|
741
|
+
hooks: [
|
|
742
|
+
{
|
|
743
|
+
type: "command",
|
|
744
|
+
command: "npx -y @keepgoingdev/mcp-server --save-checkpoint"
|
|
745
|
+
}
|
|
746
|
+
]
|
|
747
|
+
};
|
|
748
|
+
var CLAUDE_MD_SECTION = `
|
|
749
|
+
## KeepGoing
|
|
750
|
+
|
|
751
|
+
After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
|
|
752
|
+
- \`summary\`: What you accomplished
|
|
753
|
+
- \`nextStep\`: What should be done next
|
|
754
|
+
- \`blocker\`: Any blocker (if applicable)
|
|
755
|
+
`;
|
|
756
|
+
function hasKeepGoingHook(hookEntries) {
|
|
757
|
+
return hookEntries.some(
|
|
758
|
+
(entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes(KEEPGOING_MARKER))
|
|
759
|
+
);
|
|
760
|
+
}
|
|
761
|
+
function registerSetupProject(server2, workspacePath2) {
|
|
762
|
+
server2.tool(
|
|
763
|
+
"setup_project",
|
|
764
|
+
"Set up KeepGoing in the current project. Adds session hooks to .claude/settings.json and CLAUDE.md instructions so checkpoints are saved automatically.",
|
|
765
|
+
{
|
|
766
|
+
sessionHooks: z3.boolean().optional().default(true).describe("Add session hooks to .claude/settings.json"),
|
|
767
|
+
claudeMd: z3.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md")
|
|
768
|
+
},
|
|
769
|
+
async ({ sessionHooks, claudeMd }) => {
|
|
770
|
+
const results = [];
|
|
771
|
+
if (sessionHooks) {
|
|
772
|
+
const claudeDir = path4.join(workspacePath2, ".claude");
|
|
773
|
+
const settingsPath = path4.join(claudeDir, "settings.json");
|
|
774
|
+
let settings = {};
|
|
775
|
+
if (fs3.existsSync(settingsPath)) {
|
|
776
|
+
settings = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
|
|
777
|
+
}
|
|
778
|
+
if (!settings.hooks) {
|
|
779
|
+
settings.hooks = {};
|
|
780
|
+
}
|
|
781
|
+
let hooksChanged = false;
|
|
782
|
+
if (!Array.isArray(settings.hooks.SessionStart)) {
|
|
783
|
+
settings.hooks.SessionStart = [];
|
|
784
|
+
}
|
|
785
|
+
if (!hasKeepGoingHook(settings.hooks.SessionStart)) {
|
|
786
|
+
settings.hooks.SessionStart.push(SESSION_START_HOOK);
|
|
787
|
+
hooksChanged = true;
|
|
788
|
+
}
|
|
789
|
+
if (!Array.isArray(settings.hooks.Stop)) {
|
|
790
|
+
settings.hooks.Stop = [];
|
|
791
|
+
}
|
|
792
|
+
if (!hasKeepGoingHook(settings.hooks.Stop)) {
|
|
793
|
+
settings.hooks.Stop.push(STOP_HOOK);
|
|
794
|
+
hooksChanged = true;
|
|
795
|
+
}
|
|
796
|
+
if (hooksChanged) {
|
|
797
|
+
if (!fs3.existsSync(claudeDir)) {
|
|
798
|
+
fs3.mkdirSync(claudeDir, { recursive: true });
|
|
799
|
+
}
|
|
800
|
+
fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
801
|
+
results.push("**Session hooks:** Added to `.claude/settings.json`");
|
|
802
|
+
} else {
|
|
803
|
+
results.push("**Session hooks:** Already present, skipped");
|
|
804
|
+
}
|
|
805
|
+
}
|
|
806
|
+
if (claudeMd) {
|
|
807
|
+
const claudeMdPath = path4.join(workspacePath2, "CLAUDE.md");
|
|
808
|
+
let existing = "";
|
|
809
|
+
if (fs3.existsSync(claudeMdPath)) {
|
|
810
|
+
existing = fs3.readFileSync(claudeMdPath, "utf-8");
|
|
811
|
+
}
|
|
812
|
+
if (existing.includes("## KeepGoing")) {
|
|
813
|
+
results.push("**CLAUDE.md:** KeepGoing section already present, skipped");
|
|
814
|
+
} else {
|
|
815
|
+
const updated = existing + CLAUDE_MD_SECTION;
|
|
816
|
+
fs3.writeFileSync(claudeMdPath, updated);
|
|
817
|
+
results.push("**CLAUDE.md:** Added KeepGoing section");
|
|
818
|
+
}
|
|
819
|
+
}
|
|
820
|
+
return {
|
|
821
|
+
content: [{ type: "text", text: results.join("\n") }]
|
|
822
|
+
};
|
|
823
|
+
}
|
|
824
|
+
);
|
|
825
|
+
}
|
|
826
|
+
|
|
572
827
|
// src/prompts/resume.ts
|
|
573
828
|
function registerResumePrompt(server2) {
|
|
574
829
|
server2.prompt(
|
|
@@ -630,6 +885,48 @@ if (process.argv.includes("--print-momentum")) {
|
|
|
630
885
|
console.log(lines.join("\n"));
|
|
631
886
|
process.exit(0);
|
|
632
887
|
}
|
|
888
|
+
if (process.argv.includes("--save-checkpoint")) {
|
|
889
|
+
const wsPath = process.argv.slice(2).find((a) => !a.startsWith("--")) || process.cwd();
|
|
890
|
+
const reader2 = new KeepGoingReader(wsPath);
|
|
891
|
+
const lastSession = reader2.getLastSession();
|
|
892
|
+
if (lastSession?.timestamp) {
|
|
893
|
+
const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();
|
|
894
|
+
if (ageMs < 2 * 60 * 1e3) {
|
|
895
|
+
process.exit(0);
|
|
896
|
+
}
|
|
897
|
+
}
|
|
898
|
+
const touchedFiles = getTouchedFiles(wsPath);
|
|
899
|
+
const commitHashes = getCommitsSince(wsPath, lastSession?.timestamp);
|
|
900
|
+
if (touchedFiles.length === 0 && commitHashes.length === 0) {
|
|
901
|
+
process.exit(0);
|
|
902
|
+
}
|
|
903
|
+
const gitBranch = getCurrentBranch(wsPath);
|
|
904
|
+
const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);
|
|
905
|
+
let summary;
|
|
906
|
+
if (commitMessages.length > 0) {
|
|
907
|
+
summary = commitMessages.slice(0, 3).join("; ");
|
|
908
|
+
} else {
|
|
909
|
+
const fileNames = touchedFiles.slice(0, 5).map((f) => path5.basename(f));
|
|
910
|
+
summary = `Worked on ${fileNames.join(", ")}`;
|
|
911
|
+
if (touchedFiles.length > 5) {
|
|
912
|
+
summary += ` and ${touchedFiles.length - 5} more`;
|
|
913
|
+
}
|
|
914
|
+
}
|
|
915
|
+
const projectName = path5.basename(wsPath);
|
|
916
|
+
const checkpoint = createCheckpoint({
|
|
917
|
+
summary,
|
|
918
|
+
nextStep: "",
|
|
919
|
+
gitBranch,
|
|
920
|
+
touchedFiles,
|
|
921
|
+
commitHashes,
|
|
922
|
+
workspaceRoot: wsPath,
|
|
923
|
+
source: "auto"
|
|
924
|
+
});
|
|
925
|
+
const writer = new KeepGoingWriter(wsPath);
|
|
926
|
+
writer.saveCheckpoint(checkpoint, projectName);
|
|
927
|
+
console.log(`[KeepGoing] Auto-checkpoint saved: ${summary}`);
|
|
928
|
+
process.exit(0);
|
|
929
|
+
}
|
|
633
930
|
var workspacePath = process.argv[2] || process.cwd();
|
|
634
931
|
var reader = new KeepGoingReader(workspacePath);
|
|
635
932
|
var server = new McpServer({
|
|
@@ -639,6 +936,8 @@ var server = new McpServer({
|
|
|
639
936
|
registerGetMomentum(server, reader, workspacePath);
|
|
640
937
|
registerGetSessionHistory(server, reader);
|
|
641
938
|
registerGetReentryBriefing(server, reader, workspacePath);
|
|
939
|
+
registerSaveCheckpoint(server, reader, workspacePath);
|
|
940
|
+
registerSetupProject(server, workspacePath);
|
|
642
941
|
registerResumePrompt(server);
|
|
643
942
|
var transport = new StdioServerTransport();
|
|
644
943
|
await server.connect(transport);
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/storage.ts","../../../packages/shared/src/timeUtils.ts","../../../packages/shared/src/gitUtils.ts","../../../packages/shared/src/reentry.ts","../src/tools/getMomentum.ts","../src/tools/getSessionHistory.ts","../src/tools/getReentryBriefing.ts","../src/prompts/resume.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { KeepGoingReader } from './storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\nimport { registerGetMomentum } from './tools/getMomentum.js';\nimport { registerGetSessionHistory } from './tools/getSessionHistory.js';\nimport { registerGetReentryBriefing } from './tools/getReentryBriefing.js';\nimport { registerResumePrompt } from './prompts/resume.js';\n\n// Handle --print-momentum CLI flag: print momentum context and exit.\n// Used by the Claude Code SessionStart hook (scripts/keepgoing-hook.sh).\nif (process.argv.includes('--print-momentum')) {\n // Workspace path is the first non-flag argument after the script path\n const wsPath = process.argv.slice(2).find(a => a !== '--print-momentum') || process.cwd();\n const reader = new KeepGoingReader(wsPath);\n\n if (!reader.exists()) {\n process.exit(0);\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n process.exit(0);\n }\n\n const touchedCount = lastSession.touchedFiles?.length ?? 0;\n const lines: string[] = [];\n lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);\n if (lastSession.summary) {\n lines.push(` Summary: ${lastSession.summary}`);\n }\n if (lastSession.nextStep) {\n lines.push(` Next step: ${lastSession.nextStep}`);\n }\n if (lastSession.blocker) {\n lines.push(` Blocker: ${lastSession.blocker}`);\n }\n if (lastSession.gitBranch) {\n lines.push(` Branch: ${lastSession.gitBranch}`);\n }\n if (touchedCount > 0) {\n lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? 'unknown branch'}`);\n }\n lines.push(' Tip: Use the get_reentry_briefing tool for a full briefing');\n\n console.log(lines.join('\\n'));\n process.exit(0);\n}\n\n// Default: start MCP server\n// Workspace path can be passed as an argument, otherwise defaults to CWD.\n// MCP hosts (Claude Code, etc.) typically launch the server with the project root as CWD.\nconst workspacePath = process.argv[2] || process.cwd();\nconst reader = new KeepGoingReader(workspacePath);\n\nconst server = new McpServer({\n name: 'keepgoing',\n version: '0.1.0',\n});\n\n// Register tools\nregisterGetMomentum(server, reader, workspacePath);\nregisterGetSessionHistory(server, reader);\nregisterGetReentryBriefing(server, reader, workspacePath);\n\n// Register prompts\nregisterResumePrompt(server);\n\n// Connect via stdio\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nconsole.error('KeepGoing MCP server started');\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n getRecentSessions,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n} from '@keepgoingdev/shared';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Read-only reader for .keepgoing/ directory.\n * Does not write or create any files.\n */\nexport class KeepGoingReader {\n private readonly storagePath: string;\n private readonly metaFilePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n }\n\n /** Check if .keepgoing/ directory exists. */\n exists(): boolean {\n return fs.existsSync(this.storagePath);\n }\n\n /** Read state.json, returns undefined if missing or corrupt. */\n getState(): ProjectState | undefined {\n return this.readJsonFile<ProjectState>(this.stateFilePath);\n }\n\n /** Read meta.json, returns undefined if missing or corrupt. */\n getMeta(): ProjectMeta | undefined {\n return this.readJsonFile<ProjectMeta>(this.metaFilePath);\n }\n\n /**\n * Read sessions from sessions.json.\n * Handles both formats:\n * - Flat array: SessionCheckpoint[] (from ProjectStorage)\n * - Wrapper object: ProjectSessions (from SessionStorage)\n */\n getSessions(): SessionCheckpoint[] {\n return this.parseSessions().sessions;\n }\n\n /**\n * Get the most recent session checkpoint.\n * Uses state.lastSessionId if available, falls back to last in array.\n */\n getLastSession(): SessionCheckpoint | undefined {\n const { sessions, wrapperLastSessionId } = this.parseSessions();\n if (sessions.length === 0) {\n return undefined;\n }\n\n const state = this.getState();\n if (state?.lastSessionId) {\n const found = sessions.find((s) => s.id === state.lastSessionId);\n if (found) {\n return found;\n }\n }\n\n if (wrapperLastSessionId) {\n const found = sessions.find((s) => s.id === wrapperLastSessionId);\n if (found) {\n return found;\n }\n }\n\n return sessions[sessions.length - 1];\n }\n\n /**\n * Returns the last N sessions, newest first.\n */\n getRecentSessions(count: number): SessionCheckpoint[] {\n return getRecentSessions(this.getSessions(), count);\n }\n\n /**\n * Parses sessions.json once, returning both the session list\n * and the optional lastSessionId from a ProjectSessions wrapper.\n */\n private parseSessions(): { sessions: SessionCheckpoint[]; wrapperLastSessionId?: string } {\n const raw = this.readJsonFile<ProjectSessions | SessionCheckpoint[]>(\n this.sessionsFilePath,\n );\n if (!raw) {\n return { sessions: [] };\n }\n if (Array.isArray(raw)) {\n return { sessions: raw };\n }\n return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };\n }\n\n private readJsonFile<T>(filePath: string): T | undefined {\n try {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n}\n","/**\n * Formats a timestamp as a human-readable relative time string.\n * Examples: \"just now\", \"5 minutes ago\", \"3 hours ago\", \"2 days ago\", \"1 week ago\"\n *\n * Note: Month and year calculations use approximations (30 days/month, 365 days/year)\n * for simplicity. These approximations are acceptable for the \"human-readable\" purpose.\n */\nexport function formatRelativeTime(timestamp: string): string {\n const now = Date.now();\n const then = new Date(timestamp).getTime();\n const diffMs = now - then;\n\n // Handle invalid dates\n if (isNaN(diffMs)) {\n return 'unknown time';\n }\n\n // Future dates\n if (diffMs < 0) {\n return 'in the future';\n }\n\n const seconds = Math.floor(diffMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30); // Approximation for human readability\n const years = Math.floor(days / 365); // Approximation, doesn't account for leap years\n\n if (seconds < 10) {\n return 'just now';\n } else if (seconds < 60) {\n return `${seconds} seconds ago`;\n } else if (minutes < 60) {\n return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;\n } else if (hours < 24) {\n return hours === 1 ? '1 hour ago' : `${hours} hours ago`;\n } else if (days < 7) {\n return days === 1 ? '1 day ago' : `${days} days ago`;\n } else if (weeks < 4) {\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n } else if (months < 12) {\n return months === 1 ? '1 month ago' : `${months} months ago`;\n } else {\n return years === 1 ? '1 year ago' : `${years} years ago`;\n }\n}\n","import { execFileSync, execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Git utilities for extracting context from the workspace.\n * These are intentionally decoupled from VSCode APIs.\n * All sync functions use execFileSync (no shell injection).\n */\n\n/**\n * Returns the current git branch name, or undefined if not in a git repo.\n */\nexport function getCurrentBranch(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs git log with the given format since a timestamp.\n * Shared implementation for commit hash and message retrieval.\n */\nfunction getGitLogSince(workspacePath: string, format: string, sinceTimestamp?: string): string[] {\n try {\n const since = sinceTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const result = execFileSync(\n 'git',\n ['log', `--since=${since}`, `--format=${format}`],\n {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n },\n );\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .filter((line: string) => line.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns a list of commit hashes since a given ISO timestamp.\n * If no timestamp is provided, returns recent commits (last 24 hours).\n */\nexport function getCommitsSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%H', sinceTimestamp);\n}\n\n/**\n * Returns a list of commit subject lines since a given ISO timestamp.\n * If no timestamp is provided, returns commit messages from the last 24 hours.\n * Results are in reverse chronological order (newest first), matching git log default.\n */\nexport function getCommitMessagesSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%s', sinceTimestamp);\n}\n\n/**\n * Returns the current HEAD commit hash, or undefined if not in a git repo.\n */\nexport function getHeadCommitHash(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getCurrentBranch. Avoids blocking the event loop.\n */\nexport async function getCurrentBranchAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getHeadCommitHash. Avoids blocking the event loop.\n */\nexport async function getHeadCommitHashAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns a list of files that have been modified (tracked and untracked)\n * in the workspace. This is best-effort and may not capture all changes.\n */\nexport function getTouchedFiles(workspacePath: string): string[] {\n try {\n const result = execFileSync('git', ['status', '--porcelain'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .map((line) => line.substring(3).trim())\n .filter((file) => file.length > 0 && !file.endsWith('/'));\n } catch {\n return [];\n }\n}\n","import type {\n SessionCheckpoint,\n ProjectState,\n ReEntryBriefing,\n} from './types';\nimport { formatRelativeTime } from './timeUtils';\n\nconst RECENT_SESSION_COUNT = 5;\n\n/**\n * Generates a synthesized re-entry briefing from stored project data.\n *\n * Uses heuristic rules (no AI) to infer focus and suggest next steps.\n * Designed to be replaced by an LLM-backed implementation in the future\n * while keeping the same input/output contract.\n */\nexport function generateBriefing(\n lastSession: SessionCheckpoint | undefined,\n recentSessions: SessionCheckpoint[],\n projectState: ProjectState,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): ReEntryBriefing | undefined {\n if (!lastSession) {\n return undefined;\n }\n\n return {\n lastWorked: formatRelativeTime(lastSession.timestamp),\n currentFocus: buildCurrentFocus(lastSession, projectState, gitBranch),\n recentActivity: buildRecentActivity(\n lastSession,\n recentSessions,\n recentCommitMessages,\n ),\n suggestedNext: buildSuggestedNext(lastSession, gitBranch),\n smallNextStep: buildSmallNextStep(\n lastSession,\n gitBranch,\n recentCommitMessages,\n ),\n };\n}\n\n/**\n * Returns the most recent N sessions in newest-first order.\n */\nexport function getRecentSessions(\n allSessions: SessionCheckpoint[],\n count: number = RECENT_SESSION_COUNT,\n): SessionCheckpoint[] {\n return allSessions.slice(-count).reverse();\n}\n\nfunction buildCurrentFocus(\n lastSession: SessionCheckpoint,\n projectState: ProjectState,\n gitBranch?: string,\n): string {\n if (projectState.derivedCurrentFocus) {\n return projectState.derivedCurrentFocus;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return branchFocus;\n }\n\n if (lastSession.summary) {\n return lastSession.summary;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return inferFocusFromFiles(lastSession.touchedFiles);\n }\n\n return 'Unknown, save a checkpoint to set context';\n}\n\nfunction buildRecentActivity(\n lastSession: SessionCheckpoint,\n recentSessions: SessionCheckpoint[],\n recentCommitMessages?: string[],\n): string {\n const parts: string[] = [];\n\n const sessionCount = recentSessions.length;\n if (sessionCount > 1) {\n parts.push(`${sessionCount} recent sessions`);\n } else if (sessionCount === 1) {\n parts.push('1 recent session');\n }\n\n if (lastSession.summary) {\n parts.push(`Last: ${lastSession.summary}`);\n }\n\n if (lastSession.touchedFiles.length > 0) {\n parts.push(`${lastSession.touchedFiles.length} files touched`);\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n parts.push(`${recentCommitMessages.length} recent commits`);\n }\n\n return parts.length > 0 ? parts.join('. ') : 'No recent activity recorded';\n}\n\nfunction buildSuggestedNext(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n): string {\n if (lastSession.nextStep) {\n return lastSession.nextStep;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Continue working on ${branchFocus}`;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return `Continue working on ${inferFocusFromFiles(lastSession.touchedFiles)}`;\n }\n\n return 'Save a checkpoint to track your next step';\n}\n\nfunction buildSmallNextStep(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): string {\n const fallback = 'Review last changed files to resume flow';\n\n if (lastSession.nextStep) {\n const distilled = distillToSmallStep(\n lastSession.nextStep,\n lastSession.touchedFiles,\n );\n if (distilled) {\n return distilled;\n }\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n const commitStep = deriveStepFromCommits(recentCommitMessages);\n if (commitStep) {\n return commitStep;\n }\n }\n\n if (lastSession.touchedFiles.length > 0) {\n const fileStep = deriveStepFromFiles(lastSession.touchedFiles);\n if (fileStep) {\n return fileStep;\n }\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Check git status for ${branchFocus}`;\n }\n\n return fallback;\n}\n\nfunction distillToSmallStep(\n nextStep: string,\n touchedFiles: string[],\n): string | undefined {\n if (!nextStep.trim()) {\n return undefined;\n }\n\n const words = nextStep.trim().split(/\\s+/);\n if (words.length <= 12) {\n if (touchedFiles.length > 0 && !mentionsFile(nextStep)) {\n const primaryFile = getPrimaryFileName(touchedFiles);\n const enhanced = `${nextStep.trim()} in ${primaryFile}`;\n if (enhanced.split(/\\s+/).length <= 12) {\n return enhanced;\n }\n }\n return nextStep.trim();\n }\n\n return words.slice(0, 12).join(' ');\n}\n\nfunction deriveStepFromCommits(\n commitMessages: string[],\n): string | undefined {\n const lastCommit = commitMessages[0];\n if (!lastCommit || !lastCommit.trim()) {\n return undefined;\n }\n\n const wipPattern =\n /^(?:wip|work in progress|started?|begin|draft)[:\\s]/i;\n if (wipPattern.test(lastCommit)) {\n const topic = lastCommit.replace(wipPattern, '').trim();\n if (topic) {\n const words = topic.split(/\\s+/).slice(0, 8).join(' ');\n return `Continue ${words}`;\n }\n }\n\n return undefined;\n}\n\nfunction deriveStepFromFiles(files: string[]): string | undefined {\n const primaryFile = getPrimaryFileName(files);\n\n if (files.length > 1) {\n return `Open ${primaryFile} and review ${files.length} changed files`;\n }\n\n return `Open ${primaryFile} and pick up where you left off`;\n}\n\nfunction getPrimaryFileName(files: string[]): string {\n const sourceFiles = files.filter((f) => {\n const lower = f.toLowerCase();\n return (\n !lower.includes('test') &&\n !lower.includes('spec') &&\n !lower.includes('.config') &&\n !lower.includes('package.json') &&\n !lower.includes('tsconfig')\n );\n });\n\n const target = sourceFiles.length > 0 ? sourceFiles[0] : files[0];\n const parts = target.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\nfunction mentionsFile(text: string): boolean {\n return /\\w+\\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|css|scss|html|json|yaml|yml|md|sql|sh)\\b/i.test(\n text,\n );\n}\n\nfunction inferFocusFromBranch(branch?: string): string | undefined {\n if (\n !branch ||\n branch === 'main' ||\n branch === 'master' ||\n branch === 'develop' ||\n branch === 'HEAD'\n ) {\n return undefined;\n }\n\n const prefixPattern =\n /^(?:feature|feat|fix|bugfix|hotfix|chore|refactor|docs|test|ci)\\//i;\n const isFix = /^(?:fix|bugfix|hotfix)\\//i.test(branch);\n const stripped = branch.replace(prefixPattern, '');\n\n const cleaned = stripped\n .replace(/[-_/]/g, ' ')\n .replace(/^\\d+\\s*/, '')\n .trim();\n\n if (!cleaned) {\n return undefined;\n }\n\n return isFix ? `${cleaned} fix` : cleaned;\n}\n\nfunction inferFocusFromFiles(files: string[]): string {\n if (files.length === 0) {\n return 'unknown files';\n }\n\n const dirs = files\n .map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n })\n .filter((d) => d.length > 0);\n\n if (dirs.length > 0) {\n const counts = new Map<string, number>();\n for (const dir of dirs) {\n counts.set(dir, (counts.get(dir) ?? 0) + 1);\n }\n let topDir = '';\n let topCount = 0;\n for (const [dir, count] of counts) {\n if (count > topCount) {\n topDir = dir;\n topCount = count;\n }\n }\n if (topDir) {\n return `files in ${topDir}`;\n }\n }\n\n const names = files.slice(0, 3).map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n });\n return names.join(', ');\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { getCurrentBranch, formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetMomentum(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_momentum',\n 'Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'KeepGoing is set up but no session checkpoints exist yet.',\n },\n ],\n };\n }\n\n const state = reader.getState();\n const currentBranch = getCurrentBranch(workspacePath);\n const branchChanged =\n lastSession.gitBranch &&\n currentBranch &&\n lastSession.gitBranch !== currentBranch;\n\n const lines: string[] = [\n `## Developer Momentum`,\n '',\n `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,\n `**Summary:** ${lastSession.summary || 'No summary'}`,\n `**Next step:** ${lastSession.nextStep || 'Not specified'}`,\n ];\n\n if (lastSession.blocker) {\n lines.push(`**Blocker:** ${lastSession.blocker}`);\n }\n\n if (lastSession.projectIntent) {\n lines.push(`**Project intent:** ${lastSession.projectIntent}`);\n }\n\n lines.push('');\n\n if (currentBranch) {\n lines.push(`**Current branch:** ${currentBranch}`);\n }\n if (branchChanged) {\n lines.push(\n `**Note:** Branch changed since last checkpoint (was \\`${lastSession.gitBranch}\\`, now \\`${currentBranch}\\`)`,\n );\n }\n\n if (lastSession.touchedFiles.length > 0) {\n lines.push('');\n lines.push(\n `**Files touched (${lastSession.touchedFiles.length}):** ${lastSession.touchedFiles.slice(0, 10).join(', ')}`,\n );\n if (lastSession.touchedFiles.length > 10) {\n lines.push(\n ` ...and ${lastSession.touchedFiles.length - 10} more`,\n );\n }\n }\n\n if (state?.derivedCurrentFocus) {\n lines.push('');\n lines.push(`**Derived focus:** ${state.derivedCurrentFocus}`);\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetSessionHistory(server: McpServer, reader: KeepGoingReader) {\n server.tool(\n 'get_session_history',\n 'Get recent session checkpoints. Returns a chronological list of what the developer worked on.',\n { limit: z.number().min(1).max(50).default(5).describe('Number of recent sessions to return (1-50, default 5)') },\n async ({ limit }) => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found.',\n },\n ],\n };\n }\n\n const sessions = reader.getRecentSessions(limit);\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session checkpoints found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Session History (last ${sessions.length})`,\n '',\n ];\n\n for (const session of sessions) {\n lines.push(`### ${formatRelativeTime(session.timestamp)}`);\n lines.push(`- **Summary:** ${session.summary || 'No summary'}`);\n lines.push(`- **Next step:** ${session.nextStep || 'Not specified'}`);\n if (session.blocker) {\n lines.push(`- **Blocker:** ${session.blocker}`);\n }\n if (session.gitBranch) {\n lines.push(`- **Branch:** ${session.gitBranch}`);\n }\n if (session.touchedFiles.length > 0) {\n lines.push(\n `- **Files:** ${session.touchedFiles.slice(0, 5).join(', ')}${session.touchedFiles.length > 5 ? ` (+${session.touchedFiles.length - 5} more)` : ''}`,\n );\n }\n lines.push('');\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n getCurrentBranch,\n getCommitMessagesSince,\n generateBriefing,\n} from '@keepgoingdev/shared';\n\nexport function registerGetReentryBriefing(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_reentry_briefing',\n 'Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n const recentSessions = reader.getRecentSessions(5);\n const state = reader.getState() ?? {};\n const gitBranch = getCurrentBranch(workspacePath);\n\n const sinceTimestamp = lastSession?.timestamp;\n const recentCommits = sinceTimestamp\n ? getCommitMessagesSince(workspacePath, sinceTimestamp)\n : [];\n\n const briefing = generateBriefing(\n lastSession,\n recentSessions,\n state,\n gitBranch,\n recentCommits,\n );\n\n if (!briefing) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session data available to generate a briefing.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Re-entry Briefing`,\n '',\n `**Last worked:** ${briefing.lastWorked}`,\n `**Current focus:** ${briefing.currentFocus}`,\n `**Recent activity:** ${briefing.recentActivity}`,\n `**Suggested next:** ${briefing.suggestedNext}`,\n `**Quick start:** ${briefing.smallNextStep}`,\n ];\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerResumePrompt(server: McpServer) {\n server.prompt(\n 'resume',\n 'Check developer momentum and suggest what to work on next',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I just opened this project and want to pick up where I left off.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Check my current momentum (get_momentum)',\n '2. Get a re-entry briefing (get_reentry_briefing)',\n '3. Based on the results, give me a concise summary of where I left off and suggest what to work on next.',\n '',\n 'Keep your response brief and actionable.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":"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","names":["workspacePath","workspacePath","workspacePath","server","reader","workspacePath","server","reader","server","reader","workspacePath","server","reader"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/storage.ts","../../../packages/shared/src/session.ts","../../../packages/shared/src/timeUtils.ts","../../../packages/shared/src/gitUtils.ts","../../../packages/shared/src/reentry.ts","../../../packages/shared/src/storage.ts","../src/tools/getMomentum.ts","../src/tools/getSessionHistory.ts","../src/tools/getReentryBriefing.ts","../src/tools/saveCheckpoint.ts","../src/tools/setupProject.ts","../src/prompts/resume.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { KeepGoingReader } from './storage.js';\nimport {\n formatRelativeTime,\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n} from '@keepgoingdev/shared';\nimport { registerGetMomentum } from './tools/getMomentum.js';\nimport { registerGetSessionHistory } from './tools/getSessionHistory.js';\nimport { registerGetReentryBriefing } from './tools/getReentryBriefing.js';\nimport { registerSaveCheckpoint } from './tools/saveCheckpoint.js';\nimport { registerSetupProject } from './tools/setupProject.js';\nimport { registerResumePrompt } from './prompts/resume.js';\n\n// Handle --print-momentum CLI flag: print momentum context and exit.\n// Used by the Claude Code SessionStart hook (scripts/keepgoing-hook.sh).\nif (process.argv.includes('--print-momentum')) {\n // Workspace path is the first non-flag argument after the script path\n const wsPath = process.argv.slice(2).find(a => a !== '--print-momentum') || process.cwd();\n const reader = new KeepGoingReader(wsPath);\n\n if (!reader.exists()) {\n process.exit(0);\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n process.exit(0);\n }\n\n const touchedCount = lastSession.touchedFiles?.length ?? 0;\n const lines: string[] = [];\n lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);\n if (lastSession.summary) {\n lines.push(` Summary: ${lastSession.summary}`);\n }\n if (lastSession.nextStep) {\n lines.push(` Next step: ${lastSession.nextStep}`);\n }\n if (lastSession.blocker) {\n lines.push(` Blocker: ${lastSession.blocker}`);\n }\n if (lastSession.gitBranch) {\n lines.push(` Branch: ${lastSession.gitBranch}`);\n }\n if (touchedCount > 0) {\n lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? 'unknown branch'}`);\n }\n lines.push(' Tip: Use the get_reentry_briefing tool for a full briefing');\n\n console.log(lines.join('\\n'));\n process.exit(0);\n}\n\n// Handle --save-checkpoint CLI flag: auto-save a checkpoint and exit.\n// Used by the Claude Code Stop hook as an automatic safety net.\nif (process.argv.includes('--save-checkpoint')) {\n const wsPath = process.argv.slice(2).find(a => !a.startsWith('--')) || process.cwd();\n const reader = new KeepGoingReader(wsPath);\n\n const lastSession = reader.getLastSession();\n\n // Skip if a checkpoint was written within the last 2 minutes (avoid duplicating extension checkpoints)\n if (lastSession?.timestamp) {\n const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();\n if (ageMs < 2 * 60 * 1000) {\n process.exit(0);\n }\n }\n\n const touchedFiles = getTouchedFiles(wsPath);\n const commitHashes = getCommitsSince(wsPath, lastSession?.timestamp);\n\n // Skip if there's nothing to capture\n if (touchedFiles.length === 0 && commitHashes.length === 0) {\n process.exit(0);\n }\n\n const gitBranch = getCurrentBranch(wsPath);\n const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);\n\n // Build a heuristic summary from commit messages or touched files\n let summary: string;\n if (commitMessages.length > 0) {\n summary = commitMessages.slice(0, 3).join('; ');\n } else {\n const fileNames = touchedFiles.slice(0, 5).map(f => path.basename(f));\n summary = `Worked on ${fileNames.join(', ')}`;\n if (touchedFiles.length > 5) {\n summary += ` and ${touchedFiles.length - 5} more`;\n }\n }\n\n const projectName = path.basename(wsPath);\n const checkpoint = createCheckpoint({\n summary,\n nextStep: '',\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: wsPath,\n source: 'auto',\n });\n\n const writer = new KeepGoingWriter(wsPath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n console.log(`[KeepGoing] Auto-checkpoint saved: ${summary}`);\n process.exit(0);\n}\n\n// Default: start MCP server\n// Workspace path can be passed as an argument, otherwise defaults to CWD.\n// MCP hosts (Claude Code, etc.) typically launch the server with the project root as CWD.\nconst workspacePath = process.argv[2] || process.cwd();\nconst reader = new KeepGoingReader(workspacePath);\n\nconst server = new McpServer({\n name: 'keepgoing',\n version: '0.1.0',\n});\n\n// Register tools\nregisterGetMomentum(server, reader, workspacePath);\nregisterGetSessionHistory(server, reader);\nregisterGetReentryBriefing(server, reader, workspacePath);\nregisterSaveCheckpoint(server, reader, workspacePath);\nregisterSetupProject(server, workspacePath);\n\n// Register prompts\nregisterResumePrompt(server);\n\n// Connect via stdio\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nconsole.error('KeepGoing MCP server started');\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n getRecentSessions,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n} from '@keepgoingdev/shared';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Read-only reader for .keepgoing/ directory.\n * Does not write or create any files.\n */\nexport class KeepGoingReader {\n private readonly storagePath: string;\n private readonly metaFilePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n }\n\n /** Check if .keepgoing/ directory exists. */\n exists(): boolean {\n return fs.existsSync(this.storagePath);\n }\n\n /** Read state.json, returns undefined if missing or corrupt. */\n getState(): ProjectState | undefined {\n return this.readJsonFile<ProjectState>(this.stateFilePath);\n }\n\n /** Read meta.json, returns undefined if missing or corrupt. */\n getMeta(): ProjectMeta | undefined {\n return this.readJsonFile<ProjectMeta>(this.metaFilePath);\n }\n\n /**\n * Read sessions from sessions.json.\n * Handles both formats:\n * - Flat array: SessionCheckpoint[] (from ProjectStorage)\n * - Wrapper object: ProjectSessions (from SessionStorage)\n */\n getSessions(): SessionCheckpoint[] {\n return this.parseSessions().sessions;\n }\n\n /**\n * Get the most recent session checkpoint.\n * Uses state.lastSessionId if available, falls back to last in array.\n */\n getLastSession(): SessionCheckpoint | undefined {\n const { sessions, wrapperLastSessionId } = this.parseSessions();\n if (sessions.length === 0) {\n return undefined;\n }\n\n const state = this.getState();\n if (state?.lastSessionId) {\n const found = sessions.find((s) => s.id === state.lastSessionId);\n if (found) {\n return found;\n }\n }\n\n if (wrapperLastSessionId) {\n const found = sessions.find((s) => s.id === wrapperLastSessionId);\n if (found) {\n return found;\n }\n }\n\n return sessions[sessions.length - 1];\n }\n\n /**\n * Returns the last N sessions, newest first.\n */\n getRecentSessions(count: number): SessionCheckpoint[] {\n return getRecentSessions(this.getSessions(), count);\n }\n\n /**\n * Parses sessions.json once, returning both the session list\n * and the optional lastSessionId from a ProjectSessions wrapper.\n */\n private parseSessions(): { sessions: SessionCheckpoint[]; wrapperLastSessionId?: string } {\n const raw = this.readJsonFile<ProjectSessions | SessionCheckpoint[]>(\n this.sessionsFilePath,\n );\n if (!raw) {\n return { sessions: [] };\n }\n if (Array.isArray(raw)) {\n return { sessions: raw };\n }\n return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };\n }\n\n private readJsonFile<T>(filePath: string): T | undefined {\n try {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectMeta, ProjectState } from './types';\n\n/**\n * Generates a UUID v4 unique ID for checkpoints.\n */\nexport function generateCheckpointId(): string {\n return randomUUID();\n}\n\n/**\n * Creates a SessionCheckpoint with auto-generated id and timestamp.\n * Consolidates the repeated checkpoint construction pattern.\n */\nexport function createCheckpoint(\n fields: Omit<SessionCheckpoint, 'id' | 'timestamp'>,\n): SessionCheckpoint {\n return {\n id: generateCheckpointId(),\n timestamp: new Date().toISOString(),\n ...fields,\n };\n}\n\n/**\n * Creates a default empty project sessions container.\n */\nexport function createEmptyProjectSessions(projectName: string): ProjectSessions {\n return {\n version: 1,\n project: projectName,\n sessions: [],\n lastSessionId: undefined,\n };\n}\n\n/**\n * Creates a default project metadata object.\n */\nexport function createProjectMeta(): ProjectMeta {\n const now = new Date().toISOString();\n return {\n projectId: randomUUID(),\n createdAt: now,\n lastUpdated: now,\n };\n}\n\n/**\n * Creates a default empty project state object.\n */\nexport function createEmptyProjectState(): ProjectState {\n return {};\n}\n","/**\n * Formats a timestamp as a human-readable relative time string.\n * Examples: \"just now\", \"5 minutes ago\", \"3 hours ago\", \"2 days ago\", \"1 week ago\"\n *\n * Note: Month and year calculations use approximations (30 days/month, 365 days/year)\n * for simplicity. These approximations are acceptable for the \"human-readable\" purpose.\n */\nexport function formatRelativeTime(timestamp: string): string {\n const now = Date.now();\n const then = new Date(timestamp).getTime();\n const diffMs = now - then;\n\n // Handle invalid dates\n if (isNaN(diffMs)) {\n return 'unknown time';\n }\n\n // Future dates\n if (diffMs < 0) {\n return 'in the future';\n }\n\n const seconds = Math.floor(diffMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30); // Approximation for human readability\n const years = Math.floor(days / 365); // Approximation, doesn't account for leap years\n\n if (seconds < 10) {\n return 'just now';\n } else if (seconds < 60) {\n return `${seconds} seconds ago`;\n } else if (minutes < 60) {\n return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;\n } else if (hours < 24) {\n return hours === 1 ? '1 hour ago' : `${hours} hours ago`;\n } else if (days < 7) {\n return days === 1 ? '1 day ago' : `${days} days ago`;\n } else if (weeks < 4) {\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n } else if (months < 12) {\n return months === 1 ? '1 month ago' : `${months} months ago`;\n } else {\n return years === 1 ? '1 year ago' : `${years} years ago`;\n }\n}\n","import { execFileSync, execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Git utilities for extracting context from the workspace.\n * These are intentionally decoupled from VSCode APIs.\n * All sync functions use execFileSync (no shell injection).\n */\n\n/**\n * Returns the current git branch name, or undefined if not in a git repo.\n */\nexport function getCurrentBranch(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs git log with the given format since a timestamp.\n * Shared implementation for commit hash and message retrieval.\n */\nfunction getGitLogSince(workspacePath: string, format: string, sinceTimestamp?: string): string[] {\n try {\n const since = sinceTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const result = execFileSync(\n 'git',\n ['log', `--since=${since}`, `--format=${format}`],\n {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n },\n );\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .filter((line: string) => line.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns a list of commit hashes since a given ISO timestamp.\n * If no timestamp is provided, returns recent commits (last 24 hours).\n */\nexport function getCommitsSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%H', sinceTimestamp);\n}\n\n/**\n * Returns a list of commit subject lines since a given ISO timestamp.\n * If no timestamp is provided, returns commit messages from the last 24 hours.\n * Results are in reverse chronological order (newest first), matching git log default.\n */\nexport function getCommitMessagesSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%s', sinceTimestamp);\n}\n\n/**\n * Returns the current HEAD commit hash, or undefined if not in a git repo.\n */\nexport function getHeadCommitHash(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getCurrentBranch. Avoids blocking the event loop.\n */\nexport async function getCurrentBranchAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getHeadCommitHash. Avoids blocking the event loop.\n */\nexport async function getHeadCommitHashAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns a list of files that have been modified (tracked and untracked)\n * in the workspace. This is best-effort and may not capture all changes.\n */\nexport function getTouchedFiles(workspacePath: string): string[] {\n try {\n const result = execFileSync('git', ['status', '--porcelain'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .map((line) => line.substring(3).trim())\n .filter((file) => file.length > 0 && !file.endsWith('/'));\n } catch {\n return [];\n }\n}\n","import type {\n SessionCheckpoint,\n ProjectState,\n ReEntryBriefing,\n} from './types';\nimport { formatRelativeTime } from './timeUtils';\n\nconst RECENT_SESSION_COUNT = 5;\n\n/**\n * Generates a synthesized re-entry briefing from stored project data.\n *\n * Uses heuristic rules (no AI) to infer focus and suggest next steps.\n * Designed to be replaced by an LLM-backed implementation in the future\n * while keeping the same input/output contract.\n */\nexport function generateBriefing(\n lastSession: SessionCheckpoint | undefined,\n recentSessions: SessionCheckpoint[],\n projectState: ProjectState,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): ReEntryBriefing | undefined {\n if (!lastSession) {\n return undefined;\n }\n\n return {\n lastWorked: formatRelativeTime(lastSession.timestamp),\n currentFocus: buildCurrentFocus(lastSession, projectState, gitBranch),\n recentActivity: buildRecentActivity(\n lastSession,\n recentSessions,\n recentCommitMessages,\n ),\n suggestedNext: buildSuggestedNext(lastSession, gitBranch),\n smallNextStep: buildSmallNextStep(\n lastSession,\n gitBranch,\n recentCommitMessages,\n ),\n };\n}\n\n/**\n * Returns the most recent N sessions in newest-first order.\n */\nexport function getRecentSessions(\n allSessions: SessionCheckpoint[],\n count: number = RECENT_SESSION_COUNT,\n): SessionCheckpoint[] {\n return allSessions.slice(-count).reverse();\n}\n\nfunction buildCurrentFocus(\n lastSession: SessionCheckpoint,\n projectState: ProjectState,\n gitBranch?: string,\n): string {\n if (projectState.derivedCurrentFocus) {\n return projectState.derivedCurrentFocus;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return branchFocus;\n }\n\n if (lastSession.summary) {\n return lastSession.summary;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return inferFocusFromFiles(lastSession.touchedFiles);\n }\n\n return 'Unknown, save a checkpoint to set context';\n}\n\nfunction buildRecentActivity(\n lastSession: SessionCheckpoint,\n recentSessions: SessionCheckpoint[],\n recentCommitMessages?: string[],\n): string {\n const parts: string[] = [];\n\n const sessionCount = recentSessions.length;\n if (sessionCount > 1) {\n parts.push(`${sessionCount} recent sessions`);\n } else if (sessionCount === 1) {\n parts.push('1 recent session');\n }\n\n if (lastSession.summary) {\n parts.push(`Last: ${lastSession.summary}`);\n }\n\n if (lastSession.touchedFiles.length > 0) {\n parts.push(`${lastSession.touchedFiles.length} files touched`);\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n parts.push(`${recentCommitMessages.length} recent commits`);\n }\n\n return parts.length > 0 ? parts.join('. ') : 'No recent activity recorded';\n}\n\nfunction buildSuggestedNext(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n): string {\n if (lastSession.nextStep) {\n return lastSession.nextStep;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Continue working on ${branchFocus}`;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return `Continue working on ${inferFocusFromFiles(lastSession.touchedFiles)}`;\n }\n\n return 'Save a checkpoint to track your next step';\n}\n\nfunction buildSmallNextStep(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): string {\n const fallback = 'Review last changed files to resume flow';\n\n if (lastSession.nextStep) {\n const distilled = distillToSmallStep(\n lastSession.nextStep,\n lastSession.touchedFiles,\n );\n if (distilled) {\n return distilled;\n }\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n const commitStep = deriveStepFromCommits(recentCommitMessages);\n if (commitStep) {\n return commitStep;\n }\n }\n\n if (lastSession.touchedFiles.length > 0) {\n const fileStep = deriveStepFromFiles(lastSession.touchedFiles);\n if (fileStep) {\n return fileStep;\n }\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Check git status for ${branchFocus}`;\n }\n\n return fallback;\n}\n\nfunction distillToSmallStep(\n nextStep: string,\n touchedFiles: string[],\n): string | undefined {\n if (!nextStep.trim()) {\n return undefined;\n }\n\n const words = nextStep.trim().split(/\\s+/);\n if (words.length <= 12) {\n if (touchedFiles.length > 0 && !mentionsFile(nextStep)) {\n const primaryFile = getPrimaryFileName(touchedFiles);\n const enhanced = `${nextStep.trim()} in ${primaryFile}`;\n if (enhanced.split(/\\s+/).length <= 12) {\n return enhanced;\n }\n }\n return nextStep.trim();\n }\n\n return words.slice(0, 12).join(' ');\n}\n\nfunction deriveStepFromCommits(\n commitMessages: string[],\n): string | undefined {\n const lastCommit = commitMessages[0];\n if (!lastCommit || !lastCommit.trim()) {\n return undefined;\n }\n\n const wipPattern =\n /^(?:wip|work in progress|started?|begin|draft)[:\\s]/i;\n if (wipPattern.test(lastCommit)) {\n const topic = lastCommit.replace(wipPattern, '').trim();\n if (topic) {\n const words = topic.split(/\\s+/).slice(0, 8).join(' ');\n return `Continue ${words}`;\n }\n }\n\n return undefined;\n}\n\nfunction deriveStepFromFiles(files: string[]): string | undefined {\n const primaryFile = getPrimaryFileName(files);\n\n if (files.length > 1) {\n return `Open ${primaryFile} and review ${files.length} changed files`;\n }\n\n return `Open ${primaryFile} and pick up where you left off`;\n}\n\nfunction getPrimaryFileName(files: string[]): string {\n const sourceFiles = files.filter((f) => {\n const lower = f.toLowerCase();\n return (\n !lower.includes('test') &&\n !lower.includes('spec') &&\n !lower.includes('.config') &&\n !lower.includes('package.json') &&\n !lower.includes('tsconfig')\n );\n });\n\n const target = sourceFiles.length > 0 ? sourceFiles[0] : files[0];\n const parts = target.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\nfunction mentionsFile(text: string): boolean {\n return /\\w+\\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|css|scss|html|json|yaml|yml|md|sql|sh)\\b/i.test(\n text,\n );\n}\n\nfunction inferFocusFromBranch(branch?: string): string | undefined {\n if (\n !branch ||\n branch === 'main' ||\n branch === 'master' ||\n branch === 'develop' ||\n branch === 'HEAD'\n ) {\n return undefined;\n }\n\n const prefixPattern =\n /^(?:feature|feat|fix|bugfix|hotfix|chore|refactor|docs|test|ci)\\//i;\n const isFix = /^(?:fix|bugfix|hotfix)\\//i.test(branch);\n const stripped = branch.replace(prefixPattern, '');\n\n const cleaned = stripped\n .replace(/[-_/]/g, ' ')\n .replace(/^\\d+\\s*/, '')\n .trim();\n\n if (!cleaned) {\n return undefined;\n }\n\n return isFix ? `${cleaned} fix` : cleaned;\n}\n\nfunction inferFocusFromFiles(files: string[]): string {\n if (files.length === 0) {\n return 'unknown files';\n }\n\n const dirs = files\n .map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n })\n .filter((d) => d.length > 0);\n\n if (dirs.length > 0) {\n const counts = new Map<string, number>();\n for (const dir of dirs) {\n counts.set(dir, (counts.get(dir) ?? 0) + 1);\n }\n let topDir = '';\n let topCount = 0;\n for (const [dir, count] of counts) {\n if (count > topCount) {\n topDir = dir;\n topCount = count;\n }\n }\n if (topDir) {\n return `files in ${topDir}`;\n }\n }\n\n const names = files.slice(0, 3).map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n });\n return names.join(', ');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectState, ProjectMeta } from './types';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Write layer for .keepgoing/ directory.\n * Creates files if they don't exist yet.\n */\nexport class KeepGoingWriter {\n private readonly storagePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n private readonly metaFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n }\n\n ensureDir(): void {\n if (!fs.existsSync(this.storagePath)) {\n fs.mkdirSync(this.storagePath, { recursive: true });\n }\n }\n\n saveCheckpoint(checkpoint: SessionCheckpoint, projectName: string): void {\n this.ensureDir();\n\n // Read existing sessions\n let sessionsData: ProjectSessions;\n try {\n if (fs.existsSync(this.sessionsFilePath)) {\n const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, 'utf-8')) as\n | ProjectSessions\n | SessionCheckpoint[];\n if (Array.isArray(raw)) {\n sessionsData = { version: 1, project: projectName, sessions: raw };\n } else {\n sessionsData = raw;\n }\n } else {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n } catch {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n\n sessionsData.sessions.push(checkpoint);\n sessionsData.lastSessionId = checkpoint.id;\n\n // Prune old sessions to keep the file bounded\n const MAX_SESSIONS = 200;\n if (sessionsData.sessions.length > MAX_SESSIONS) {\n sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);\n }\n\n fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), 'utf-8');\n\n // Update state.json\n const state: ProjectState = {\n lastSessionId: checkpoint.id,\n lastKnownBranch: checkpoint.gitBranch,\n lastActivityAt: checkpoint.timestamp,\n };\n fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), 'utf-8');\n\n // Update meta.json\n let meta: ProjectMeta;\n try {\n if (fs.existsSync(this.metaFilePath)) {\n meta = JSON.parse(fs.readFileSync(this.metaFilePath, 'utf-8')) as ProjectMeta;\n meta.lastUpdated = checkpoint.timestamp;\n } else {\n meta = {\n projectId: randomUUID(),\n createdAt: checkpoint.timestamp,\n lastUpdated: checkpoint.timestamp,\n };\n }\n } catch {\n meta = {\n projectId: randomUUID(),\n createdAt: checkpoint.timestamp,\n lastUpdated: checkpoint.timestamp,\n };\n }\n fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), 'utf-8');\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { getCurrentBranch, formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetMomentum(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_momentum',\n 'Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'KeepGoing is set up but no session checkpoints exist yet.',\n },\n ],\n };\n }\n\n const state = reader.getState();\n const currentBranch = getCurrentBranch(workspacePath);\n const branchChanged =\n lastSession.gitBranch &&\n currentBranch &&\n lastSession.gitBranch !== currentBranch;\n\n const lines: string[] = [\n `## Developer Momentum`,\n '',\n `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,\n `**Summary:** ${lastSession.summary || 'No summary'}`,\n `**Next step:** ${lastSession.nextStep || 'Not specified'}`,\n ];\n\n if (lastSession.blocker) {\n lines.push(`**Blocker:** ${lastSession.blocker}`);\n }\n\n if (lastSession.projectIntent) {\n lines.push(`**Project intent:** ${lastSession.projectIntent}`);\n }\n\n lines.push('');\n\n if (currentBranch) {\n lines.push(`**Current branch:** ${currentBranch}`);\n }\n if (branchChanged) {\n lines.push(\n `**Note:** Branch changed since last checkpoint (was \\`${lastSession.gitBranch}\\`, now \\`${currentBranch}\\`)`,\n );\n }\n\n if (lastSession.touchedFiles.length > 0) {\n lines.push('');\n lines.push(\n `**Files touched (${lastSession.touchedFiles.length}):** ${lastSession.touchedFiles.slice(0, 10).join(', ')}`,\n );\n if (lastSession.touchedFiles.length > 10) {\n lines.push(\n ` ...and ${lastSession.touchedFiles.length - 10} more`,\n );\n }\n }\n\n if (state?.derivedCurrentFocus) {\n lines.push('');\n lines.push(`**Derived focus:** ${state.derivedCurrentFocus}`);\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetSessionHistory(server: McpServer, reader: KeepGoingReader) {\n server.tool(\n 'get_session_history',\n 'Get recent session checkpoints. Returns a chronological list of what the developer worked on.',\n { limit: z.number().min(1).max(50).default(5).describe('Number of recent sessions to return (1-50, default 5)') },\n async ({ limit }) => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found.',\n },\n ],\n };\n }\n\n const sessions = reader.getRecentSessions(limit);\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session checkpoints found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Session History (last ${sessions.length})`,\n '',\n ];\n\n for (const session of sessions) {\n lines.push(`### ${formatRelativeTime(session.timestamp)}`);\n lines.push(`- **Summary:** ${session.summary || 'No summary'}`);\n lines.push(`- **Next step:** ${session.nextStep || 'Not specified'}`);\n if (session.blocker) {\n lines.push(`- **Blocker:** ${session.blocker}`);\n }\n if (session.gitBranch) {\n lines.push(`- **Branch:** ${session.gitBranch}`);\n }\n if (session.touchedFiles.length > 0) {\n lines.push(\n `- **Files:** ${session.touchedFiles.slice(0, 5).join(', ')}${session.touchedFiles.length > 5 ? ` (+${session.touchedFiles.length - 5} more)` : ''}`,\n );\n }\n lines.push('');\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n getCurrentBranch,\n getCommitMessagesSince,\n generateBriefing,\n} from '@keepgoingdev/shared';\n\nexport function registerGetReentryBriefing(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_reentry_briefing',\n 'Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n const recentSessions = reader.getRecentSessions(5);\n const state = reader.getState() ?? {};\n const gitBranch = getCurrentBranch(workspacePath);\n\n const sinceTimestamp = lastSession?.timestamp;\n const recentCommits = sinceTimestamp\n ? getCommitMessagesSince(workspacePath, sinceTimestamp)\n : [];\n\n const briefing = generateBriefing(\n lastSession,\n recentSessions,\n state,\n gitBranch,\n recentCommits,\n );\n\n if (!briefing) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session data available to generate a briefing.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Re-entry Briefing`,\n '',\n `**Last worked:** ${briefing.lastWorked}`,\n `**Current focus:** ${briefing.currentFocus}`,\n `**Recent activity:** ${briefing.recentActivity}`,\n `**Suggested next:** ${briefing.suggestedNext}`,\n `**Quick start:** ${briefing.smallNextStep}`,\n ];\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n} from '@keepgoingdev/shared';\n\nexport function registerSaveCheckpoint(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'save_checkpoint',\n 'Save a development checkpoint. Call this after completing a task or meaningful piece of work, not just at end of session. Each checkpoint helps the next session (or developer) pick up exactly where you left off.',\n {\n summary: z.string().describe('What was accomplished in this session'),\n nextStep: z.string().optional().describe('What to do next'),\n blocker: z.string().optional().describe('Any blocker preventing progress'),\n },\n async ({ summary, nextStep, blocker }) => {\n const lastSession = reader.getLastSession();\n\n const gitBranch = getCurrentBranch(workspacePath);\n const touchedFiles = getTouchedFiles(workspacePath);\n const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);\n const projectName = path.basename(workspacePath);\n\n const checkpoint = createCheckpoint({\n summary,\n nextStep: nextStep || '',\n blocker,\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: workspacePath,\n source: 'manual',\n });\n\n const writer = new KeepGoingWriter(workspacePath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n const lines: string[] = [\n `Checkpoint saved.`,\n `- **ID:** ${checkpoint.id}`,\n `- **Branch:** ${gitBranch || 'unknown'}`,\n `- **Files tracked:** ${touchedFiles.length}`,\n `- **Commits captured:** ${commitHashes.length}`,\n ];\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nconst KEEPGOING_MARKER = '@keepgoingdev/mcp-server';\n\nconst SESSION_START_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --print-momentum',\n },\n ],\n};\n\nconst STOP_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --save-checkpoint',\n },\n ],\n};\n\nconst CLAUDE_MD_SECTION = `\n## KeepGoing\n\nAfter completing a task or meaningful piece of work, call the \\`save_checkpoint\\` MCP tool with:\n- \\`summary\\`: What you accomplished\n- \\`nextStep\\`: What should be done next\n- \\`blocker\\`: Any blocker (if applicable)\n`;\n\nfunction hasKeepGoingHook(hookEntries: unknown[]): boolean {\n return hookEntries.some((entry: any) =>\n entry?.hooks?.some((h: any) => typeof h?.command === 'string' && h.command.includes(KEEPGOING_MARKER)),\n );\n}\n\nexport function registerSetupProject(server: McpServer, workspacePath: string) {\n server.tool(\n 'setup_project',\n 'Set up KeepGoing in the current project. Adds session hooks to .claude/settings.json and CLAUDE.md instructions so checkpoints are saved automatically.',\n {\n sessionHooks: z.boolean().optional().default(true).describe('Add session hooks to .claude/settings.json'),\n claudeMd: z.boolean().optional().default(true).describe('Add KeepGoing instructions to CLAUDE.md'),\n },\n async ({ sessionHooks, claudeMd }) => {\n const results: string[] = [];\n\n // --- Session hooks ---\n if (sessionHooks) {\n const claudeDir = path.join(workspacePath, '.claude');\n const settingsPath = path.join(claudeDir, 'settings.json');\n\n let settings: any = {};\n if (fs.existsSync(settingsPath)) {\n settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));\n }\n\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n let hooksChanged = false;\n\n // SessionStart\n if (!Array.isArray(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart = [];\n }\n if (!hasKeepGoingHook(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart.push(SESSION_START_HOOK);\n hooksChanged = true;\n }\n\n // Stop\n if (!Array.isArray(settings.hooks.Stop)) {\n settings.hooks.Stop = [];\n }\n if (!hasKeepGoingHook(settings.hooks.Stop)) {\n settings.hooks.Stop.push(STOP_HOOK);\n hooksChanged = true;\n }\n\n if (hooksChanged) {\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true });\n }\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n results.push('**Session hooks:** Added to `.claude/settings.json`');\n } else {\n results.push('**Session hooks:** Already present, skipped');\n }\n }\n\n // --- CLAUDE.md ---\n if (claudeMd) {\n const claudeMdPath = path.join(workspacePath, 'CLAUDE.md');\n\n let existing = '';\n if (fs.existsSync(claudeMdPath)) {\n existing = fs.readFileSync(claudeMdPath, 'utf-8');\n }\n\n if (existing.includes('## KeepGoing')) {\n results.push('**CLAUDE.md:** KeepGoing section already present, skipped');\n } else {\n const updated = existing + CLAUDE_MD_SECTION;\n fs.writeFileSync(claudeMdPath, updated);\n results.push('**CLAUDE.md:** Added KeepGoing section');\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: results.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerResumePrompt(server: McpServer) {\n server.prompt(\n 'resume',\n 'Check developer momentum and suggest what to work on next',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I just opened this project and want to pick up where I left off.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Check my current momentum (get_momentum)',\n '2. Get a re-entry briefing (get_reentry_briefing)',\n '3. Based on the results, give me a concise summary of where I left off and suggest what to work on next.',\n '',\n 'Keep your response brief and actionable.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":"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","names":["path","fs","path","workspacePath","workspacePath","randomUUID","workspacePath","STORAGE_DIR","META_FILE","SESSIONS_FILE","STATE_FILE","workspacePath","path","fs","server","reader","workspacePath","server","reader","server","reader","workspacePath","path","z","server","reader","workspacePath","z","path","fs","path","z","server","workspacePath","server","reader","path"]}
|