@keepgoingdev/mcp-server 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -80,14 +80,12 @@ import { promisify } from "util";
80
80
  var execFileAsync = promisify(execFile);
81
81
  function findGitRoot(startPath) {
82
82
  try {
83
- const gitCommonDir = execFileSync("git", ["rev-parse", "--git-common-dir"], {
83
+ const toplevel = execFileSync("git", ["rev-parse", "--show-toplevel"], {
84
84
  cwd: startPath,
85
85
  encoding: "utf-8",
86
86
  timeout: 5e3
87
87
  }).trim();
88
- if (!gitCommonDir) return startPath;
89
- const absoluteGitDir = path.isAbsolute(gitCommonDir) ? gitCommonDir : path.resolve(startPath, gitCommonDir);
90
- return path.dirname(absoluteGitDir);
88
+ return toplevel || startPath;
91
89
  } catch {
92
90
  return startPath;
93
91
  }
@@ -518,12 +516,11 @@ var DecisionStorage = class {
518
516
  fs2.writeFileSync(this.decisionsFilePath, content, "utf-8");
519
517
  }
520
518
  /**
521
- * Save a decision record. Returns true if saved, false if gated or error.
519
+ * Save a decision record as a draft. Always persists regardless of Pro
520
+ * status so decisions are captured at the correct time. Returns true if
521
+ * saved, false on I/O error.
522
522
  */
523
523
  saveDecision(decision) {
524
- if (!isDecisionsEnabled()) {
525
- return false;
526
- }
527
524
  const data = this.load();
528
525
  data.decisions.push(decision);
529
526
  data.lastDecisionId = decision.id;
@@ -830,16 +827,54 @@ var REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1e3;
830
827
 
831
828
  // ../../packages/shared/src/licenseClient.ts
832
829
  var BASE_URL = "https://api.lemonsqueezy.com/v1/licenses";
833
- async function activateLicense(licenseKey, instanceName) {
830
+ var REQUEST_TIMEOUT_MS = 15e3;
831
+ var EXPECTED_STORE_ID = 301555;
832
+ var EXPECTED_PRODUCT_ID = 864311;
833
+ function fetchWithTimeout(url, init) {
834
+ const controller = new AbortController();
835
+ const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
836
+ return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));
837
+ }
838
+ function validateProductIdentity(meta) {
839
+ if (!meta) return "License response missing product metadata.";
840
+ if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {
841
+ return "This license key does not belong to KeepGoing.";
842
+ }
843
+ return void 0;
844
+ }
845
+ async function safeJson(res) {
846
+ try {
847
+ const text = await res.text();
848
+ return JSON.parse(text);
849
+ } catch {
850
+ return null;
851
+ }
852
+ }
853
+ async function activateLicense(licenseKey, instanceName, options) {
834
854
  try {
835
- const res = await fetch(`${BASE_URL}/activate`, {
855
+ const res = await fetchWithTimeout(`${BASE_URL}/activate`, {
836
856
  method: "POST",
837
857
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
838
858
  body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName })
839
859
  });
840
- const data = await res.json();
841
- if (!res.ok || !data.activated) {
842
- return { valid: false, error: data.error || `Activation failed (${res.status})` };
860
+ const data = await safeJson(res);
861
+ if (!res.ok || !data?.activated) {
862
+ return { valid: false, error: data?.error || `Activation failed (${res.status})` };
863
+ }
864
+ if (!options?.allowTestMode && data.license_key?.test_mode) {
865
+ if (data.license_key?.key && data.instance?.id) {
866
+ await deactivateLicense(data.license_key.key, data.instance.id);
867
+ }
868
+ return { valid: false, error: "This is a test license key. Please use a production license key from your purchase confirmation." };
869
+ }
870
+ if (!options?.allowTestMode) {
871
+ const productError = validateProductIdentity(data.meta);
872
+ if (productError) {
873
+ if (data.license_key?.key && data.instance?.id) {
874
+ await deactivateLicense(data.license_key.key, data.instance.id);
875
+ }
876
+ return { valid: false, error: productError };
877
+ }
843
878
  }
844
879
  return {
845
880
  valid: true,
@@ -849,23 +884,25 @@ async function activateLicense(licenseKey, instanceName) {
849
884
  productName: data.meta?.product_name
850
885
  };
851
886
  } catch (err) {
852
- return { valid: false, error: err instanceof Error ? err.message : "Network error" };
887
+ const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
888
+ return { valid: false, error: message };
853
889
  }
854
890
  }
855
891
  async function deactivateLicense(licenseKey, instanceId) {
856
892
  try {
857
- const res = await fetch(`${BASE_URL}/deactivate`, {
893
+ const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {
858
894
  method: "POST",
859
895
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
860
896
  body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId })
861
897
  });
862
- const data = await res.json();
863
- if (!res.ok || !data.deactivated) {
864
- return { deactivated: false, error: data.error || `Deactivation failed (${res.status})` };
898
+ const data = await safeJson(res);
899
+ if (!res.ok || !data?.deactivated) {
900
+ return { deactivated: false, error: data?.error || `Deactivation failed (${res.status})` };
865
901
  }
866
902
  return { deactivated: true };
867
903
  } catch (err) {
868
- return { deactivated: false, error: err instanceof Error ? err.message : "Network error" };
904
+ const message = err instanceof Error && err.name === "AbortError" ? "Request timed out. Please check your network connection and try again." : err instanceof Error ? err.message : "Network error";
905
+ return { deactivated: false, error: message };
869
906
  }
870
907
  }
871
908
 
@@ -876,13 +913,19 @@ var SESSIONS_FILE2 = "sessions.json";
876
913
  var DECISIONS_FILE2 = "decisions.json";
877
914
  var STATE_FILE2 = "state.json";
878
915
  var KeepGoingReader = class {
916
+ workspacePath;
879
917
  storagePath;
880
918
  metaFilePath;
881
919
  sessionsFilePath;
882
920
  decisionsFilePath;
883
921
  stateFilePath;
922
+ _isWorktree;
923
+ _cachedBranch = null;
924
+ // null = not yet resolved
884
925
  constructor(workspacePath2) {
926
+ this.workspacePath = workspacePath2;
885
927
  const mainRoot = resolveStorageRoot(workspacePath2);
928
+ this._isWorktree = mainRoot !== workspacePath2;
886
929
  this.storagePath = path5.join(mainRoot, STORAGE_DIR3);
887
930
  this.metaFilePath = path5.join(this.storagePath, META_FILE2);
888
931
  this.sessionsFilePath = path5.join(this.storagePath, SESSIONS_FILE2);
@@ -953,6 +996,85 @@ var KeepGoingReader = class {
953
996
  getLicenseCache() {
954
997
  return readLicenseCache();
955
998
  }
999
+ /** Get the last session checkpoint for a specific branch. */
1000
+ getLastSessionForBranch(branch) {
1001
+ const sessions = this.getSessions().filter((s) => s.gitBranch === branch);
1002
+ return sessions.length > 0 ? sessions[sessions.length - 1] : void 0;
1003
+ }
1004
+ /** Returns the last N sessions for a specific branch, newest first. */
1005
+ getRecentSessionsForBranch(branch, count) {
1006
+ const filtered = this.getSessions().filter((s) => s.gitBranch === branch);
1007
+ return filtered.slice(-count).reverse();
1008
+ }
1009
+ /** Returns the last N decisions for a specific branch, newest first. */
1010
+ getRecentDecisionsForBranch(branch, count) {
1011
+ const filtered = this.getDecisions().filter((d) => d.gitBranch === branch);
1012
+ return filtered.slice(-count).reverse();
1013
+ }
1014
+ /** Whether the workspace is inside a git worktree. */
1015
+ get isWorktree() {
1016
+ return this._isWorktree;
1017
+ }
1018
+ /**
1019
+ * Returns the current git branch for this workspace.
1020
+ * Lazily cached: the branch is resolved once per KeepGoingReader instance.
1021
+ */
1022
+ getCurrentBranch() {
1023
+ if (this._cachedBranch === null) {
1024
+ this._cachedBranch = getCurrentBranch(this.workspacePath);
1025
+ }
1026
+ return this._cachedBranch;
1027
+ }
1028
+ /**
1029
+ * Worktree-aware last session lookup.
1030
+ * In a worktree, scopes to the current branch with fallback to global.
1031
+ * Returns the session and whether it fell back to global.
1032
+ */
1033
+ getScopedLastSession() {
1034
+ const branch = this.getCurrentBranch();
1035
+ if (this._isWorktree && branch) {
1036
+ const scoped = this.getLastSessionForBranch(branch);
1037
+ if (scoped) return { session: scoped, isFallback: false };
1038
+ return { session: this.getLastSession(), isFallback: true };
1039
+ }
1040
+ return { session: this.getLastSession(), isFallback: false };
1041
+ }
1042
+ /** Worktree-aware recent sessions. Scopes to current branch in a worktree. */
1043
+ getScopedRecentSessions(count) {
1044
+ const branch = this.getCurrentBranch();
1045
+ if (this._isWorktree && branch) {
1046
+ return this.getRecentSessionsForBranch(branch, count);
1047
+ }
1048
+ return this.getRecentSessions(count);
1049
+ }
1050
+ /** Worktree-aware recent decisions. Scopes to current branch in a worktree. */
1051
+ getScopedRecentDecisions(count) {
1052
+ const branch = this.getCurrentBranch();
1053
+ if (this._isWorktree && branch) {
1054
+ return this.getRecentDecisionsForBranch(branch, count);
1055
+ }
1056
+ return this.getRecentDecisions(count);
1057
+ }
1058
+ /**
1059
+ * Resolves branch scope from an explicit `branch` parameter.
1060
+ * Used by tools that accept a `branch` argument (e.g. get_session_history, get_decisions).
1061
+ * - `"all"` returns no filter.
1062
+ * - An explicit branch name uses that.
1063
+ * - `undefined` auto-scopes to the current branch in a worktree, or all branches otherwise.
1064
+ */
1065
+ resolveBranchScope(branch) {
1066
+ if (branch === "all") {
1067
+ return { effectiveBranch: void 0, scopeLabel: "all branches" };
1068
+ }
1069
+ if (branch) {
1070
+ return { effectiveBranch: branch, scopeLabel: `branch \`${branch}\`` };
1071
+ }
1072
+ const currentBranch = this.getCurrentBranch();
1073
+ if (this._isWorktree && currentBranch) {
1074
+ return { effectiveBranch: currentBranch, scopeLabel: `branch \`${currentBranch}\` (worktree)` };
1075
+ }
1076
+ return { effectiveBranch: void 0, scopeLabel: "all branches" };
1077
+ }
956
1078
  /**
957
1079
  * Parses sessions.json once, returning both the session list
958
1080
  * and the optional lastSessionId from a ProjectSessions wrapper.
@@ -1006,7 +1128,8 @@ function registerGetMomentum(server2, reader2, workspacePath2) {
1006
1128
  ]
1007
1129
  };
1008
1130
  }
1009
- const lastSession = reader2.getLastSession();
1131
+ const { session: lastSession, isFallback } = reader2.getScopedLastSession();
1132
+ const currentBranch = reader2.getCurrentBranch();
1010
1133
  if (!lastSession) {
1011
1134
  return {
1012
1135
  content: [
@@ -1018,15 +1141,23 @@ function registerGetMomentum(server2, reader2, workspacePath2) {
1018
1141
  };
1019
1142
  }
1020
1143
  const state = reader2.getState();
1021
- const currentBranch = getCurrentBranch(workspacePath2);
1022
1144
  const branchChanged = lastSession.gitBranch && currentBranch && lastSession.gitBranch !== currentBranch;
1023
1145
  const lines = [
1024
1146
  `## Developer Momentum`,
1025
- "",
1147
+ ""
1148
+ ];
1149
+ if (reader2.isWorktree && currentBranch) {
1150
+ lines.push(`**Worktree context:** Scoped to branch \`${currentBranch}\``);
1151
+ if (isFallback) {
1152
+ lines.push(`**Note:** No checkpoints found for branch \`${currentBranch}\`. Showing last global checkpoint.`);
1153
+ }
1154
+ lines.push("");
1155
+ }
1156
+ lines.push(
1026
1157
  `**Last checkpoint:** ${formatRelativeTime(lastSession.timestamp)}`,
1027
1158
  `**Summary:** ${lastSession.summary || "No summary"}`,
1028
1159
  `**Next step:** ${lastSession.nextStep || "Not specified"}`
1029
- ];
1160
+ );
1030
1161
  if (lastSession.blocker) {
1031
1162
  lines.push(`**Blocker:** ${lastSession.blocker}`);
1032
1163
  }
@@ -1037,7 +1168,7 @@ function registerGetMomentum(server2, reader2, workspacePath2) {
1037
1168
  if (currentBranch) {
1038
1169
  lines.push(`**Current branch:** ${currentBranch}`);
1039
1170
  }
1040
- if (branchChanged) {
1171
+ if (branchChanged && !reader2.isWorktree) {
1041
1172
  lines.push(
1042
1173
  `**Note:** Branch changed since last checkpoint (was \`${lastSession.gitBranch}\`, now \`${currentBranch}\`)`
1043
1174
  );
@@ -1070,8 +1201,11 @@ function registerGetSessionHistory(server2, reader2) {
1070
1201
  server2.tool(
1071
1202
  "get_session_history",
1072
1203
  "Get recent session checkpoints. Returns a chronological list of what the developer worked on.",
1073
- { limit: z.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)") },
1074
- async ({ limit }) => {
1204
+ {
1205
+ limit: z.number().min(1).max(50).default(5).describe("Number of recent sessions to return (1-50, default 5)"),
1206
+ branch: z.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
1207
+ },
1208
+ async ({ limit, branch }) => {
1075
1209
  if (!reader2.exists()) {
1076
1210
  return {
1077
1211
  content: [
@@ -1082,19 +1216,20 @@ function registerGetSessionHistory(server2, reader2) {
1082
1216
  ]
1083
1217
  };
1084
1218
  }
1085
- const sessions = reader2.getRecentSessions(limit);
1219
+ const { effectiveBranch, scopeLabel } = reader2.resolveBranchScope(branch);
1220
+ const sessions = effectiveBranch ? reader2.getRecentSessionsForBranch(effectiveBranch, limit) : reader2.getRecentSessions(limit);
1086
1221
  if (sessions.length === 0) {
1087
1222
  return {
1088
1223
  content: [
1089
1224
  {
1090
1225
  type: "text",
1091
- text: "No session checkpoints found."
1226
+ text: effectiveBranch ? `No session checkpoints found for branch \`${effectiveBranch}\`. Use branch: "all" to see all branches.` : "No session checkpoints found."
1092
1227
  }
1093
1228
  ]
1094
1229
  };
1095
1230
  }
1096
1231
  const lines = [
1097
- `## Session History (last ${sessions.length})`,
1232
+ `## Session History (last ${sessions.length}, ${scopeLabel})`,
1098
1233
  ""
1099
1234
  ];
1100
1235
  for (const session of sessions) {
@@ -1138,10 +1273,10 @@ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
1138
1273
  ]
1139
1274
  };
1140
1275
  }
1141
- const lastSession = reader2.getLastSession();
1142
- const recentSessions = reader2.getRecentSessions(5);
1276
+ const gitBranch = reader2.getCurrentBranch();
1277
+ const { session: lastSession } = reader2.getScopedLastSession();
1278
+ const recentSessions = reader2.getScopedRecentSessions(5);
1143
1279
  const state = reader2.getState() ?? {};
1144
- const gitBranch = getCurrentBranch(workspacePath2);
1145
1280
  const sinceTimestamp = lastSession?.timestamp;
1146
1281
  const recentCommits = sinceTimestamp ? getCommitMessagesSince(workspacePath2, sinceTimestamp) : [];
1147
1282
  const briefing = generateBriefing(
@@ -1163,14 +1298,20 @@ function registerGetReentryBriefing(server2, reader2, workspacePath2) {
1163
1298
  }
1164
1299
  const lines = [
1165
1300
  `## Re-entry Briefing`,
1166
- "",
1301
+ ""
1302
+ ];
1303
+ if (reader2.isWorktree && gitBranch) {
1304
+ lines.push(`**Worktree context:** Scoped to branch \`${gitBranch}\``);
1305
+ lines.push("");
1306
+ }
1307
+ lines.push(
1167
1308
  `**Last worked:** ${briefing.lastWorked}`,
1168
1309
  `**Current focus:** ${briefing.currentFocus}`,
1169
1310
  `**Recent activity:** ${briefing.recentActivity}`,
1170
1311
  `**Suggested next:** ${briefing.suggestedNext}`,
1171
1312
  `**Quick start:** ${briefing.smallNextStep}`
1172
- ];
1173
- const recentDecisions = reader2.getRecentDecisions(3);
1313
+ );
1314
+ const recentDecisions = reader2.getScopedRecentDecisions(3);
1174
1315
  if (recentDecisions.length > 0) {
1175
1316
  lines.push("");
1176
1317
  lines.push("### Recent decisions");
@@ -1253,8 +1394,11 @@ function registerGetDecisions(server2, reader2) {
1253
1394
  server2.tool(
1254
1395
  "get_decisions",
1255
1396
  "Get recent decision records. Returns detected high-signal commits with their category, confidence, and rationale.",
1256
- { limit: z3.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)") },
1257
- async ({ limit }) => {
1397
+ {
1398
+ limit: z3.number().min(1).max(50).default(10).describe("Number of recent decisions to return (1-50, default 10)"),
1399
+ branch: z3.string().optional().describe('Filter to a specific branch name, or "all" to show all branches. Auto-detected from worktree context by default.')
1400
+ },
1401
+ async ({ limit, branch }) => {
1258
1402
  if (!reader2.exists()) {
1259
1403
  return {
1260
1404
  content: [
@@ -1271,24 +1415,25 @@ function registerGetDecisions(server2, reader2) {
1271
1415
  content: [
1272
1416
  {
1273
1417
  type: "text",
1274
- text: "Decision Detection requires a Pro license. Use the activate_license tool, run `keepgoing activate <key>` in your terminal, or visit https://keepgoing.dev/pricing to purchase."
1418
+ text: "Decision Detection requires a Pro license. Use the activate_license tool, run `keepgoing activate <key>` in your terminal, or visit https://keepgoing.dev/add-ons to purchase."
1275
1419
  }
1276
1420
  ]
1277
1421
  };
1278
1422
  }
1279
- const decisions = reader2.getRecentDecisions(limit);
1423
+ const { effectiveBranch, scopeLabel } = reader2.resolveBranchScope(branch);
1424
+ const decisions = effectiveBranch ? reader2.getRecentDecisionsForBranch(effectiveBranch, limit) : reader2.getRecentDecisions(limit);
1280
1425
  if (decisions.length === 0) {
1281
1426
  return {
1282
1427
  content: [
1283
1428
  {
1284
1429
  type: "text",
1285
- text: "No decision records found."
1430
+ text: effectiveBranch ? `No decision records found for branch \`${effectiveBranch}\`. Use branch: "all" to see all branches.` : "No decision records found."
1286
1431
  }
1287
1432
  ]
1288
1433
  };
1289
1434
  }
1290
1435
  const lines = [
1291
- `## Decisions (last ${decisions.length})`,
1436
+ `## Decisions (last ${decisions.length}, ${scopeLabel})`,
1292
1437
  ""
1293
1438
  ];
1294
1439
  for (const decision of decisions) {
@@ -1542,6 +1687,62 @@ function registerResumePrompt(server2) {
1542
1687
  );
1543
1688
  }
1544
1689
 
1690
+ // src/prompts/decisions.ts
1691
+ function registerDecisionsPrompt(server2) {
1692
+ server2.prompt(
1693
+ "decisions",
1694
+ "Review recent architectural decisions and their rationale",
1695
+ async () => ({
1696
+ messages: [
1697
+ {
1698
+ role: "user",
1699
+ content: {
1700
+ type: "text",
1701
+ text: [
1702
+ "I want to review recent architectural decisions in this project.",
1703
+ "",
1704
+ "Please use the KeepGoing tools to:",
1705
+ "1. Fetch recent decision records (get_decisions)",
1706
+ "2. Get my current branch context (get_momentum)",
1707
+ "3. Summarize the decisions, highlighting any that were made on the current branch",
1708
+ "",
1709
+ "Keep your response brief and organized."
1710
+ ].join("\n")
1711
+ }
1712
+ }
1713
+ ]
1714
+ })
1715
+ );
1716
+ }
1717
+
1718
+ // src/prompts/progress.ts
1719
+ function registerProgressPrompt(server2) {
1720
+ server2.prompt(
1721
+ "progress",
1722
+ "Summarize recent development progress across sessions",
1723
+ async () => ({
1724
+ messages: [
1725
+ {
1726
+ role: "user",
1727
+ content: {
1728
+ type: "text",
1729
+ text: [
1730
+ "I need a summary of recent development progress for this project.",
1731
+ "",
1732
+ "Please use the KeepGoing tools to:",
1733
+ "1. Fetch session history with a higher limit for broader coverage (get_session_history, limit: 20)",
1734
+ "2. Get my current branch context (get_momentum)",
1735
+ "3. Synthesize a progress summary grouped by branch or feature, highlighting the current branch",
1736
+ "",
1737
+ "Format the summary so it can be used in a standup or sprint review."
1738
+ ].join("\n")
1739
+ }
1740
+ }
1741
+ ]
1742
+ })
1743
+ );
1744
+ }
1745
+
1545
1746
  // src/index.ts
1546
1747
  if (process.argv.includes("--print-momentum")) {
1547
1748
  const wsPath = findGitRoot(process.argv.slice(2).find((a) => a !== "--print-momentum") || process.cwd());
@@ -1549,7 +1750,7 @@ if (process.argv.includes("--print-momentum")) {
1549
1750
  if (!reader2.exists()) {
1550
1751
  process.exit(0);
1551
1752
  }
1552
- const lastSession = reader2.getLastSession();
1753
+ const { session: lastSession } = reader2.getScopedLastSession();
1553
1754
  if (!lastSession) {
1554
1755
  process.exit(0);
1555
1756
  }
@@ -1578,7 +1779,7 @@ if (process.argv.includes("--print-momentum")) {
1578
1779
  if (process.argv.includes("--save-checkpoint")) {
1579
1780
  const wsPath = findGitRoot(process.argv.slice(2).find((a) => !a.startsWith("--")) || process.cwd());
1580
1781
  const reader2 = new KeepGoingReader(wsPath);
1581
- const lastSession = reader2.getLastSession();
1782
+ const { session: lastSession } = reader2.getScopedLastSession();
1582
1783
  if (lastSession?.timestamp) {
1583
1784
  const ageMs = Date.now() - new Date(lastSession.timestamp).getTime();
1584
1785
  if (ageMs < 2 * 60 * 1e3) {
@@ -1649,6 +1850,8 @@ registerSetupProject(server, workspacePath);
1649
1850
  registerActivateLicense(server);
1650
1851
  registerDeactivateLicense(server);
1651
1852
  registerResumePrompt(server);
1853
+ registerDecisionsPrompt(server);
1854
+ registerProgressPrompt(server);
1652
1855
  var transport = new StdioServerTransport();
1653
1856
  await server.connect(transport);
1654
1857
  console.error("KeepGoing MCP server started");
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","../../../packages/shared/src/decisionStorage.ts","../../../packages/shared/src/featureGate.ts","../../../packages/shared/src/decisionDetection.ts","../../../packages/shared/src/license.ts","../../../packages/shared/src/licenseClient.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/tools/activateLicense.ts","../src/tools/deactivateLicense.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 resolveStorageRoot,\n formatRelativeTime,\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n getHeadCommitHash,\n tryDetectDecision,\n readLicenseCache,\n isCachedLicenseValid,\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 { registerActivateLicense } from './tools/activateLicense.js';\nimport { registerDeactivateLicense } from './tools/deactivateLicense.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(resolveStorageRoot(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 (Pro feature, requires valid license)\n const licenseCache = readLicenseCache();\n if (isCachedLicenseValid(licenseCache) && 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);\nregisterActivateLicense(server);\nregisterDeactivateLicense(server);\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 readLicenseCache,\n resolveStorageRoot,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n type DecisionRecord,\n type ProjectDecisions,\n type LicenseCache,\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 const mainRoot = resolveStorageRoot(workspacePath);\n this.storagePath = path.join(mainRoot, 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 /** Read cached license data from the global `~/.keepgoing/license.json`. */\n getLicenseCache(): LicenseCache | undefined {\n return readLicenseCache();\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 path from 'node:path';\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 // Use --git-common-dir instead of --show-toplevel\n // because --show-toplevel returns the worktree root in a worktree,\n // while --git-common-dir always returns the real .git directory\n const gitCommonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n\n if (!gitCommonDir) return startPath;\n\n // Resolve relative paths (git may return relative like \".git\")\n const absoluteGitDir = path.isAbsolute(gitCommonDir)\n ? gitCommonDir\n : path.resolve(startPath, gitCommonDir);\n\n // The repo root is the parent of the .git directory\n return path.dirname(absoluteGitDir);\n } catch {\n return startPath;\n }\n}\n\n/**\n * Cache for resolveStorageRoot results to avoid redundant subprocess calls.\n */\nconst storageRootCache = new Map<string, string>();\n\n/**\n * Resolves the main repository root for storage purposes.\n * In a git worktree, `--show-toplevel` returns the worktree root, but\n * `.keepgoing/` should always live in the main repo root so data persists\n * across worktrees. This function uses `--git-common-dir` to find the\n * shared `.git` directory and derives the main repo root from it.\n *\n * Falls back to `startPath` if git commands fail (non-git directories, tests).\n */\nexport function resolveStorageRoot(startPath: string): string {\n const cached = storageRootCache.get(startPath);\n if (cached !== undefined) {\n return cached;\n }\n\n try {\n const toplevel = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n\n const commonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n\n // commonDir is relative to the worktree root (e.g. \"../../.git\" or just \".git\")\n const absoluteCommonDir = path.resolve(toplevel, commonDir);\n const mainRoot = path.dirname(absoluteCommonDir);\n\n storageRootCache.set(startPath, mainRoot);\n return mainRoot;\n } catch {\n storageRootCache.set(startPath, startPath);\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';\nimport { resolveStorageRoot } from './gitUtils';\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 const mainRoot = resolveStorageRoot(workspacePath);\n this.storagePath = path.join(mainRoot, 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';\nimport { resolveStorageRoot } from './gitUtils';\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 const mainRoot = resolveStorageRoot(workspacePath);\n this.storagePath = path.join(mainRoot, 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\n/**\n * Feature gate controlled by license status.\n * When pro is disabled, gated features (decisions) are blocked.\n */\nexport class LicenseFeatureGate implements FeatureGate {\n constructor(private proEnabled: boolean) {}\n\n isEnabled(feature: GatedFeature): boolean {\n return feature === 'decisions' ? this.proEnabled : true;\n }\n\n setProEnabled(enabled: boolean): void {\n this.proEnabled = enabled;\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 crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nconst LICENSE_FILE = 'license.json';\nconst DEVICE_ID_FILE = 'device-id';\n\n/**\n * Directory for device-wide license storage: `~/.keepgoing/`\n */\nexport function getGlobalLicenseDir(): string {\n return path.join(os.homedir(), '.keepgoing');\n}\n\n/**\n * Full path to the global license cache: `~/.keepgoing/license.json`\n */\nexport function getGlobalLicensePath(): string {\n return path.join(getGlobalLicenseDir(), LICENSE_FILE);\n}\n\n/**\n * Returns a stable, persistent device identifier shared by all consumers\n * (VS Code extension, CLI, MCP server) so each physical machine counts\n * as one activation.\n *\n * The ID is a random UUID stored in `~/.keepgoing/device-id`. Once\n * generated it never changes, even if the hostname or OS is updated.\n */\nexport function getDeviceId(): string {\n const dir = getGlobalLicenseDir();\n const filePath = path.join(dir, DEVICE_ID_FILE);\n\n try {\n const existing = fs.readFileSync(filePath, 'utf-8').trim();\n if (existing) return existing;\n } catch {\n // File doesn't exist yet, generate below\n }\n\n const id = crypto.randomUUID();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, id, 'utf-8');\n return id;\n}\n\nexport type LicenseStatus = 'active' | 'inactive' | 'expired' | 'disabled';\n\nexport interface LicenseCache {\n licenseKey: string;\n instanceId: string;\n status: LicenseStatus;\n lastValidatedAt: string; // ISO 8601\n activatedAt: string; // ISO 8601\n customerName?: string;\n productName?: string;\n}\n\n/**\n * Read cached license data from `~/.keepgoing/license.json`.\n * Returns undefined if the file is missing or corrupt.\n */\nexport function readLicenseCache(): LicenseCache | undefined {\n const licensePath = getGlobalLicensePath();\n try {\n if (!fs.existsSync(licensePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(licensePath, 'utf-8');\n return JSON.parse(raw) as LicenseCache;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write license cache to `~/.keepgoing/license.json`.\n * Creates the `~/.keepgoing/` directory if it doesn't exist.\n */\nexport function writeLicenseCache(cache: LicenseCache): void {\n const dirPath = getGlobalLicenseDir();\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true });\n }\n const licensePath = path.join(dirPath, LICENSE_FILE);\n fs.writeFileSync(licensePath, JSON.stringify(cache, null, 2), 'utf-8');\n}\n\n/**\n * Delete the cached license file.\n */\nexport function deleteLicenseCache(): void {\n const licensePath = getGlobalLicensePath();\n try {\n if (fs.existsSync(licensePath)) {\n fs.unlinkSync(licensePath);\n }\n } catch {\n // Ignore errors when deleting\n }\n}\n\n/**\n * Check if a cached license is considered valid (active).\n */\nexport function isCachedLicenseValid(cache: LicenseCache | undefined): boolean {\n return cache?.status === 'active';\n}\n\nconst REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * Check if the cached license needs revalidation (last validated > 24 hours ago).\n */\nexport function needsRevalidation(cache: LicenseCache): boolean {\n const lastValidated = new Date(cache.lastValidatedAt).getTime();\n return Date.now() - lastValidated > REVALIDATION_THRESHOLD_MS;\n}\n","/**\n * HTTP client for the LemonSqueezy license API.\n * Uses Node's built-in fetch (no new dependencies).\n *\n * API docs: https://docs.lemonsqueezy.com/api/license-api\n * These endpoints accept application/x-www-form-urlencoded bodies\n * and do not require an API key header.\n */\n\nconst BASE_URL = 'https://api.lemonsqueezy.com/v1/licenses';\n\ninterface LemonSqueezyLicenseMeta {\n store_id: number;\n product_id: number;\n product_name: string;\n variant_id: number;\n variant_name: string;\n customer_id: number;\n customer_name: string;\n customer_email: string;\n}\n\ninterface LemonSqueezyInstance {\n id: string;\n name: string;\n created_at: string;\n}\n\nexport interface ActivateResult {\n valid: boolean;\n error?: string;\n licenseKey?: string;\n instanceId?: string;\n customerName?: string;\n productName?: string;\n}\n\nexport interface ValidateResult {\n valid: boolean;\n error?: string;\n licenseKey?: string;\n customerName?: string;\n productName?: string;\n}\n\nexport interface DeactivateResult {\n deactivated: boolean;\n error?: string;\n}\n\n/**\n * Activate a license key on a device.\n */\nexport async function activateLicense(\n licenseKey: string,\n instanceName: string,\n): Promise<ActivateResult> {\n try {\n const res = await fetch(`${BASE_URL}/activate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName }),\n });\n\n const data = await res.json() as {\n activated?: boolean;\n error?: string;\n license_key?: { key: string };\n instance?: LemonSqueezyInstance;\n meta?: LemonSqueezyLicenseMeta;\n };\n\n if (!res.ok || !data.activated) {\n return { valid: false, error: data.error || `Activation failed (${res.status})` };\n }\n\n return {\n valid: true,\n licenseKey: data.license_key?.key,\n instanceId: data.instance?.id,\n customerName: data.meta?.customer_name,\n productName: data.meta?.product_name,\n };\n } catch (err) {\n return { valid: false, error: err instanceof Error ? err.message : 'Network error' };\n }\n}\n\n/**\n * Validate a previously activated license key.\n */\nexport async function validateLicense(\n licenseKey: string,\n instanceId: string,\n): Promise<ValidateResult> {\n try {\n const res = await fetch(`${BASE_URL}/validate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId }),\n });\n\n const data = await res.json() as {\n valid?: boolean;\n error?: string;\n license_key?: { key: string };\n meta?: LemonSqueezyLicenseMeta;\n };\n\n if (!res.ok || !data.valid) {\n return { valid: false, error: data.error || `Validation failed (${res.status})` };\n }\n\n return {\n valid: true,\n licenseKey: data.license_key?.key,\n customerName: data.meta?.customer_name,\n productName: data.meta?.product_name,\n };\n } catch (err) {\n return { valid: false, error: err instanceof Error ? err.message : 'Network error' };\n }\n}\n\n/**\n * Deactivate a license key from a device.\n */\nexport async function deactivateLicense(\n licenseKey: string,\n instanceId: string,\n): Promise<DeactivateResult> {\n try {\n const res = await fetch(`${BASE_URL}/deactivate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId }),\n });\n\n const data = await res.json() as {\n deactivated?: boolean;\n error?: string;\n };\n\n if (!res.ok || !data.deactivated) {\n return { deactivated: false, error: data.error || `Deactivation failed (${res.status})` };\n }\n\n return { deactivated: true };\n } catch (err) {\n return { deactivated: false, error: err instanceof Error ? err.message : 'Network error' };\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 resolveStorageRoot,\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(resolveStorageRoot(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, isCachedLicenseValid } 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 licenseCache = reader.getLicenseCache();\n if (!isCachedLicenseValid(licenseCache)) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Decision Detection requires a Pro license. Use the activate_license tool, run `keepgoing activate <key>` in your terminal, or visit https://keepgoing.dev/pricing to purchase.',\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 { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n readLicenseCache,\n writeLicenseCache,\n isCachedLicenseValid,\n getDeviceId,\n activateLicense,\n} from '@keepgoingdev/shared';\n\nexport function registerActivateLicense(server: McpServer) {\n server.tool(\n 'activate_license',\n 'Activate a KeepGoing Pro license on this device. Unlocks Decision Detection and future Pro features.',\n { license_key: z.string().describe('Your KeepGoing Pro license key') },\n async ({ license_key }) => {\n // Check if already activated\n const existing = readLicenseCache();\n if (isCachedLicenseValid(existing)) {\n const who = existing!.customerName ? ` (${existing!.customerName})` : '';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Pro license is already active${who}. No action needed.`,\n },\n ],\n };\n }\n\n const result = await activateLicense(license_key, getDeviceId());\n\n if (!result.valid) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Activation failed: ${result.error ?? 'unknown error'}`,\n },\n ],\n };\n }\n\n const now = new Date().toISOString();\n writeLicenseCache({\n licenseKey: result.licenseKey || license_key,\n instanceId: result.instanceId || getDeviceId(),\n status: 'active',\n lastValidatedAt: now,\n activatedAt: now,\n customerName: result.customerName,\n productName: result.productName,\n });\n\n const who = result.customerName ? ` Welcome, ${result.customerName}!` : '';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Pro license activated successfully.${who} Decision Detection is now enabled.`,\n },\n ],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n readLicenseCache,\n deleteLicenseCache,\n deactivateLicense,\n} from '@keepgoingdev/shared';\n\nexport function registerDeactivateLicense(server: McpServer) {\n server.tool(\n 'deactivate_license',\n 'Deactivate the KeepGoing Pro license on this device.',\n {},\n async () => {\n const cache = readLicenseCache();\n if (!cache) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No active license found on this device.',\n },\n ],\n };\n }\n\n const result = await deactivateLicense(cache.licenseKey, cache.instanceId);\n deleteLicenseCache();\n\n if (!result.deactivated) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `License cleared locally, but remote deactivation failed: ${result.error ?? 'unknown error'}`,\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Pro license deactivated successfully. The activation slot has been freed.',\n },\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","path","randomUUID","workspacePath","path","randomUUID","fs","path","STORAGE_DIR","workspacePath","path","fs","fs","path","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","z","server","z","who","server","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","../../../packages/shared/src/license.ts","../../../packages/shared/src/licenseClient.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/tools/activateLicense.ts","../src/tools/deactivateLicense.ts","../src/prompts/resume.ts","../src/prompts/decisions.ts","../src/prompts/progress.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 resolveStorageRoot,\n formatRelativeTime,\n KeepGoingWriter,\n createCheckpoint,\n getCurrentBranch,\n getTouchedFiles,\n getCommitsSince,\n getCommitMessagesSince,\n getHeadCommitHash,\n tryDetectDecision,\n readLicenseCache,\n isCachedLicenseValid,\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 { registerActivateLicense } from './tools/activateLicense.js';\nimport { registerDeactivateLicense } from './tools/deactivateLicense.js';\nimport { registerResumePrompt } from './prompts/resume.js';\nimport { registerDecisionsPrompt } from './prompts/decisions.js';\nimport { registerProgressPrompt } from './prompts/progress.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 { session: lastSession } = reader.getScopedLastSession();\n\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 { session: lastSession } = reader.getScopedLastSession();\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(resolveStorageRoot(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 (Pro feature, requires valid license)\n const licenseCache = readLicenseCache();\n if (isCachedLicenseValid(licenseCache) && 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);\nregisterActivateLicense(server);\nregisterDeactivateLicense(server);\n\n// Register prompts\nregisterResumePrompt(server);\nregisterDecisionsPrompt(server);\nregisterProgressPrompt(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 readLicenseCache,\n resolveStorageRoot,\n getCurrentBranch,\n type SessionCheckpoint,\n type ProjectSessions,\n type ProjectState,\n type ProjectMeta,\n type DecisionRecord,\n type ProjectDecisions,\n type LicenseCache,\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/** Result of worktree-aware branch scoping. */\nexport interface BranchScope {\n /** The branch to filter by, or undefined for all branches. */\n effectiveBranch: string | undefined;\n /** Human-readable label for output headers. */\n scopeLabel: string;\n}\n\n/**\n * Read-only reader for .keepgoing/ directory.\n * Does not write or create any files.\n */\nexport class KeepGoingReader {\n private readonly workspacePath: string;\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 private readonly _isWorktree: boolean;\n private _cachedBranch: string | undefined | null = null; // null = not yet resolved\n\n constructor(workspacePath: string) {\n this.workspacePath = workspacePath;\n const mainRoot = resolveStorageRoot(workspacePath);\n this._isWorktree = mainRoot !== workspacePath;\n this.storagePath = path.join(mainRoot, 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 /** Read cached license data from the global `~/.keepgoing/license.json`. */\n getLicenseCache(): LicenseCache | undefined {\n return readLicenseCache();\n }\n\n /** Get the last session checkpoint for a specific branch. */\n getLastSessionForBranch(branch: string): SessionCheckpoint | undefined {\n const sessions = this.getSessions().filter(s => s.gitBranch === branch);\n return sessions.length > 0 ? sessions[sessions.length - 1] : undefined;\n }\n\n /** Returns the last N sessions for a specific branch, newest first. */\n getRecentSessionsForBranch(branch: string, count: number): SessionCheckpoint[] {\n const filtered = this.getSessions().filter(s => s.gitBranch === branch);\n return filtered.slice(-count).reverse();\n }\n\n /** Returns the last N decisions for a specific branch, newest first. */\n getRecentDecisionsForBranch(branch: string, count: number): DecisionRecord[] {\n const filtered = this.getDecisions().filter(d => d.gitBranch === branch);\n return filtered.slice(-count).reverse();\n }\n\n /** Whether the workspace is inside a git worktree. */\n get isWorktree(): boolean {\n return this._isWorktree;\n }\n\n /**\n * Returns the current git branch for this workspace.\n * Lazily cached: the branch is resolved once per KeepGoingReader instance.\n */\n getCurrentBranch(): string | undefined {\n if (this._cachedBranch === null) {\n this._cachedBranch = getCurrentBranch(this.workspacePath);\n }\n return this._cachedBranch;\n }\n\n /**\n * Worktree-aware last session lookup.\n * In a worktree, scopes to the current branch with fallback to global.\n * Returns the session and whether it fell back to global.\n */\n getScopedLastSession(): { session: SessionCheckpoint | undefined; isFallback: boolean } {\n const branch = this.getCurrentBranch();\n if (this._isWorktree && branch) {\n const scoped = this.getLastSessionForBranch(branch);\n if (scoped) return { session: scoped, isFallback: false };\n return { session: this.getLastSession(), isFallback: true };\n }\n return { session: this.getLastSession(), isFallback: false };\n }\n\n /** Worktree-aware recent sessions. Scopes to current branch in a worktree. */\n getScopedRecentSessions(count: number): SessionCheckpoint[] {\n const branch = this.getCurrentBranch();\n if (this._isWorktree && branch) {\n return this.getRecentSessionsForBranch(branch, count);\n }\n return this.getRecentSessions(count);\n }\n\n /** Worktree-aware recent decisions. Scopes to current branch in a worktree. */\n getScopedRecentDecisions(count: number): DecisionRecord[] {\n const branch = this.getCurrentBranch();\n if (this._isWorktree && branch) {\n return this.getRecentDecisionsForBranch(branch, count);\n }\n return this.getRecentDecisions(count);\n }\n\n /**\n * Resolves branch scope from an explicit `branch` parameter.\n * Used by tools that accept a `branch` argument (e.g. get_session_history, get_decisions).\n * - `\"all\"` returns no filter.\n * - An explicit branch name uses that.\n * - `undefined` auto-scopes to the current branch in a worktree, or all branches otherwise.\n */\n resolveBranchScope(branch?: string): BranchScope {\n if (branch === 'all') {\n return { effectiveBranch: undefined, scopeLabel: 'all branches' };\n }\n if (branch) {\n return { effectiveBranch: branch, scopeLabel: `branch \\`${branch}\\`` };\n }\n const currentBranch = this.getCurrentBranch();\n if (this._isWorktree && currentBranch) {\n return { effectiveBranch: currentBranch, scopeLabel: `branch \\`${currentBranch}\\` (worktree)` };\n }\n return { effectiveBranch: undefined, scopeLabel: 'all branches' };\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 path from 'node:path';\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 working tree root for the given path.\n * In a worktree, returns the worktree root (not the main repo root).\n * Use `resolveStorageRoot()` to get the main repo root for storage.\n * Falls back to the original path if not inside a git repo.\n */\nexport function findGitRoot(startPath: string): string {\n try {\n const toplevel = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n return toplevel || startPath;\n } catch {\n return startPath;\n }\n}\n\n/**\n * Cache for resolveStorageRoot results to avoid redundant subprocess calls.\n */\nconst storageRootCache = new Map<string, string>();\n\n/**\n * Clears the resolveStorageRoot cache.\n * Call this in long-lived processes (e.g. VS Code extension) when\n * worktree configuration may have changed.\n */\nexport function clearStorageRootCache(): void {\n storageRootCache.clear();\n}\n\n/**\n * Resolves the main repository root for storage purposes.\n * In a git worktree, `--show-toplevel` returns the worktree root, but\n * `.keepgoing/` should always live in the main repo root so data persists\n * across worktrees. This function uses `--git-common-dir` to find the\n * shared `.git` directory and derives the main repo root from it.\n *\n * Falls back to `startPath` if git commands fail (non-git directories, tests).\n */\nexport function resolveStorageRoot(startPath: string): string {\n const cached = storageRootCache.get(startPath);\n if (cached !== undefined) {\n return cached;\n }\n\n try {\n const toplevel = execFileSync('git', ['rev-parse', '--show-toplevel'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n\n const commonDir = execFileSync('git', ['rev-parse', '--git-common-dir'], {\n cwd: startPath,\n encoding: 'utf-8',\n timeout: 5000,\n }).trim();\n\n // commonDir is relative to the worktree root (e.g. \"../../.git\" or just \".git\")\n const absoluteCommonDir = path.resolve(toplevel, commonDir);\n const mainRoot = path.dirname(absoluteCommonDir);\n\n storageRootCache.set(startPath, mainRoot);\n return mainRoot;\n } catch {\n storageRootCache.set(startPath, startPath);\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';\nimport { resolveStorageRoot } from './gitUtils';\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 const mainRoot = resolveStorageRoot(workspacePath);\n this.storagePath = path.join(mainRoot, 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';\nimport { resolveStorageRoot } from './gitUtils';\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 * saveDecision() always persists (captures drafts for all users).\n * updateDecision() is gated behind the 'decisions' feature flag (Pro perk).\n * 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 const mainRoot = resolveStorageRoot(workspacePath);\n this.storagePath = path.join(mainRoot, 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 as a draft. Always persists regardless of Pro\n * status so decisions are captured at the correct time. Returns true if\n * saved, false on I/O error.\n */\n saveDecision(decision: DecisionRecord): boolean {\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\n/**\n * Feature gate controlled by license status.\n * When pro is disabled, gated features (decisions) are blocked.\n */\nexport class LicenseFeatureGate implements FeatureGate {\n constructor(private proEnabled: boolean) {}\n\n isEnabled(feature: GatedFeature): boolean {\n return feature === 'decisions' ? this.proEnabled : true;\n }\n\n setProEnabled(enabled: boolean): void {\n this.proEnabled = enabled;\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 crypto from 'node:crypto';\nimport fs from 'node:fs';\nimport os from 'node:os';\nimport path from 'node:path';\n\nconst LICENSE_FILE = 'license.json';\nconst DEVICE_ID_FILE = 'device-id';\n\n/**\n * Directory for device-wide license storage: `~/.keepgoing/`\n */\nexport function getGlobalLicenseDir(): string {\n return path.join(os.homedir(), '.keepgoing');\n}\n\n/**\n * Full path to the global license cache: `~/.keepgoing/license.json`\n */\nexport function getGlobalLicensePath(): string {\n return path.join(getGlobalLicenseDir(), LICENSE_FILE);\n}\n\n/**\n * Returns a stable, persistent device identifier shared by all consumers\n * (VS Code extension, CLI, MCP server) so each physical machine counts\n * as one activation.\n *\n * The ID is a random UUID stored in `~/.keepgoing/device-id`. Once\n * generated it never changes, even if the hostname or OS is updated.\n */\nexport function getDeviceId(): string {\n const dir = getGlobalLicenseDir();\n const filePath = path.join(dir, DEVICE_ID_FILE);\n\n try {\n const existing = fs.readFileSync(filePath, 'utf-8').trim();\n if (existing) return existing;\n } catch {\n // File doesn't exist yet, generate below\n }\n\n const id = crypto.randomUUID();\n if (!fs.existsSync(dir)) {\n fs.mkdirSync(dir, { recursive: true });\n }\n fs.writeFileSync(filePath, id, 'utf-8');\n return id;\n}\n\nexport type LicenseStatus = 'active' | 'inactive' | 'expired' | 'disabled';\n\nexport interface LicenseCache {\n licenseKey: string;\n instanceId: string;\n status: LicenseStatus;\n lastValidatedAt: string; // ISO 8601\n activatedAt: string; // ISO 8601\n customerName?: string;\n productName?: string;\n}\n\n/**\n * Read cached license data from `~/.keepgoing/license.json`.\n * Returns undefined if the file is missing or corrupt.\n */\nexport function readLicenseCache(): LicenseCache | undefined {\n const licensePath = getGlobalLicensePath();\n try {\n if (!fs.existsSync(licensePath)) {\n return undefined;\n }\n const raw = fs.readFileSync(licensePath, 'utf-8');\n return JSON.parse(raw) as LicenseCache;\n } catch {\n return undefined;\n }\n}\n\n/**\n * Write license cache to `~/.keepgoing/license.json`.\n * Creates the `~/.keepgoing/` directory if it doesn't exist.\n */\nexport function writeLicenseCache(cache: LicenseCache): void {\n const dirPath = getGlobalLicenseDir();\n if (!fs.existsSync(dirPath)) {\n fs.mkdirSync(dirPath, { recursive: true });\n }\n const licensePath = path.join(dirPath, LICENSE_FILE);\n fs.writeFileSync(licensePath, JSON.stringify(cache, null, 2), 'utf-8');\n}\n\n/**\n * Delete the cached license file.\n */\nexport function deleteLicenseCache(): void {\n const licensePath = getGlobalLicensePath();\n try {\n if (fs.existsSync(licensePath)) {\n fs.unlinkSync(licensePath);\n }\n } catch {\n // Ignore errors when deleting\n }\n}\n\n/**\n * Check if a cached license is considered valid (active).\n */\nexport function isCachedLicenseValid(cache: LicenseCache | undefined): boolean {\n return cache?.status === 'active';\n}\n\nconst REVALIDATION_THRESHOLD_MS = 24 * 60 * 60 * 1000; // 24 hours\n\n/**\n * Check if the cached license needs revalidation (last validated > 24 hours ago).\n */\nexport function needsRevalidation(cache: LicenseCache): boolean {\n const lastValidated = new Date(cache.lastValidatedAt).getTime();\n return Date.now() - lastValidated > REVALIDATION_THRESHOLD_MS;\n}\n","/**\n * HTTP client for the LemonSqueezy license API.\n * Uses Node's built-in fetch (no new dependencies).\n *\n * API docs: https://docs.lemonsqueezy.com/api/license-api\n * These endpoints accept application/x-www-form-urlencoded bodies\n * and do not require an API key header.\n */\n\nconst BASE_URL = 'https://api.lemonsqueezy.com/v1/licenses';\nconst REQUEST_TIMEOUT_MS = 15_000; // 15 seconds\nconst EXPECTED_STORE_ID = 301555;\nconst EXPECTED_PRODUCT_ID = 864311;\n\nfunction fetchWithTimeout(url: string, init: RequestInit): Promise<Response> {\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);\n return fetch(url, { ...init, signal: controller.signal }).finally(() => clearTimeout(timer));\n}\n\ninterface LemonSqueezyLicenseMeta {\n store_id: number;\n product_id: number;\n product_name: string;\n variant_id: number;\n variant_name: string;\n customer_id: number;\n customer_name: string;\n customer_email: string;\n}\n\ninterface LemonSqueezyInstance {\n id: string;\n name: string;\n created_at: string;\n}\n\nexport interface ActivateResult {\n valid: boolean;\n error?: string;\n licenseKey?: string;\n instanceId?: string;\n customerName?: string;\n productName?: string;\n}\n\nexport interface ValidateResult {\n valid: boolean;\n error?: string;\n licenseKey?: string;\n customerName?: string;\n productName?: string;\n}\n\nexport interface LicenseClientOptions {\n allowTestMode?: boolean;\n}\n\nexport interface DeactivateResult {\n deactivated: boolean;\n error?: string;\n}\n\nfunction validateProductIdentity(meta: LemonSqueezyLicenseMeta | undefined): string | undefined {\n if (!meta) return 'License response missing product metadata.';\n if (meta.store_id !== EXPECTED_STORE_ID || meta.product_id !== EXPECTED_PRODUCT_ID) {\n return 'This license key does not belong to KeepGoing.';\n }\n return undefined;\n}\n\n/**\n * Safely parse JSON from a fetch response, returning null on failure.\n */\nasync function safeJson(res: Response): Promise<Record<string, unknown> | null> {\n try {\n const text = await res.text();\n return JSON.parse(text) as Record<string, unknown>;\n } catch {\n return null;\n }\n}\n\n/**\n * Activate a license key on a device.\n */\nexport async function activateLicense(\n licenseKey: string,\n instanceName: string,\n options?: LicenseClientOptions,\n): Promise<ActivateResult> {\n try {\n const res = await fetchWithTimeout(`${BASE_URL}/activate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_name: instanceName }),\n });\n\n const data = await safeJson(res) as {\n activated?: boolean;\n error?: string;\n license_key?: { key: string; test_mode?: boolean };\n instance?: LemonSqueezyInstance;\n meta?: LemonSqueezyLicenseMeta;\n } | null;\n\n if (!res.ok || !data?.activated) {\n return { valid: false, error: (data?.error as string) || `Activation failed (${res.status})` };\n }\n\n if (!options?.allowTestMode && data.license_key?.test_mode) {\n // Release the activation slot we just consumed\n if (data.license_key?.key && data.instance?.id) {\n await deactivateLicense(data.license_key.key, data.instance.id);\n }\n return { valid: false, error: 'This is a test license key. Please use a production license key from your purchase confirmation.' };\n }\n\n if (!options?.allowTestMode) {\n const productError = validateProductIdentity(data.meta);\n if (productError) {\n // Release the activation slot we just consumed\n if (data.license_key?.key && data.instance?.id) {\n await deactivateLicense(data.license_key.key, data.instance.id);\n }\n return { valid: false, error: productError };\n }\n }\n\n return {\n valid: true,\n licenseKey: data.license_key?.key,\n instanceId: data.instance?.id,\n customerName: data.meta?.customer_name,\n productName: data.meta?.product_name,\n };\n } catch (err) {\n const message = err instanceof Error && err.name === 'AbortError'\n ? 'Request timed out. Please check your network connection and try again.'\n : err instanceof Error ? err.message : 'Network error';\n return { valid: false, error: message };\n }\n}\n\n/**\n * Validate a previously activated license key.\n */\nexport async function validateLicense(\n licenseKey: string,\n instanceId: string,\n options?: LicenseClientOptions,\n): Promise<ValidateResult> {\n try {\n const res = await fetchWithTimeout(`${BASE_URL}/validate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId }),\n });\n\n const data = await safeJson(res) as {\n valid?: boolean;\n error?: string;\n license_key?: { key: string; test_mode?: boolean };\n meta?: LemonSqueezyLicenseMeta;\n } | null;\n\n if (!res.ok || !data?.valid) {\n return { valid: false, error: (data?.error as string) || `Validation failed (${res.status})` };\n }\n\n if (!options?.allowTestMode && data.license_key?.test_mode) {\n return { valid: false, error: 'This is a test license key. Please use a production license key from your purchase confirmation.' };\n }\n\n if (!options?.allowTestMode) {\n const productError = validateProductIdentity(data.meta);\n if (productError) {\n return { valid: false, error: productError };\n }\n }\n\n return {\n valid: true,\n licenseKey: data.license_key?.key,\n customerName: data.meta?.customer_name,\n productName: data.meta?.product_name,\n };\n } catch (err) {\n const message = err instanceof Error && err.name === 'AbortError'\n ? 'Request timed out. Please check your network connection and try again.'\n : err instanceof Error ? err.message : 'Network error';\n return { valid: false, error: message };\n }\n}\n\n/**\n * Deactivate a license key from a device.\n */\nexport async function deactivateLicense(\n licenseKey: string,\n instanceId: string,\n): Promise<DeactivateResult> {\n try {\n const res = await fetchWithTimeout(`${BASE_URL}/deactivate`, {\n method: 'POST',\n headers: { 'Content-Type': 'application/x-www-form-urlencoded' },\n body: new URLSearchParams({ license_key: licenseKey, instance_id: instanceId }),\n });\n\n const data = await safeJson(res) as {\n deactivated?: boolean;\n error?: string;\n } | null;\n\n if (!res.ok || !data?.deactivated) {\n return { deactivated: false, error: (data?.error as string) || `Deactivation failed (${res.status})` };\n }\n\n return { deactivated: true };\n } catch (err) {\n const message = err instanceof Error && err.name === 'AbortError'\n ? 'Request timed out. Please check your network connection and try again.'\n : err instanceof Error ? err.message : 'Network error';\n return { deactivated: false, error: message };\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { KeepGoingReader } from '../storage.js';\nimport { 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 { session: lastSession, isFallback } = reader.getScopedLastSession();\n const currentBranch = reader.getCurrentBranch();\n\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 branchChanged =\n lastSession.gitBranch &&\n currentBranch &&\n lastSession.gitBranch !== currentBranch;\n\n const lines: string[] = [\n `## Developer Momentum`,\n '',\n ];\n\n if (reader.isWorktree && currentBranch) {\n lines.push(`**Worktree context:** Scoped to branch \\`${currentBranch}\\``);\n if (isFallback) {\n lines.push(`**Note:** No checkpoints found for branch \\`${currentBranch}\\`. Showing last global checkpoint.`);\n }\n lines.push('');\n }\n\n lines.push(\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 && !reader.isWorktree) {\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 {\n limit: z.number().min(1).max(50).default(5).describe('Number of recent sessions to return (1-50, default 5)'),\n branch: z.string().optional().describe('Filter to a specific branch name, or \"all\" to show all branches. Auto-detected from worktree context by default.'),\n },\n async ({ limit, branch }) => {\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 { effectiveBranch, scopeLabel } = reader.resolveBranchScope(branch);\n\n const sessions = effectiveBranch\n ? reader.getRecentSessionsForBranch(effectiveBranch, limit)\n : reader.getRecentSessions(limit);\n\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: effectiveBranch\n ? `No session checkpoints found for branch \\`${effectiveBranch}\\`. Use branch: \"all\" to see all branches.`\n : 'No session checkpoints found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Session History (last ${sessions.length}, ${scopeLabel})`,\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 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 gitBranch = reader.getCurrentBranch();\n const { session: lastSession } = reader.getScopedLastSession();\n const recentSessions = reader.getScopedRecentSessions(5);\n const state = reader.getState() ?? {};\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 ];\n\n if (reader.isWorktree && gitBranch) {\n lines.push(`**Worktree context:** Scoped to branch \\`${gitBranch}\\``);\n lines.push('');\n }\n\n lines.push(\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 (also scoped by worktree)\n const recentDecisions = reader.getScopedRecentDecisions(3);\n\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 resolveStorageRoot,\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(resolveStorageRoot(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, isCachedLicenseValid } 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 {\n limit: z.number().min(1).max(50).default(10).describe('Number of recent decisions to return (1-50, default 10)'),\n branch: z.string().optional().describe('Filter to a specific branch name, or \"all\" to show all branches. Auto-detected from worktree context by default.'),\n },\n async ({ limit, branch }) => {\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 licenseCache = reader.getLicenseCache();\n if (!isCachedLicenseValid(licenseCache)) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Decision Detection requires a Pro license. Use the activate_license tool, run `keepgoing activate <key>` in your terminal, or visit https://keepgoing.dev/add-ons to purchase.',\n },\n ],\n };\n }\n\n const { effectiveBranch, scopeLabel } = reader.resolveBranchScope(branch);\n\n const decisions = effectiveBranch\n ? reader.getRecentDecisionsForBranch(effectiveBranch, limit)\n : reader.getRecentDecisions(limit);\n\n if (decisions.length === 0) {\n return {\n content: [\n {\n type: 'text' as const,\n text: effectiveBranch\n ? `No decision records found for branch \\`${effectiveBranch}\\`. Use branch: \"all\" to see all branches.`\n : 'No decision records found.',\n },\n ],\n };\n }\n\n const lines: string[] = [\n `## Decisions (last ${decisions.length}, ${scopeLabel})`,\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 { z } from 'zod';\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n readLicenseCache,\n writeLicenseCache,\n isCachedLicenseValid,\n getDeviceId,\n activateLicense,\n} from '@keepgoingdev/shared';\n\nexport function registerActivateLicense(server: McpServer) {\n server.tool(\n 'activate_license',\n 'Activate a KeepGoing Pro license on this device. Unlocks Decision Detection and future Pro features.',\n { license_key: z.string().describe('Your KeepGoing Pro license key') },\n async ({ license_key }) => {\n // Check if already activated\n const existing = readLicenseCache();\n if (isCachedLicenseValid(existing)) {\n const who = existing!.customerName ? ` (${existing!.customerName})` : '';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Pro license is already active${who}. No action needed.`,\n },\n ],\n };\n }\n\n const result = await activateLicense(license_key, getDeviceId());\n\n if (!result.valid) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `Activation failed: ${result.error ?? 'unknown error'}`,\n },\n ],\n };\n }\n\n const now = new Date().toISOString();\n writeLicenseCache({\n licenseKey: result.licenseKey || license_key,\n instanceId: result.instanceId || getDeviceId(),\n status: 'active',\n lastValidatedAt: now,\n activatedAt: now,\n customerName: result.customerName,\n productName: result.productName,\n });\n\n const who = result.customerName ? ` Welcome, ${result.customerName}!` : '';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Pro license activated successfully.${who} Decision Detection is now enabled.`,\n },\n ],\n };\n },\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport {\n readLicenseCache,\n deleteLicenseCache,\n deactivateLicense,\n} from '@keepgoingdev/shared';\n\nexport function registerDeactivateLicense(server: McpServer) {\n server.tool(\n 'deactivate_license',\n 'Deactivate the KeepGoing Pro license on this device.',\n {},\n async () => {\n const cache = readLicenseCache();\n if (!cache) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No active license found on this device.',\n },\n ],\n };\n }\n\n const result = await deactivateLicense(cache.licenseKey, cache.instanceId);\n deleteLicenseCache();\n\n if (!result.deactivated) {\n return {\n content: [\n {\n type: 'text' as const,\n text: `License cleared locally, but remote deactivation failed: ${result.error ?? 'unknown error'}`,\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Pro license deactivated successfully. The activation slot has been freed.',\n },\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","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerDecisionsPrompt(server: McpServer) {\n server.prompt(\n 'decisions',\n 'Review recent architectural decisions and their rationale',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I want to review recent architectural decisions in this project.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Fetch recent decision records (get_decisions)',\n '2. Get my current branch context (get_momentum)',\n '3. Summarize the decisions, highlighting any that were made on the current branch',\n '',\n 'Keep your response brief and organized.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nexport function registerProgressPrompt(server: McpServer) {\n server.prompt(\n 'progress',\n 'Summarize recent development progress across sessions',\n async () => ({\n messages: [\n {\n role: 'user' as const,\n content: {\n type: 'text' as const,\n text: [\n 'I need a summary of recent development progress for this project.',\n '',\n 'Please use the KeepGoing tools to:',\n '1. Fetch session history with a higher limit for broader coverage (get_session_history, limit: 20)',\n '2. Get my current branch context (get_momentum)',\n '3. Synthesize a progress summary grouped by branch or feature, highlighting the current branch',\n '',\n 'Format the summary so it can be used in a standup or sprint review.',\n ].join('\\n'),\n },\n },\n ],\n }),\n );\n}\n"],"mappings":"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","names":["path","fs","path","workspacePath","workspacePath","workspacePath","path","randomUUID","workspacePath","path","randomUUID","fs","path","STORAGE_DIR","workspacePath","path","fs","fs","path","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","z","server","z","who","server","server","server","server","reader","path"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@keepgoingdev/mcp-server",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "./dist/index.js",
6
6
  "bin": {