@keepgoingdev/mcp-server 0.2.0 → 0.2.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,13 +1,13 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import path5 from "path";
4
+ import path6 from "path";
5
5
  import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
6
6
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
7
7
 
8
8
  // src/storage.ts
9
- import fs2 from "fs";
10
- import path2 from "path";
9
+ import fs3 from "fs";
10
+ import path3 from "path";
11
11
 
12
12
  // ../../packages/shared/src/session.ts
13
13
  import { randomUUID } from "crypto";
@@ -21,6 +21,20 @@ function createCheckpoint(fields) {
21
21
  ...fields
22
22
  };
23
23
  }
24
+ function createDecisionRecord(fields) {
25
+ return {
26
+ id: randomUUID(),
27
+ ...fields
28
+ };
29
+ }
30
+ function createEmptyProjectDecisions(projectName) {
31
+ return {
32
+ version: 1,
33
+ project: projectName,
34
+ decisions: [],
35
+ lastDecisionId: void 0
36
+ };
37
+ }
24
38
 
25
39
  // ../../packages/shared/src/timeUtils.ts
26
40
  function formatRelativeTime(timestamp) {
@@ -63,6 +77,18 @@ function formatRelativeTime(timestamp) {
63
77
  import { execFileSync, execFile } from "child_process";
64
78
  import { promisify } from "util";
65
79
  var execFileAsync = promisify(execFile);
80
+ function findGitRoot(startPath) {
81
+ try {
82
+ const result = execFileSync("git", ["rev-parse", "--show-toplevel"], {
83
+ cwd: startPath,
84
+ encoding: "utf-8",
85
+ timeout: 5e3
86
+ });
87
+ return result.trim() || startPath;
88
+ } catch {
89
+ return startPath;
90
+ }
91
+ }
66
92
  function getCurrentBranch(workspacePath2) {
67
93
  try {
68
94
  const result = execFileSync("git", ["rev-parse", "--abbrev-ref", "HEAD"], {
@@ -101,6 +127,18 @@ function getCommitsSince(workspacePath2, sinceTimestamp) {
101
127
  function getCommitMessagesSince(workspacePath2, sinceTimestamp) {
102
128
  return getGitLogSince(workspacePath2, "%s", sinceTimestamp);
103
129
  }
130
+ function getHeadCommitHash(workspacePath2) {
131
+ try {
132
+ const result = execFileSync("git", ["rev-parse", "HEAD"], {
133
+ cwd: workspacePath2,
134
+ encoding: "utf-8",
135
+ timeout: 5e3
136
+ });
137
+ return result.trim() || void 0;
138
+ } catch {
139
+ return void 0;
140
+ }
141
+ }
104
142
  function getTouchedFiles(workspacePath2) {
105
143
  try {
106
144
  const result = execFileSync("git", ["status", "--porcelain"], {
@@ -371,48 +409,354 @@ var KeepGoingWriter = class {
371
409
  lastActivityAt: checkpoint.timestamp
372
410
  };
373
411
  fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
412
+ this.updateMeta(checkpoint.timestamp);
413
+ }
414
+ updateMeta(timestamp) {
374
415
  let meta;
375
416
  try {
376
417
  if (fs.existsSync(this.metaFilePath)) {
377
418
  meta = JSON.parse(fs.readFileSync(this.metaFilePath, "utf-8"));
378
- meta.lastUpdated = checkpoint.timestamp;
419
+ meta.lastUpdated = timestamp;
379
420
  } else {
380
421
  meta = {
381
422
  projectId: randomUUID2(),
382
- createdAt: checkpoint.timestamp,
383
- lastUpdated: checkpoint.timestamp
423
+ createdAt: timestamp,
424
+ lastUpdated: timestamp
384
425
  };
385
426
  }
386
427
  } catch {
387
428
  meta = {
388
429
  projectId: randomUUID2(),
389
- createdAt: checkpoint.timestamp,
390
- lastUpdated: checkpoint.timestamp
430
+ createdAt: timestamp,
431
+ lastUpdated: timestamp
391
432
  };
392
433
  }
393
434
  fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
394
435
  }
395
436
  };
396
437
 
397
- // src/storage.ts
438
+ // ../../packages/shared/src/decisionStorage.ts
439
+ import fs2 from "fs";
440
+ import path2 from "path";
441
+
442
+ // ../../packages/shared/src/featureGate.ts
443
+ var DefaultFeatureGate = class {
444
+ isEnabled(_feature) {
445
+ return true;
446
+ }
447
+ };
448
+ var currentGate = new DefaultFeatureGate();
449
+ function isDecisionsEnabled() {
450
+ return currentGate.isEnabled("decisions");
451
+ }
452
+
453
+ // ../../packages/shared/src/decisionStorage.ts
398
454
  var STORAGE_DIR2 = ".keepgoing";
455
+ var DECISIONS_FILE = "decisions.json";
456
+ var MAX_DECISIONS = 100;
457
+ var DecisionStorage = class {
458
+ storagePath;
459
+ decisionsFilePath;
460
+ constructor(workspacePath2) {
461
+ this.storagePath = path2.join(workspacePath2, STORAGE_DIR2);
462
+ this.decisionsFilePath = path2.join(this.storagePath, DECISIONS_FILE);
463
+ }
464
+ ensureStorageDir() {
465
+ if (!fs2.existsSync(this.storagePath)) {
466
+ fs2.mkdirSync(this.storagePath, { recursive: true });
467
+ }
468
+ }
469
+ getProjectName() {
470
+ return path2.basename(path2.dirname(this.storagePath));
471
+ }
472
+ load() {
473
+ try {
474
+ if (!fs2.existsSync(this.decisionsFilePath)) {
475
+ return createEmptyProjectDecisions(this.getProjectName());
476
+ }
477
+ const raw = fs2.readFileSync(this.decisionsFilePath, "utf-8");
478
+ const data = JSON.parse(raw);
479
+ return data;
480
+ } catch {
481
+ return createEmptyProjectDecisions(this.getProjectName());
482
+ }
483
+ }
484
+ save(decisions) {
485
+ this.ensureStorageDir();
486
+ const content = JSON.stringify(decisions, null, 2);
487
+ fs2.writeFileSync(this.decisionsFilePath, content, "utf-8");
488
+ }
489
+ /**
490
+ * Save a decision record. Returns true if saved, false if gated or error.
491
+ */
492
+ saveDecision(decision) {
493
+ if (!isDecisionsEnabled()) {
494
+ return false;
495
+ }
496
+ const data = this.load();
497
+ data.decisions.push(decision);
498
+ data.lastDecisionId = decision.id;
499
+ if (data.decisions.length > MAX_DECISIONS) {
500
+ data.decisions = data.decisions.slice(-MAX_DECISIONS);
501
+ }
502
+ this.save(data);
503
+ return true;
504
+ }
505
+ getLastDecision() {
506
+ const data = this.load();
507
+ if (data.decisions.length === 0) {
508
+ return void 0;
509
+ }
510
+ if (data.lastDecisionId) {
511
+ const found = data.decisions.find((d) => d.id === data.lastDecisionId);
512
+ if (found) {
513
+ return found;
514
+ }
515
+ }
516
+ return data.decisions[data.decisions.length - 1];
517
+ }
518
+ getAllDecisions() {
519
+ const data = this.load();
520
+ return data.decisions;
521
+ }
522
+ getRecentDecisions(limit = 10) {
523
+ const data = this.load();
524
+ return data.decisions.slice(-limit).reverse();
525
+ }
526
+ /**
527
+ * Update a decision record. Returns true if updated, false if gated or not found.
528
+ */
529
+ updateDecision(id, updates) {
530
+ if (!isDecisionsEnabled()) {
531
+ return false;
532
+ }
533
+ const data = this.load();
534
+ const index = data.decisions.findIndex((d) => d.id === id);
535
+ if (index === -1) {
536
+ return false;
537
+ }
538
+ data.decisions[index] = {
539
+ ...data.decisions[index],
540
+ ...updates
541
+ };
542
+ this.save(data);
543
+ return true;
544
+ }
545
+ getStoragePath() {
546
+ return this.storagePath;
547
+ }
548
+ };
549
+
550
+ // ../../packages/shared/src/decisionDetection.ts
551
+ var MESSAGE_KEYWORDS = [
552
+ /\bdeploy\b/,
553
+ /\bworkflow\b/,
554
+ /\bmigrate\b/,
555
+ /\bmigration\b/,
556
+ /\bredirect\b/,
557
+ /\borigin\b/,
558
+ /\btrusted\b/,
559
+ /\boidc\b/,
560
+ /\boauth\b/,
561
+ /\bpostgres\b/,
562
+ /\bsupabase\b/,
563
+ /\bdocker\b/,
564
+ /\bterraform\b/,
565
+ /\bk8s\b/
566
+ ];
567
+ var HIGH_SIGNAL_TYPES = /* @__PURE__ */ new Set([
568
+ "ci",
569
+ "build",
570
+ "infra",
571
+ "ops",
572
+ "auth",
573
+ "oauth",
574
+ "oidc",
575
+ "migration",
576
+ "db"
577
+ ]);
578
+ var NEGATIVE_PATH_PATTERNS = [/\.lock$/, /generated/i, /(?:^|\/)dist\//];
579
+ function matchHighSignalPath(filePath) {
580
+ if (/^\.github\/workflows\//i.test(filePath)) {
581
+ return { label: ".github/workflows", tier: "infra" };
582
+ }
583
+ if (/(?:^|\/)fly\.toml$/i.test(filePath)) {
584
+ return { label: "fly.toml", tier: "infra" };
585
+ }
586
+ if (/(?:^|\/)Dockerfile/i.test(filePath)) {
587
+ return { label: "Dockerfile", tier: "infra" };
588
+ }
589
+ if (/(?:^|\/)docker-compose/i.test(filePath)) {
590
+ return { label: "docker-compose", tier: "infra" };
591
+ }
592
+ if (/(?:^|\/)terraform\//i.test(filePath)) {
593
+ return { label: "terraform/", tier: "infra" };
594
+ }
595
+ if (/(?:^|\/)k8s\//i.test(filePath)) {
596
+ return { label: "k8s/", tier: "infra" };
597
+ }
598
+ if (/(?:^|\/)supabase\//i.test(filePath)) {
599
+ return { label: "supabase/", tier: "infra" };
600
+ }
601
+ if (/(?:^|\/)migrations?(?:\/|\.)/i.test(filePath)) {
602
+ return { label: "*migration*", tier: "infra" };
603
+ }
604
+ if (/(?:^|\/)auth(?:\/|\.)/i.test(filePath)) {
605
+ return { label: "*auth*", tier: "contextual" };
606
+ }
607
+ if (/(?:^|\/)oidc(?:\/|\.)/i.test(filePath)) {
608
+ return { label: "*oidc*", tier: "contextual" };
609
+ }
610
+ if (/(?:^|\/)oauth(?:\/|\.)/i.test(filePath)) {
611
+ return { label: "*oauth*", tier: "contextual" };
612
+ }
613
+ if (/(?:^|\/)redirect(?:\/|\.)/i.test(filePath)) {
614
+ return { label: "*redirect*", tier: "contextual" };
615
+ }
616
+ if (/(?:^|\/)origin(?:\/|\.)/i.test(filePath)) {
617
+ return { label: "*origin*", tier: "contextual" };
618
+ }
619
+ return null;
620
+ }
621
+ function parseConventionalCommit(message) {
622
+ const match = /^([a-z]+)(\(([^)]+)\))?\s*(!)?:/.exec(message.trim());
623
+ if (!match) {
624
+ return {};
625
+ }
626
+ return { type: match[1], scope: match[3], breaking: match[4] === "!" };
627
+ }
628
+ function inferCategory(matchedKeywords, matchedTypes, matchedPaths) {
629
+ const all = [...matchedKeywords, ...matchedTypes, ...matchedPaths].join(" ").toLowerCase();
630
+ if (/auth|oidc|oauth|redirect|origin|trusted/.test(all)) {
631
+ return "auth";
632
+ }
633
+ if (/migrat|postgres|db|supabase/.test(all)) {
634
+ return "migration";
635
+ }
636
+ if (/ci|workflow|build|deploy/.test(all)) {
637
+ return "deploy";
638
+ }
639
+ if (/fly|docker|k8s|terraform|infra|ops/.test(all)) {
640
+ return "infra";
641
+ }
642
+ return "unknown";
643
+ }
644
+ function classifyCommit(commit) {
645
+ const { message, filesChanged } = commit;
646
+ const messageLower = message.toLowerCase();
647
+ const parsed = parseConventionalCommit(message);
648
+ const type = commit.type ?? parsed.type;
649
+ const scope = commit.scope ?? parsed.scope;
650
+ const reasons = [];
651
+ let confidence = 0;
652
+ const matchedKeywords = MESSAGE_KEYWORDS.filter((kw) => kw.test(messageLower));
653
+ if (matchedKeywords.length > 0) {
654
+ confidence += 0.3;
655
+ const labels = matchedKeywords.slice(0, 3).map((kw) => kw.source.replace(/\\b/g, ""));
656
+ reasons.push(`commit message contains: ${labels.join(", ")}`);
657
+ }
658
+ const matchedTypes = [];
659
+ if (type && HIGH_SIGNAL_TYPES.has(type)) {
660
+ matchedTypes.push(`type:${type}`);
661
+ confidence += 0.35;
662
+ reasons.push(`conventional commit type '${type}' is high-signal`);
663
+ }
664
+ if (scope && HIGH_SIGNAL_TYPES.has(scope)) {
665
+ matchedTypes.push(`scope:${scope}`);
666
+ confidence += 0.25;
667
+ reasons.push(`conventional commit scope '${scope}' is high-signal`);
668
+ }
669
+ if (parsed.breaking) {
670
+ confidence += 0.4;
671
+ reasons.push("breaking change indicated by ! marker");
672
+ }
673
+ const matchedPaths = [];
674
+ let bestTier = null;
675
+ for (const file of filesChanged) {
676
+ const pm = matchHighSignalPath(file);
677
+ if (pm && !matchedPaths.includes(pm.label)) {
678
+ matchedPaths.push(pm.label);
679
+ if (bestTier !== "infra") {
680
+ bestTier = pm.tier;
681
+ }
682
+ }
683
+ }
684
+ if (matchedPaths.length > 0) {
685
+ confidence += bestTier === "infra" ? 0.4 : 0.2;
686
+ reasons.push(`commit touched: ${matchedPaths.slice(0, 3).join(", ")}`);
687
+ }
688
+ if (filesChanged.length > 0 && filesChanged.every((f) => NEGATIVE_PATH_PATTERNS.some((p) => p.test(f)))) {
689
+ confidence -= 0.5;
690
+ reasons.push("all changed files are low-signal (lock files, generated code, dist)");
691
+ }
692
+ if (filesChanged.length > 0 && filesChanged.every((f) => /favicon/i.test(f) || /\.(svg|png|ico)$/i.test(f))) {
693
+ confidence -= 0.5;
694
+ reasons.push("all changed files are UI assets (favicon, SVG, PNG)");
695
+ }
696
+ if (filesChanged.length > 0 && filesChanged.every((f) => /\.css$/i.test(f)) && /tailwind/i.test(messageLower)) {
697
+ confidence -= 0.5;
698
+ reasons.push("Tailwind generated CSS change (low signal)");
699
+ }
700
+ const isDecisionCandidate = confidence >= 0.4;
701
+ const keywordLabels = matchedKeywords.map((kw) => kw.source.replace(/\\b/g, ""));
702
+ const category = isDecisionCandidate ? inferCategory(keywordLabels, matchedTypes, matchedPaths) : "unknown";
703
+ return {
704
+ isDecisionCandidate,
705
+ confidence: Math.max(0, Math.min(1, confidence)),
706
+ reasons,
707
+ category
708
+ };
709
+ }
710
+ function tryDetectDecision(opts) {
711
+ if (!isDecisionsEnabled()) {
712
+ return void 0;
713
+ }
714
+ const classification = classifyCommit({
715
+ message: opts.commitMessage,
716
+ filesChanged: opts.filesChanged
717
+ });
718
+ if (!classification.isDecisionCandidate) {
719
+ return void 0;
720
+ }
721
+ const decision = createDecisionRecord({
722
+ checkpointId: opts.checkpointId,
723
+ gitBranch: opts.gitBranch,
724
+ commitHash: opts.commitHash,
725
+ commitMessage: opts.commitMessage,
726
+ filesChanged: opts.filesChanged,
727
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
728
+ classification
729
+ });
730
+ const storage = new DecisionStorage(opts.workspacePath);
731
+ storage.saveDecision(decision);
732
+ return {
733
+ category: classification.category,
734
+ confidence: classification.confidence
735
+ };
736
+ }
737
+
738
+ // src/storage.ts
739
+ var STORAGE_DIR3 = ".keepgoing";
399
740
  var META_FILE2 = "meta.json";
400
741
  var SESSIONS_FILE2 = "sessions.json";
742
+ var DECISIONS_FILE2 = "decisions.json";
401
743
  var STATE_FILE2 = "state.json";
402
744
  var KeepGoingReader = class {
403
745
  storagePath;
404
746
  metaFilePath;
405
747
  sessionsFilePath;
748
+ decisionsFilePath;
406
749
  stateFilePath;
407
750
  constructor(workspacePath2) {
408
- this.storagePath = path2.join(workspacePath2, STORAGE_DIR2);
409
- this.metaFilePath = path2.join(this.storagePath, META_FILE2);
410
- this.sessionsFilePath = path2.join(this.storagePath, SESSIONS_FILE2);
411
- this.stateFilePath = path2.join(this.storagePath, STATE_FILE2);
751
+ this.storagePath = path3.join(workspacePath2, STORAGE_DIR3);
752
+ this.metaFilePath = path3.join(this.storagePath, META_FILE2);
753
+ this.sessionsFilePath = path3.join(this.storagePath, SESSIONS_FILE2);
754
+ this.decisionsFilePath = path3.join(this.storagePath, DECISIONS_FILE2);
755
+ this.stateFilePath = path3.join(this.storagePath, STATE_FILE2);
412
756
  }
413
757
  /** Check if .keepgoing/ directory exists. */
414
758
  exists() {
415
- return fs2.existsSync(this.storagePath);
759
+ return fs3.existsSync(this.storagePath);
416
760
  }
417
761
  /** Read state.json, returns undefined if missing or corrupt. */
418
762
  getState() {
@@ -461,6 +805,15 @@ var KeepGoingReader = class {
461
805
  getRecentSessions(count) {
462
806
  return getRecentSessions(this.getSessions(), count);
463
807
  }
808
+ /** Read all decisions from decisions.json. */
809
+ getDecisions() {
810
+ return this.parseDecisions().decisions;
811
+ }
812
+ /** Returns the last N decisions, newest first. */
813
+ getRecentDecisions(count) {
814
+ const all = this.getDecisions();
815
+ return all.slice(-count).reverse();
816
+ }
464
817
  /**
465
818
  * Parses sessions.json once, returning both the session list
466
819
  * and the optional lastSessionId from a ProjectSessions wrapper.
@@ -477,12 +830,19 @@ var KeepGoingReader = class {
477
830
  }
478
831
  return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };
479
832
  }
833
+ parseDecisions() {
834
+ const raw = this.readJsonFile(this.decisionsFilePath);
835
+ if (!raw) {
836
+ return { decisions: [] };
837
+ }
838
+ return { decisions: raw.decisions ?? [], lastDecisionId: raw.lastDecisionId };
839
+ }
480
840
  readJsonFile(filePath) {
481
841
  try {
482
- if (!fs2.existsSync(filePath)) {
842
+ if (!fs3.existsSync(filePath)) {
483
843
  return void 0;
484
844
  }
485
- const raw = fs2.readFileSync(filePath, "utf-8");
845
+ const raw = fs3.readFileSync(filePath, "utf-8");
486
846
  return JSON.parse(raw);
487
847
  } catch {
488
848
  return void 0;
@@ -671,6 +1031,15 @@ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
671
1031
  `**Suggested next:** ${briefing.suggestedNext}`,
672
1032
  `**Quick start:** ${briefing.smallNextStep}`
673
1033
  ];
1034
+ const recentDecisions = reader2.getRecentDecisions(3);
1035
+ if (recentDecisions.length > 0) {
1036
+ lines.push("");
1037
+ lines.push("### Recent decisions");
1038
+ for (const decision of recentDecisions) {
1039
+ const rationale = decision.rationale ? ` - ${decision.rationale}` : "";
1040
+ lines.push(`- **${decision.classification.category}:** ${decision.commitMessage}${rationale}`);
1041
+ }
1042
+ }
674
1043
  return {
675
1044
  content: [{ type: "text", text: lines.join("\n") }]
676
1045
  };
@@ -679,7 +1048,7 @@ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
679
1048
  }
680
1049
 
681
1050
  // src/tools/saveCheckpoint.ts
682
- import path3 from "path";
1051
+ import path4 from "path";
683
1052
  import { z as z2 } from "zod";
684
1053
  function registerSaveCheckpoint(server2, reader2, workspacePath2) {
685
1054
  server2.tool(
@@ -695,7 +1064,7 @@ function registerSaveCheckpoint(server2, reader2, workspacePath2) {
695
1064
  const gitBranch = getCurrentBranch(workspacePath2);
696
1065
  const touchedFiles = getTouchedFiles(workspacePath2);
697
1066
  const commitHashes = getCommitsSince(workspacePath2, lastSession?.timestamp);
698
- const projectName = path3.basename(workspacePath2);
1067
+ const projectName = path4.basename(workspacePath2);
699
1068
  const checkpoint = createCheckpoint({
700
1069
  summary,
701
1070
  nextStep: nextStep || "",
@@ -715,6 +1084,23 @@ function registerSaveCheckpoint(server2, reader2, workspacePath2) {
715
1084
  `- **Files tracked:** ${touchedFiles.length}`,
716
1085
  `- **Commits captured:** ${commitHashes.length}`
717
1086
  ];
1087
+ if (commitHashes.length > 0) {
1088
+ const commitMessages = getCommitMessagesSince(workspacePath2, lastSession?.timestamp);
1089
+ const headHash = getHeadCommitHash(workspacePath2);
1090
+ if (commitMessages.length > 0 && headHash) {
1091
+ const detected = tryDetectDecision({
1092
+ workspacePath: workspacePath2,
1093
+ checkpointId: checkpoint.id,
1094
+ gitBranch,
1095
+ commitHash: headHash,
1096
+ commitMessage: commitMessages[0],
1097
+ filesChanged: touchedFiles
1098
+ });
1099
+ if (detected) {
1100
+ lines.push(`- **Decision detected:** ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);
1101
+ }
1102
+ }
1103
+ }
718
1104
  return {
719
1105
  content: [{ type: "text", text: lines.join("\n") }]
720
1106
  };
@@ -722,10 +1108,66 @@ function registerSaveCheckpoint(server2, reader2, workspacePath2) {
722
1108
  );
723
1109
  }
724
1110
 
725
- // src/tools/setupProject.ts
726
- import fs3 from "fs";
727
- import path4 from "path";
1111
+ // src/tools/getDecisions.ts
728
1112
  import { z as z3 } from "zod";
1113
+ function registerGetDecisions(server2, reader2) {
1114
+ server2.tool(
1115
+ "get_decisions",
1116
+ "Get recent decision records. Returns detected high-signal commits with their category, confidence, and rationale.",
1117
+ { limit: z3.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)") },
1118
+ async ({ limit }) => {
1119
+ if (!reader2.exists()) {
1120
+ return {
1121
+ content: [
1122
+ {
1123
+ type: "text",
1124
+ text: "No KeepGoing data found."
1125
+ }
1126
+ ]
1127
+ };
1128
+ }
1129
+ const decisions = reader2.getRecentDecisions(limit);
1130
+ if (decisions.length === 0) {
1131
+ return {
1132
+ content: [
1133
+ {
1134
+ type: "text",
1135
+ text: "No decision records found."
1136
+ }
1137
+ ]
1138
+ };
1139
+ }
1140
+ const lines = [
1141
+ `## Decisions (last ${decisions.length})`,
1142
+ ""
1143
+ ];
1144
+ for (const decision of decisions) {
1145
+ lines.push(`### ${decision.commitMessage}`);
1146
+ lines.push(`- **When:** ${formatRelativeTime(decision.timestamp)}`);
1147
+ lines.push(`- **Category:** ${decision.classification.category}`);
1148
+ lines.push(`- **Confidence:** ${(decision.classification.confidence * 100).toFixed(0)}%`);
1149
+ if (decision.gitBranch) {
1150
+ lines.push(`- **Branch:** ${decision.gitBranch}`);
1151
+ }
1152
+ if (decision.rationale) {
1153
+ lines.push(`- **Rationale:** ${decision.rationale}`);
1154
+ }
1155
+ if (decision.classification.reasons.length > 0) {
1156
+ lines.push(`- **Signals:** ${decision.classification.reasons.join("; ")}`);
1157
+ }
1158
+ lines.push("");
1159
+ }
1160
+ return {
1161
+ content: [{ type: "text", text: lines.join("\n") }]
1162
+ };
1163
+ }
1164
+ );
1165
+ }
1166
+
1167
+ // src/tools/setupProject.ts
1168
+ import fs4 from "fs";
1169
+ import path5 from "path";
1170
+ import { z as z4 } from "zod";
729
1171
  var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
730
1172
  var SESSION_START_HOOK = {
731
1173
  matcher: "",
@@ -763,17 +1205,17 @@ function registerSetupProject(server2, workspacePath2) {
763
1205
  "setup_project",
764
1206
  "Set up KeepGoing in the current project. Adds session hooks to .claude/settings.json and CLAUDE.md instructions so checkpoints are saved automatically.",
765
1207
  {
766
- sessionHooks: z3.boolean().optional().default(true).describe("Add session hooks to .claude/settings.json"),
767
- claudeMd: z3.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md")
1208
+ sessionHooks: z4.boolean().optional().default(true).describe("Add session hooks to .claude/settings.json"),
1209
+ claudeMd: z4.boolean().optional().default(true).describe("Add KeepGoing instructions to CLAUDE.md")
768
1210
  },
769
1211
  async ({ sessionHooks, claudeMd }) => {
770
1212
  const results = [];
771
1213
  if (sessionHooks) {
772
- const claudeDir = path4.join(workspacePath2, ".claude");
773
- const settingsPath = path4.join(claudeDir, "settings.json");
1214
+ const claudeDir = path5.join(workspacePath2, ".claude");
1215
+ const settingsPath = path5.join(claudeDir, "settings.json");
774
1216
  let settings = {};
775
- if (fs3.existsSync(settingsPath)) {
776
- settings = JSON.parse(fs3.readFileSync(settingsPath, "utf-8"));
1217
+ if (fs4.existsSync(settingsPath)) {
1218
+ settings = JSON.parse(fs4.readFileSync(settingsPath, "utf-8"));
777
1219
  }
778
1220
  if (!settings.hooks) {
779
1221
  settings.hooks = {};
@@ -794,26 +1236,28 @@ function registerSetupProject(server2, workspacePath2) {
794
1236
  hooksChanged = true;
795
1237
  }
796
1238
  if (hooksChanged) {
797
- if (!fs3.existsSync(claudeDir)) {
798
- fs3.mkdirSync(claudeDir, { recursive: true });
1239
+ if (!fs4.existsSync(claudeDir)) {
1240
+ fs4.mkdirSync(claudeDir, { recursive: true });
799
1241
  }
800
- fs3.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
1242
+ fs4.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
801
1243
  results.push("**Session hooks:** Added to `.claude/settings.json`");
802
1244
  } else {
803
1245
  results.push("**Session hooks:** Already present, skipped");
804
1246
  }
805
1247
  }
806
1248
  if (claudeMd) {
807
- const claudeMdPath = path4.join(workspacePath2, "CLAUDE.md");
1249
+ const dotClaudeMdPath = path5.join(workspacePath2, ".claude", "CLAUDE.md");
1250
+ const rootClaudeMdPath = path5.join(workspacePath2, "CLAUDE.md");
1251
+ const claudeMdPath = fs4.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath;
808
1252
  let existing = "";
809
- if (fs3.existsSync(claudeMdPath)) {
810
- existing = fs3.readFileSync(claudeMdPath, "utf-8");
1253
+ if (fs4.existsSync(claudeMdPath)) {
1254
+ existing = fs4.readFileSync(claudeMdPath, "utf-8");
811
1255
  }
812
1256
  if (existing.includes("## KeepGoing")) {
813
1257
  results.push("**CLAUDE.md:** KeepGoing section already present, skipped");
814
1258
  } else {
815
1259
  const updated = existing + CLAUDE_MD_SECTION;
816
- fs3.writeFileSync(claudeMdPath, updated);
1260
+ fs4.writeFileSync(claudeMdPath, updated);
817
1261
  results.push("**CLAUDE.md:** Added KeepGoing section");
818
1262
  }
819
1263
  }
@@ -854,7 +1298,7 @@ function registerResumePrompt(server2) {
854
1298
 
855
1299
  // src/index.ts
856
1300
  if (process.argv.includes("--print-momentum")) {
857
- const wsPath = process.argv.slice(2).find((a) => a !== "--print-momentum") || process.cwd();
1301
+ const wsPath = findGitRoot(process.argv.slice(2).find((a) => a !== "--print-momentum") || process.cwd());
858
1302
  const reader2 = new KeepGoingReader(wsPath);
859
1303
  if (!reader2.exists()) {
860
1304
  process.exit(0);
@@ -886,7 +1330,7 @@ if (process.argv.includes("--print-momentum")) {
886
1330
  process.exit(0);
887
1331
  }
888
1332
  if (process.argv.includes("--save-checkpoint")) {
889
- const wsPath = process.argv.slice(2).find((a) => !a.startsWith("--")) || process.cwd();
1333
+ const wsPath = findGitRoot(process.argv.slice(2).find((a) => !a.startsWith("--")) || process.cwd());
890
1334
  const reader2 = new KeepGoingReader(wsPath);
891
1335
  const lastSession = reader2.getLastSession();
892
1336
  if (lastSession?.timestamp) {
@@ -906,13 +1350,13 @@ if (process.argv.includes("--save-checkpoint")) {
906
1350
  if (commitMessages.length > 0) {
907
1351
  summary = commitMessages.slice(0, 3).join("; ");
908
1352
  } else {
909
- const fileNames = touchedFiles.slice(0, 5).map((f) => path5.basename(f));
1353
+ const fileNames = touchedFiles.slice(0, 5).map((f) => path6.basename(f));
910
1354
  summary = `Worked on ${fileNames.join(", ")}`;
911
1355
  if (touchedFiles.length > 5) {
912
1356
  summary += ` and ${touchedFiles.length - 5} more`;
913
1357
  }
914
1358
  }
915
- const projectName = path5.basename(wsPath);
1359
+ const projectName = path6.basename(wsPath);
916
1360
  const checkpoint = createCheckpoint({
917
1361
  summary,
918
1362
  nextStep: "",
@@ -924,10 +1368,26 @@ if (process.argv.includes("--save-checkpoint")) {
924
1368
  });
925
1369
  const writer = new KeepGoingWriter(wsPath);
926
1370
  writer.saveCheckpoint(checkpoint, projectName);
1371
+ if (commitMessages.length > 0) {
1372
+ const headHash = getHeadCommitHash(wsPath) || commitHashes[0];
1373
+ if (headHash) {
1374
+ const detected = tryDetectDecision({
1375
+ workspacePath: wsPath,
1376
+ checkpointId: checkpoint.id,
1377
+ gitBranch,
1378
+ commitHash: headHash,
1379
+ commitMessage: commitMessages[0],
1380
+ filesChanged: touchedFiles
1381
+ });
1382
+ if (detected) {
1383
+ console.log(`[KeepGoing] Decision detected: ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);
1384
+ }
1385
+ }
1386
+ }
927
1387
  console.log(`[KeepGoing] Auto-checkpoint saved: ${summary}`);
928
1388
  process.exit(0);
929
1389
  }
930
- var workspacePath = process.argv[2] || process.cwd();
1390
+ var workspacePath = findGitRoot(process.argv[2] || process.cwd());
931
1391
  var reader = new KeepGoingReader(workspacePath);
932
1392
  var server = new McpServer({
933
1393
  name: "keepgoing",
@@ -936,6 +1396,7 @@ var server = new McpServer({
936
1396
  registerGetMomentum(server, reader, workspacePath);
937
1397
  registerGetSessionHistory(server, reader);
938
1398
  registerGetReentryBriefing(server, reader, workspacePath);
1399
+ registerGetDecisions(server, reader);
939
1400
  registerSaveCheckpoint(server, reader, workspacePath);
940
1401
  registerSetupProject(server, workspacePath);
941
1402
  registerResumePrompt(server);
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/storage.ts","../../../packages/shared/src/session.ts","../../../packages/shared/src/timeUtils.ts","../../../packages/shared/src/gitUtils.ts","../../../packages/shared/src/reentry.ts","../../../packages/shared/src/storage.ts","../src/tools/getMomentum.ts","../src/tools/getSessionHistory.ts","../src/tools/getReentryBriefing.ts","../src/tools/saveCheckpoint.ts","../src/tools/setupProject.ts","../src/prompts/resume.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { KeepGoingReader } from './storage.js';\nimport {\n formatRelativeTime,\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n} from '@keepgoingdev/shared';\nimport { registerGetMomentum } from './tools/getMomentum.js';\nimport { registerGetSessionHistory } from './tools/getSessionHistory.js';\nimport { registerGetReentryBriefing } from './tools/getReentryBriefing.js';\nimport { registerSaveCheckpoint } from './tools/saveCheckpoint.js';\nimport { registerSetupProject } from './tools/setupProject.js';\nimport { registerResumePrompt } from './prompts/resume.js';\n\n// Handle --print-momentum CLI flag: print momentum context and exit.\n// Used by the Claude Code SessionStart hook (scripts/keepgoing-hook.sh).\nif (process.argv.includes('--print-momentum')) {\n // Workspace path is the first non-flag argument after the script path\n const wsPath = process.argv.slice(2).find(a => a !== '--print-momentum') || process.cwd();\n const reader = new KeepGoingReader(wsPath);\n\n if (!reader.exists()) {\n process.exit(0);\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n process.exit(0);\n }\n\n const touchedCount = lastSession.touchedFiles?.length ?? 0;\n const lines: string[] = [];\n lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);\n if (lastSession.summary) {\n lines.push(` Summary: ${lastSession.summary}`);\n }\n if (lastSession.nextStep) {\n lines.push(` Next step: ${lastSession.nextStep}`);\n }\n if (lastSession.blocker) {\n lines.push(` Blocker: ${lastSession.blocker}`);\n }\n if (lastSession.gitBranch) {\n lines.push(` Branch: ${lastSession.gitBranch}`);\n }\n if (touchedCount > 0) {\n lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? 'unknown branch'}`);\n }\n lines.push(' Tip: Use the get_reentry_briefing tool for a full briefing');\n\n console.log(lines.join('\\n'));\n process.exit(0);\n}\n\n// Handle --save-checkpoint CLI flag: auto-save a checkpoint and exit.\n// Used by the Claude Code Stop hook as an automatic safety net.\nif (process.argv.includes('--save-checkpoint')) {\n const wsPath = process.argv.slice(2).find(a => !a.startsWith('--')) || process.cwd();\n const reader = new KeepGoingReader(wsPath);\n\n const lastSession = reader.getLastSession();\n\n // Skip if a checkpoint was written within the last 2 minutes (avoid duplicating extension checkpoints)\n if (lastSession?.timestamp) {\n const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();\n if (ageMs < 2 * 60 * 1000) {\n process.exit(0);\n }\n }\n\n const touchedFiles = getTouchedFiles(wsPath);\n const commitHashes = getCommitsSince(wsPath, lastSession?.timestamp);\n\n // Skip if there's nothing to capture\n if (touchedFiles.length === 0 && commitHashes.length === 0) {\n process.exit(0);\n }\n\n const gitBranch = getCurrentBranch(wsPath);\n const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);\n\n // Build a heuristic summary from commit messages or touched files\n let summary: string;\n if (commitMessages.length > 0) {\n summary = commitMessages.slice(0, 3).join('; ');\n } else {\n const fileNames = touchedFiles.slice(0, 5).map(f => path.basename(f));\n summary = `Worked on ${fileNames.join(', ')}`;\n if (touchedFiles.length > 5) {\n summary += ` and ${touchedFiles.length - 5} more`;\n }\n }\n\n const projectName = path.basename(wsPath);\n const checkpoint = createCheckpoint({\n summary,\n nextStep: '',\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: wsPath,\n source: 'auto',\n });\n\n const writer = new KeepGoingWriter(wsPath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n console.log(`[KeepGoing] Auto-checkpoint saved: ${summary}`);\n process.exit(0);\n}\n\n// Default: start MCP server\n// Workspace path can be passed as an argument, otherwise defaults to CWD.\n// MCP hosts (Claude Code, etc.) typically launch the server with the project root as CWD.\nconst workspacePath = process.argv[2] || process.cwd();\nconst reader = new KeepGoingReader(workspacePath);\n\nconst server = new McpServer({\n name: 'keepgoing',\n version: '0.1.0',\n});\n\n// Register tools\nregisterGetMomentum(server, reader, workspacePath);\nregisterGetSessionHistory(server, reader);\nregisterGetReentryBriefing(server, reader, workspacePath);\nregisterSaveCheckpoint(server, reader, workspacePath);\nregisterSetupProject(server, workspacePath);\n\n// Register prompts\nregisterResumePrompt(server);\n\n// Connect via stdio\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nconsole.error('KeepGoing MCP server started');\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n getRecentSessions,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n} from '@keepgoingdev/shared';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Read-only reader for .keepgoing/ directory.\n * Does not write or create any files.\n */\nexport class KeepGoingReader {\n private readonly storagePath: string;\n private readonly metaFilePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n }\n\n /** Check if .keepgoing/ directory exists. */\n exists(): boolean {\n return fs.existsSync(this.storagePath);\n }\n\n /** Read state.json, returns undefined if missing or corrupt. */\n getState(): ProjectState | undefined {\n return this.readJsonFile<ProjectState>(this.stateFilePath);\n }\n\n /** Read meta.json, returns undefined if missing or corrupt. */\n getMeta(): ProjectMeta | undefined {\n return this.readJsonFile<ProjectMeta>(this.metaFilePath);\n }\n\n /**\n * Read sessions from sessions.json.\n * Handles both formats:\n * - Flat array: SessionCheckpoint[] (from ProjectStorage)\n * - Wrapper object: ProjectSessions (from SessionStorage)\n */\n getSessions(): SessionCheckpoint[] {\n return this.parseSessions().sessions;\n }\n\n /**\n * Get the most recent session checkpoint.\n * Uses state.lastSessionId if available, falls back to last in array.\n */\n getLastSession(): SessionCheckpoint | undefined {\n const { sessions, wrapperLastSessionId } = this.parseSessions();\n if (sessions.length === 0) {\n return undefined;\n }\n\n const state = this.getState();\n if (state?.lastSessionId) {\n const found = sessions.find((s) => s.id === state.lastSessionId);\n if (found) {\n return found;\n }\n }\n\n if (wrapperLastSessionId) {\n const found = sessions.find((s) => s.id === wrapperLastSessionId);\n if (found) {\n return found;\n }\n }\n\n return sessions[sessions.length - 1];\n }\n\n /**\n * Returns the last N sessions, newest first.\n */\n getRecentSessions(count: number): SessionCheckpoint[] {\n return getRecentSessions(this.getSessions(), count);\n }\n\n /**\n * Parses sessions.json once, returning both the session list\n * and the optional lastSessionId from a ProjectSessions wrapper.\n */\n private parseSessions(): { sessions: SessionCheckpoint[]; wrapperLastSessionId?: string } {\n const raw = this.readJsonFile<ProjectSessions | SessionCheckpoint[]>(\n this.sessionsFilePath,\n );\n if (!raw) {\n return { sessions: [] };\n }\n if (Array.isArray(raw)) {\n return { sessions: raw };\n }\n return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };\n }\n\n private readJsonFile<T>(filePath: string): T | undefined {\n try {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectMeta, ProjectState } from './types';\n\n/**\n * Generates a UUID v4 unique ID for checkpoints.\n */\nexport function generateCheckpointId(): string {\n return randomUUID();\n}\n\n/**\n * Creates a SessionCheckpoint with auto-generated id and timestamp.\n * Consolidates the repeated checkpoint construction pattern.\n */\nexport function createCheckpoint(\n fields: Omit<SessionCheckpoint, 'id' | 'timestamp'>,\n): SessionCheckpoint {\n return {\n id: generateCheckpointId(),\n timestamp: new Date().toISOString(),\n ...fields,\n };\n}\n\n/**\n * Creates a default empty project sessions container.\n */\nexport function createEmptyProjectSessions(projectName: string): ProjectSessions {\n return {\n version: 1,\n project: projectName,\n sessions: [],\n lastSessionId: undefined,\n };\n}\n\n/**\n * Creates a default project metadata object.\n */\nexport function createProjectMeta(): ProjectMeta {\n const now = new Date().toISOString();\n return {\n projectId: randomUUID(),\n createdAt: now,\n lastUpdated: now,\n };\n}\n\n/**\n * Creates a default empty project state object.\n */\nexport function createEmptyProjectState(): ProjectState {\n return {};\n}\n","/**\n * Formats a timestamp as a human-readable relative time string.\n * Examples: \"just now\", \"5 minutes ago\", \"3 hours ago\", \"2 days ago\", \"1 week ago\"\n *\n * Note: Month and year calculations use approximations (30 days/month, 365 days/year)\n * for simplicity. These approximations are acceptable for the \"human-readable\" purpose.\n */\nexport function formatRelativeTime(timestamp: string): string {\n const now = Date.now();\n const then = new Date(timestamp).getTime();\n const diffMs = now - then;\n\n // Handle invalid dates\n if (isNaN(diffMs)) {\n return 'unknown time';\n }\n\n // Future dates\n if (diffMs < 0) {\n return 'in the future';\n }\n\n const seconds = Math.floor(diffMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30); // Approximation for human readability\n const years = Math.floor(days / 365); // Approximation, doesn't account for leap years\n\n if (seconds < 10) {\n return 'just now';\n } else if (seconds < 60) {\n return `${seconds} seconds ago`;\n } else if (minutes < 60) {\n return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;\n } else if (hours < 24) {\n return hours === 1 ? '1 hour ago' : `${hours} hours ago`;\n } else if (days < 7) {\n return days === 1 ? '1 day ago' : `${days} days ago`;\n } else if (weeks < 4) {\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n } else if (months < 12) {\n return months === 1 ? '1 month ago' : `${months} months ago`;\n } else {\n return years === 1 ? '1 year ago' : `${years} years ago`;\n }\n}\n","import { execFileSync, execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Git utilities for extracting context from the workspace.\n * These are intentionally decoupled from VSCode APIs.\n * All sync functions use execFileSync (no shell injection).\n */\n\n/**\n * Returns the current git branch name, or undefined if not in a git repo.\n */\nexport function getCurrentBranch(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs git log with the given format since a timestamp.\n * Shared implementation for commit hash and message retrieval.\n */\nfunction getGitLogSince(workspacePath: string, format: string, sinceTimestamp?: string): string[] {\n try {\n const since = sinceTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const result = execFileSync(\n 'git',\n ['log', `--since=${since}`, `--format=${format}`],\n {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n },\n );\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .filter((line: string) => line.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns a list of commit hashes since a given ISO timestamp.\n * If no timestamp is provided, returns recent commits (last 24 hours).\n */\nexport function getCommitsSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%H', sinceTimestamp);\n}\n\n/**\n * Returns a list of commit subject lines since a given ISO timestamp.\n * If no timestamp is provided, returns commit messages from the last 24 hours.\n * Results are in reverse chronological order (newest first), matching git log default.\n */\nexport function getCommitMessagesSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%s', sinceTimestamp);\n}\n\n/**\n * Returns the current HEAD commit hash, or undefined if not in a git repo.\n */\nexport function getHeadCommitHash(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getCurrentBranch. Avoids blocking the event loop.\n */\nexport async function getCurrentBranchAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getHeadCommitHash. Avoids blocking the event loop.\n */\nexport async function getHeadCommitHashAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns a list of files that have been modified (tracked and untracked)\n * in the workspace. This is best-effort and may not capture all changes.\n */\nexport function getTouchedFiles(workspacePath: string): string[] {\n try {\n const result = execFileSync('git', ['status', '--porcelain'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .map((line) => line.substring(3).trim())\n .filter((file) => file.length > 0 && !file.endsWith('/'));\n } catch {\n return [];\n }\n}\n","import type {\n SessionCheckpoint,\n ProjectState,\n ReEntryBriefing,\n} from './types';\nimport { formatRelativeTime } from './timeUtils';\n\nconst RECENT_SESSION_COUNT = 5;\n\n/**\n * Generates a synthesized re-entry briefing from stored project data.\n *\n * Uses heuristic rules (no AI) to infer focus and suggest next steps.\n * Designed to be replaced by an LLM-backed implementation in the future\n * while keeping the same input/output contract.\n */\nexport function generateBriefing(\n lastSession: SessionCheckpoint | undefined,\n recentSessions: SessionCheckpoint[],\n projectState: ProjectState,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): ReEntryBriefing | undefined {\n if (!lastSession) {\n return undefined;\n }\n\n return {\n lastWorked: formatRelativeTime(lastSession.timestamp),\n currentFocus: buildCurrentFocus(lastSession, projectState, gitBranch),\n recentActivity: buildRecentActivity(\n lastSession,\n recentSessions,\n recentCommitMessages,\n ),\n suggestedNext: buildSuggestedNext(lastSession, gitBranch),\n smallNextStep: buildSmallNextStep(\n lastSession,\n gitBranch,\n recentCommitMessages,\n ),\n };\n}\n\n/**\n * Returns the most recent N sessions in newest-first order.\n */\nexport function getRecentSessions(\n allSessions: SessionCheckpoint[],\n count: number = RECENT_SESSION_COUNT,\n): SessionCheckpoint[] {\n return allSessions.slice(-count).reverse();\n}\n\nfunction buildCurrentFocus(\n lastSession: SessionCheckpoint,\n projectState: ProjectState,\n gitBranch?: string,\n): string {\n if (projectState.derivedCurrentFocus) {\n return projectState.derivedCurrentFocus;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return branchFocus;\n }\n\n if (lastSession.summary) {\n return lastSession.summary;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return inferFocusFromFiles(lastSession.touchedFiles);\n }\n\n return 'Unknown, save a checkpoint to set context';\n}\n\nfunction buildRecentActivity(\n lastSession: SessionCheckpoint,\n recentSessions: SessionCheckpoint[],\n recentCommitMessages?: string[],\n): string {\n const parts: string[] = [];\n\n const sessionCount = recentSessions.length;\n if (sessionCount > 1) {\n parts.push(`${sessionCount} recent sessions`);\n } else if (sessionCount === 1) {\n parts.push('1 recent session');\n }\n\n if (lastSession.summary) {\n parts.push(`Last: ${lastSession.summary}`);\n }\n\n if (lastSession.touchedFiles.length > 0) {\n parts.push(`${lastSession.touchedFiles.length} files touched`);\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n parts.push(`${recentCommitMessages.length} recent commits`);\n }\n\n return parts.length > 0 ? parts.join('. ') : 'No recent activity recorded';\n}\n\nfunction buildSuggestedNext(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n): string {\n if (lastSession.nextStep) {\n return lastSession.nextStep;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Continue working on ${branchFocus}`;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return `Continue working on ${inferFocusFromFiles(lastSession.touchedFiles)}`;\n }\n\n return 'Save a checkpoint to track your next step';\n}\n\nfunction buildSmallNextStep(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): string {\n const fallback = 'Review last changed files to resume flow';\n\n if (lastSession.nextStep) {\n const distilled = distillToSmallStep(\n lastSession.nextStep,\n lastSession.touchedFiles,\n );\n if (distilled) {\n return distilled;\n }\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n const commitStep = deriveStepFromCommits(recentCommitMessages);\n if (commitStep) {\n return commitStep;\n }\n }\n\n if (lastSession.touchedFiles.length > 0) {\n const fileStep = deriveStepFromFiles(lastSession.touchedFiles);\n if (fileStep) {\n return fileStep;\n }\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Check git status for ${branchFocus}`;\n }\n\n return fallback;\n}\n\nfunction distillToSmallStep(\n nextStep: string,\n touchedFiles: string[],\n): string | undefined {\n if (!nextStep.trim()) {\n return undefined;\n }\n\n const words = nextStep.trim().split(/\\s+/);\n if (words.length <= 12) {\n if (touchedFiles.length > 0 && !mentionsFile(nextStep)) {\n const primaryFile = getPrimaryFileName(touchedFiles);\n const enhanced = `${nextStep.trim()} in ${primaryFile}`;\n if (enhanced.split(/\\s+/).length <= 12) {\n return enhanced;\n }\n }\n return nextStep.trim();\n }\n\n return words.slice(0, 12).join(' ');\n}\n\nfunction deriveStepFromCommits(\n commitMessages: string[],\n): string | undefined {\n const lastCommit = commitMessages[0];\n if (!lastCommit || !lastCommit.trim()) {\n return undefined;\n }\n\n const wipPattern =\n /^(?:wip|work in progress|started?|begin|draft)[:\\s]/i;\n if (wipPattern.test(lastCommit)) {\n const topic = lastCommit.replace(wipPattern, '').trim();\n if (topic) {\n const words = topic.split(/\\s+/).slice(0, 8).join(' ');\n return `Continue ${words}`;\n }\n }\n\n return undefined;\n}\n\nfunction deriveStepFromFiles(files: string[]): string | undefined {\n const primaryFile = getPrimaryFileName(files);\n\n if (files.length > 1) {\n return `Open ${primaryFile} and review ${files.length} changed files`;\n }\n\n return `Open ${primaryFile} and pick up where you left off`;\n}\n\nfunction getPrimaryFileName(files: string[]): string {\n const sourceFiles = files.filter((f) => {\n const lower = f.toLowerCase();\n return (\n !lower.includes('test') &&\n !lower.includes('spec') &&\n !lower.includes('.config') &&\n !lower.includes('package.json') &&\n !lower.includes('tsconfig')\n );\n });\n\n const target = sourceFiles.length > 0 ? sourceFiles[0] : files[0];\n const parts = target.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\nfunction mentionsFile(text: string): boolean {\n return /\\w+\\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|css|scss|html|json|yaml|yml|md|sql|sh)\\b/i.test(\n text,\n );\n}\n\nfunction inferFocusFromBranch(branch?: string): string | undefined {\n if (\n !branch ||\n branch === 'main' ||\n branch === 'master' ||\n branch === 'develop' ||\n branch === 'HEAD'\n ) {\n return undefined;\n }\n\n const prefixPattern =\n /^(?:feature|feat|fix|bugfix|hotfix|chore|refactor|docs|test|ci)\\//i;\n const isFix = /^(?:fix|bugfix|hotfix)\\//i.test(branch);\n const stripped = branch.replace(prefixPattern, '');\n\n const cleaned = stripped\n .replace(/[-_/]/g, ' ')\n .replace(/^\\d+\\s*/, '')\n .trim();\n\n if (!cleaned) {\n return undefined;\n }\n\n return isFix ? `${cleaned} fix` : cleaned;\n}\n\nfunction inferFocusFromFiles(files: string[]): string {\n if (files.length === 0) {\n return 'unknown files';\n }\n\n const dirs = files\n .map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n })\n .filter((d) => d.length > 0);\n\n if (dirs.length > 0) {\n const counts = new Map<string, number>();\n for (const dir of dirs) {\n counts.set(dir, (counts.get(dir) ?? 0) + 1);\n }\n let topDir = '';\n let topCount = 0;\n for (const [dir, count] of counts) {\n if (count > topCount) {\n topDir = dir;\n topCount = count;\n }\n }\n if (topDir) {\n return `files in ${topDir}`;\n }\n }\n\n const names = files.slice(0, 3).map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n });\n return names.join(', ');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectState, ProjectMeta } from './types';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Write layer for .keepgoing/ directory.\n * Creates files if they don't exist yet.\n */\nexport class KeepGoingWriter {\n private readonly storagePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n private readonly metaFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n }\n\n ensureDir(): void {\n if (!fs.existsSync(this.storagePath)) {\n fs.mkdirSync(this.storagePath, { recursive: true });\n }\n }\n\n saveCheckpoint(checkpoint: SessionCheckpoint, projectName: string): void {\n this.ensureDir();\n\n // Read existing sessions\n let sessionsData: ProjectSessions;\n try {\n if (fs.existsSync(this.sessionsFilePath)) {\n const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, 'utf-8')) as\n | ProjectSessions\n | SessionCheckpoint[];\n if (Array.isArray(raw)) {\n sessionsData = { version: 1, project: projectName, sessions: raw };\n } else {\n sessionsData = raw;\n }\n } else {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n } catch {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n\n sessionsData.sessions.push(checkpoint);\n sessionsData.lastSessionId = checkpoint.id;\n\n // Prune old sessions to keep the file bounded\n const MAX_SESSIONS = 200;\n if (sessionsData.sessions.length > MAX_SESSIONS) {\n sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);\n }\n\n fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), 'utf-8');\n\n // Update state.json\n const state: ProjectState = {\n lastSessionId: checkpoint.id,\n lastKnownBranch: checkpoint.gitBranch,\n lastActivityAt: checkpoint.timestamp,\n };\n fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), 'utf-8');\n\n // Update meta.json\n let meta: ProjectMeta;\n try {\n if (fs.existsSync(this.metaFilePath)) {\n meta = JSON.parse(fs.readFileSync(this.metaFilePath, 'utf-8')) as ProjectMeta;\n meta.lastUpdated = checkpoint.timestamp;\n } else {\n meta = {\n projectId: randomUUID(),\n createdAt: checkpoint.timestamp,\n lastUpdated: checkpoint.timestamp,\n };\n }\n } catch {\n meta = {\n projectId: randomUUID(),\n createdAt: checkpoint.timestamp,\n lastUpdated: checkpoint.timestamp,\n };\n }\n fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), 'utf-8');\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { getCurrentBranch, formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetMomentum(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_momentum',\n 'Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'KeepGoing is set up but no session checkpoints exist yet.',\n },\n ],\n };\n }\n\n const state = reader.getState();\n const currentBranch = getCurrentBranch(workspacePath);\n const branchChanged =\n lastSession.gitBranch &&\n currentBranch &&\n lastSession.gitBranch !== currentBranch;\n\n const lines: string[] = [\n `## Developer Momentum`,\n '',\n `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,\n `**Summary:** ${lastSession.summary || 'No summary'}`,\n `**Next step:** ${lastSession.nextStep || 'Not specified'}`,\n ];\n\n if (lastSession.blocker) {\n lines.push(`**Blocker:** ${lastSession.blocker}`);\n }\n\n if (lastSession.projectIntent) {\n lines.push(`**Project intent:** ${lastSession.projectIntent}`);\n }\n\n lines.push('');\n\n if (currentBranch) {\n lines.push(`**Current branch:** ${currentBranch}`);\n }\n if (branchChanged) {\n lines.push(\n `**Note:** Branch changed since last checkpoint (was \\`${lastSession.gitBranch}\\`, now \\`${currentBranch}\\`)`,\n );\n }\n\n if (lastSession.touchedFiles.length > 0) {\n lines.push('');\n lines.push(\n `**Files touched (${lastSession.touchedFiles.length}):** ${lastSession.touchedFiles.slice(0, 10).join(', ')}`,\n );\n if (lastSession.touchedFiles.length > 10) {\n lines.push(\n ` ...and ${lastSession.touchedFiles.length - 10} more`,\n );\n }\n }\n\n if (state?.derivedCurrentFocus) {\n lines.push('');\n lines.push(`**Derived focus:** ${state.derivedCurrentFocus}`);\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetSessionHistory(server: McpServer, reader: KeepGoingReader) {\n server.tool(\n 'get_session_history',\n 'Get recent session checkpoints. Returns a chronological list of what the developer worked on.',\n { limit: z.number().min(1).max(50).default(5).describe('Number of recent sessions to return (1-50, default 5)') },\n async ({ limit }) => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found.',\n },\n ],\n };\n }\n\n const sessions = reader.getRecentSessions(limit);\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session checkpoints found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Session History (last ${sessions.length})`,\n '',\n ];\n\n for (const session of sessions) {\n lines.push(`### ${formatRelativeTime(session.timestamp)}`);\n lines.push(`- **Summary:** ${session.summary || 'No summary'}`);\n lines.push(`- **Next step:** ${session.nextStep || 'Not specified'}`);\n if (session.blocker) {\n lines.push(`- **Blocker:** ${session.blocker}`);\n }\n if (session.gitBranch) {\n lines.push(`- **Branch:** ${session.gitBranch}`);\n }\n if (session.touchedFiles.length > 0) {\n lines.push(\n `- **Files:** ${session.touchedFiles.slice(0, 5).join(', ')}${session.touchedFiles.length > 5 ? ` (+${session.touchedFiles.length - 5} more)` : ''}`,\n );\n }\n lines.push('');\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n getCurrentBranch,\n getCommitMessagesSince,\n generateBriefing,\n} from '@keepgoingdev/shared';\n\nexport function registerGetReentryBriefing(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_reentry_briefing',\n 'Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n const recentSessions = reader.getRecentSessions(5);\n const state = reader.getState() ?? {};\n const gitBranch = getCurrentBranch(workspacePath);\n\n const sinceTimestamp = lastSession?.timestamp;\n const recentCommits = sinceTimestamp\n ? getCommitMessagesSince(workspacePath, sinceTimestamp)\n : [];\n\n const briefing = generateBriefing(\n lastSession,\n recentSessions,\n state,\n gitBranch,\n recentCommits,\n );\n\n if (!briefing) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session data available to generate a briefing.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Re-entry Briefing`,\n '',\n `**Last worked:** ${briefing.lastWorked}`,\n `**Current focus:** ${briefing.currentFocus}`,\n `**Recent activity:** ${briefing.recentActivity}`,\n `**Suggested next:** ${briefing.suggestedNext}`,\n `**Quick start:** ${briefing.smallNextStep}`,\n ];\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n} from '@keepgoingdev/shared';\n\nexport function registerSaveCheckpoint(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'save_checkpoint',\n 'Save a development checkpoint. Call this after completing a task or meaningful piece of work, not just at end of session. Each checkpoint helps the next session (or developer) pick up exactly where you left off.',\n {\n summary: z.string().describe('What was accomplished in this session'),\n nextStep: z.string().optional().describe('What to do next'),\n blocker: z.string().optional().describe('Any blocker preventing progress'),\n },\n async ({ summary, nextStep, blocker }) => {\n const lastSession = reader.getLastSession();\n\n const gitBranch = getCurrentBranch(workspacePath);\n const touchedFiles = getTouchedFiles(workspacePath);\n const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);\n const projectName = path.basename(workspacePath);\n\n const checkpoint = createCheckpoint({\n summary,\n nextStep: nextStep || '',\n blocker,\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: workspacePath,\n source: 'manual',\n });\n\n const writer = new KeepGoingWriter(workspacePath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n const lines: string[] = [\n `Checkpoint saved.`,\n `- **ID:** ${checkpoint.id}`,\n `- **Branch:** ${gitBranch || 'unknown'}`,\n `- **Files tracked:** ${touchedFiles.length}`,\n `- **Commits captured:** ${commitHashes.length}`,\n ];\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nconst KEEPGOING_MARKER = '@keepgoingdev/mcp-server';\n\nconst SESSION_START_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --print-momentum',\n },\n ],\n};\n\nconst STOP_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --save-checkpoint',\n },\n ],\n};\n\nconst CLAUDE_MD_SECTION = `\n## KeepGoing\n\nAfter completing a task or meaningful piece of work, call the \\`save_checkpoint\\` MCP tool with:\n- \\`summary\\`: What you accomplished\n- \\`nextStep\\`: What should be done next\n- \\`blocker\\`: Any blocker (if applicable)\n`;\n\nfunction hasKeepGoingHook(hookEntries: unknown[]): boolean {\n return hookEntries.some((entry: any) =>\n entry?.hooks?.some((h: any) => typeof h?.command === 'string' && h.command.includes(KEEPGOING_MARKER)),\n );\n}\n\nexport function registerSetupProject(server: McpServer, workspacePath: string) {\n server.tool(\n 'setup_project',\n 'Set up KeepGoing in the current project. Adds session hooks to .claude/settings.json and CLAUDE.md instructions so checkpoints are saved automatically.',\n {\n sessionHooks: z.boolean().optional().default(true).describe('Add session hooks to .claude/settings.json'),\n claudeMd: z.boolean().optional().default(true).describe('Add KeepGoing instructions to CLAUDE.md'),\n },\n async ({ sessionHooks, claudeMd }) => {\n const results: string[] = [];\n\n // --- Session hooks ---\n if (sessionHooks) {\n const claudeDir = path.join(workspacePath, '.claude');\n const settingsPath = path.join(claudeDir, 'settings.json');\n\n let settings: any = {};\n if (fs.existsSync(settingsPath)) {\n settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));\n }\n\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n let hooksChanged = false;\n\n // SessionStart\n if (!Array.isArray(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart = [];\n }\n if (!hasKeepGoingHook(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart.push(SESSION_START_HOOK);\n hooksChanged = true;\n }\n\n // Stop\n if (!Array.isArray(settings.hooks.Stop)) {\n settings.hooks.Stop = [];\n }\n if (!hasKeepGoingHook(settings.hooks.Stop)) {\n settings.hooks.Stop.push(STOP_HOOK);\n hooksChanged = true;\n }\n\n if (hooksChanged) {\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true });\n }\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n results.push('**Session hooks:** Added to `.claude/settings.json`');\n } else {\n results.push('**Session hooks:** Already present, skipped');\n }\n }\n\n // --- CLAUDE.md ---\n if (claudeMd) {\n const claudeMdPath = path.join(workspacePath, 'CLAUDE.md');\n\n let existing = '';\n if (fs.existsSync(claudeMdPath)) {\n existing = fs.readFileSync(claudeMdPath, 'utf-8');\n }\n\n if (existing.includes('## KeepGoing')) {\n results.push('**CLAUDE.md:** KeepGoing section already present, skipped');\n } else {\n const updated = existing + CLAUDE_MD_SECTION;\n fs.writeFileSync(claudeMdPath, updated);\n results.push('**CLAUDE.md:** Added KeepGoing section');\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: results.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerResumePrompt(server: McpServer) {\n server.prompt(\n 'resume',\n 'Check developer momentum and suggest what to work on next',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I just opened this project and want to pick up where I left off.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Check my current momentum (get_momentum)',\n '2. Get a re-entry briefing (get_reentry_briefing)',\n '3. Based on the results, give me a concise summary of where I left off and suggest what to work on next.',\n '',\n 'Keep your response brief and actionable.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":"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","names":["path","fs","path","workspacePath","workspacePath","randomUUID","workspacePath","STORAGE_DIR","META_FILE","SESSIONS_FILE","STATE_FILE","workspacePath","path","fs","server","reader","workspacePath","server","reader","server","reader","workspacePath","path","z","server","reader","workspacePath","z","path","fs","path","z","server","workspacePath","server","reader","path"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/storage.ts","../../../packages/shared/src/session.ts","../../../packages/shared/src/timeUtils.ts","../../../packages/shared/src/gitUtils.ts","../../../packages/shared/src/reentry.ts","../../../packages/shared/src/storage.ts","../../../packages/shared/src/decisionStorage.ts","../../../packages/shared/src/featureGate.ts","../../../packages/shared/src/decisionDetection.ts","../src/tools/getMomentum.ts","../src/tools/getSessionHistory.ts","../src/tools/getReentryBriefing.ts","../src/tools/saveCheckpoint.ts","../src/tools/getDecisions.ts","../src/tools/setupProject.ts","../src/prompts/resume.ts"],"sourcesContent":["#!/usr/bin/env node\n\nimport path from 'node:path';\nimport { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\nimport { KeepGoingReader } from './storage.js';\nimport {\n findGitRoot,\n formatRelativeTime,\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n getHeadCommitHash,\n tryDetectDecision,\n} from '@keepgoingdev/shared';\nimport { registerGetMomentum } from './tools/getMomentum.js';\nimport { registerGetSessionHistory } from './tools/getSessionHistory.js';\nimport { registerGetReentryBriefing } from './tools/getReentryBriefing.js';\nimport { registerSaveCheckpoint } from './tools/saveCheckpoint.js';\nimport { registerGetDecisions } from './tools/getDecisions.js';\nimport { registerSetupProject } from './tools/setupProject.js';\nimport { registerResumePrompt } from './prompts/resume.js';\n\n// Handle --print-momentum CLI flag: print momentum context and exit.\n// Used by the Claude Code SessionStart hook (scripts/keepgoing-hook.sh).\nif (process.argv.includes('--print-momentum')) {\n // Workspace path is the first non-flag argument after the script path\n const wsPath = findGitRoot(process.argv.slice(2).find(a => a !== '--print-momentum') || process.cwd());\n const reader = new KeepGoingReader(wsPath);\n\n if (!reader.exists()) {\n process.exit(0);\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n process.exit(0);\n }\n\n const touchedCount = lastSession.touchedFiles?.length ?? 0;\n const lines: string[] = [];\n lines.push(`[KeepGoing] Last checkpoint: ${formatRelativeTime(lastSession.timestamp)}`);\n if (lastSession.summary) {\n lines.push(` Summary: ${lastSession.summary}`);\n }\n if (lastSession.nextStep) {\n lines.push(` Next step: ${lastSession.nextStep}`);\n }\n if (lastSession.blocker) {\n lines.push(` Blocker: ${lastSession.blocker}`);\n }\n if (lastSession.gitBranch) {\n lines.push(` Branch: ${lastSession.gitBranch}`);\n }\n if (touchedCount > 0) {\n lines.push(` Worked on ${touchedCount} files on ${lastSession.gitBranch ?? 'unknown branch'}`);\n }\n lines.push(' Tip: Use the get_reentry_briefing tool for a full briefing');\n\n console.log(lines.join('\\n'));\n process.exit(0);\n}\n\n// Handle --save-checkpoint CLI flag: auto-save a checkpoint and exit.\n// Used by the Claude Code Stop hook as an automatic safety net.\nif (process.argv.includes('--save-checkpoint')) {\n const wsPath = findGitRoot(process.argv.slice(2).find(a => !a.startsWith('--')) || process.cwd());\n const reader = new KeepGoingReader(wsPath);\n\n const lastSession = reader.getLastSession();\n\n // Skip if a checkpoint was written within the last 2 minutes (avoid duplicating extension checkpoints)\n if (lastSession?.timestamp) {\n const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();\n if (ageMs < 2 * 60 * 1000) {\n process.exit(0);\n }\n }\n\n const touchedFiles = getTouchedFiles(wsPath);\n const commitHashes = getCommitsSince(wsPath, lastSession?.timestamp);\n\n // Skip if there's nothing to capture\n if (touchedFiles.length === 0 && commitHashes.length === 0) {\n process.exit(0);\n }\n\n const gitBranch = getCurrentBranch(wsPath);\n const commitMessages = getCommitMessagesSince(wsPath, lastSession?.timestamp);\n\n // Build a heuristic summary from commit messages or touched files\n let summary: string;\n if (commitMessages.length > 0) {\n summary = commitMessages.slice(0, 3).join('; ');\n } else {\n const fileNames = touchedFiles.slice(0, 5).map(f => path.basename(f));\n summary = `Worked on ${fileNames.join(', ')}`;\n if (touchedFiles.length > 5) {\n summary += ` and ${touchedFiles.length - 5} more`;\n }\n }\n\n const projectName = path.basename(wsPath);\n const checkpoint = createCheckpoint({\n summary,\n nextStep: '',\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: wsPath,\n source: 'auto',\n });\n\n const writer = new KeepGoingWriter(wsPath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n // Decision detection\n if (commitMessages.length > 0) {\n const headHash = getHeadCommitHash(wsPath) || commitHashes[0];\n if (headHash) {\n const detected = tryDetectDecision({\n workspacePath: wsPath,\n checkpointId: checkpoint.id,\n gitBranch,\n commitHash: headHash,\n commitMessage: commitMessages[0],\n filesChanged: touchedFiles,\n });\n if (detected) {\n console.log(`[KeepGoing] Decision detected: ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);\n }\n }\n }\n\n console.log(`[KeepGoing] Auto-checkpoint saved: ${summary}`);\n process.exit(0);\n}\n\n// Default: start MCP server\n// Workspace path can be passed as an argument, otherwise defaults to CWD.\n// MCP hosts (Claude Code, etc.) typically launch the server with the project root as CWD.\nconst workspacePath = findGitRoot(process.argv[2] || process.cwd());\nconst reader = new KeepGoingReader(workspacePath);\n\nconst server = new McpServer({\n name: 'keepgoing',\n version: '0.1.0',\n});\n\n// Register tools\nregisterGetMomentum(server, reader, workspacePath);\nregisterGetSessionHistory(server, reader);\nregisterGetReentryBriefing(server, reader, workspacePath);\nregisterGetDecisions(server, reader);\nregisterSaveCheckpoint(server, reader, workspacePath);\nregisterSetupProject(server, workspacePath);\n\n// Register prompts\nregisterResumePrompt(server);\n\n// Connect via stdio\nconst transport = new StdioServerTransport();\nawait server.connect(transport);\n\nconsole.error('KeepGoing MCP server started');\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport {\n getRecentSessions,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n type DecisionRecord,\n type ProjectDecisions,\n} from '@keepgoingdev/shared';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst DECISIONS_FILE = 'decisions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Read-only reader for .keepgoing/ directory.\n * Does not write or create any files.\n */\nexport class KeepGoingReader {\n private readonly storagePath: string;\n private readonly metaFilePath: string;\n private readonly sessionsFilePath: string;\n private readonly decisionsFilePath: string;\n private readonly stateFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.decisionsFilePath = path.join(this.storagePath, DECISIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n }\n\n /** Check if .keepgoing/ directory exists. */\n exists(): boolean {\n return fs.existsSync(this.storagePath);\n }\n\n /** Read state.json, returns undefined if missing or corrupt. */\n getState(): ProjectState | undefined {\n return this.readJsonFile<ProjectState>(this.stateFilePath);\n }\n\n /** Read meta.json, returns undefined if missing or corrupt. */\n getMeta(): ProjectMeta | undefined {\n return this.readJsonFile<ProjectMeta>(this.metaFilePath);\n }\n\n /**\n * Read sessions from sessions.json.\n * Handles both formats:\n * - Flat array: SessionCheckpoint[] (from ProjectStorage)\n * - Wrapper object: ProjectSessions (from SessionStorage)\n */\n getSessions(): SessionCheckpoint[] {\n return this.parseSessions().sessions;\n }\n\n /**\n * Get the most recent session checkpoint.\n * Uses state.lastSessionId if available, falls back to last in array.\n */\n getLastSession(): SessionCheckpoint | undefined {\n const { sessions, wrapperLastSessionId } = this.parseSessions();\n if (sessions.length === 0) {\n return undefined;\n }\n\n const state = this.getState();\n if (state?.lastSessionId) {\n const found = sessions.find((s) => s.id === state.lastSessionId);\n if (found) {\n return found;\n }\n }\n\n if (wrapperLastSessionId) {\n const found = sessions.find((s) => s.id === wrapperLastSessionId);\n if (found) {\n return found;\n }\n }\n\n return sessions[sessions.length - 1];\n }\n\n /**\n * Returns the last N sessions, newest first.\n */\n getRecentSessions(count: number): SessionCheckpoint[] {\n return getRecentSessions(this.getSessions(), count);\n }\n\n /** Read all decisions from decisions.json. */\n getDecisions(): DecisionRecord[] {\n return this.parseDecisions().decisions;\n }\n\n /** Returns the last N decisions, newest first. */\n getRecentDecisions(count: number): DecisionRecord[] {\n const all = this.getDecisions();\n return all.slice(-count).reverse();\n }\n\n /**\n * Parses sessions.json once, returning both the session list\n * and the optional lastSessionId from a ProjectSessions wrapper.\n */\n private parseSessions(): { sessions: SessionCheckpoint[]; wrapperLastSessionId?: string } {\n const raw = this.readJsonFile<ProjectSessions | SessionCheckpoint[]>(\n this.sessionsFilePath,\n );\n if (!raw) {\n return { sessions: [] };\n }\n if (Array.isArray(raw)) {\n return { sessions: raw };\n }\n return { sessions: raw.sessions ?? [], wrapperLastSessionId: raw.lastSessionId };\n }\n\n private parseDecisions(): { decisions: DecisionRecord[]; lastDecisionId?: string } {\n const raw = this.readJsonFile<ProjectDecisions>(this.decisionsFilePath);\n if (!raw) {\n return { decisions: [] };\n }\n return { decisions: raw.decisions ?? [], lastDecisionId: raw.lastDecisionId };\n }\n\n private readJsonFile<T>(filePath: string): T | undefined {\n try {\n if (!fs.existsSync(filePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(filePath, 'utf-8');\n return JSON.parse(raw) as T;\n } catch {\n return undefined;\n }\n }\n}\n","import { randomUUID } from 'crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectMeta, ProjectState, DecisionRecord, ProjectDecisions } from './types';\n\n/**\n * Generates a UUID v4 unique ID for checkpoints.\n */\nexport function generateCheckpointId(): string {\n return randomUUID();\n}\n\n/**\n * Creates a SessionCheckpoint with auto-generated id and timestamp.\n * Consolidates the repeated checkpoint construction pattern.\n */\nexport function createCheckpoint(\n fields: Omit<SessionCheckpoint, 'id' | 'timestamp'>,\n): SessionCheckpoint {\n return {\n id: generateCheckpointId(),\n timestamp: new Date().toISOString(),\n ...fields,\n };\n}\n\n/**\n * Creates a default empty project sessions container.\n */\nexport function createEmptyProjectSessions(projectName: string): ProjectSessions {\n return {\n version: 1,\n project: projectName,\n sessions: [],\n lastSessionId: undefined,\n };\n}\n\n/**\n * Creates a default project metadata object.\n */\nexport function createProjectMeta(): ProjectMeta {\n const now = new Date().toISOString();\n return {\n projectId: randomUUID(),\n createdAt: now,\n lastUpdated: now,\n };\n}\n\n/**\n * Creates a default empty project state object.\n */\nexport function createEmptyProjectState(): ProjectState {\n return {};\n}\n\n/**\n * Creates a DecisionRecord with an auto-generated UUID id.\n */\nexport function createDecisionRecord(\n fields: Omit<DecisionRecord, 'id'>,\n): DecisionRecord {\n return {\n id: randomUUID(),\n ...fields,\n };\n}\n\n/**\n * Creates a default empty project decisions container.\n */\nexport function createEmptyProjectDecisions(projectName: string): ProjectDecisions {\n return {\n version: 1,\n project: projectName,\n decisions: [],\n lastDecisionId: undefined,\n };\n}\n","/**\n * Formats a timestamp as a human-readable relative time string.\n * Examples: \"just now\", \"5 minutes ago\", \"3 hours ago\", \"2 days ago\", \"1 week ago\"\n *\n * Note: Month and year calculations use approximations (30 days/month, 365 days/year)\n * for simplicity. These approximations are acceptable for the \"human-readable\" purpose.\n */\nexport function formatRelativeTime(timestamp: string): string {\n const now = Date.now();\n const then = new Date(timestamp).getTime();\n const diffMs = now - then;\n\n // Handle invalid dates\n if (isNaN(diffMs)) {\n return 'unknown time';\n }\n\n // Future dates\n if (diffMs < 0) {\n return 'in the future';\n }\n\n const seconds = Math.floor(diffMs / 1000);\n const minutes = Math.floor(seconds / 60);\n const hours = Math.floor(minutes / 60);\n const days = Math.floor(hours / 24);\n const weeks = Math.floor(days / 7);\n const months = Math.floor(days / 30); // Approximation for human readability\n const years = Math.floor(days / 365); // Approximation, doesn't account for leap years\n\n if (seconds < 10) {\n return 'just now';\n } else if (seconds < 60) {\n return `${seconds} seconds ago`;\n } else if (minutes < 60) {\n return minutes === 1 ? '1 minute ago' : `${minutes} minutes ago`;\n } else if (hours < 24) {\n return hours === 1 ? '1 hour ago' : `${hours} hours ago`;\n } else if (days < 7) {\n return days === 1 ? '1 day ago' : `${days} days ago`;\n } else if (weeks < 4) {\n return weeks === 1 ? '1 week ago' : `${weeks} weeks ago`;\n } else if (months < 12) {\n return months === 1 ? '1 month ago' : `${months} months ago`;\n } else {\n return years === 1 ? '1 year ago' : `${years} years ago`;\n }\n}\n","import { execFileSync, execFile } from 'child_process';\nimport { promisify } from 'util';\n\nconst execFileAsync = promisify(execFile);\n\n/**\n * Git utilities for extracting context from the workspace.\n * These are intentionally decoupled from VSCode APIs.\n * All sync functions use execFileSync (no shell injection).\n */\n\n/**\n * Returns the git repository root for the given path.\n * Falls back to the original path if not inside a git repo.\n */\nexport function findGitRoot(startPath: string): string {\n try {\n const result = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || startPath;\n } catch {\n return startPath;\n }\n}\n\n/**\n * Returns the current git branch name, or undefined if not in a git repo.\n */\nexport function getCurrentBranch(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Runs git log with the given format since a timestamp.\n * Shared implementation for commit hash and message retrieval.\n */\nfunction getGitLogSince(workspacePath: string, format: string, sinceTimestamp?: string): string[] {\n try {\n const since = sinceTimestamp || new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const result = execFileSync(\n 'git',\n ['log', `--since=${since}`, `--format=${format}`],\n {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n },\n );\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .filter((line: string) => line.length > 0);\n } catch {\n return [];\n }\n}\n\n/**\n * Returns a list of commit hashes since a given ISO timestamp.\n * If no timestamp is provided, returns recent commits (last 24 hours).\n */\nexport function getCommitsSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%H', sinceTimestamp);\n}\n\n/**\n * Returns a list of commit subject lines since a given ISO timestamp.\n * If no timestamp is provided, returns commit messages from the last 24 hours.\n * Results are in reverse chronological order (newest first), matching git log default.\n */\nexport function getCommitMessagesSince(workspacePath: string, sinceTimestamp?: string): string[] {\n return getGitLogSince(workspacePath, '%s', sinceTimestamp);\n}\n\n/**\n * Returns the commit message (subject line) for a specific commit hash.\n * Uses hash-based lookup instead of timestamp-based --since to avoid race conditions.\n */\nexport function getCommitMessageByHash(workspacePath: string, commitHash: string): string | undefined {\n try {\n const result = execFileSync('git', ['log', '-1', '--format=%s', commitHash], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns the current HEAD commit hash, or undefined if not in a git repo.\n */\nexport function getHeadCommitHash(workspacePath: string): string | undefined {\n try {\n const result = execFileSync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return result.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getCurrentBranch. Avoids blocking the event loop.\n */\nexport async function getCurrentBranchAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', '--abbrev-ref', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Async version of getHeadCommitHash. Avoids blocking the event loop.\n */\nexport async function getHeadCommitHashAsync(workspacePath: string): Promise<string | undefined> {\n try {\n const { stdout } = await execFileAsync('git', ['rev-parse', 'HEAD'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n return stdout.trim() || undefined;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Returns a list of files that have been modified (tracked and untracked)\n * in the workspace. This is best-effort and may not capture all changes.\n */\nexport function getTouchedFiles(workspacePath: string): string[] {\n try {\n const result = execFileSync('git', ['status', '--porcelain'], {\n cwd: workspacePath,\n encoding: 'utf-8',\n timeout: 5000,\n });\n\n if (!result.trim()) {\n return [];\n }\n\n return result\n .trim()\n .split('\\n')\n .map((line) => line.substring(3).trim())\n .filter((file) => file.length > 0 && !file.endsWith('/'));\n } catch {\n return [];\n }\n}\n","import type {\n SessionCheckpoint,\n ProjectState,\n ReEntryBriefing,\n} from './types';\nimport { formatRelativeTime } from './timeUtils';\n\nconst RECENT_SESSION_COUNT = 5;\n\n/**\n * Generates a synthesized re-entry briefing from stored project data.\n *\n * Uses heuristic rules (no AI) to infer focus and suggest next steps.\n * Designed to be replaced by an LLM-backed implementation in the future\n * while keeping the same input/output contract.\n */\nexport function generateBriefing(\n lastSession: SessionCheckpoint | undefined,\n recentSessions: SessionCheckpoint[],\n projectState: ProjectState,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): ReEntryBriefing | undefined {\n if (!lastSession) {\n return undefined;\n }\n\n return {\n lastWorked: formatRelativeTime(lastSession.timestamp),\n currentFocus: buildCurrentFocus(lastSession, projectState, gitBranch),\n recentActivity: buildRecentActivity(\n lastSession,\n recentSessions,\n recentCommitMessages,\n ),\n suggestedNext: buildSuggestedNext(lastSession, gitBranch),\n smallNextStep: buildSmallNextStep(\n lastSession,\n gitBranch,\n recentCommitMessages,\n ),\n };\n}\n\n/**\n * Returns the most recent N sessions in newest-first order.\n */\nexport function getRecentSessions(\n allSessions: SessionCheckpoint[],\n count: number = RECENT_SESSION_COUNT,\n): SessionCheckpoint[] {\n return allSessions.slice(-count).reverse();\n}\n\nfunction buildCurrentFocus(\n lastSession: SessionCheckpoint,\n projectState: ProjectState,\n gitBranch?: string,\n): string {\n if (projectState.derivedCurrentFocus) {\n return projectState.derivedCurrentFocus;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return branchFocus;\n }\n\n if (lastSession.summary) {\n return lastSession.summary;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return inferFocusFromFiles(lastSession.touchedFiles);\n }\n\n return 'Unknown, save a checkpoint to set context';\n}\n\nfunction buildRecentActivity(\n lastSession: SessionCheckpoint,\n recentSessions: SessionCheckpoint[],\n recentCommitMessages?: string[],\n): string {\n const parts: string[] = [];\n\n const sessionCount = recentSessions.length;\n if (sessionCount > 1) {\n parts.push(`${sessionCount} recent sessions`);\n } else if (sessionCount === 1) {\n parts.push('1 recent session');\n }\n\n if (lastSession.summary) {\n parts.push(`Last: ${lastSession.summary}`);\n }\n\n if (lastSession.touchedFiles.length > 0) {\n parts.push(`${lastSession.touchedFiles.length} files touched`);\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n parts.push(`${recentCommitMessages.length} recent commits`);\n }\n\n return parts.length > 0 ? parts.join('. ') : 'No recent activity recorded';\n}\n\nfunction buildSuggestedNext(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n): string {\n if (lastSession.nextStep) {\n return lastSession.nextStep;\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Continue working on ${branchFocus}`;\n }\n\n if (lastSession.touchedFiles.length > 0) {\n return `Continue working on ${inferFocusFromFiles(lastSession.touchedFiles)}`;\n }\n\n return 'Save a checkpoint to track your next step';\n}\n\nfunction buildSmallNextStep(\n lastSession: SessionCheckpoint,\n gitBranch?: string,\n recentCommitMessages?: string[],\n): string {\n const fallback = 'Review last changed files to resume flow';\n\n if (lastSession.nextStep) {\n const distilled = distillToSmallStep(\n lastSession.nextStep,\n lastSession.touchedFiles,\n );\n if (distilled) {\n return distilled;\n }\n }\n\n if (recentCommitMessages && recentCommitMessages.length > 0) {\n const commitStep = deriveStepFromCommits(recentCommitMessages);\n if (commitStep) {\n return commitStep;\n }\n }\n\n if (lastSession.touchedFiles.length > 0) {\n const fileStep = deriveStepFromFiles(lastSession.touchedFiles);\n if (fileStep) {\n return fileStep;\n }\n }\n\n const branchFocus = inferFocusFromBranch(gitBranch);\n if (branchFocus) {\n return `Check git status for ${branchFocus}`;\n }\n\n return fallback;\n}\n\nfunction distillToSmallStep(\n nextStep: string,\n touchedFiles: string[],\n): string | undefined {\n if (!nextStep.trim()) {\n return undefined;\n }\n\n const words = nextStep.trim().split(/\\s+/);\n if (words.length <= 12) {\n if (touchedFiles.length > 0 && !mentionsFile(nextStep)) {\n const primaryFile = getPrimaryFileName(touchedFiles);\n const enhanced = `${nextStep.trim()} in ${primaryFile}`;\n if (enhanced.split(/\\s+/).length <= 12) {\n return enhanced;\n }\n }\n return nextStep.trim();\n }\n\n return words.slice(0, 12).join(' ');\n}\n\nfunction deriveStepFromCommits(\n commitMessages: string[],\n): string | undefined {\n const lastCommit = commitMessages[0];\n if (!lastCommit || !lastCommit.trim()) {\n return undefined;\n }\n\n const wipPattern =\n /^(?:wip|work in progress|started?|begin|draft)[:\\s]/i;\n if (wipPattern.test(lastCommit)) {\n const topic = lastCommit.replace(wipPattern, '').trim();\n if (topic) {\n const words = topic.split(/\\s+/).slice(0, 8).join(' ');\n return `Continue ${words}`;\n }\n }\n\n return undefined;\n}\n\nfunction deriveStepFromFiles(files: string[]): string | undefined {\n const primaryFile = getPrimaryFileName(files);\n\n if (files.length > 1) {\n return `Open ${primaryFile} and review ${files.length} changed files`;\n }\n\n return `Open ${primaryFile} and pick up where you left off`;\n}\n\nfunction getPrimaryFileName(files: string[]): string {\n const sourceFiles = files.filter((f) => {\n const lower = f.toLowerCase();\n return (\n !lower.includes('test') &&\n !lower.includes('spec') &&\n !lower.includes('.config') &&\n !lower.includes('package.json') &&\n !lower.includes('tsconfig')\n );\n });\n\n const target = sourceFiles.length > 0 ? sourceFiles[0] : files[0];\n const parts = target.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n}\n\nfunction mentionsFile(text: string): boolean {\n return /\\w+\\.(?:ts|tsx|js|jsx|py|go|rs|java|rb|css|scss|html|json|yaml|yml|md|sql|sh)\\b/i.test(\n text,\n );\n}\n\nfunction inferFocusFromBranch(branch?: string): string | undefined {\n if (\n !branch ||\n branch === 'main' ||\n branch === 'master' ||\n branch === 'develop' ||\n branch === 'HEAD'\n ) {\n return undefined;\n }\n\n const prefixPattern =\n /^(?:feature|feat|fix|bugfix|hotfix|chore|refactor|docs|test|ci)\\//i;\n const isFix = /^(?:fix|bugfix|hotfix)\\//i.test(branch);\n const stripped = branch.replace(prefixPattern, '');\n\n const cleaned = stripped\n .replace(/[-_/]/g, ' ')\n .replace(/^\\d+\\s*/, '')\n .trim();\n\n if (!cleaned) {\n return undefined;\n }\n\n return isFix ? `${cleaned} fix` : cleaned;\n}\n\nfunction inferFocusFromFiles(files: string[]): string {\n if (files.length === 0) {\n return 'unknown files';\n }\n\n const dirs = files\n .map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts.length > 1 ? parts.slice(0, -1).join('/') : '';\n })\n .filter((d) => d.length > 0);\n\n if (dirs.length > 0) {\n const counts = new Map<string, number>();\n for (const dir of dirs) {\n counts.set(dir, (counts.get(dir) ?? 0) + 1);\n }\n let topDir = '';\n let topCount = 0;\n for (const [dir, count] of counts) {\n if (count > topCount) {\n topDir = dir;\n topCount = count;\n }\n }\n if (topDir) {\n return `files in ${topDir}`;\n }\n }\n\n const names = files.slice(0, 3).map((f) => {\n const parts = f.replace(/\\\\/g, '/').split('/');\n return parts[parts.length - 1];\n });\n return names.join(', ');\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { randomUUID } from 'node:crypto';\nimport type { SessionCheckpoint, ProjectSessions, ProjectState, ProjectMeta } from './types';\n\nconst STORAGE_DIR = '.keepgoing';\nconst META_FILE = 'meta.json';\nconst SESSIONS_FILE = 'sessions.json';\nconst STATE_FILE = 'state.json';\n\n/**\n * Write layer for .keepgoing/ directory.\n * Creates files if they don't exist yet.\n */\nexport class KeepGoingWriter {\n private readonly storagePath: string;\n private readonly sessionsFilePath: string;\n private readonly stateFilePath: string;\n private readonly metaFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.sessionsFilePath = path.join(this.storagePath, SESSIONS_FILE);\n this.stateFilePath = path.join(this.storagePath, STATE_FILE);\n this.metaFilePath = path.join(this.storagePath, META_FILE);\n }\n\n ensureDir(): void {\n if (!fs.existsSync(this.storagePath)) {\n fs.mkdirSync(this.storagePath, { recursive: true });\n }\n }\n\n saveCheckpoint(checkpoint: SessionCheckpoint, projectName: string): void {\n this.ensureDir();\n\n // Read existing sessions\n let sessionsData: ProjectSessions;\n try {\n if (fs.existsSync(this.sessionsFilePath)) {\n const raw = JSON.parse(fs.readFileSync(this.sessionsFilePath, 'utf-8')) as\n | ProjectSessions\n | SessionCheckpoint[];\n if (Array.isArray(raw)) {\n sessionsData = { version: 1, project: projectName, sessions: raw };\n } else {\n sessionsData = raw;\n }\n } else {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n } catch {\n sessionsData = { version: 1, project: projectName, sessions: [] };\n }\n\n sessionsData.sessions.push(checkpoint);\n sessionsData.lastSessionId = checkpoint.id;\n\n // Prune old sessions to keep the file bounded\n const MAX_SESSIONS = 200;\n if (sessionsData.sessions.length > MAX_SESSIONS) {\n sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);\n }\n\n fs.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), 'utf-8');\n\n // Update state.json\n const state: ProjectState = {\n lastSessionId: checkpoint.id,\n lastKnownBranch: checkpoint.gitBranch,\n lastActivityAt: checkpoint.timestamp,\n };\n fs.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), 'utf-8');\n\n this.updateMeta(checkpoint.timestamp);\n }\n\n private updateMeta(timestamp: string): void {\n let meta: ProjectMeta;\n try {\n if (fs.existsSync(this.metaFilePath)) {\n meta = JSON.parse(fs.readFileSync(this.metaFilePath, 'utf-8')) as ProjectMeta;\n meta.lastUpdated = timestamp;\n } else {\n meta = {\n projectId: randomUUID(),\n createdAt: timestamp,\n lastUpdated: timestamp,\n };\n }\n } catch {\n meta = {\n projectId: randomUUID(),\n createdAt: timestamp,\n lastUpdated: timestamp,\n };\n }\n fs.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), 'utf-8');\n }\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ProjectDecisions, DecisionRecord } from './types';\nimport { createEmptyProjectDecisions } from './session';\nimport { isDecisionsEnabled } from './featureGate';\n\nconst STORAGE_DIR = '.keepgoing';\nconst DECISIONS_FILE = 'decisions.json';\nconst MAX_DECISIONS = 100;\n\n/**\n * Storage layer for persisting decision records as JSON files\n * within a .keepgoing/ folder in the workspace.\n *\n * Write operations (saveDecision, updateDecision) are gated behind the\n * 'decisions' feature flag. Read operations are always available.\n */\nexport class DecisionStorage {\n private readonly storagePath: string;\n private readonly decisionsFilePath: string;\n\n constructor(workspacePath: string) {\n this.storagePath = path.join(workspacePath, STORAGE_DIR);\n this.decisionsFilePath = path.join(this.storagePath, DECISIONS_FILE);\n }\n\n private ensureStorageDir(): void {\n if (!fs.existsSync(this.storagePath)) {\n fs.mkdirSync(this.storagePath, { recursive: true });\n }\n }\n\n private getProjectName(): string {\n return path.basename(path.dirname(this.storagePath));\n }\n\n private load(): ProjectDecisions {\n try {\n if (!fs.existsSync(this.decisionsFilePath)) {\n return createEmptyProjectDecisions(this.getProjectName());\n }\n const raw = fs.readFileSync(this.decisionsFilePath, 'utf-8');\n const data = JSON.parse(raw) as ProjectDecisions;\n return data;\n } catch {\n return createEmptyProjectDecisions(this.getProjectName());\n }\n }\n\n private save(decisions: ProjectDecisions): void {\n this.ensureStorageDir();\n const content = JSON.stringify(decisions, null, 2);\n fs.writeFileSync(this.decisionsFilePath, content, 'utf-8');\n }\n\n /**\n * Save a decision record. Returns true if saved, false if gated or error.\n */\n saveDecision(decision: DecisionRecord): boolean {\n if (!isDecisionsEnabled()) {\n return false;\n }\n\n const data = this.load();\n data.decisions.push(decision);\n data.lastDecisionId = decision.id;\n\n if (data.decisions.length > MAX_DECISIONS) {\n data.decisions = data.decisions.slice(-MAX_DECISIONS);\n }\n\n this.save(data);\n return true;\n }\n\n getLastDecision(): DecisionRecord | undefined {\n const data = this.load();\n if (data.decisions.length === 0) {\n return undefined;\n }\n if (data.lastDecisionId) {\n const found = data.decisions.find(d => d.id === data.lastDecisionId);\n if (found) {\n return found;\n }\n }\n return data.decisions[data.decisions.length - 1];\n }\n\n getAllDecisions(): DecisionRecord[] {\n const data = this.load();\n return data.decisions;\n }\n\n getRecentDecisions(limit: number = 10): DecisionRecord[] {\n const data = this.load();\n return data.decisions.slice(-limit).reverse();\n }\n\n /**\n * Update a decision record. Returns true if updated, false if gated or not found.\n */\n updateDecision(id: string, updates: Partial<Omit<DecisionRecord, 'id'>>): boolean {\n if (!isDecisionsEnabled()) {\n return false;\n }\n\n const data = this.load();\n const index = data.decisions.findIndex(d => d.id === id);\n if (index === -1) {\n return false;\n }\n\n data.decisions[index] = {\n ...data.decisions[index],\n ...updates,\n };\n this.save(data);\n return true;\n }\n\n getStoragePath(): string {\n return this.storagePath;\n }\n}\n","export type GatedFeature = 'decisions';\n\nexport interface FeatureGate {\n isEnabled(feature: GatedFeature): boolean;\n}\n\nclass DefaultFeatureGate implements FeatureGate {\n isEnabled(_feature: GatedFeature): boolean {\n return true; // All features enabled until license system exists\n }\n}\n\nlet currentGate: FeatureGate = new DefaultFeatureGate();\n\nexport function getFeatureGate(): FeatureGate { return currentGate; }\nexport function setFeatureGate(gate: FeatureGate): void { currentGate = gate; }\nexport function isDecisionsEnabled(): boolean { return currentGate.isEnabled('decisions'); }\n","import { DecisionClassification, DecisionCategory } from './types';\nimport { createDecisionRecord } from './session';\nimport { DecisionStorage } from './decisionStorage';\nimport { isDecisionsEnabled } from './featureGate';\n\n/**\n * Input describing a git commit for decision classification.\n */\nexport interface CommitInfo {\n /** Full commit message */\n message: string;\n\n /** List of file paths changed in the commit (relative to repo root) */\n filesChanged: string[];\n\n /**\n * Conventional commit type, if known (e.g. 'ci', 'feat').\n * If omitted, it is parsed from the message automatically.\n */\n type?: string;\n\n /**\n * Conventional commit scope, if known (e.g. 'auth', 'db').\n * If omitted, it is parsed from the message automatically.\n */\n scope?: string;\n}\n\n/** Keywords in the commit message that suggest a decision was made, matched with word boundaries. */\nconst MESSAGE_KEYWORDS: RegExp[] = [\n /\\bdeploy\\b/,\n /\\bworkflow\\b/,\n /\\bmigrate\\b/,\n /\\bmigration\\b/,\n /\\bredirect\\b/,\n /\\borigin\\b/,\n /\\btrusted\\b/,\n /\\boidc\\b/,\n /\\boauth\\b/,\n /\\bpostgres\\b/,\n /\\bsupabase\\b/,\n /\\bdocker\\b/,\n /\\bterraform\\b/,\n /\\bk8s\\b/,\n];\n\n/** Conventional commit types/scopes that indicate high-signal changes. */\nconst HIGH_SIGNAL_TYPES = new Set([\n 'ci',\n 'build',\n 'infra',\n 'ops',\n 'auth',\n 'oauth',\n 'oidc',\n 'migration',\n 'db',\n]);\n\n/** Path patterns that reduce confidence (low-signal changes). */\nconst NEGATIVE_PATH_PATTERNS = [/\\.lock$/, /generated/i, /(?:^|\\/)dist\\//];\n\ntype PathTier = 'infra' | 'contextual';\ninterface PathMatch { label: string; tier: PathTier; }\n\n/**\n * Checks whether a file path matches a high-signal pattern.\n * Returns a {@link PathMatch} with the matched label and its tier, or null if no match.\n *\n * - **infra** tier: inherently significant (CI workflows, Dockerfiles, IaC, etc.)\n * - **contextual** tier: needs corroboration from another signal to be meaningful\n */\nfunction matchHighSignalPath(filePath: string): PathMatch | null {\n // Infra tier: .github/workflows is always at repo root\n if (/^\\.github\\/workflows\\//i.test(filePath)) {\n return { label: '.github/workflows', tier: 'infra' };\n }\n // Infra tier: the rest use (?:^|\\/) to support monorepo nesting\n if (/(?:^|\\/)fly\\.toml$/i.test(filePath)) {\n return { label: 'fly.toml', tier: 'infra' };\n }\n if (/(?:^|\\/)Dockerfile/i.test(filePath)) {\n return { label: 'Dockerfile', tier: 'infra' };\n }\n if (/(?:^|\\/)docker-compose/i.test(filePath)) {\n return { label: 'docker-compose', tier: 'infra' };\n }\n if (/(?:^|\\/)terraform\\//i.test(filePath)) {\n return { label: 'terraform/', tier: 'infra' };\n }\n if (/(?:^|\\/)k8s\\//i.test(filePath)) {\n return { label: 'k8s/', tier: 'infra' };\n }\n if (/(?:^|\\/)supabase\\//i.test(filePath)) {\n return { label: 'supabase/', tier: 'infra' };\n }\n if (/(?:^|\\/)migrations?(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*migration*', tier: 'infra' };\n }\n // Contextual tier: need corroboration\n if (/(?:^|\\/)auth(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*auth*', tier: 'contextual' };\n }\n if (/(?:^|\\/)oidc(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*oidc*', tier: 'contextual' };\n }\n if (/(?:^|\\/)oauth(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*oauth*', tier: 'contextual' };\n }\n if (/(?:^|\\/)redirect(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*redirect*', tier: 'contextual' };\n }\n if (/(?:^|\\/)origin(?:\\/|\\.)/i.test(filePath)) {\n return { label: '*origin*', tier: 'contextual' };\n }\n return null;\n}\n\n/**\n * Extracts the conventional commit type, scope, and breaking-change marker\n * from a commit message.\n * Handles formats like `feat(auth): ...`, `ci: ...`, or `feat!: ...`.\n */\nfunction parseConventionalCommit(message: string): { type?: string; scope?: string; breaking?: boolean } {\n const match = /^([a-z]+)(\\(([^)]+)\\))?\\s*(!)?:/.exec(message.trim());\n if (!match) {\n return {};\n }\n return { type: match[1], scope: match[3], breaking: match[4] === '!' };\n}\n\n/**\n * Infers a broad decision category from the signals that were matched.\n */\nfunction inferCategory(\n matchedKeywords: string[],\n matchedTypes: string[],\n matchedPaths: string[],\n): DecisionCategory {\n const all = [...matchedKeywords, ...matchedTypes, ...matchedPaths].join(' ').toLowerCase();\n if (/auth|oidc|oauth|redirect|origin|trusted/.test(all)) {\n return 'auth';\n }\n if (/migrat|postgres|db|supabase/.test(all)) {\n return 'migration';\n }\n if (/ci|workflow|build|deploy/.test(all)) {\n return 'deploy';\n }\n if (/fly|docker|k8s|terraform|infra|ops/.test(all)) {\n return 'infra';\n }\n return 'unknown';\n}\n\n/**\n * Classifies a git commit as a potential decision point using heuristic signals:\n * - Commit message keywords (deploy, migrate, oauth, etc.)\n * - Conventional commit type/scope (ci, auth, db, etc.)\n * - Changed file paths (.github/workflows, fly.toml, migrations, etc.)\n * - Negative filters (lock files, generated code, pure UI asset changes)\n *\n * Returns a classification with a confidence score (0–1) and human-readable reasons.\n */\nexport function classifyCommit(commit: CommitInfo): DecisionClassification {\n const { message, filesChanged } = commit;\n const messageLower = message.toLowerCase();\n\n // Parse conventional commit type/scope from message if not explicitly provided\n const parsed = parseConventionalCommit(message);\n const type = commit.type ?? parsed.type;\n const scope = commit.scope ?? parsed.scope;\n\n const reasons: string[] = [];\n let confidence = 0;\n\n // Signal 1: commit message keywords (word-boundary matching)\n const matchedKeywords = MESSAGE_KEYWORDS.filter(kw => kw.test(messageLower));\n if (matchedKeywords.length > 0) {\n confidence += 0.3;\n const labels = matchedKeywords.slice(0, 3).map(kw => kw.source.replace(/\\\\b/g, ''));\n reasons.push(`commit message contains: ${labels.join(', ')}`);\n }\n\n // Signal 2: conventional commit type\n const matchedTypes: string[] = [];\n if (type && HIGH_SIGNAL_TYPES.has(type)) {\n matchedTypes.push(`type:${type}`);\n confidence += 0.35;\n reasons.push(`conventional commit type '${type}' is high-signal`);\n }\n\n // Signal 3: conventional commit scope\n if (scope && HIGH_SIGNAL_TYPES.has(scope)) {\n matchedTypes.push(`scope:${scope}`);\n confidence += 0.25;\n reasons.push(`conventional commit scope '${scope}' is high-signal`);\n }\n\n // Signal 4: breaking change marker (feat!:, fix(auth)!:, etc.)\n if (parsed.breaking) {\n confidence += 0.4;\n reasons.push('breaking change indicated by ! marker');\n }\n\n // Signal 5: changed file paths (two-tier scoring)\n const matchedPaths: string[] = [];\n let bestTier: PathTier | null = null;\n for (const file of filesChanged) {\n const pm = matchHighSignalPath(file);\n if (pm && !matchedPaths.includes(pm.label)) {\n matchedPaths.push(pm.label);\n if (bestTier !== 'infra') {\n bestTier = pm.tier;\n }\n }\n }\n if (matchedPaths.length > 0) {\n // Infra paths are inherently significant (+0.40).\n // Contextual paths alone need corroboration (+0.20).\n confidence += bestTier === 'infra' ? 0.4 : 0.2;\n reasons.push(`commit touched: ${matchedPaths.slice(0, 3).join(', ')}`);\n }\n\n // Negative filter 1: all files are low-signal (lock, generated, dist)\n if (filesChanged.length > 0 && filesChanged.every(f => NEGATIVE_PATH_PATTERNS.some(p => p.test(f)))) {\n confidence -= 0.5;\n reasons.push('all changed files are low-signal (lock files, generated code, dist)');\n }\n\n // Negative filter 2: only UI asset changes (favicon, SVG, PNG)\n if (filesChanged.length > 0 && filesChanged.every(f => /favicon/i.test(f) || /\\.(svg|png|ico)$/i.test(f))) {\n confidence -= 0.5;\n reasons.push('all changed files are UI assets (favicon, SVG, PNG)');\n }\n\n // Negative filter 3: only CSS changes with Tailwind in message\n if (\n filesChanged.length > 0 &&\n filesChanged.every(f => /\\.css$/i.test(f)) &&\n /tailwind/i.test(messageLower)\n ) {\n confidence -= 0.5;\n reasons.push('Tailwind generated CSS change (low signal)');\n }\n\n const isDecisionCandidate = confidence >= 0.4;\n const keywordLabels = matchedKeywords.map(kw => kw.source.replace(/\\\\b/g, ''));\n const category = isDecisionCandidate\n ? inferCategory(keywordLabels, matchedTypes, matchedPaths)\n : 'unknown';\n\n return {\n isDecisionCandidate,\n confidence: Math.max(0, Math.min(1, confidence)),\n reasons,\n category,\n };\n}\n\n/**\n * Options for {@link tryDetectDecision}.\n */\nexport interface DetectDecisionOpts {\n workspacePath: string;\n checkpointId: string;\n gitBranch?: string;\n commitHash: string;\n commitMessage: string;\n filesChanged: string[];\n}\n\n/**\n * Result returned when a decision is detected.\n */\nexport interface DetectedDecision {\n category: DecisionCategory;\n confidence: number;\n}\n\n/**\n * Classifies a commit, and if it is a decision candidate, persists a\n * {@link DecisionRecord} via {@link DecisionStorage}.\n *\n * Returns the detected category and confidence when a decision was saved,\n * or `undefined` when the feature is gated or the commit is not a candidate.\n */\nexport function tryDetectDecision(opts: DetectDecisionOpts): DetectedDecision | undefined {\n if (!isDecisionsEnabled()) {\n return undefined;\n }\n\n const classification = classifyCommit({\n message: opts.commitMessage,\n filesChanged: opts.filesChanged,\n });\n\n if (!classification.isDecisionCandidate) {\n return undefined;\n }\n\n const decision = createDecisionRecord({\n checkpointId: opts.checkpointId,\n gitBranch: opts.gitBranch,\n commitHash: opts.commitHash,\n commitMessage: opts.commitMessage,\n filesChanged: opts.filesChanged,\n timestamp: new Date().toISOString(),\n classification,\n });\n\n const storage = new DecisionStorage(opts.workspacePath);\n storage.saveDecision(decision);\n\n return {\n category: classification.category,\n confidence: classification.confidence,\n };\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { getCurrentBranch, formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetMomentum(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_momentum',\n 'Get current developer momentum: last checkpoint, next step, blockers, and branch context. Use this to understand where the developer left off.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n if (!lastSession) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'KeepGoing is set up but no session checkpoints exist yet.',\n },\n ],\n };\n }\n\n const state = reader.getState();\n const currentBranch = getCurrentBranch(workspacePath);\n const branchChanged =\n lastSession.gitBranch &&\n currentBranch &&\n lastSession.gitBranch !== currentBranch;\n\n const lines: string[] = [\n `## Developer Momentum`,\n '',\n `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,\n `**Summary:** ${lastSession.summary || 'No summary'}`,\n `**Next step:** ${lastSession.nextStep || 'Not specified'}`,\n ];\n\n if (lastSession.blocker) {\n lines.push(`**Blocker:** ${lastSession.blocker}`);\n }\n\n if (lastSession.projectIntent) {\n lines.push(`**Project intent:** ${lastSession.projectIntent}`);\n }\n\n lines.push('');\n\n if (currentBranch) {\n lines.push(`**Current branch:** ${currentBranch}`);\n }\n if (branchChanged) {\n lines.push(\n `**Note:** Branch changed since last checkpoint (was \\`${lastSession.gitBranch}\\`, now \\`${currentBranch}\\`)`,\n );\n }\n\n if (lastSession.touchedFiles.length > 0) {\n lines.push('');\n lines.push(\n `**Files touched (${lastSession.touchedFiles.length}):** ${lastSession.touchedFiles.slice(0, 10).join(', ')}`,\n );\n if (lastSession.touchedFiles.length > 10) {\n lines.push(\n ` ...and ${lastSession.touchedFiles.length - 10} more`,\n );\n }\n }\n\n if (state?.derivedCurrentFocus) {\n lines.push('');\n lines.push(`**Derived focus:** ${state.derivedCurrentFocus}`);\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetSessionHistory(server: McpServer, reader: KeepGoingReader) {\n server.tool(\n 'get_session_history',\n 'Get recent session checkpoints. Returns a chronological list of what the developer worked on.',\n { limit: z.number().min(1).max(50).default(5).describe('Number of recent sessions to return (1-50, default 5)') },\n async ({ limit }) => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found.',\n },\n ],\n };\n }\n\n const sessions = reader.getRecentSessions(limit);\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session checkpoints found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Session History (last ${sessions.length})`,\n '',\n ];\n\n for (const session of sessions) {\n lines.push(`### ${formatRelativeTime(session.timestamp)}`);\n lines.push(`- **Summary:** ${session.summary || 'No summary'}`);\n lines.push(`- **Next step:** ${session.nextStep || 'Not specified'}`);\n if (session.blocker) {\n lines.push(`- **Blocker:** ${session.blocker}`);\n }\n if (session.gitBranch) {\n lines.push(`- **Branch:** ${session.gitBranch}`);\n }\n if (session.touchedFiles.length > 0) {\n lines.push(\n `- **Files:** ${session.touchedFiles.slice(0, 5).join(', ')}${session.touchedFiles.length > 5 ? ` (+${session.touchedFiles.length - 5} more)` : ''}`,\n );\n }\n lines.push('');\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n getCurrentBranch,\n getCommitMessagesSince,\n generateBriefing,\n} from '@keepgoingdev/shared';\n\nexport function registerGetReentryBriefing(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'get_reentry_briefing',\n 'Get a synthesized re-entry briefing that helps a developer understand where they left off. Includes focus, recent activity, and suggested next steps.',\n {},\n async () => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found. The developer has not saved any checkpoints yet.',\n },\n ],\n };\n }\n\n const lastSession = reader.getLastSession();\n const recentSessions = reader.getRecentSessions(5);\n const state = reader.getState() ?? {};\n const gitBranch = getCurrentBranch(workspacePath);\n\n const sinceTimestamp = lastSession?.timestamp;\n const recentCommits = sinceTimestamp\n ? getCommitMessagesSince(workspacePath, sinceTimestamp)\n : [];\n\n const briefing = generateBriefing(\n lastSession,\n recentSessions,\n state,\n gitBranch,\n recentCommits,\n );\n\n if (!briefing) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No session data available to generate a briefing.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Re-entry Briefing`,\n '',\n `**Last worked:** ${briefing.lastWorked}`,\n `**Current focus:** ${briefing.currentFocus}`,\n `**Recent activity:** ${briefing.recentActivity}`,\n `**Suggested next:** ${briefing.suggestedNext}`,\n `**Quick start:** ${briefing.smallNextStep}`,\n ];\n\n // Append recent decisions if any exist\n const recentDecisions = reader.getRecentDecisions(3);\n if (recentDecisions.length > 0) {\n lines.push('');\n lines.push('### Recent decisions');\n for (const decision of recentDecisions) {\n const rationale = decision.rationale ? ` - ${decision.rationale}` : '';\n lines.push(`- **${decision.classification.category}:** ${decision.commitMessage}${rationale}`);\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport {\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n getHeadCommitHash,\n tryDetectDecision,\n} from '@keepgoingdev/shared';\n\nexport function registerSaveCheckpoint(server: McpServer, reader: KeepGoingReader, workspacePath: string) {\n server.tool(\n 'save_checkpoint',\n 'Save a development checkpoint. Call this after completing a task or meaningful piece of work, not just at end of session. Each checkpoint helps the next session (or developer) pick up exactly where you left off.',\n {\n summary: z.string().describe('What was accomplished in this session'),\n nextStep: z.string().optional().describe('What to do next'),\n blocker: z.string().optional().describe('Any blocker preventing progress'),\n },\n async ({ summary, nextStep, blocker }) => {\n const lastSession = reader.getLastSession();\n\n const gitBranch = getCurrentBranch(workspacePath);\n const touchedFiles = getTouchedFiles(workspacePath);\n const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);\n const projectName = path.basename(workspacePath);\n\n const checkpoint = createCheckpoint({\n summary,\n nextStep: nextStep || '',\n blocker,\n gitBranch,\n touchedFiles,\n commitHashes,\n workspaceRoot: workspacePath,\n source: 'manual',\n });\n\n const writer = new KeepGoingWriter(workspacePath);\n writer.saveCheckpoint(checkpoint, projectName);\n\n const lines: string[] = [\n `Checkpoint saved.`,\n `- **ID:** ${checkpoint.id}`,\n `- **Branch:** ${gitBranch || 'unknown'}`,\n `- **Files tracked:** ${touchedFiles.length}`,\n `- **Commits captured:** ${commitHashes.length}`,\n ];\n\n // Decision detection\n if (commitHashes.length > 0) {\n const commitMessages = getCommitMessagesSince(workspacePath, lastSession?.timestamp);\n const headHash = getHeadCommitHash(workspacePath);\n if (commitMessages.length > 0 && headHash) {\n const detected = tryDetectDecision({\n workspacePath,\n checkpointId: checkpoint.id,\n gitBranch,\n commitHash: headHash,\n commitMessage: commitMessages[0],\n filesChanged: touchedFiles,\n });\n if (detected) {\n lines.push(`- **Decision detected:** ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);\n }\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { formatRelativeTime } from '@keepgoingdev/shared';\n\nexport function registerGetDecisions(server: McpServer, reader: KeepGoingReader) {\n server.tool(\n 'get_decisions',\n 'Get recent decision records. Returns detected high-signal commits with their category, confidence, and rationale.',\n { limit: z.number().min(1).max(50).default(10).describe('Number of recent decisions to return (1-50, default 10)') },\n async ({ limit }) => {\n if (!reader.exists()) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No KeepGoing data found.',\n },\n ],\n };\n }\n\n const decisions = reader.getRecentDecisions(limit);\n if (decisions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No decision records found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Decisions (last ${decisions.length})`,\n '',\n ];\n\n for (const decision of decisions) {\n lines.push(`### ${decision.commitMessage}`);\n lines.push(`- **When:** ${formatRelativeTime(decision.timestamp)}`);\n lines.push(`- **Category:** ${decision.classification.category}`);\n lines.push(`- **Confidence:** ${(decision.classification.confidence * 100).toFixed(0)}%`);\n if (decision.gitBranch) {\n lines.push(`- **Branch:** ${decision.gitBranch}`);\n }\n if (decision.rationale) {\n lines.push(`- **Rationale:** ${decision.rationale}`);\n }\n if (decision.classification.reasons.length > 0) {\n lines.push(`- **Signals:** ${decision.classification.reasons.join('; ')}`);\n }\n lines.push('');\n }\n\n return {\n content: [{ type: 'text' as const, text: lines.join('\\n') }],\n };\n },\n );\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nconst KEEPGOING_MARKER = '@keepgoingdev/mcp-server';\n\nconst SESSION_START_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --print-momentum',\n },\n ],\n};\n\nconst STOP_HOOK = {\n matcher: '',\n hooks: [\n {\n type: 'command',\n command: 'npx -y @keepgoingdev/mcp-server --save-checkpoint',\n },\n ],\n};\n\nconst CLAUDE_MD_SECTION = `\n## KeepGoing\n\nAfter completing a task or meaningful piece of work, call the \\`save_checkpoint\\` MCP tool with:\n- \\`summary\\`: What you accomplished\n- \\`nextStep\\`: What should be done next\n- \\`blocker\\`: Any blocker (if applicable)\n`;\n\nfunction hasKeepGoingHook(hookEntries: unknown[]): boolean {\n return hookEntries.some((entry: any) =>\n entry?.hooks?.some((h: any) => typeof h?.command === 'string' && h.command.includes(KEEPGOING_MARKER)),\n );\n}\n\nexport function registerSetupProject(server: McpServer, workspacePath: string) {\n server.tool(\n 'setup_project',\n 'Set up KeepGoing in the current project. Adds session hooks to .claude/settings.json and CLAUDE.md instructions so checkpoints are saved automatically.',\n {\n sessionHooks: z.boolean().optional().default(true).describe('Add session hooks to .claude/settings.json'),\n claudeMd: z.boolean().optional().default(true).describe('Add KeepGoing instructions to CLAUDE.md'),\n },\n async ({ sessionHooks, claudeMd }) => {\n const results: string[] = [];\n\n // --- Session hooks ---\n if (sessionHooks) {\n const claudeDir = path.join(workspacePath, '.claude');\n const settingsPath = path.join(claudeDir, 'settings.json');\n\n let settings: any = {};\n if (fs.existsSync(settingsPath)) {\n settings = JSON.parse(fs.readFileSync(settingsPath, 'utf-8'));\n }\n\n if (!settings.hooks) {\n settings.hooks = {};\n }\n\n let hooksChanged = false;\n\n // SessionStart\n if (!Array.isArray(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart = [];\n }\n if (!hasKeepGoingHook(settings.hooks.SessionStart)) {\n settings.hooks.SessionStart.push(SESSION_START_HOOK);\n hooksChanged = true;\n }\n\n // Stop\n if (!Array.isArray(settings.hooks.Stop)) {\n settings.hooks.Stop = [];\n }\n if (!hasKeepGoingHook(settings.hooks.Stop)) {\n settings.hooks.Stop.push(STOP_HOOK);\n hooksChanged = true;\n }\n\n if (hooksChanged) {\n if (!fs.existsSync(claudeDir)) {\n fs.mkdirSync(claudeDir, { recursive: true });\n }\n fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\\n');\n results.push('**Session hooks:** Added to `.claude/settings.json`');\n } else {\n results.push('**Session hooks:** Already present, skipped');\n }\n }\n\n // --- CLAUDE.md ---\n // Prefer .claude/CLAUDE.md if it exists, otherwise fall back to ./CLAUDE.md\n if (claudeMd) {\n const dotClaudeMdPath = path.join(workspacePath, '.claude', 'CLAUDE.md');\n const rootClaudeMdPath = path.join(workspacePath, 'CLAUDE.md');\n const claudeMdPath = fs.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath;\n\n let existing = '';\n if (fs.existsSync(claudeMdPath)) {\n existing = fs.readFileSync(claudeMdPath, 'utf-8');\n }\n\n if (existing.includes('## KeepGoing')) {\n results.push('**CLAUDE.md:** KeepGoing section already present, skipped');\n } else {\n const updated = existing + CLAUDE_MD_SECTION;\n fs.writeFileSync(claudeMdPath, updated);\n results.push('**CLAUDE.md:** Added KeepGoing section');\n }\n }\n\n return {\n content: [{ type: 'text' as const, text: results.join('\\n') }],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerResumePrompt(server: McpServer) {\n server.prompt(\n 'resume',\n 'Check developer momentum and suggest what to work on next',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I just opened this project and want to pick up where I left off.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Check my current momentum (get_momentum)',\n '2. Get a re-entry briefing (get_reentry_briefing)',\n '3. Based on the results, give me a concise summary of where I left off and suggest what to work on next.',\n '',\n 'Keep your response brief and actionable.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":"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","names":["path","fs","path","workspacePath","workspacePath","workspacePath","randomUUID","workspacePath","fs","path","STORAGE_DIR","workspacePath","path","fs","STORAGE_DIR","META_FILE","SESSIONS_FILE","DECISIONS_FILE","STATE_FILE","workspacePath","path","fs","server","reader","workspacePath","server","reader","server","reader","workspacePath","path","z","server","reader","workspacePath","z","path","z","server","reader","z","fs","path","z","server","workspacePath","server","reader","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/mcp-server",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {