@keepgoingdev/mcp-server 0.5.7 → 0.6.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 CHANGED
@@ -189,6 +189,62 @@ function getTouchedFiles(workspacePath) {
189
189
  }
190
190
  }
191
191
 
192
+ // ../../packages/shared/src/briefingTier.ts
193
+ var MODEL_TIERS = {
194
+ // Standard: small/fast models (compact is only reachable via explicit tier or context window)
195
+ "claude-3-haiku": "standard",
196
+ "claude-3.5-haiku": "standard",
197
+ "claude-haiku-4-5": "standard",
198
+ "gpt-4o-mini": "standard",
199
+ "gemini-flash": "standard",
200
+ // Detailed: mid-tier models
201
+ "claude-3.5-sonnet": "detailed",
202
+ "claude-sonnet-4": "detailed",
203
+ "claude-sonnet-4-5": "detailed",
204
+ "claude-sonnet-4-6": "detailed",
205
+ "gpt-4o": "detailed",
206
+ "gemini-pro": "detailed",
207
+ // Full: large context models
208
+ "claude-opus-4": "full",
209
+ "claude-opus-4-5": "full",
210
+ "claude-opus-4-6": "full",
211
+ "o1": "full",
212
+ "o3": "full",
213
+ "gemini-ultra": "full"
214
+ };
215
+ function resolveTier(opts) {
216
+ if (opts?.tier) {
217
+ return opts.tier;
218
+ }
219
+ if (opts?.model) {
220
+ const fromModel = tierFromModelName(opts.model);
221
+ if (fromModel) return fromModel;
222
+ }
223
+ if (opts?.contextWindow !== void 0) {
224
+ return tierFromContextWindow(opts.contextWindow);
225
+ }
226
+ return "standard";
227
+ }
228
+ function tierFromModelName(model) {
229
+ const normalized = model.toLowerCase().trim();
230
+ if (MODEL_TIERS[normalized]) {
231
+ return MODEL_TIERS[normalized];
232
+ }
233
+ const entries = Object.entries(MODEL_TIERS).sort((a, b) => b[0].length - a[0].length);
234
+ for (const [key, tier] of entries) {
235
+ if (normalized.startsWith(key)) {
236
+ return tier;
237
+ }
238
+ }
239
+ return void 0;
240
+ }
241
+ function tierFromContextWindow(tokens) {
242
+ if (tokens < 16e3) return "compact";
243
+ if (tokens < 64e3) return "standard";
244
+ if (tokens < 2e5) return "detailed";
245
+ return "full";
246
+ }
247
+
192
248
  // ../../packages/shared/src/reentry.ts
193
249
  var RECENT_SESSION_COUNT = 5;
