@keepgoingdev/mcp-server 0.1.0 → 0.1.2

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 CHANGED
@@ -1,64 +1,646 @@
1
1
  #!/usr/bin/env node
2
- import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { KeepGoingReader } from './storage.js';
5
- import { formatRelativeTime } from '@keepgoingdev/shared';
6
- import { registerGetMomentum } from './tools/getMomentum.js';
7
- import { registerGetSessionHistory } from './tools/getSessionHistory.js';
8
- import { registerGetReentryBriefing } from './tools/getReentryBriefing.js';
9
- import { registerResumePrompt } from './prompts/resume.js';
10
- // Handle --print-momentum CLI flag: print momentum context and exit.
11
- // Used by the Claude Code SessionStart hook (scripts/keepgoing-hook.sh).
12
- if (process.argv.includes('--print-momentum')) {
13
- // Workspace path is the first non-flag argument after the script path
14
- const wsPath = process.argv.slice(2).find(a => a !== '--print-momentum') || process.cwd();
15
- const reader = new KeepGoingReader(wsPath);
16
- if (!reader.exists()) {
17
- process.exit(0);
18
- }
19
- const lastSession = reader.getLastSession();
20
- if (!lastSession) {
21
- process.exit(0);
22
- }
23
- const touchedCount = lastSession.touchedFiles?.length ?? 0;
24
- const lines = [];
25
- lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);
26
- if (lastSession.summary) {
27
- lines.push(` Summary: ${lastSession.summary}`);
28
- }
29
- if (lastSession.nextStep) {
30
- lines.push(` Next step: ${lastSession.nextStep}`);
31
- }
32
- if (lastSession.blocker) {
33
- lines.push(` Blocker: ${lastSession.blocker}`);
34
- }
35
- if (lastSession.gitBranch) {
36
- lines.push(` Branch: ${lastSession.gitBranch}`);
37
- }
38
- if (touchedCount > 0) {
39
- lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? 'unknown branch'}`);
40
- }
41
- lines.push(' Tip: Use the get_reentry_briefing tool for a full briefing');
42
- console.log(lines.join('\n'));
2
+
3
+ // src/index.ts
4
+ import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+
7
+ // src/storage.ts
8
+ import fs from "fs";
9
+ import path from "path";
10
+
11
+ // ../../packages/shared/src/timeUtils.ts
12
+ function formatRelativeTime(timestamp) {
13
+ const now = Date.now();
14
+ const then = new Date(timestamp).getTime();
15
+ const diffMs = now - then;
16
+ if (isNaN(diffMs)) {
17
+ return "unknown time";
18
+ }
19
+ if (diffMs < 0) {
20
+ return "in the future";
21
+ }
22
+ const seconds = Math.floor(diffMs / 1e3);
23
+ const minutes = Math.floor(seconds / 60);
24
+ const hours = Math.floor(minutes / 60);
25
+ const days = Math.floor(hours / 24);
26
+ const weeks = Math.floor(days / 7);
27
+ const months = Math.floor(days / 30);
28
+ const years = Math.floor(days / 365);
29
+ if (seconds < 10) {
30
+ return "just now";
31
+ } else if (seconds < 60) {
32
+ return `${seconds} seconds ago`;
33
+ } else if (minutes < 60) {
34
+ return minutes === 1 ? "1 minute ago" : `${minutes} minutes ago`;
35
+ } else if (hours < 24) {
36
+ return hours === 1 ? "1 hour ago" : `${hours} hours ago`;
37
+ } else if (days < 7) {
38
+ return days === 1 ? "1 day ago" : `${days} days ago`;
39
+ } else if (weeks < 4) {
40
+ return weeks === 1 ? "1 week ago" : `${weeks} weeks ago`;
41
+ } else if (months < 12) {
42
+ return months === 1 ? "1 month ago" : `${months} months ago`;
43
+ } else {
44
+ return years === 1 ? "1 year ago" : `${years} years ago`;
45
+ }
46
+ }
47
+
48
+ // ../../packages/shared/src/gitUtils.ts
49
+ import { execFileSync, execFile } from "child_process";
50
+ import { promisify } from "util";
51
+ var execFileAsync = promisify(execFile);
52
+ function getCurrentBranch(workspacePath2) {
53
+ try {
54
+ const result = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
55
+ cwd: workspacePath2,
56
+ encoding: "utf-8",
57
+ timeout: 5e3
58
+ });
59
+ return result.trim() || void 0;
60
+ } catch {
61
+ return void 0;
62
+ }
63
+ }
64
+ function getGitLogSince(workspacePath2, format, sinceTimestamp) {
65
+ try {
66
+ const since = sinceTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1e3).toISOString();
67
+ const result = execFileSync(
68
+ "git",
69
+ ["log", `--since=${since}`, `--format=${format}`],
70
+ {
71
+ cwd: workspacePath2,
72
+ encoding: "utf-8",
73
+ timeout: 5e3
74
+ }
75
+ );
76
+ if (!result.trim()) {
77
+ return [];
78
+ }
79
+ return result.trim().split("\n").filter((line) => line.length > 0);
80
+ } catch {
81
+ return [];
82
+ }
83
+ }
84
+ function getCommitMessagesSince(workspacePath2, sinceTimestamp) {
85
+ return getGitLogSince(workspacePath2, "%s", sinceTimestamp);
86
+ }
87
+
88
+ // ../../packages/shared/src/reentry.ts
89
+ var RECENT_SESSION_COUNT = 5;
90
+ function generateBriefing(lastSession, recentSessions, projectState, gitBranch, recentCommitMessages) {
91
+ if (!lastSession) {
92
+ return void 0;
93
+ }
94
+ return {
95
+ lastWorked: formatRelativeTime(lastSession.timestamp),
96
+ currentFocus: buildCurrentFocus(lastSession, projectState, gitBranch),
97
+ recentActivity: buildRecentActivity(
98
+ lastSession,
99
+ recentSessions,
100
+ recentCommitMessages
101
+ ),
102
+ suggestedNext: buildSuggestedNext(lastSession, gitBranch),
103
+ smallNextStep: buildSmallNextStep(
104
+ lastSession,
105
+ gitBranch,
106
+ recentCommitMessages
107
+ )
108
+ };
109
+ }
110
+ function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
111
+ return allSessions.slice(-count).reverse();
112
+ }
113
+ function buildCurrentFocus(lastSession, projectState, gitBranch) {
114
+ if (projectState.derivedCurrentFocus) {
115
+ return projectState.derivedCurrentFocus;
116
+ }
117
+ const branchFocus = inferFocusFromBranch(gitBranch);
118
+ if (branchFocus) {
119
+ return branchFocus;
120
+ }
121
+ if (lastSession.summary) {
122
+ return lastSession.summary;
123
+ }
124
+ if (lastSession.touchedFiles.length > 0) {
125
+ return inferFocusFromFiles(lastSession.touchedFiles);
126
+ }
127
+ return "Unknown, save a checkpoint to set context";
128
+ }
129
+ function buildRecentActivity(lastSession, recentSessions, recentCommitMessages) {
130
+ const parts = [];
131
+ const sessionCount = recentSessions.length;
132
+ if (sessionCount > 1) {
133
+ parts.push(`${sessionCount} recent sessions`);
134
+ } else if (sessionCount === 1) {
135
+ parts.push("1 recent session");
136
+ }
137
+ if (lastSession.summary) {
138
+ parts.push(`Last: ${lastSession.summary}`);
139
+ }
140
+ if (lastSession.touchedFiles.length > 0) {
141
+ parts.push(`${lastSession.touchedFiles.length} files touched`);
142
+ }
143
+ if (recentCommitMessages && recentCommitMessages.length > 0) {
144
+ parts.push(`${recentCommitMessages.length} recent commits`);
145
+ }
146
+ return parts.length > 0 ? parts.join(". ") : "No recent activity recorded";
147
+ }
148
+ function buildSuggestedNext(lastSession, gitBranch) {
149
+ if (lastSession.nextStep) {
150
+ return lastSession.nextStep;
151
+ }
152
+ const branchFocus = inferFocusFromBranch(gitBranch);
153
+ if (branchFocus) {
154
+ return `Continue working on ${branchFocus}`;
155
+ }
156
+ if (lastSession.touchedFiles.length > 0) {
157
+ return `Continue working on ${inferFocusFromFiles(lastSession.touchedFiles)}`;
158
+ }
159
+ return "Save a checkpoint to track your next step";
160
+ }
161
+ function buildSmallNextStep(lastSession, gitBranch, recentCommitMessages) {
162
+ const fallback = "Review last changed files to resume flow";
163
+ if (lastSession.nextStep) {
164
+ const distilled = distillToSmallStep(
165
+ lastSession.nextStep,
166
+ lastSession.touchedFiles
167
+ );
168
+ if (distilled) {
169
+ return distilled;
170
+ }
171
+ }
172
+ if (recentCommitMessages && recentCommitMessages.length > 0) {
173
+ const commitStep = deriveStepFromCommits(recentCommitMessages);
174
+ if (commitStep) {
175
+ return commitStep;
176
+ }
177
+ }
178
+ if (lastSession.touchedFiles.length > 0) {
179
+ const fileStep = deriveStepFromFiles(lastSession.touchedFiles);
180
+ if (fileStep) {
181
+ return fileStep;
182
+ }
183
+ }
184
+ const branchFocus = inferFocusFromBranch(gitBranch);
185
+ if (branchFocus) {
186
+ return `Check git status for ${branchFocus}`;
187
+ }
188
+ return fallback;
189
+ }
190
+ function distillToSmallStep(nextStep, touchedFiles) {
191
+ if (!nextStep.trim()) {
192
+ return void 0;
193
+ }
194
+ const words = nextStep.trim().split(/\s+/);
195
+ if (words.length <= 12) {
196
+ if (touchedFiles.length > 0 && !mentionsFile(nextStep)) {
197
+ const primaryFile = getPrimaryFileName(touchedFiles);
198
+ const enhanced = `${nextStep.trim()} in ${primaryFile}`;
199
+ if (enhanced.split(/\s+/).length <= 12) {
200
+ return enhanced;
201
+ }
202
+ }
203
+ return nextStep.trim();
204
+ }
205
+ return words.slice(0, 12).join(" ");
206
+ }
207
+ function deriveStepFromCommits(commitMessages) {
208
+ const lastCommit = commitMessages[0];
209
+ if (!lastCommit || !lastCommit.trim()) {
210
+ return void 0;
211
+ }
212
+ const wipPattern = /^(?:wip|work in progress|started?|begin|draft)[:\s]/i;
213
+ if (wipPattern.test(lastCommit)) {
214
+ const topic = lastCommit.replace(wipPattern, "").trim();
215
+ if (topic) {
216
+ const words = topic.split(/\s+/).slice(0, 8).join(" ");
217
+ return `Continue ${words}`;
218
+ }
219
+ }
220
+ return void 0;
221
+ }
222
+ function deriveStepFromFiles(files) {
223
+ const primaryFile = getPrimaryFileName(files);
224
+ if (files.length > 1) {
225
+ return `Open ${primaryFile} and review ${files.length} changed files`;
226
+ }
227
+ return `Open ${primaryFile} and pick up where you left off`;
228
+ }
229
+ function getPrimaryFileName(files) {
230
+ const sourceFiles = files.filter((f) => {
231
+ const lower = f.toLowerCase();
232
+ return !lower.includes("test") && !lower.includes("spec") && !lower.includes(".config") && !lower.includes("package.json") && !lower.includes("tsconfig");
233
+ });
234
+ const target = sourceFiles.length > 0 ? sourceFiles[0] : files[0];
235
+ const parts = target.replace(/\\/g, "/").split("/");
236
+ return parts[parts.length - 1];
237
+ }
238
+ function mentionsFile(text) {
239
+ return /\w+\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|css|scss|html|json|yaml|yml|md|sql|sh)\b/i.test(
240
+ text
241
+ );
242
+ }
243
+ function inferFocusFromBranch(branch) {
244
+ if (!branch || branch === "main" || branch === "master" || branch === "develop" || branch === "HEAD") {
245
+ return void 0;
246
+ }
247
+ const prefixPattern = /^(?:feature|feat|fix|bugfix|hotfix|chore|refactor|docs|test|ci)\//i;
248
+ const isFix = /^(?:fix|bugfix|hotfix)\//i.test(branch);
249
+ const stripped = branch.replace(prefixPattern, "");
250
+ const cleaned = stripped.replace(/[-_/]/g, " ").replace(/^\d+\s*/, "").trim();
251
+ if (!cleaned) {
252
+ return void 0;
253
+ }
254
+ return isFix ? `${cleaned} fix` : cleaned;
255
+ }
256
+ function inferFocusFromFiles(files) {
257
+ if (files.length === 0) {
258
+ return "unknown files";
259
+ }
260
+ const dirs = files.map((f) => {
261
+ const parts = f.replace(/\\/g, "/").split("/");
262
+ return parts.length > 1 ? parts.slice(0, -1).join("/") : "";
263
+ }).filter((d) => d.length > 0);
264
+ if (dirs.length > 0) {
265
+ const counts = /* @__PURE__ */ new Map();
266
+ for (const dir of dirs) {
267
+ counts.set(dir, (counts.get(dir) ?? 0) + 1);
268
+ }
269
+ let topDir = "";
270
+ let topCount = 0;
271
+ for (const [dir, count] of counts) {
272
+ if (count > topCount) {
273
+ topDir = dir;
274
+ topCount = count;
275
+ }
276
+ }
277
+ if (topDir) {
278
+ return `files in ${topDir}`;
279
+ }
280
+ }
281
+ const names = files.slice(0, 3).map((f) => {
282
+ const parts = f.replace(/\\/g, "/").split("/");
283
+ return parts[parts.length - 1];
284
+ });
285
+ return names.join(", ");
286
+ }
287
+
288
+ // src/storage.ts
289
+ var STORAGE_DIR = ".keepgoing";
290
+ var META_FILE = "meta.json";
291
+ var SESSIONS_FILE = "sessions.json";
292
+ var STATE_FILE = "state.json";
293
+ var KeepGoingReader = class {
294
+ storagePath;
295
+ metaFilePath;
296
+ sessionsFilePath;
297
+ stateFilePath;
298
+ constructor(workspacePath2) {
299
+ this.storagePath = path.join(workspacePath2, STORAGE_DIR);
300
+ this.metaFilePath = path.join(this.storagePath, META_FILE);
301
+ this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);
302
+ this.stateFilePath = path.join(this.storagePath, STATE_FILE);
303
+ }
304
+ /** Check if .keepgoing/ directory exists. */
305
+ exists() {
306
+ return fs.existsSync(this.storagePath);
307
+ }
308
+ /** Read state.json, returns undefined if missing or corrupt. */
309
+ getState() {
310
+ return this.readJsonFile(this.stateFilePath);
311
+ }
312
+ /** Read meta.json, returns undefined if missing or corrupt. */
313
+ getMeta() {
314
+ return this.readJsonFile(this.metaFilePath);
315
+ }
316
+ /**
317
+ * Read sessions from sessions.json.
318
+ * Handles both formats:
319
+ * - Flat array: SessionCheckpoint[] (from ProjectStorage)
320
+ * - Wrapper object: ProjectSessions (from SessionStorage)
321
+ */
322
+ getSessions() {
323
+ return this.parseSessions().sessions;
324
+ }
325
+ /**
326
+ * Get the most recent session checkpoint.
327
+ * Uses state.lastSessionId if available, falls back to last in array.
328
+ */
329
+ getLastSession() {
330
+ const { sessions, wrapperLastSessionId } = this.parseSessions();
331
+ if (sessions.length === 0) {
332
+ return void 0;
333
+ }
334
+ const state = this.getState();
335
+ if (state?.lastSessionId) {
336
+ const found = sessions.find((s) => s.id === state.lastSessionId);
337
+ if (found) {
338
+ return found;
339
+ }
340
+ }
341
+ if (wrapperLastSessionId) {
342
+ const found = sessions.find((s) => s.id === wrapperLastSessionId);
343
+ if (found) {
344
+ return found;
345
+ }
346
+ }
347
+ return sessions[sessions.length - 1];
348
+ }
349
+ /**
350
+ * Returns the last N sessions, newest first.
351
+ */
352
+ getRecentSessions(count) {
353
+ return getRecentSessions(this.getSessions(), count);
354
+ }
355
+ /**
356
+ * Parses sessions.json once, returning both the session list
357
+ * and the optional lastSessionId from a ProjectSessions wrapper.
358
+ */
359
+ parseSessions() {
360
+ const raw = this.readJsonFile(
361
+ this.sessionsFilePath
362
+ );
363
+ if (!raw) {
364
+ return { sessions: [] };
365
+ }
366
+ if (Array.isArray(raw)) {
367
+ return { sessions: raw };
368
+ }
369
+ return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };
370
+ }
371
+ readJsonFile(filePath) {
372
+ try {
373
+ if (!fs.existsSync(filePath)) {
374
+ return void 0;
375
+ }
376
+ const raw = fs.readFileSync(filePath, "utf-8");
377
+ return JSON.parse(raw);
378
+ } catch {
379
+ return void 0;
380
+ }
381
+ }
382
+ };
383
+
384
+ // src/tools/getMomentum.ts
385
+ function registerGetMomentum(server2, reader2, workspacePath2) {
386
+ server2.tool(
387
+ "get_momentum",
388
+ "Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.",
389
+ {},
390
+ async () => {
391
+ if (!reader2.exists()) {
392
+ return {
393
+ content: [
394
+ {
395
+ type: "text",
396
+ text: "No KeepGoing data found. The developer has not saved any checkpoints yet."
397
+ }
398
+ ]
399
+ };
400
+ }
401
+ const lastSession = reader2.getLastSession();
402
+ if (!lastSession) {
403
+ return {
404
+ content: [
405
+ {
406
+ type: "text",
407
+ text: "KeepGoing is set up but no session checkpoints exist yet."
408
+ }
409
+ ]
410
+ };
411
+ }
412
+ const state = reader2.getState();
413
+ const currentBranch = getCurrentBranch(workspacePath2);
414
+ const branchChanged = lastSession.gitBranch && currentBranch && lastSession.gitBranch !== currentBranch;
415
+ const lines = [
416
+ `## Developer Momentum`,
417
+ "",
418
+ `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,
419
+ `**Summary:** ${lastSession.summary || "No summary"}`,
420
+ `**Next step:** ${lastSession.nextStep || "Not specified"}`
421
+ ];
422
+ if (lastSession.blocker) {
423
+ lines.push(`**Blocker:** ${lastSession.blocker}`);
424
+ }
425
+ if (lastSession.projectIntent) {
426
+ lines.push(`**Project intent:** ${lastSession.projectIntent}`);
427
+ }
428
+ lines.push("");
429
+ if (currentBranch) {
430
+ lines.push(`**Current branch:** ${currentBranch}`);
431
+ }
432
+ if (branchChanged) {
433
+ lines.push(
434
+ `**Note:** Branch changed since last checkpoint (was \`${lastSession.gitBranch}\`, now \`${currentBranch}\`)`
435
+ );
436
+ }
437
+ if (lastSession.touchedFiles.length > 0) {
438
+ lines.push("");
439
+ lines.push(
440
+ `**Files touched (${lastSession.touchedFiles.length}):** ${lastSession.touchedFiles.slice(0, 10).join(", ")}`
441
+ );
442
+ if (lastSession.touchedFiles.length > 10) {
443
+ lines.push(
444
+ ` ...and ${lastSession.touchedFiles.length - 10} more`
445
+ );
446
+ }
447
+ }
448
+ if (state?.derivedCurrentFocus) {
449
+ lines.push("");
450
+ lines.push(`**Derived focus:** ${state.derivedCurrentFocus}`);
451
+ }
452
+ return {
453
+ content: [{ type: "text", text: lines.join("\n") }]
454
+ };
455
+ }
456
+ );
457
+ }
458
+
459
+ // src/tools/getSessionHistory.ts
460
+ import { z } from "zod";
461
+ function registerGetSessionHistory(server2, reader2) {
462
+ server2.tool(
463
+ "get_session_history",
464
+ "Get recent session checkpoints. Returns a chronological list of what the developer worked on.",
465
+ { limit: z.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)") },
466
+ async ({ limit }) => {
467
+ if (!reader2.exists()) {
468
+ return {
469
+ content: [
470
+ {
471
+ type: "text",
472
+ text: "No KeepGoing data found."
473
+ }
474
+ ]
475
+ };
476
+ }
477
+ const sessions = reader2.getRecentSessions(limit);
478
+ if (sessions.length === 0) {
479
+ return {
480
+ content: [
481
+ {
482
+ type: "text",
483
+ text: "No session checkpoints found."
484
+ }
485
+ ]
486
+ };
487
+ }
488
+ const lines = [
489
+ `## Session History (last ${sessions.length})`,
490
+ ""
491
+ ];
492
+ for (const session of sessions) {
493
+ lines.push(`### ${formatRelativeTime(session.timestamp)}`);
494
+ lines.push(`- **Summary:** ${session.summary || "No summary"}`);
495
+ lines.push(`- **Next step:** ${session.nextStep || "Not specified"}`);
496
+ if (session.blocker) {
497
+ lines.push(`- **Blocker:** ${session.blocker}`);
498
+ }
499
+ if (session.gitBranch) {
500
+ lines.push(`- **Branch:** ${session.gitBranch}`);
501
+ }
502
+ if (session.touchedFiles.length > 0) {
503
+ lines.push(
504
+ `- **Files:** ${session.touchedFiles.slice(0, 5).join(", ")}${session.touchedFiles.length > 5 ? ` (+${session.touchedFiles.length - 5} more)` : ""}`
505
+ );
506
+ }
507
+ lines.push("");
508
+ }
509
+ return {
510
+ content: [{ type: "text", text: lines.join("\n") }]
511
+ };
512
+ }
513
+ );
514
+ }
515
+
516
+ // src/tools/getReentryBriefing.ts
517
+ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
518
+ server2.tool(
519
+ "get_reentry_briefing",
520
+ "Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.",
521
+ {},
522
+ async () => {
523
+ if (!reader2.exists()) {
524
+ return {
525
+ content: [
526
+ {
527
+ type: "text",
528
+ text: "No KeepGoing data found. The developer has not saved any checkpoints yet."
529
+ }
530
+ ]
531
+ };
532
+ }
533
+ const lastSession = reader2.getLastSession();
534
+ const recentSessions = reader2.getRecentSessions(5);
535
+ const state = reader2.getState() ?? {};
536
+ const gitBranch = getCurrentBranch(workspacePath2);
537
+ const sinceTimestamp = lastSession?.timestamp;
538
+ const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath2, sinceTimestamp) : [];
539
+ const briefing = generateBriefing(
540
+ lastSession,
541
+ recentSessions,
542
+ state,
543
+ gitBranch,
544
+ recentCommits
545
+ );
546
+ if (!briefing) {
547
+ return {
548
+ content: [
549
+ {
550
+ type: "text",
551
+ text: "No session data available to generate a briefing."
552
+ }
553
+ ]
554
+ };
555
+ }
556
+ const lines = [
557
+ `## Re-entry Briefing`,
558
+ "",
559
+ `**Last worked:** ${briefing.lastWorked}`,
560
+ `**Current focus:** ${briefing.currentFocus}`,
561
+ `**Recent activity:** ${briefing.recentActivity}`,
562
+ `**Suggested next:** ${briefing.suggestedNext}`,
563
+ `**Quick start:** ${briefing.smallNextStep}`
564
+ ];
565
+ return {
566
+ content: [{ type: "text", text: lines.join("\n") }]
567
+ };
568
+ }
569
+ );
570
+ }
571
+
572
+ // src/prompts/resume.ts
573
+ function registerResumePrompt(server2) {
574
+ server2.prompt(
575
+ "resume",
576
+ "Check developer momentum and suggest what to work on next",
577
+ async () => ({
578
+ messages: [
579
+ {
580
+ role: "user",
581
+ content: {
582
+ type: "text",
583
+ text: [
584
+ "I just opened this project and want to pick up where I left off.",
585
+ "",
586
+ "Please use the KeepGoing tools to:",
587
+ "1. Check my current momentum (get_momentum)",
588
+ "2. Get a re-entry briefing (get_reentry_briefing)",
589
+ "3. Based on the results, give me a concise summary of where I left off and suggest what to work on next.",
590
+ "",
591
+ "Keep your response brief and actionable."
592
+ ].join("\n")
593
+ }
594
+ }
595
+ ]
596
+ })
597
+ );
598
+ }
599
+
600
+ // src/index.ts
601
+ if (process.argv.includes("--print-momentum")) {
602
+ const wsPath = process.argv.slice(2).find((a) => a !== "--print-momentum") || process.cwd();
603
+ const reader2 = new KeepGoingReader(wsPath);
604
+ if (!reader2.exists()) {
605
+ process.exit(0);
606
+ }
607
+ const lastSession = reader2.getLastSession();
608
+ if (!lastSession) {
43
609
  process.exit(0);
610
+ }
611
+ const touchedCount = lastSession.touchedFiles?.length ?? 0;
612
+ const lines = [];
613
+ lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);
614
+ if (lastSession.summary) {
615
+ lines.push(` Summary: ${lastSession.summary}`);
616
+ }
617
+ if (lastSession.nextStep) {
618
+ lines.push(` Next step: ${lastSession.nextStep}`);
619
+ }
620
+ if (lastSession.blocker) {
621
+ lines.push(` Blocker: ${lastSession.blocker}`);
622
+ }
623
+ if (lastSession.gitBranch) {
624
+ lines.push(` Branch: ${lastSession.gitBranch}`);
625
+ }
626
+ if (touchedCount > 0) {
627
+ lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? "unknown branch"}`);
628
+ }
629
+ lines.push(" Tip: Use the get_reentry_briefing tool for a full briefing");
630
+ console.log(lines.join("\n"));
631
+ process.exit(0);
44
632
  }
45
- // Default: start MCP server
46
- // Workspace path can be passed as an argument, otherwise defaults to CWD.
47
- // MCP hosts (Claude Code, etc.) typically launch the server with the project root as CWD.
48
- const workspacePath = process.argv[2] || process.cwd();
49
- const reader = new KeepGoingReader(workspacePath);
50
- const server = new McpServer({
51
- name: 'keepgoing',
52
- version: '0.1.0',
633
+ var workspacePath = process.argv[2] || process.cwd();
634
+ var reader = new KeepGoingReader(workspacePath);
635
+ var server = new McpServer({
636
+ name: "keepgoing",
637
+ version: "0.1.0"
53
638
  });
54
- // Register tools
55
639
  registerGetMomentum(server, reader, workspacePath);
56
640
  registerGetSessionHistory(server, reader);
57
641
  registerGetReentryBriefing(server, reader, workspacePath);
58
- // Register prompts
59
642
  registerResumePrompt(server);
60
- // Connect via stdio
61
- const transport = new StdioServerTransport();
643
+ var transport = new StdioServerTransport();
62
644
  await server.connect(transport);
63
- console.error('KeepGoing MCP server started');
645
+ console.error("KeepGoing MCP server started");
64
646
  //# sourceMappingURL=index.js.map