@keepgoingdev/mcp-server 0.5.7 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.js +650 -124
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
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 =
|
|
418
|
-
this.sessionsFilePath =
|
|
419
|
-
this.stateFilePath =
|
|
420
|
-
this.metaFilePath =
|
|
421
|
-
this.currentTasksFilePath =
|
|
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
|
|
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
|
|
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
|
|
1046
|
+
return path4.join(os.homedir(), ".keepgoing");
|
|
746
1047
|
}
|
|
747
1048
|
function getGlobalLicensePath() {
|
|
748
|
-
return
|
|
1049
|
+
return path4.join(getGlobalLicenseDir(), LICENSE_FILE);
|
|
749
1050
|
}
|
|
750
1051
|
function getDeviceId() {
|
|
751
1052
|
const dir = getGlobalLicenseDir();
|
|
752
|
-
const filePath =
|
|
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 =
|
|
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 =
|
|
872
|
-
this.decisionsFilePath =
|
|
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
|
|
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
|
|
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 =
|
|
1172
|
-
this.metaFilePath =
|
|
1173
|
-
this.sessionsFilePath =
|
|
1174
|
-
this.decisionsFilePath =
|
|
1175
|
-
this.stateFilePath =
|
|
1176
|
-
this.currentTasksFilePath =
|
|
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
|
|
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 =
|
|
1778
|
+
const claudeDir2 = path7.join(os2.homedir(), ".claude");
|
|
1478
1779
|
return {
|
|
1479
1780
|
claudeDir: claudeDir2,
|
|
1480
|
-
settingsPath:
|
|
1481
|
-
claudeMdPath:
|
|
1781
|
+
settingsPath: path7.join(claudeDir2, "settings.json"),
|
|
1782
|
+
claudeMdPath: path7.join(claudeDir2, "CLAUDE.md")
|
|
1482
1783
|
};
|
|
1483
1784
|
}
|
|
1484
|
-
const claudeDir =
|
|
1485
|
-
const dotClaudeMdPath =
|
|
1486
|
-
const rootClaudeMdPath =
|
|
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:
|
|
1790
|
+
settingsPath: path7.join(claudeDir, "settings.json"),
|
|
1490
1791
|
claudeMdPath: fs5.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath
|
|
1491
1792
|
};
|
|
1492
1793
|
}
|
|
@@ -1543,7 +1844,6 @@ function setupProject(options) {
|
|
|
1543
1844
|
scope = "project",
|
|
1544
1845
|
sessionHooks = true,
|
|
1545
1846
|
claudeMd = true,
|
|
1546
|
-
hasProLicense = false,
|
|
1547
1847
|
statusline
|
|
1548
1848
|
} = options;
|
|
1549
1849
|
const messages = [];
|
|
@@ -1568,7 +1868,7 @@ function setupProject(options) {
|
|
|
1568
1868
|
messages.push(`Warning: ${conflict}`);
|
|
1569
1869
|
}
|
|
1570
1870
|
}
|
|
1571
|
-
if (scope === "project"
|
|
1871
|
+
if (scope === "project") {
|
|
1572
1872
|
const needsUpdate = settings.statusLine?.command && statusline?.isLegacy?.(settings.statusLine.command);
|
|
1573
1873
|
if (!settings.statusLine || needsUpdate) {
|
|
1574
1874
|
settings.statusLine = {
|
|
@@ -1599,7 +1899,7 @@ function setupProject(options) {
|
|
|
1599
1899
|
messages.push(`CLAUDE.md: KeepGoing section already present in ${mdLabel}, skipped`);
|
|
1600
1900
|
} else {
|
|
1601
1901
|
const updated = existing + CLAUDE_MD_SECTION;
|
|
1602
|
-
const mdDir =
|
|
1902
|
+
const mdDir = path7.dirname(claudeMdPath);
|
|
1603
1903
|
if (!fs5.existsSync(mdDir)) {
|
|
1604
1904
|
fs5.mkdirSync(mdDir, { recursive: true });
|
|
1605
1905
|
}
|
|
@@ -1701,12 +2001,16 @@ async function deactivateLicense(licenseKey, instanceId) {
|
|
|
1701
2001
|
}
|
|
1702
2002
|
|
|
1703
2003
|
// src/tools/getMomentum.ts
|
|
2004
|
+
import { z } from "zod";
|
|
1704
2005
|
function registerGetMomentum(server, reader, workspacePath) {
|
|
1705
2006
|
server.tool(
|
|
1706
2007
|
"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
|
-
|
|
2008
|
+
"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.",
|
|
2009
|
+
{
|
|
2010
|
+
tier: z.enum(["compact", "standard", "detailed", "full"]).optional().describe("Briefing detail level. compact (~150 tokens), standard (~400), detailed (~800), full (~1500). Default: standard."),
|
|
2011
|
+
model: z.string().optional().describe('Model name (e.g. "claude-opus-4") to auto-resolve tier. Ignored if tier is set.')
|
|
2012
|
+
},
|
|
2013
|
+
async ({ tier, model }) => {
|
|
1710
2014
|
if (!reader.exists()) {
|
|
1711
2015
|
return {
|
|
1712
2016
|
content: [
|
|
@@ -1717,6 +2021,46 @@ function registerGetMomentum(server, reader, workspacePath) {
|
|
|
1717
2021
|
]
|
|
1718
2022
|
};
|
|
1719
2023
|
}
|
|
2024
|
+
if (tier || model) {
|
|
2025
|
+
const gitBranch = reader.getCurrentBranch();
|
|
2026
|
+
const { session: lastSession2 } = reader.getScopedLastSession();
|
|
2027
|
+
const recentSessions = reader.getScopedRecentSessions(5);
|
|
2028
|
+
const state2 = reader.getState() ?? {};
|
|
2029
|
+
const sinceTimestamp = lastSession2?.timestamp;
|
|
2030
|
+
const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
|
|
2031
|
+
const decisions = reader.getScopedRecentDecisions(10);
|
|
2032
|
+
const allSessions = reader.getSessions();
|
|
2033
|
+
const fileConflicts = reader.detectFileConflicts();
|
|
2034
|
+
const branchOverlaps = reader.detectBranchOverlap();
|
|
2035
|
+
const briefing = generateEnrichedBriefing({
|
|
2036
|
+
tier,
|
|
2037
|
+
model,
|
|
2038
|
+
lastSession: lastSession2,
|
|
2039
|
+
recentSessions,
|
|
2040
|
+
projectState: state2,
|
|
2041
|
+
gitBranch,
|
|
2042
|
+
recentCommits,
|
|
2043
|
+
decisions,
|
|
2044
|
+
allTouchedFiles: lastSession2?.touchedFiles,
|
|
2045
|
+
allSessions,
|
|
2046
|
+
fileConflicts,
|
|
2047
|
+
branchOverlaps,
|
|
2048
|
+
isWorktree: reader.isWorktree
|
|
2049
|
+
});
|
|
2050
|
+
if (!briefing) {
|
|
2051
|
+
return {
|
|
2052
|
+
content: [
|
|
2053
|
+
{
|
|
2054
|
+
type: "text",
|
|
2055
|
+
text: "KeepGoing is set up but no session checkpoints exist yet."
|
|
2056
|
+
}
|
|
2057
|
+
]
|
|
2058
|
+
};
|
|
2059
|
+
}
|
|
2060
|
+
return {
|
|
2061
|
+
content: [{ type: "text", text: formatEnrichedBriefing(briefing) }]
|
|
2062
|
+
};
|
|
2063
|
+
}
|
|
1720
2064
|
const { session: lastSession, isFallback } = reader.getScopedLastSession();
|
|
1721
2065
|
const currentBranch = reader.getCurrentBranch();
|
|
1722
2066
|
if (!lastSession) {
|
|
@@ -1785,14 +2129,14 @@ function registerGetMomentum(server, reader, workspacePath) {
|
|
|
1785
2129
|
}
|
|
1786
2130
|
|
|
1787
2131
|
// src/tools/getSessionHistory.ts
|
|
1788
|
-
import { z } from "zod";
|
|
2132
|
+
import { z as z2 } from "zod";
|
|
1789
2133
|
function registerGetSessionHistory(server, reader) {
|
|
1790
2134
|
server.tool(
|
|
1791
2135
|
"get_session_history",
|
|
1792
2136
|
"Get recent session checkpoints. Returns a chronological list of what the developer worked on.",
|
|
1793
2137
|
{
|
|
1794
|
-
limit:
|
|
1795
|
-
branch:
|
|
2138
|
+
limit: z2.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)"),
|
|
2139
|
+
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
2140
|
},
|
|
1797
2141
|
async ({ limit, branch }) => {
|
|
1798
2142
|
if (!reader.exists()) {
|
|
@@ -1846,12 +2190,16 @@ function registerGetSessionHistory(server, reader) {
|
|
|
1846
2190
|
}
|
|
1847
2191
|
|
|
1848
2192
|
// src/tools/getReentryBriefing.ts
|
|
2193
|
+
import { z as z3 } from "zod";
|
|
1849
2194
|
function registerGetReentryBriefing(server, reader, workspacePath) {
|
|
1850
2195
|
server.tool(
|
|
1851
2196
|
"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
|
-
|
|
2197
|
+
"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.",
|
|
2198
|
+
{
|
|
2199
|
+
tier: z3.enum(["compact", "standard", "detailed", "full"]).optional().describe("Briefing detail level. compact (~150 tokens), standard (~400), detailed (~800), full (~1500). Default: standard."),
|
|
2200
|
+
model: z3.string().optional().describe('Model name (e.g. "claude-opus-4") to auto-resolve tier. Ignored if tier is set.')
|
|
2201
|
+
},
|
|
2202
|
+
async ({ tier, model }) => {
|
|
1855
2203
|
if (!reader.exists()) {
|
|
1856
2204
|
return {
|
|
1857
2205
|
content: [
|
|
@@ -1868,13 +2216,25 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
|
|
|
1868
2216
|
const state = reader.getState() ?? {};
|
|
1869
2217
|
const sinceTimestamp = lastSession?.timestamp;
|
|
1870
2218
|
const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath, sinceTimestamp) : [];
|
|
1871
|
-
const
|
|
2219
|
+
const decisions = reader.getScopedRecentDecisions(10);
|
|
2220
|
+
const allSessions = reader.getSessions();
|
|
2221
|
+
const fileConflicts = reader.detectFileConflicts();
|
|
2222
|
+
const branchOverlaps = reader.detectBranchOverlap();
|
|
2223
|
+
const briefing = generateEnrichedBriefing({
|
|
2224
|
+
tier,
|
|
2225
|
+
model,
|
|
1872
2226
|
lastSession,
|
|
1873
2227
|
recentSessions,
|
|
1874
|
-
state,
|
|
2228
|
+
projectState: state,
|
|
1875
2229
|
gitBranch,
|
|
1876
|
-
recentCommits
|
|
1877
|
-
|
|
2230
|
+
recentCommits,
|
|
2231
|
+
decisions,
|
|
2232
|
+
allTouchedFiles: lastSession?.touchedFiles,
|
|
2233
|
+
allSessions,
|
|
2234
|
+
fileConflicts,
|
|
2235
|
+
branchOverlaps,
|
|
2236
|
+
isWorktree: reader.isWorktree
|
|
2237
|
+
});
|
|
1878
2238
|
if (!briefing) {
|
|
1879
2239
|
return {
|
|
1880
2240
|
content: [
|
|
@@ -1885,55 +2245,31 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
|
|
|
1885
2245
|
]
|
|
1886
2246
|
};
|
|
1887
2247
|
}
|
|
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
2248
|
return {
|
|
1913
|
-
content: [{ type: "text", text:
|
|
2249
|
+
content: [{ type: "text", text: formatEnrichedBriefing(briefing) }]
|
|
1914
2250
|
};
|
|
1915
2251
|
}
|
|
1916
2252
|
);
|
|
1917
2253
|
}
|
|
1918
2254
|
|
|
1919
2255
|
// src/tools/saveCheckpoint.ts
|
|
1920
|
-
import
|
|
1921
|
-
import { z as
|
|
2256
|
+
import path8 from "path";
|
|
2257
|
+
import { z as z4 } from "zod";
|
|
1922
2258
|
function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
1923
2259
|
server.tool(
|
|
1924
2260
|
"save_checkpoint",
|
|
1925
2261
|
"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
2262
|
{
|
|
1927
|
-
summary:
|
|
1928
|
-
nextStep:
|
|
1929
|
-
blocker:
|
|
2263
|
+
summary: z4.string().describe("What was accomplished in this session"),
|
|
2264
|
+
nextStep: z4.string().optional().describe("What to do next"),
|
|
2265
|
+
blocker: z4.string().optional().describe("Any blocker preventing progress")
|
|
1930
2266
|
},
|
|
1931
2267
|
async ({ summary, nextStep, blocker }) => {
|
|
1932
2268
|
const lastSession = reader.getLastSession();
|
|
1933
2269
|
const gitBranch = getCurrentBranch(workspacePath);
|
|
1934
2270
|
const touchedFiles = getTouchedFiles(workspacePath);
|
|
1935
2271
|
const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);
|
|
1936
|
-
const projectName =
|
|
2272
|
+
const projectName = path8.basename(resolveStorageRoot(workspacePath));
|
|
1937
2273
|
const sessionId = generateSessionId({ workspaceRoot: workspacePath, branch: gitBranch ?? void 0, worktreePath: workspacePath });
|
|
1938
2274
|
const checkpoint = createCheckpoint({
|
|
1939
2275
|
summary,
|
|
@@ -1980,14 +2316,14 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
|
1980
2316
|
}
|
|
1981
2317
|
|
|
1982
2318
|
// src/tools/getDecisions.ts
|
|
1983
|
-
import { z as
|
|
2319
|
+
import { z as z5 } from "zod";
|
|
1984
2320
|
function registerGetDecisions(server, reader) {
|
|
1985
2321
|
server.tool(
|
|
1986
2322
|
"get_decisions",
|
|
1987
2323
|
"Get recent decision records. Returns detected high-signal commits with their category, confidence, and rationale.",
|
|
1988
2324
|
{
|
|
1989
|
-
limit:
|
|
1990
|
-
branch:
|
|
2325
|
+
limit: z5.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)"),
|
|
2326
|
+
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
2327
|
},
|
|
1992
2328
|
async ({ limit, branch }) => {
|
|
1993
2329
|
if (!reader.exists()) {
|
|
@@ -2145,18 +2481,18 @@ function registerGetCurrentTask(server, reader) {
|
|
|
2145
2481
|
}
|
|
2146
2482
|
|
|
2147
2483
|
// src/tools/setupProject.ts
|
|
2148
|
-
import { z as
|
|
2484
|
+
import { z as z6 } from "zod";
|
|
2149
2485
|
|
|
2150
2486
|
// src/cli/migrate.ts
|
|
2151
2487
|
import fs6 from "fs";
|
|
2152
2488
|
import os3 from "os";
|
|
2153
|
-
import
|
|
2489
|
+
import path9 from "path";
|
|
2154
2490
|
var STATUSLINE_CMD2 = "npx -y @keepgoingdev/mcp-server --statusline";
|
|
2155
2491
|
function isLegacyStatusline(command) {
|
|
2156
2492
|
return !command.includes("--statusline") && command.includes("keepgoing-statusline");
|
|
2157
2493
|
}
|
|
2158
2494
|
function migrateStatusline(wsPath) {
|
|
2159
|
-
const settingsPath =
|
|
2495
|
+
const settingsPath = path9.join(wsPath, ".claude", "settings.json");
|
|
2160
2496
|
if (!fs6.existsSync(settingsPath)) return void 0;
|
|
2161
2497
|
try {
|
|
2162
2498
|
const settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
|
|
@@ -2174,7 +2510,7 @@ function migrateStatusline(wsPath) {
|
|
|
2174
2510
|
}
|
|
2175
2511
|
}
|
|
2176
2512
|
function cleanupLegacyScript() {
|
|
2177
|
-
const legacyScript =
|
|
2513
|
+
const legacyScript = path9.join(os3.homedir(), ".claude", "keepgoing-statusline.sh");
|
|
2178
2514
|
if (fs6.existsSync(legacyScript)) {
|
|
2179
2515
|
try {
|
|
2180
2516
|
fs6.unlinkSync(legacyScript);
|
|
@@ -2189,18 +2525,16 @@ function registerSetupProject(server, workspacePath) {
|
|
|
2189
2525
|
"setup_project",
|
|
2190
2526
|
'Set up KeepGoing hooks and instructions. Use scope "user" for global setup (all projects) or "project" for per-project setup.',
|
|
2191
2527
|
{
|
|
2192
|
-
sessionHooks:
|
|
2193
|
-
claudeMd:
|
|
2194
|
-
scope:
|
|
2528
|
+
sessionHooks: z6.boolean().optional().default(true).describe("Add session hooks to settings.json"),
|
|
2529
|
+
claudeMd: z6.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md"),
|
|
2530
|
+
scope: z6.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)')
|
|
2195
2531
|
},
|
|
2196
2532
|
async ({ sessionHooks, claudeMd, scope }) => {
|
|
2197
|
-
const hasProLicense = process.env.KEEPGOING_PRO_BYPASS === "1" || !!getLicenseForFeature("session-awareness");
|
|
2198
2533
|
const result = setupProject({
|
|
2199
2534
|
workspacePath,
|
|
2200
2535
|
scope,
|
|
2201
2536
|
sessionHooks,
|
|
2202
2537
|
claudeMd,
|
|
2203
|
-
hasProLicense,
|
|
2204
2538
|
statusline: {
|
|
2205
2539
|
isLegacy: isLegacyStatusline,
|
|
2206
2540
|
cleanup: cleanupLegacyScript
|
|
@@ -2217,12 +2551,12 @@ function registerSetupProject(server, workspacePath) {
|
|
|
2217
2551
|
}
|
|
2218
2552
|
|
|
2219
2553
|
// src/tools/activateLicense.ts
|
|
2220
|
-
import { z as
|
|
2554
|
+
import { z as z7 } from "zod";
|
|
2221
2555
|
function registerActivateLicense(server) {
|
|
2222
2556
|
server.tool(
|
|
2223
2557
|
"activate_license",
|
|
2224
2558
|
"Activate a KeepGoing Pro license on this device. Unlocks add-ons like Decision Detection and Session Awareness.",
|
|
2225
|
-
{ license_key:
|
|
2559
|
+
{ license_key: z7.string().describe("Your KeepGoing Pro license key") },
|
|
2226
2560
|
async ({ license_key }) => {
|
|
2227
2561
|
const store = readLicenseStore();
|
|
2228
2562
|
const existingForKey = store.licenses.find(
|
|
@@ -2296,13 +2630,13 @@ function registerActivateLicense(server) {
|
|
|
2296
2630
|
}
|
|
2297
2631
|
|
|
2298
2632
|
// src/tools/deactivateLicense.ts
|
|
2299
|
-
import { z as
|
|
2633
|
+
import { z as z8 } from "zod";
|
|
2300
2634
|
function registerDeactivateLicense(server) {
|
|
2301
2635
|
server.tool(
|
|
2302
2636
|
"deactivate_license",
|
|
2303
2637
|
"Deactivate the KeepGoing Pro license on this device.",
|
|
2304
2638
|
{
|
|
2305
|
-
license_key:
|
|
2639
|
+
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
2640
|
},
|
|
2307
2641
|
async ({ license_key }) => {
|
|
2308
2642
|
const store = readLicenseStore();
|
|
@@ -2368,6 +2702,48 @@ function registerDeactivateLicense(server) {
|
|
|
2368
2702
|
);
|
|
2369
2703
|
}
|
|
2370
2704
|
|
|
2705
|
+
// src/tools/continueOn.ts
|
|
2706
|
+
import { z as z9 } from "zod";
|
|
2707
|
+
function registerContinueOn(server, reader, workspacePath) {
|
|
2708
|
+
server.tool(
|
|
2709
|
+
"continue_on",
|
|
2710
|
+
"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.",
|
|
2711
|
+
{
|
|
2712
|
+
target: z9.enum(["chatgpt", "gemini", "copilot", "claude", "general"]).optional().describe("Target AI tool (currently used for future format tuning)"),
|
|
2713
|
+
include_commits: z9.boolean().default(true).describe("Include recent commit messages in the prompt"),
|
|
2714
|
+
include_files: z9.boolean().default(true).describe("Include touched file paths in the prompt")
|
|
2715
|
+
},
|
|
2716
|
+
async ({ target, include_commits, include_files }) => {
|
|
2717
|
+
if (!reader.exists()) {
|
|
2718
|
+
return {
|
|
2719
|
+
content: [{
|
|
2720
|
+
type: "text",
|
|
2721
|
+
text: "No KeepGoing data found. Save a checkpoint first to use Continue On."
|
|
2722
|
+
}]
|
|
2723
|
+
};
|
|
2724
|
+
}
|
|
2725
|
+
const context = gatherContinueOnContext(reader, workspacePath);
|
|
2726
|
+
if (!context.lastCheckpoint && !context.briefing) {
|
|
2727
|
+
return {
|
|
2728
|
+
content: [{
|
|
2729
|
+
type: "text",
|
|
2730
|
+
text: "No session data available. Save a checkpoint first."
|
|
2731
|
+
}]
|
|
2732
|
+
};
|
|
2733
|
+
}
|
|
2734
|
+
const formatOpts = {
|
|
2735
|
+
target,
|
|
2736
|
+
includeCommits: include_commits,
|
|
2737
|
+
includeFiles: include_files
|
|
2738
|
+
};
|
|
2739
|
+
const prompt = formatContinueOnPrompt(context, formatOpts);
|
|
2740
|
+
return {
|
|
2741
|
+
content: [{ type: "text", text: prompt }]
|
|
2742
|
+
};
|
|
2743
|
+
}
|
|
2744
|
+
);
|
|
2745
|
+
}
|
|
2746
|
+
|
|
2371
2747
|
// src/prompts/resume.ts
|
|
2372
2748
|
function registerResumePrompt(server) {
|
|
2373
2749
|
server.prompt(
|
|
@@ -2533,7 +2909,7 @@ async function handlePrintCurrent() {
|
|
|
2533
2909
|
}
|
|
2534
2910
|
|
|
2535
2911
|
// src/cli/saveCheckpoint.ts
|
|
2536
|
-
import
|
|
2912
|
+
import path10 from "path";
|
|
2537
2913
|
async function handleSaveCheckpoint() {
|
|
2538
2914
|
const wsPath = resolveWsPath();
|
|
2539
2915
|
const reader = new KeepGoingReader(wsPath);
|
|
@@ -2561,9 +2937,9 @@ async function handleSaveCheckpoint() {
|
|
|
2561
2937
|
sessionStartTime: lastSession?.timestamp ?? now,
|
|
2562
2938
|
lastActivityTime: now
|
|
2563
2939
|
});
|
|
2564
|
-
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) =>
|
|
2940
|
+
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path10.basename(f)).join(", ")}`;
|
|
2565
2941
|
const nextStep = buildSmartNextStep(events);
|
|
2566
|
-
const projectName =
|
|
2942
|
+
const projectName = path10.basename(resolveStorageRoot(wsPath));
|
|
2567
2943
|
const sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
2568
2944
|
const checkpoint = createCheckpoint({
|
|
2569
2945
|
summary,
|
|
@@ -2604,6 +2980,126 @@ async function handleSaveCheckpoint() {
|
|
|
2604
2980
|
process.exit(0);
|
|
2605
2981
|
}
|
|
2606
2982
|
|
|
2983
|
+
// src/cli/transcriptUtils.ts
|
|
2984
|
+
import fs7 from "fs";
|
|
2985
|
+
var TAIL_READ_BYTES = 8192;
|
|
2986
|
+
var TOOL_VERB_MAP = {
|
|
2987
|
+
Edit: "editing",
|
|
2988
|
+
MultiEdit: "editing",
|
|
2989
|
+
Write: "editing",
|
|
2990
|
+
Read: "researching",
|
|
2991
|
+
Glob: "researching",
|
|
2992
|
+
Grep: "researching",
|
|
2993
|
+
Bash: "running",
|
|
2994
|
+
Agent: "delegating",
|
|
2995
|
+
WebFetch: "browsing",
|
|
2996
|
+
WebSearch: "browsing",
|
|
2997
|
+
TodoWrite: "planning"
|
|
2998
|
+
};
|
|
2999
|
+
function truncateAtWord(text, max) {
|
|
3000
|
+
if (text.length <= max) return text;
|
|
3001
|
+
const cut = text.slice(0, max);
|
|
3002
|
+
const lastSpace = cut.lastIndexOf(" ");
|
|
3003
|
+
return (lastSpace > max / 2 ? cut.slice(0, lastSpace) : cut.slice(0, max - 1)) + "\u2026";
|
|
3004
|
+
}
|
|
3005
|
+
var FILLER_PREFIX_RE = /^(i want to|can you|please|let['']?s|could you|help me|i need to|i['']d like to|implement the following plan[:\s]*|implement this plan[:\s]*)\s*/i;
|
|
3006
|
+
var MARKDOWN_HEADING_RE = /^#+\s+/;
|
|
3007
|
+
function extractTextFromContent(content) {
|
|
3008
|
+
if (typeof content === "string") return content;
|
|
3009
|
+
if (!Array.isArray(content)) return "";
|
|
3010
|
+
let text = "";
|
|
3011
|
+
for (const part of content) {
|
|
3012
|
+
if (part.type === "text" && typeof part.text === "string") {
|
|
3013
|
+
text += part.text + " ";
|
|
3014
|
+
}
|
|
3015
|
+
}
|
|
3016
|
+
return text.trim();
|
|
3017
|
+
}
|
|
3018
|
+
function isUserEntry(entry) {
|
|
3019
|
+
return entry.type === "user" && entry.message?.role === "user";
|
|
3020
|
+
}
|
|
3021
|
+
function getToolUseFromEntry(entry) {
|
|
3022
|
+
const content = entry.message?.content;
|
|
3023
|
+
if (!Array.isArray(content)) return null;
|
|
3024
|
+
for (const part of content.slice().reverse()) {
|
|
3025
|
+
if (part.type === "tool_use" && typeof part.name === "string") {
|
|
3026
|
+
return part.name;
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
return null;
|
|
3030
|
+
}
|
|
3031
|
+
function isAssistantEntry(entry) {
|
|
3032
|
+
return entry.message?.role === "assistant";
|
|
3033
|
+
}
|
|
3034
|
+
function extractSessionLabel(transcriptPath) {
|
|
3035
|
+
if (!transcriptPath || !fs7.existsSync(transcriptPath)) return null;
|
|
3036
|
+
try {
|
|
3037
|
+
const raw = fs7.readFileSync(transcriptPath, "utf-8");
|
|
3038
|
+
for (const line of raw.split("\n")) {
|
|
3039
|
+
const trimmed = line.trim();
|
|
3040
|
+
if (!trimmed) continue;
|
|
3041
|
+
let entry;
|
|
3042
|
+
try {
|
|
3043
|
+
entry = JSON.parse(trimmed);
|
|
3044
|
+
} catch {
|
|
3045
|
+
continue;
|
|
3046
|
+
}
|
|
3047
|
+
if (!isUserEntry(entry)) continue;
|
|
3048
|
+
let text = extractTextFromContent(entry.message?.content);
|
|
3049
|
+
if (!text) continue;
|
|
3050
|
+
if (text.startsWith("[") || /^<[a-z][\w-]*>/.test(text)) continue;
|
|
3051
|
+
text = text.replace(/@[\w./\-]+/g, "").trim();
|
|
3052
|
+
text = text.replace(FILLER_PREFIX_RE, "").trim();
|
|
3053
|
+
text = text.replace(MARKDOWN_HEADING_RE, "").trim();
|
|
3054
|
+
text = text.replace(/\s+/g, " ").trim();
|
|
3055
|
+
if (text.length < 20) continue;
|
|
3056
|
+
if (text.length > 80) {
|
|
3057
|
+
text = text.slice(0, 80);
|
|
3058
|
+
}
|
|
3059
|
+
return text;
|
|
3060
|
+
}
|
|
3061
|
+
} catch {
|
|
3062
|
+
}
|
|
3063
|
+
return null;
|
|
3064
|
+
}
|
|
3065
|
+
function extractCurrentAction(transcriptPath) {
|
|
3066
|
+
if (!transcriptPath || !fs7.existsSync(transcriptPath)) return null;
|
|
3067
|
+
try {
|
|
3068
|
+
const stat = fs7.statSync(transcriptPath);
|
|
3069
|
+
const fileSize = stat.size;
|
|
3070
|
+
if (fileSize === 0) return null;
|
|
3071
|
+
const readSize = Math.min(fileSize, TAIL_READ_BYTES);
|
|
3072
|
+
const offset = fileSize - readSize;
|
|
3073
|
+
const buf = Buffer.alloc(readSize);
|
|
3074
|
+
const fd = fs7.openSync(transcriptPath, "r");
|
|
3075
|
+
try {
|
|
3076
|
+
fs7.readSync(fd, buf, 0, readSize, offset);
|
|
3077
|
+
} finally {
|
|
3078
|
+
fs7.closeSync(fd);
|
|
3079
|
+
}
|
|
3080
|
+
const tail = buf.toString("utf-8");
|
|
3081
|
+
const lines = tail.split("\n").reverse();
|
|
3082
|
+
for (const line of lines) {
|
|
3083
|
+
const trimmed = line.trim();
|
|
3084
|
+
if (!trimmed) continue;
|
|
3085
|
+
let entry;
|
|
3086
|
+
try {
|
|
3087
|
+
entry = JSON.parse(trimmed);
|
|
3088
|
+
} catch {
|
|
3089
|
+
continue;
|
|
3090
|
+
}
|
|
3091
|
+
if (!isAssistantEntry(entry)) continue;
|
|
3092
|
+
const toolName = getToolUseFromEntry(entry);
|
|
3093
|
+
if (toolName) {
|
|
3094
|
+
return TOOL_VERB_MAP[toolName] ?? "working";
|
|
3095
|
+
}
|
|
3096
|
+
return "done";
|
|
3097
|
+
}
|
|
3098
|
+
} catch {
|
|
3099
|
+
}
|
|
3100
|
+
return null;
|
|
3101
|
+
}
|
|
3102
|
+
|
|
2607
3103
|
// src/cli/updateTask.ts
|
|
2608
3104
|
async function handleUpdateTask() {
|
|
2609
3105
|
const args = process.argv.slice(2);
|
|
@@ -2655,7 +3151,8 @@ async function handleUpdateTaskFromHook() {
|
|
|
2655
3151
|
const writer = new KeepGoingWriter(wsPath);
|
|
2656
3152
|
const existing = writer.readCurrentTasks();
|
|
2657
3153
|
const sessionIdFromHook = hookData.session_id;
|
|
2658
|
-
const
|
|
3154
|
+
const existingSession = sessionIdFromHook ? existing.find((t) => t.sessionId === sessionIdFromHook) : void 0;
|
|
3155
|
+
const cachedBranch = existingSession?.branch;
|
|
2659
3156
|
const branch = cachedBranch ?? getCurrentBranch(wsPath) ?? void 0;
|
|
2660
3157
|
const task = {
|
|
2661
3158
|
taskSummary: fileName ? `${toolName} ${fileName}` : `Used ${toolName}`,
|
|
@@ -2667,6 +3164,10 @@ async function handleUpdateTaskFromHook() {
|
|
|
2667
3164
|
};
|
|
2668
3165
|
const sessionId = hookData.session_id || generateSessionId({ ...task, workspaceRoot: wsPath });
|
|
2669
3166
|
task.sessionId = sessionId;
|
|
3167
|
+
if (!existingSession?.sessionLabel && hookData.transcript_path) {
|
|
3168
|
+
const label = extractSessionLabel(hookData.transcript_path);
|
|
3169
|
+
if (label) task.sessionLabel = label;
|
|
3170
|
+
}
|
|
2670
3171
|
writer.upsertSession(task);
|
|
2671
3172
|
} catch {
|
|
2672
3173
|
}
|
|
@@ -2676,8 +3177,8 @@ async function handleUpdateTaskFromHook() {
|
|
|
2676
3177
|
}
|
|
2677
3178
|
|
|
2678
3179
|
// src/cli/statusline.ts
|
|
2679
|
-
import
|
|
2680
|
-
import
|
|
3180
|
+
import fs8 from "fs";
|
|
3181
|
+
import path11 from "path";
|
|
2681
3182
|
var STDIN_TIMEOUT_MS2 = 3e3;
|
|
2682
3183
|
async function handleStatusline() {
|
|
2683
3184
|
const chunks = [];
|
|
@@ -2691,36 +3192,43 @@ async function handleStatusline() {
|
|
|
2691
3192
|
clearTimeout(timeout);
|
|
2692
3193
|
try {
|
|
2693
3194
|
const raw = Buffer.concat(chunks).toString("utf-8").trim();
|
|
2694
|
-
if (!raw)
|
|
2695
|
-
process.exit(0);
|
|
2696
|
-
}
|
|
3195
|
+
if (!raw) process.exit(0);
|
|
2697
3196
|
const input = JSON.parse(raw);
|
|
2698
3197
|
const dir = input.workspace?.current_dir ?? input.cwd;
|
|
2699
|
-
if (!dir)
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
3198
|
+
if (!dir) process.exit(0);
|
|
3199
|
+
const transcriptPath = input.transcript_path;
|
|
3200
|
+
const sessionId = input.session_id;
|
|
3201
|
+
let label = null;
|
|
3202
|
+
if (input.agent?.name) {
|
|
3203
|
+
label = input.agent.name;
|
|
3204
|
+
}
|
|
3205
|
+
if (!label) {
|
|
3206
|
+
try {
|
|
3207
|
+
const gitRoot = findGitRoot(dir);
|
|
3208
|
+
const tasksFile = path11.join(gitRoot, ".keepgoing", "current-tasks.json");
|
|
3209
|
+
if (fs8.existsSync(tasksFile)) {
|
|
3210
|
+
const data = JSON.parse(fs8.readFileSync(tasksFile, "utf-8"));
|
|
3211
|
+
const tasks = pruneStaleTasks(data.tasks ?? []);
|
|
3212
|
+
const match = sessionId ? tasks.find((t) => t.sessionId === sessionId) : void 0;
|
|
3213
|
+
if (match?.sessionLabel) {
|
|
3214
|
+
label = match.sessionLabel;
|
|
3215
|
+
}
|
|
3216
|
+
}
|
|
3217
|
+
} catch {
|
|
3218
|
+
}
|
|
2712
3219
|
}
|
|
2713
|
-
|
|
2714
|
-
|
|
2715
|
-
const summary = task.taskSummary;
|
|
2716
|
-
if (!summary) {
|
|
2717
|
-
process.exit(0);
|
|
3220
|
+
if (!label && transcriptPath) {
|
|
3221
|
+
label = extractSessionLabel(transcriptPath);
|
|
2718
3222
|
}
|
|
2719
|
-
if (
|
|
2720
|
-
|
|
3223
|
+
if (!label) process.exit(0);
|
|
3224
|
+
const action = transcriptPath ? extractCurrentAction(transcriptPath) : null;
|
|
3225
|
+
const budget = action ? 40 : 55;
|
|
3226
|
+
const displayLabel = truncateAtWord(label, budget);
|
|
3227
|
+
if (action) {
|
|
3228
|
+
process.stdout.write(`[KG] ${displayLabel} \xB7 ${action}
|
|
2721
3229
|
`);
|
|
2722
3230
|
} else {
|
|
2723
|
-
process.stdout.write(`[KG] ${
|
|
3231
|
+
process.stdout.write(`[KG] ${displayLabel}
|
|
2724
3232
|
`);
|
|
2725
3233
|
}
|
|
2726
3234
|
} catch {
|
|
@@ -2730,6 +3238,22 @@ async function handleStatusline() {
|
|
|
2730
3238
|
process.stdin.resume();
|
|
2731
3239
|
}
|
|
2732
3240
|
|
|
3241
|
+
// src/cli/continueOn.ts
|
|
3242
|
+
async function handleContinueOn() {
|
|
3243
|
+
const wsPath = resolveWsPath();
|
|
3244
|
+
const reader = new KeepGoingReader(wsPath);
|
|
3245
|
+
if (!reader.exists()) {
|
|
3246
|
+
process.exit(0);
|
|
3247
|
+
}
|
|
3248
|
+
const context = gatherContinueOnContext(reader, wsPath);
|
|
3249
|
+
if (!context.lastCheckpoint && !context.briefing) {
|
|
3250
|
+
process.exit(0);
|
|
3251
|
+
}
|
|
3252
|
+
const prompt = formatContinueOnPrompt(context);
|
|
3253
|
+
console.log(prompt);
|
|
3254
|
+
process.exit(0);
|
|
3255
|
+
}
|
|
3256
|
+
|
|
2733
3257
|
// src/index.ts
|
|
2734
3258
|
var CLI_HANDLERS = {
|
|
2735
3259
|
"--print-momentum": handlePrintMomentum,
|
|
@@ -2737,7 +3261,8 @@ var CLI_HANDLERS = {
|
|
|
2737
3261
|
"--update-task": handleUpdateTask,
|
|
2738
3262
|
"--update-task-from-hook": handleUpdateTaskFromHook,
|
|
2739
3263
|
"--print-current": handlePrintCurrent,
|
|
2740
|
-
"--statusline": handleStatusline
|
|
3264
|
+
"--statusline": handleStatusline,
|
|
3265
|
+
"--continue-on": handleContinueOn
|
|
2741
3266
|
};
|
|
2742
3267
|
var flag = process.argv.slice(2).find((a) => a in CLI_HANDLERS);
|
|
2743
3268
|
if (flag) {
|
|
@@ -2755,6 +3280,7 @@ if (flag) {
|
|
|
2755
3280
|
registerGetDecisions(server, reader);
|
|
2756
3281
|
registerGetCurrentTask(server, reader);
|
|
2757
3282
|
registerSaveCheckpoint(server, reader, workspacePath);
|
|
3283
|
+
registerContinueOn(server, reader, workspacePath);
|
|
2758
3284
|
registerSetupProject(server, workspacePath);
|
|
2759
3285
|
registerActivateLicense(server);
|
|
2760
3286
|
registerDeactivateLicense(server);
|