194
250
  function generateBriefing(lastSession, recentSessions, projectState, gitBranch, recentCommitMessages) {
@@ -214,6 +270,61 @@ function generateBriefing(lastSession, recentSessions, projectState, gitBranch,
214
270
  function getRecentSessions(allSessions, count = RECENT_SESSION_COUNT) {
215
271
  return allSessions.slice(-count).reverse();
216
272
  }
273
+ function generateEnrichedBriefing(opts) {
274
+ const tier = resolveTier({
275
+ tier: opts.tier,
276
+ model: opts.model,
277
+ contextWindow: opts.contextWindow
278
+ });
279
+ const core = generateBriefing(
280
+ opts.lastSession,
281
+ opts.recentSessions,
282
+ opts.projectState,
283
+ opts.gitBranch,
284
+ opts.recentCommits
285
+ );
286
+ if (!core) return void 0;
287
+ const enriched = { tier, core };
288
+ if (tier === "compact") {
289
+ return enriched;
290
+ }
291
+ enriched.blocker = opts.lastSession?.blocker;
292
+ enriched.gitBranch = opts.gitBranch;
293
+ enriched.isWorktree = opts.isWorktree;
294
+ if (tier === "standard") {
295
+ return enriched;
296
+ }
297
+ if (opts.decisions && opts.decisions.length > 0) {
298
+ enriched.decisions = opts.decisions.slice(0, tier === "detailed" ? 3 : 10);
299
+ }
300
+ if (opts.allTouchedFiles && opts.allTouchedFiles.length > 0) {
301
+ enriched.touchedFiles = tier === "detailed" ? opts.allTouchedFiles.slice(0, 10) : opts.allTouchedFiles;
302
+ } else if (opts.lastSession?.touchedFiles && opts.lastSession.touchedFiles.length > 0) {
303
+ enriched.touchedFiles = tier === "detailed" ? opts.lastSession.touchedFiles.slice(0, 10) : opts.lastSession.touchedFiles;
304
+ }
305
+ if (tier === "detailed") {
306
+ return enriched;
307
+ }
308
+ if (opts.allSessions && opts.allSessions.length > 0) {
309
+ const recent = opts.allSessions.slice(-5).reverse();
310
+ enriched.sessionHistory = recent.map((s) => ({
311
+ timestamp: s.timestamp,
312
+ summary: s.summary || "",
313
+ nextStep: s.nextStep || "",
314
+ branch: s.gitBranch
315
+ }));
316
+ }
317
+ if (opts.recentCommits && opts.recentCommits.length > 0) {
318
+ enriched.recentCommits = opts.recentCommits;
319
+ }
320
+ if (opts.fileConflicts && opts.fileConflicts.length > 0) {
321
+ enriched.fileConflicts = opts.fileConflicts;
322
+ }
323
+ if (opts.branchOverlaps && opts.branchOverlaps.length > 0) {
324
+ enriched.branchOverlaps = opts.branchOverlaps;
325
+ }
326
+ return enriched;
327
+ }
217
328
  function buildCurrentFocus(lastSession, projectState, gitBranch) {
218
329
  if (projectState.derivedCurrentFocus) {
219
330
  return projectState.derivedCurrentFocus;
@@ -389,9 +500,199 @@ function inferFocusFromFiles(files) {
389
500
  return names.join(", ");
390
501
  }
391
502
 
503
+ // ../../packages/shared/src/briefingFormatter.ts
504
+ function formatEnrichedBriefing(briefing) {
505
+ const { tier, core } = briefing;
506
+ const lines = [];
507
+ lines.push("## Re-entry Briefing");
508
+ lines.push("");
509
+ if (briefing.isWorktree && briefing.gitBranch) {
510
+ lines.push(`**Worktree context:** Scoped to branch \`${briefing.gitBranch}\``);
511
+ lines.push("");
512
+ }
513
+ if (tier === "compact") {
514
+ lines.push(`**Last worked:** ${core.lastWorked}`);
515
+ lines.push(`**Focus:** ${core.currentFocus}`);
516
+ lines.push(`**Quick start:** ${core.smallNextStep}`);
517
+ return lines.join("\n");
518
+ }
519
+ lines.push(`**Last worked:** ${core.lastWorked}`);
520
+ lines.push(`**Current focus:** ${core.currentFocus}`);
521
+ lines.push(`**Recent activity:** ${core.recentActivity}`);
522
+ lines.push(`**Suggested next:** ${core.suggestedNext}`);
523
+ lines.push(`**Quick start:** ${core.smallNextStep}`);
524
+ if (briefing.blocker) {
525
+ lines.push(`**Blocker:** ${briefing.blocker}`);
526
+ }
527
+ if (briefing.decisions && briefing.decisions.length > 0) {
528
+ lines.push("");
529
+ lines.push("### Recent decisions");
530
+ for (const d of briefing.decisions) {
531
+ const rationale = d.rationale ? ` - ${d.rationale}` : "";
532
+ lines.push(`- **${d.classification.category}:** ${d.commitMessage}${rationale}`);
533
+ }
534
+ }
535
+ if (briefing.touchedFiles && briefing.touchedFiles.length > 0) {
536
+ lines.push("");
537
+ lines.push(`### Files touched (${briefing.touchedFiles.length})`);
538
+ for (const f of briefing.touchedFiles) {
539
+ lines.push(`- ${f}`);
540
+ }
541
+ }
542
+ if (briefing.sessionHistory && briefing.sessionHistory.length > 0) {
543
+ lines.push("");
544
+ lines.push("### Session history");
545
+ for (const s of briefing.sessionHistory) {
546
+ const relTime = formatRelativeTime(s.timestamp);
547
+ const branch = s.branch ? ` (${s.branch})` : "";
548
+ lines.push(`- **${relTime}${branch}:** ${s.summary || "No summary"}. Next: ${s.nextStep || "Not specified"}`);
549
+ }
550
+ }
551
+ if (briefing.recentCommits && briefing.recentCommits.length > 0) {
552
+ lines.push("");
553
+ lines.push("### Recent commits");
554
+ for (const msg of briefing.recentCommits.slice(0, 10)) {
555
+ lines.push(`- ${msg}`);
556
+ }
557
+ }
558
+ if (briefing.fileConflicts && briefing.fileConflicts.length > 0) {
559
+ lines.push("");
560
+ lines.push("### File conflicts");
561
+ lines.push("Multiple sessions are editing the same files:");
562
+ for (const c of briefing.fileConflicts) {
563
+ const sessionLabels = c.sessions.map((s) => s.agentLabel || s.sessionId).join(", ");
564
+ lines.push(`- \`${c.file}\`: ${sessionLabels}`);
565
+ }
566
+ }
567
+ if (briefing.branchOverlaps && briefing.branchOverlaps.length > 0) {
568
+ lines.push("");
569
+ lines.push("### Branch overlaps");
570
+ lines.push("Multiple sessions on the same branch:");
571
+ for (const o of briefing.branchOverlaps) {
572
+ const sessionLabels = o.sessions.map((s) => s.agentLabel || s.sessionId).join(", ");
573
+ lines.push(`- \`${o.branch}\`: ${sessionLabels}`);
574
+ }
575
+ }
576
+ return lines.join("\n");
577
+ }
578
+
579
+ // ../../packages/shared/src/continueOn.ts
580
+ import path2 from "path";
581
+ function gatherContinueOnContext(reader, workspacePath) {
582
+ const projectName = path2.basename(workspacePath);
583
+ const gitBranch = reader.getCurrentBranch();
584
+ if (!reader.exists()) {
585
+ return {
586
+ projectName,
587
+ gitBranch,
588
+ recentCheckpoints: [],
589
+ recentDecisions: [],
590
+ currentTasks: [],
591
+ recentCommitMessages: []
592
+ };
593
+ }
594
+ const { session: lastCheckpoint } = reader.getScopedLastSession();
595
+ const recentCheckpoints = reader.getScopedRecentSessions(5);
596
+ const recentDecisions = reader.getScopedRecentDecisions(3);
597
+ const currentTasks = reader.getCurrentTasks();
598
+ const state = reader.getState() ?? {};
599
+ const sinceTimestamp = lastCheckpoint?.timestamp;
600
+ const recentCommitMessages = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
601
+ const briefing = generateBriefing(
602
+ lastCheckpoint,
603
+ recentCheckpoints,
604
+ state,
605
+ gitBranch,
606
+ recentCommitMessages
607
+ );
608
+ return {
609
+ projectName,
610
+ gitBranch,
611
+ briefing,
612
+ lastCheckpoint,
613
+ recentCheckpoints,
614
+ recentDecisions,
615
+ currentTasks,
616
+ recentCommitMessages
617
+ };
618
+ }
619
+ function formatContinueOnPrompt(context, options) {
620
+ const opts = { includeCommits: true, includeFiles: true, ...options };
621
+ const lines = [];
622
+ lines.push(`# Project Context: ${context.projectName}`);
623
+ lines.push("");
624
+ lines.push("I'm continuing work on a project. Here is my development context from KeepGoing.");
625
+ lines.push("");
626
+ lines.push("## Current Status");
627
+ if (context.gitBranch) {
628
+ lines.push(`- **Branch:** ${context.gitBranch}`);
629
+ }
630
+ if (context.briefing) {
631
+ lines.push(`- **Last worked:** ${context.briefing.lastWorked}`);
632
+ lines.push(`- **Focus:** ${context.briefing.currentFocus}`);
633
+ } else if (context.lastCheckpoint) {
634
+ lines.push(`- **Last worked:** ${formatRelativeTime(context.lastCheckpoint.timestamp)}`);
635
+ if (context.lastCheckpoint.summary) {
636
+ lines.push(`- **Focus:** ${context.lastCheckpoint.summary}`);
637
+ }
638
+ }
639
+ if (context.lastCheckpoint) {
640
+ lines.push("");
641
+ lines.push("## Last Session");
642
+ if (context.lastCheckpoint.summary) {
643
+ lines.push(`- **Summary:** ${context.lastCheckpoint.summary}`);
644
+ }
645
+ if (context.lastCheckpoint.nextStep) {
646
+ lines.push(`- **Next step:** ${context.lastCheckpoint.nextStep}`);
647
+ }
648
+ if (context.lastCheckpoint.blocker) {
649
+ lines.push(`- **Blocker:** ${context.lastCheckpoint.blocker}`);
650
+ }
651
+ if (opts.includeFiles && context.lastCheckpoint.touchedFiles.length > 0) {
652
+ const files = context.lastCheckpoint.touchedFiles.slice(0, 10).join(", ");
653
+ const extra = context.lastCheckpoint.touchedFiles.length > 10 ? ` (+${context.lastCheckpoint.touchedFiles.length - 10} more)` : "";
654
+ lines.push(`- **Files touched:** ${files}${extra}`);
655
+ }
656
+ }
657
+ const activeTasks = context.currentTasks.filter((t) => t.sessionActive);
658
+ if (activeTasks.length > 0) {
659
+ lines.push("");
660
+ lines.push("## Active Sessions");
661
+ for (const task of activeTasks) {
662
+ const label = task.agentLabel || "Unknown agent";
663
+ const branch = task.branch ? ` on \`${task.branch}\`` : "";
664
+ lines.push(`- **${label}**${branch}: ${task.taskSummary || "Working"}`);
665
+ }
666
+ }
667
+ if (context.recentDecisions.length > 0) {
668
+ lines.push("");
669
+ lines.push("## Recent Decisions");
670
+ for (const d of context.recentDecisions) {
671
+ lines.push(`- ${d.classification.category}: ${d.commitMessage}`);
672
+ }
673
+ }
674
+ if (opts.includeCommits && context.recentCommitMessages.length > 0) {
675
+ lines.push("");
676
+ lines.push("## Recent Commits");
677
+ const commits = context.recentCommitMessages.slice(0, 10);
678
+ for (const msg of commits) {
679
+ lines.push(`- ${msg}`);
680
+ }
681
+ }
682
+ lines.push("");
683
+ lines.push("---");
684
+ const nextStep = context.lastCheckpoint?.nextStep || context.briefing?.suggestedNext || "continue where I left off";
685
+ lines.push(`Please help me continue this work. My next step is: ${nextStep}.`);
686
+ let result = lines.join("\n");
687
+ if (opts.maxLength && result.length > opts.maxLength) {
688
+ result = result.slice(0, opts.maxLength - 3) + "...";
689
+ }
690
+ return result;
691
+ }
692
+
392
693
  // ../../packages/shared/src/storage.ts
393
694
  import fs from "fs";
394
- import path2 from "path";
695
+ import path3 from "path";
395
696
  import { randomUUID as randomUUID2, createHash } from "crypto";
396
697
  var STORAGE_DIR = ".keepgoing";
397
698
  var META_FILE = "meta.json";
@@ -414,11 +715,11 @@ var KeepGoingWriter = class {
414
715
  currentTasksFilePath;
415
716
  constructor(workspacePath) {
416
717
  const mainRoot = resolveStorageRoot(workspacePath);
417
- this.storagePath = path2.join(mainRoot, STORAGE_DIR);
418
- this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE);
419
- this.stateFilePath = path2.join(this.storagePath, STATE_FILE);
420
- this.metaFilePath = path2.join(this.storagePath, META_FILE);
421
- this.currentTasksFilePath = path2.join(this.storagePath, CURRENT_TASKS_FILE);
718
+ this.storagePath = path3.join(mainRoot, STORAGE_DIR);
719
+ this.sessionsFilePath = path3.join(this.storagePath, SESSIONS_FILE);
720
+ this.stateFilePath = path3.join(this.storagePath, STATE_FILE);
721
+ this.metaFilePath = path3.join(this.storagePath, META_FILE);
722
+ this.currentTasksFilePath = path3.join(this.storagePath, CURRENT_TASKS_FILE);
422
723
  }
423
724
  ensureDir() {
424
725
  if (!fs.existsSync(this.storagePath)) {
@@ -732,24 +1033,24 @@ function capitalize(s) {
732
1033
 
733
1034
  // ../../packages/shared/src/decisionStorage.ts
734
1035
  import fs3 from "fs";
735
- import path4 from "path";
1036
+ import path5 from "path";
736
1037
 
737
1038
  // ../../packages/shared/src/license.ts
738
1039
  import crypto from "crypto";
739
1040
  import fs2 from "fs";
740
1041
  import os from "os";
741
- import path3 from "path";
1042
+ import path4 from "path";
742
1043
  var LICENSE_FILE = "license.json";
743
1044
  var DEVICE_ID_FILE = "device-id";
744
1045
  function getGlobalLicenseDir() {
745
- return path3.join(os.homedir(), ".keepgoing");
1046
+ return path4.join(os.homedir(), ".keepgoing");
746
1047
  }
747
1048
  function getGlobalLicensePath() {
748
- return path3.join(getGlobalLicenseDir(), LICENSE_FILE);
1049
+ return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
749
1050
  }
750
1051
  function getDeviceId() {
751
1052
  const dir = getGlobalLicenseDir();
752
- const filePath = path3.join(dir, DEVICE_ID_FILE);
1053
+ const filePath = path4.join(dir, DEVICE_ID_FILE);
753
1054
  try {
754
1055
  const existing = fs2.readFileSync(filePath, "utf-8").trim();
755
1056
  if (existing) return existing;
@@ -816,7 +1117,7 @@ function writeLicenseStore(store) {
816
1117
  if (!fs2.existsSync(dirPath)) {
817
1118
  fs2.mkdirSync(dirPath, { recursive: true });
818
1119
  }
819
- const licensePath = path3.join(dirPath, LICENSE_FILE);
1120
+ const licensePath = path4.join(dirPath, LICENSE_FILE);
820
1121
  fs2.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
821
1122
  _cachedStore = store;
822
1123
  _cacheTimestamp = Date.now();
@@ -868,8 +1169,8 @@ var DecisionStorage = class {
868
1169
  decisionsFilePath;
869
1170
  constructor(workspacePath) {
870
1171
  const mainRoot = resolveStorageRoot(workspacePath);
871
- this.storagePath = path4.join(mainRoot, STORAGE_DIR2);
872
- this.decisionsFilePath = path4.join(this.storagePath, DECISIONS_FILE);
1172
+ this.storagePath = path5.join(mainRoot, STORAGE_DIR2);
1173
+ this.decisionsFilePath = path5.join(this.storagePath, DECISIONS_FILE);
873
1174
  }
874
1175
  ensureStorageDir() {
875
1176
  if (!fs3.existsSync(this.storagePath)) {
@@ -877,7 +1178,7 @@ var DecisionStorage = class {
877
1178
  }
878
1179
  }
879
1180
  getProjectName() {
880
- return path4.basename(path4.dirname(this.storagePath));
1181
+ return path5.basename(path5.dirname(this.storagePath));
881
1182
  }
882
1183
  load() {
883
1184
  try {
@@ -1146,7 +1447,7 @@ function tryDetectDecision(opts) {
1146
1447
 
1147
1448
  // ../../packages/shared/src/reader.ts
1148
1449
  import fs4 from "fs";
1149
- import path5 from "path";
1450
+ import path6 from "path";
1150
1451
  var STORAGE_DIR3 = ".keepgoing";
1151
1452
  var META_FILE2 = "meta.json";
1152
1453
  var SESSIONS_FILE2 = "sessions.json";
@@ -1168,12 +1469,12 @@ var KeepGoingReader = class {
1168
1469
  this.workspacePath = workspacePath;
1169
1470
  const mainRoot = resolveStorageRoot(workspacePath);
1170
1471
  this._isWorktree = mainRoot !== workspacePath;
1171
- this.storagePath = path5.join(mainRoot, STORAGE_DIR3);
1172
- this.metaFilePath = path5.join(this.storagePath, META_FILE2);
1173
- this.sessionsFilePath = path5.join(this.storagePath, SESSIONS_FILE2);
1174
- this.decisionsFilePath = path5.join(this.storagePath, DECISIONS_FILE2);
1175
- this.stateFilePath = path5.join(this.storagePath, STATE_FILE2);
1176
- this.currentTasksFilePath = path5.join(this.storagePath, CURRENT_TASKS_FILE2);
1472
+ this.storagePath = path6.join(mainRoot, STORAGE_DIR3);
1473
+ this.metaFilePath = path6.join(this.storagePath, META_FILE2);
1474
+ this.sessionsFilePath = path6.join(this.storagePath, SESSIONS_FILE2);
1475
+ this.decisionsFilePath = path6.join(this.storagePath, DECISIONS_FILE2);
1476
+ this.stateFilePath = path6.join(this.storagePath, STATE_FILE2);
1477
+ this.currentTasksFilePath = path6.join(this.storagePath, CURRENT_TASKS_FILE2);
1177
1478
  }
1178
1479
  /** Check if .keepgoing/ directory exists. */
1179
1480
  exists() {
@@ -1429,7 +1730,7 @@ var KeepGoingReader = class {
1429
1730
  // ../../packages/shared/src/setup.ts
1430
1731
  import fs5 from "fs";
1431
1732
  import os2 from "os";
1432
- import path6 from "path";
1733
+ import path7 from "path";
1433
1734
  var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
1434
1735
  var SESSION_START_HOOK = {
1435
1736
  matcher: "",
@@ -1474,19 +1775,19 @@ function hasKeepGoingHook(hookEntries) {
1474
1775
  }
1475
1776
  function resolveScopePaths(scope, workspacePath) {
1476
1777
  if (scope === "user") {
1477
- const claudeDir2 = path6.join(os2.homedir(), ".claude");
1778
+ const claudeDir2 = path7.join(os2.homedir(), ".claude");
1478
1779
  return {
1479
1780
  claudeDir: claudeDir2,
1480
- settingsPath: path6.join(claudeDir2, "settings.json"),
1481
- claudeMdPath: path6.join(claudeDir2, "CLAUDE.md")
1781
+ settingsPath: path7.join(claudeDir2, "settings.json"),
1782
+ claudeMdPath: path7.join(claudeDir2, "CLAUDE.md")
1482
1783
  };
1483
1784
  }
1484
- const claudeDir = path6.join(workspacePath, ".claude");
1485
- const dotClaudeMdPath = path6.join(workspacePath, ".claude", "CLAUDE.md");
1486
- const rootClaudeMdPath = path6.join(workspacePath, "CLAUDE.md");
1785
+ const claudeDir = path7.join(workspacePath, ".claude");
1786
+ const dotClaudeMdPath = path7.join(workspacePath, ".claude", "CLAUDE.md");
1787
+ const rootClaudeMdPath = path7.join(workspacePath, "CLAUDE.md");
1487
1788
  return {
1488
1789
  claudeDir,
1489
- settingsPath: path6.join(claudeDir, "settings.json"),
1790
+ settingsPath: path7.join(claudeDir, "settings.json"),
1490
1791
  claudeMdPath: fs5.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
1491
1792
  };
1492
1793
  }
@@ -1599,7 +1900,7 @@ function setupProject(options) {
1599
1900
  messages.push(`CLAUDE.md: KeepGoing section already present in ${mdLabel}, skipped`);
1600
1901
  } else {
1601
1902
  const updated = existing + CLAUDE_MD_SECTION;
1602
- const mdDir = path6.dirname(claudeMdPath);
1903
+ const mdDir = path7.dirname(claudeMdPath);
1603
1904
  if (!fs5.existsSync(mdDir)) {
1604
1905
  fs5.mkdirSync(mdDir, { recursive: true });
1605
1906
  }
@@ -1701,12 +2002,16 @@ async function deactivateLicense(licenseKey, instanceId) {
1701
2002
  }
1702
2003
 
1703
2004
  // src/tools/getMomentum.ts
2005
+ import { z } from "zod";
1704
2006
  function registerGetMomentum(server, reader, workspacePath) {
1705
2007
  server.tool(
1706
2008
  "get_momentum",
1707
- "Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.",
1708
- {},
1709
- async () => {
2009
+ "Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off. Pass tier or model to control detail level.",
2010
+ {
2011
+ tier: z.enum(["compact", "standard", "detailed", "full"]).optional().describe("Briefing detail level. compact (~150 tokens), standard (~400), detailed (~800), full (~1500). Default: standard."),
2012
+ model: z.string().optional().describe('Model name (e.g. "claude-opus-4") to auto-resolve tier. Ignored if tier is set.')
2013
+ },
2014
+ async ({ tier, model }) => {
1710
2015
  if (!reader.exists()) {
1711
2016
  return {
1712
2017
  content: [
@@ -1717,6 +2022,46 @@ function registerGetMomentum(server, reader, workspacePath) {
1717
2022
  ]
1718
2023
  };
1719
2024
  }
2025
+ if (tier || model) {
2026
+ const gitBranch = reader.getCurrentBranch();
2027
+ const { session: lastSession2 } = reader.getScopedLastSession();
2028
+ const recentSessions = reader.getScopedRecentSessions(5);
2029
+ const state2 = reader.getState() ?? {};
2030
+ const sinceTimestamp = lastSession2?.timestamp;
2031
+ const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
2032
+ const decisions = reader.getScopedRecentDecisions(10);
2033
+ const allSessions = reader.getSessions();
2034
+ const fileConflicts = reader.detectFileConflicts();
2035
+ const branchOverlaps = reader.detectBranchOverlap();
2036
+ const briefing = generateEnrichedBriefing({
2037
+ tier,
2038
+ model,
2039
+ lastSession: lastSession2,
2040
+ recentSessions,
2041
+ projectState: state2,
2042
+ gitBranch,
2043
+ recentCommits,
2044
+ decisions,
2045
+ allTouchedFiles: lastSession2?.touchedFiles,
2046
+ allSessions,
2047
+ fileConflicts,
2048
+ branchOverlaps,
2049
+ isWorktree: reader.isWorktree
2050
+ });
2051
+ if (!briefing) {
2052
+ return {
2053
+ content: [
2054
+ {
2055
+ type: "text",
2056
+ text: "KeepGoing is set up but no session checkpoints exist yet."
2057
+ }
2058
+ ]
2059
+ };
2060
+ }
2061
+ return {
2062
+ content: [{ type: "text", text: formatEnrichedBriefing(briefing) }]
2063
+ };
2064
+ }
1720
2065
  const { session: lastSession, isFallback } = reader.getScopedLastSession();
1721
2066
  const currentBranch = reader.getCurrentBranch();
1722
2067
  if (!lastSession) {
@@ -1785,14 +2130,14 @@ function registerGetMomentum(server, reader, workspacePath) {
1785
2130
  }
1786
2131
 
1787
2132
  // src/tools/getSessionHistory.ts
1788
- import { z } from "zod";
2133
+ import { z as z2 } from "zod";
1789
2134
  function registerGetSessionHistory(server, reader) {
1790
2135
  server.tool(
1791
2136
  "get_session_history",
1792
2137
  "Get recent session checkpoints. Returns a chronological list of what the developer worked on.",
1793
2138
  {
1794
- limit: z.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)"),
1795
- branch: z.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
2139
+ limit: z2.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)"),
2140
+ branch: z2.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
1796
2141
  },
1797
2142
  async ({ limit, branch }) => {
1798
2143
  if (!reader.exists()) {
@@ -1846,12 +2191,16 @@ function registerGetSessionHistory(server, reader) {
1846
2191
  }
1847
2192
 
1848
2193
  // src/tools/getReentryBriefing.ts
2194
+ import { z as z3 } from "zod";
1849
2195
  function registerGetReentryBriefing(server, reader, workspacePath) {
1850
2196
  server.tool(
1851
2197
  "get_reentry_briefing",
1852
- "Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.",
1853
- {},
1854
- async () => {
2198
+ "Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps. Pass tier or model to control detail level.",
2199
+ {
2200
+ tier: z3.enum(["compact", "standard", "detailed", "full"]).optional().describe("Briefing detail level. compact (~150 tokens), standard (~400), detailed (~800), full (~1500). Default: standard."),
2201
+ model: z3.string().optional().describe('Model name (e.g. "claude-opus-4") to auto-resolve tier. Ignored if tier is set.')
2202
+ },
2203
+ async ({ tier, model }) => {
1855
2204
  if (!reader.exists()) {
1856
2205
  return {
1857
2206
  content: [
@@ -1868,13 +2217,25 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
1868
2217
  const state = reader.getState() ?? {};
1869
2218
  const sinceTimestamp = lastSession?.timestamp;
1870
2219
  const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
1871
- const briefing = generateBriefing(
2220
+ const decisions = reader.getScopedRecentDecisions(10);
2221
+ const allSessions = reader.getSessions();
2222
+ const fileConflicts = reader.detectFileConflicts();
2223
+ const branchOverlaps = reader.detectBranchOverlap();
2224
+ const briefing = generateEnrichedBriefing({
2225
+ tier,
2226
+ model,
1872
2227
  lastSession,
1873
2228
  recentSessions,
1874
- state,
2229
+ projectState: state,
1875
2230
  gitBranch,
1876
- recentCommits
1877
- );
2231
+ recentCommits,
2232
+ decisions,
2233
+ allTouchedFiles: lastSession?.touchedFiles,
2234
+ allSessions,
2235
+ fileConflicts,
2236
+ branchOverlaps,
2237
+ isWorktree: reader.isWorktree
2238
+ });
1878
2239
  if (!briefing) {
1879
2240
  return {
1880
2241
  content: [
@@ -1885,55 +2246,31 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
1885
2246
  ]
1886
2247
  };
1887
2248
  }
1888
- const lines = [
1889
- `## Re-entry Briefing`,
1890
- ""
1891
- ];
1892
- if (reader.isWorktree && gitBranch) {
1893
- lines.push(`**Worktree context:** Scoped to branch \`${gitBranch}\``);
1894
- lines.push("");
1895
- }
1896
- lines.push(
1897
- `**Last worked:** ${briefing.lastWorked}`,
1898
- `**Current focus:** ${briefing.currentFocus}`,
1899
- `**Recent activity:** ${briefing.recentActivity}`,
1900
- `**Suggested next:** ${briefing.suggestedNext}`,
1901
- `**Quick start:** ${briefing.smallNextStep}`
1902
- );
1903
- const recentDecisions = reader.getScopedRecentDecisions(3);
1904
- if (recentDecisions.length > 0) {
1905
- lines.push("");
1906
- lines.push("### Recent decisions");
1907
- for (const decision of recentDecisions) {
1908
- const rationale = decision.rationale ? ` - ${decision.rationale}` : "";
1909
- lines.push(`- **${decision.classification.category}:** ${decision.commitMessage}${rationale}`);
1910
- }
1911
- }
1912
2249
  return {
1913
- content: [{ type: "text", text: lines.join("\n") }]
2250
+ content: [{ type: "text", text: formatEnrichedBriefing(briefing) }]
1914
2251
  };
1915
2252
  }
1916
2253
  );
1917
2254
  }
1918
2255
 
1919
2256
  // src/tools/saveCheckpoint.ts
1920
- import path7 from "path";
1921
- import { z as z2 } from "zod";
2257
+ import path8 from "path";
2258
+ import { z as z4 } from "zod";
1922
2259
  function registerSaveCheckpoint(server, reader, workspacePath) {
1923
2260
  server.tool(
1924
2261
  "save_checkpoint",
1925
2262
  "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.",
1926
2263
  {
1927
- summary: z2.string().describe("What was accomplished in this session"),
1928
- nextStep: z2.string().optional().describe("What to do next"),
1929
- blocker: z2.string().optional().describe("Any blocker preventing progress")
2264
+ summary: z4.string().describe("What was accomplished in this session"),
2265
+ nextStep: z4.string().optional().describe("What to do next"),
2266
+ blocker: z4.string().optional().describe("Any blocker preventing progress")
1930
2267
  },
1931
2268
  async ({ summary, nextStep, blocker }) => {
1932
2269
  const lastSession = reader.getLastSession();
1933
2270
  const gitBranch = getCurrentBranch(workspacePath);
1934
2271
  const touchedFiles = getTouchedFiles(workspacePath);
1935
2272
  const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);
1936
- const projectName = path7.basename(resolveStorageRoot(workspacePath));
2273
+ const projectName = path8.basename(resolveStorageRoot(workspacePath));
1937
2274
  const sessionId = generateSessionId({ workspaceRoot: workspacePath, branch: gitBranch ?? void 0, worktreePath: workspacePath });
1938
2275
  const checkpoint = createCheckpoint({
1939
2276
  summary,
@@ -1980,14 +2317,14 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
1980
2317
  }
1981
2318
 
1982
2319
  // src/tools/getDecisions.ts
1983
- import { z as z3 } from "zod";
2320
+ import { z as z5 } from "zod";
1984
2321
  function registerGetDecisions(server, reader) {
1985
2322
  server.tool(
1986
2323
  "get_decisions",
1987
2324
  "Get recent decision records. Returns detected high-signal commits with their category, confidence, and rationale.",
1988
2325
  {
1989
- limit: z3.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)"),
1990
- branch: z3.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
2326
+ limit: z5.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)"),
2327
+ branch: z5.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
1991
2328
  },
1992
2329
  async ({ limit, branch }) => {
1993
2330
  if (!reader.exists()) {
@@ -2145,18 +2482,18 @@ function registerGetCurrentTask(server, reader) {
2145
2482
  }
2146
2483
 
2147
2484
  // src/tools/setupProject.ts
2148
- import { z as z4 } from "zod";
2485
+ import { z as z6 } from "zod";
2149
2486
 
2150
2487
  // src/cli/migrate.ts
2151
2488
  import fs6 from "fs";
2152
2489
  import os3 from "os";
2153
- import path8 from "path";
2490
+ import path9 from "path";
2154
2491
  var STATUSLINE_CMD2 = "npx -y @keepgoingdev/mcp-server --statusline";
2155
2492
  function isLegacyStatusline(command) {
2156
2493
  return !command.includes("--statusline") && command.includes("keepgoing-statusline");
2157
2494
  }
2158
2495
  function migrateStatusline(wsPath) {
2159
- const settingsPath = path8.join(wsPath, ".claude", "settings.json");
2496
+ const settingsPath = path9.join(wsPath, ".claude", "settings.json");
2160
2497
  if (!fs6.existsSync(settingsPath)) return void 0;
2161
2498
  try {
2162
2499
  const settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
@@ -2174,7 +2511,7 @@ function migrateStatusline(wsPath) {
2174
2511
  }
2175
2512
  }
2176
2513
  function cleanupLegacyScript() {
2177
- const legacyScript = path8.join(os3.homedir(), ".claude", "keepgoing-statusline.sh");
2514
+ const legacyScript = path9.join(os3.homedir(), ".claude", "keepgoing-statusline.sh");
2178
2515
  if (fs6.existsSync(legacyScript)) {
2179
2516
  try {
2180
2517
  fs6.unlinkSync(legacyScript);
@@ -2189,9 +2526,9 @@ function registerSetupProject(server, workspacePath) {
2189
2526
  "setup_project",
2190
2527
  'Set up KeepGoing hooks and instructions. Use scope "user" for global setup (all projects) or "project" for per-project setup.',
2191
2528
  {
2192
- sessionHooks: z4.boolean().optional().default(true).describe("Add session hooks to settings.json"),
2193
- claudeMd: z4.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md"),
2194
- scope: z4.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)')
2529
+ sessionHooks: z6.boolean().optional().default(true).describe("Add session hooks to settings.json"),
2530
+ claudeMd: z6.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md"),
2531
+ scope: z6.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)')
2195
2532
  },
2196
2533
  async ({ sessionHooks, claudeMd, scope }) => {
2197
2534
  const hasProLicense = process.env.KEEPGOING_PRO_BYPASS === "1" || !!getLicenseForFeature("session-awareness");
@@ -2217,12 +2554,12 @@ function registerSetupProject(server, workspacePath) {
2217
2554
  }
2218
2555
 
2219
2556
  // src/tools/activateLicense.ts
2220
- import { z as z5 } from "zod";
2557
+ import { z as z7 } from "zod";
2221
2558
  function registerActivateLicense(server) {
2222
2559
  server.tool(
2223
2560
  "activate_license",
2224
2561
  "Activate a KeepGoing Pro license on this device. Unlocks add-ons like Decision Detection and Session Awareness.",
2225
- { license_key: z5.string().describe("Your KeepGoing Pro license key") },
2562
+ { license_key: z7.string().describe("Your KeepGoing Pro license key") },
2226
2563
  async ({ license_key }) => {
2227
2564
  const store = readLicenseStore();
2228
2565
  const existingForKey = store.licenses.find(
@@ -2296,13 +2633,13 @@ function registerActivateLicense(server) {
2296
2633
  }
2297
2634
 
2298
2635
  // src/tools/deactivateLicense.ts
2299
- import { z as z6 } from "zod";
2636
+ import { z as z8 } from "zod";
2300
2637
  function registerDeactivateLicense(server) {
2301
2638
  server.tool(
2302
2639
  "deactivate_license",
2303
2640
  "Deactivate the KeepGoing Pro license on this device.",
2304
2641
  {
2305
- license_key: z6.string().optional().describe("Specific license key to deactivate. If omitted and only one license is active, deactivates it. If multiple are active, lists them.")
2642
+ license_key: z8.string().optional().describe("Specific license key to deactivate. If omitted and only one license is active, deactivates it. If multiple are active, lists them.")
2306
2643
  },
2307
2644
  async ({ license_key }) => {
2308
2645
  const store = readLicenseStore();
@@ -2368,6 +2705,48 @@ function registerDeactivateLicense(server) {
2368
2705
  );
2369
2706
  }
2370
2707
 
2708
+ // src/tools/continueOn.ts
2709
+ import { z as z9 } from "zod";
2710
+ function registerContinueOn(server, reader, workspacePath) {
2711
+ server.tool(
2712
+ "continue_on",
2713
+ "Export KeepGoing context as a formatted prompt for use in another AI tool (ChatGPT, Gemini, Copilot, etc.). Returns a markdown prompt with project status, last session, decisions, and recent commits.",
2714
+ {
2715
+ target: z9.enum(["chatgpt", "gemini", "copilot", "claude", "general"]).optional().describe("Target AI tool (currently used for future format tuning)"),
2716
+ include_commits: z9.boolean().default(true).describe("Include recent commit messages in the prompt"),
2717
+ include_files: z9.boolean().default(true).describe("Include touched file paths in the prompt")
2718
+ },
2719
+ async ({ target, include_commits, include_files }) => {
2720
+ if (!reader.exists()) {
2721
+ return {
2722
+ content: [{
2723
+ type: "text",
2724
+ text: "No KeepGoing data found. Save a checkpoint first to use Continue On."
2725
+ }]
2726
+ };
2727
+ }
2728
+ const context = gatherContinueOnContext(reader, workspacePath);
2729
+ if (!context.lastCheckpoint && !context.briefing) {
2730
+ return {
2731
+ content: [{
2732
+ type: "text",
2733
+ text: "No session data available. Save a checkpoint first."
2734
+ }]
2735
+ };
2736
+ }
2737
+ const formatOpts = {
2738
+ target,
2739
+ includeCommits: include_commits,
2740
+ includeFiles: include_files
2741
+ };
2742
+ const prompt = formatContinueOnPrompt(context, formatOpts);
2743
+ return {
2744
+ content: [{ type: "text", text: prompt }]
2745
+ };
2746
+ }
2747
+ );
2748
+ }
2749
+
2371
2750
  // src/prompts/resume.ts
2372
2751
  function registerResumePrompt(server) {
2373
2752
  server.prompt(
@@ -2533,7 +2912,7 @@ async function handlePrintCurrent() {
2533
2912
  }
2534
2913
 
2535
2914
  // src/cli/saveCheckpoint.ts
2536
- import path9 from "path";
2915
+ import path10 from "path";
2537
2916
  async function handleSaveCheckpoint() {
2538
2917
  const wsPath = resolveWsPath();
2539
2918
  const reader = new KeepGoingReader(wsPath);
@@ -2561,9 +2940,9 @@ async function handleSaveCheckpoint() {
2561
2940
  sessionStartTime: lastSession?.timestamp ?? now,
2562
2941
  lastActivityTime: now
2563
2942
  });
2564
- const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path9.basename(f)).join(", ")}`;
2943
+ const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path10.basename(f)).join(", ")}`;
2565
2944
  const nextStep = buildSmartNextStep(events);
2566
- const projectName = path9.basename(resolveStorageRoot(wsPath));
2945
+ const projectName = path10.basename(resolveStorageRoot(wsPath));
2567
2946
  const sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
2568
2947
  const checkpoint = createCheckpoint({
2569
2948
  summary,
@@ -2677,7 +3056,7 @@ async function handleUpdateTaskFromHook() {
2677
3056
 
2678
3057
  // src/cli/statusline.ts
2679
3058
  import fs7 from "fs";
2680
- import path10 from "path";
3059
+ import path11 from "path";
2681
3060
  var STDIN_TIMEOUT_MS2 = 3e3;
2682
3061
  async function handleStatusline() {
2683
3062
  const chunks = [];
@@ -2700,7 +3079,7 @@ async function handleStatusline() {
2700
3079
  process.exit(0);
2701
3080
  }
2702
3081
  const gitRoot = findGitRoot(dir);
2703
- const tasksFile = path10.join(gitRoot, ".keepgoing", "current-tasks.json");
3082
+ const tasksFile = path11.join(gitRoot, ".keepgoing", "current-tasks.json");
2704
3083
  if (!fs7.existsSync(tasksFile)) {
2705
3084
  process.exit(0);
2706
3085
  }
@@ -2730,6 +3109,22 @@ async function handleStatusline() {
2730
3109
  process.stdin.resume();
2731
3110
  }
2732
3111
 
3112
+ // src/cli/continueOn.ts
3113
+ async function handleContinueOn() {
3114
+ const wsPath = resolveWsPath();
3115
+ const reader = new KeepGoingReader(wsPath);
3116
+ if (!reader.exists()) {
3117
+ process.exit(0);
3118
+ }
3119
+ const context = gatherContinueOnContext(reader, wsPath);
3120
+ if (!context.lastCheckpoint && !context.briefing) {
3121
+ process.exit(0);
3122
+ }
3123
+ const prompt = formatContinueOnPrompt(context);
3124
+ console.log(prompt);
3125
+ process.exit(0);
3126
+ }
3127
+
2733
3128
  // src/index.ts
2734
3129
  var CLI_HANDLERS = {
2735
3130
  "--print-momentum": handlePrintMomentum,
@@ -2737,7 +3132,8 @@ var CLI_HANDLERS = {
2737
3132
  "--update-task": handleUpdateTask,
2738
3133
  "--update-task-from-hook": handleUpdateTaskFromHook,
2739
3134
  "--print-current": handlePrintCurrent,
2740
- "--statusline": handleStatusline
3135
+ "--statusline": handleStatusline,
3136
+ "--continue-on": handleContinueOn
2741
3137
  };
2742
3138
  var flag = process.argv.slice(2).find((a) => a in CLI_HANDLERS);
2743
3139
  if (flag) {
@@ -2755,6 +3151,7 @@ if (flag) {
2755
3151
  registerGetDecisions(server, reader);
2756
3152
  registerGetCurrentTask(server, reader);
2757
3153
  registerSaveCheckpoint(server, reader, workspacePath);
3154
+ registerContinueOn(server, reader, workspacePath);
2758
3155
  registerSetupProject(server, workspacePath);
2759
3156
  registerActivateLicense(server);
2760
3157
  registerDeactivateLicense(server);