@keepgoingdev/mcp-server 0.6.1 → 0.7.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 +287 -135
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -149,6 +149,18 @@ function getCommitsSince(workspacePath, sinceTimestamp) {
|
|
|
149
149
|
function getCommitMessagesSince(workspacePath, sinceTimestamp) {
|
|
150
150
|
return getGitLogSince(workspacePath, "%s", sinceTimestamp);
|
|
151
151
|
}
|
|
152
|
+
function getCommitMessageByHash(workspacePath, commitHash) {
|
|
153
|
+
try {
|
|
154
|
+
const result = execFileSync("git", ["log", "-1", "--format=%s", commitHash], {
|
|
155
|
+
cwd: workspacePath,
|
|
156
|
+
encoding: "utf-8",
|
|
157
|
+
timeout: 5e3
|
|
158
|
+
});
|
|
159
|
+
return result.trim() || void 0;
|
|
160
|
+
} catch {
|
|
161
|
+
return void 0;
|
|
162
|
+
}
|
|
163
|
+
}
|
|
152
164
|
function getFilesChangedInCommit(workspacePath, commitHash) {
|
|
153
165
|
try {
|
|
154
166
|
const result = execFileSync("git", ["diff-tree", "--no-commit-id", "--name-only", "-r", commitHash], {
|
|
@@ -350,7 +362,8 @@ function buildRecentActivity(lastSession, recentSessions, recentCommitMessages)
|
|
|
350
362
|
parts.push("1 recent session");
|
|
351
363
|
}
|
|
352
364
|
if (lastSession.summary) {
|
|
353
|
-
|
|
365
|
+
const brief = lastSession.summary.length > 120 ? lastSession.summary.slice(0, 117) + "..." : lastSession.summary;
|
|
366
|
+
parts.push(`Last: ${brief}`);
|
|
354
367
|
}
|
|
355
368
|
if (lastSession.touchedFiles.length > 0) {
|
|
356
369
|
parts.push(`${lastSession.touchedFiles.length} files touched`);
|
|
@@ -691,9 +704,59 @@ function formatContinueOnPrompt(context, options) {
|
|
|
691
704
|
}
|
|
692
705
|
|
|
693
706
|
// ../../packages/shared/src/storage.ts
|
|
707
|
+
import fs2 from "fs";
|
|
708
|
+
import path4 from "path";
|
|
709
|
+
import { randomUUID as randomUUID2, createHash } from "crypto";
|
|
710
|
+
|
|
711
|
+
// ../../packages/shared/src/registry.ts
|
|
694
712
|
import fs from "fs";
|
|
713
|
+
import os from "os";
|
|
695
714
|
import path3 from "path";
|
|
696
|
-
|
|
715
|
+
var KEEPGOING_DIR = path3.join(os.homedir(), ".keepgoing");
|
|
716
|
+
var KNOWN_PROJECTS_FILE = path3.join(KEEPGOING_DIR, "known-projects.json");
|
|
717
|
+
var STALE_PROJECT_MS = 90 * 24 * 60 * 60 * 1e3;
|
|
718
|
+
function readKnownProjects() {
|
|
719
|
+
try {
|
|
720
|
+
if (fs.existsSync(KNOWN_PROJECTS_FILE)) {
|
|
721
|
+
const raw = JSON.parse(fs.readFileSync(KNOWN_PROJECTS_FILE, "utf-8"));
|
|
722
|
+
if (raw && Array.isArray(raw.projects)) {
|
|
723
|
+
return raw;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
} catch {
|
|
727
|
+
}
|
|
728
|
+
return { version: 1, projects: [] };
|
|
729
|
+
}
|
|
730
|
+
function writeKnownProjects(data) {
|
|
731
|
+
if (!fs.existsSync(KEEPGOING_DIR)) {
|
|
732
|
+
fs.mkdirSync(KEEPGOING_DIR, { recursive: true });
|
|
733
|
+
}
|
|
734
|
+
const tmpFile = KNOWN_PROJECTS_FILE + ".tmp";
|
|
735
|
+
fs.writeFileSync(tmpFile, JSON.stringify(data, null, 2) + "\n", "utf-8");
|
|
736
|
+
fs.renameSync(tmpFile, KNOWN_PROJECTS_FILE);
|
|
737
|
+
}
|
|
738
|
+
function registerProject(projectPath, projectName) {
|
|
739
|
+
try {
|
|
740
|
+
const data = readKnownProjects();
|
|
741
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
742
|
+
const name = projectName || path3.basename(projectPath);
|
|
743
|
+
const existingIdx = data.projects.findIndex((p) => p.path === projectPath);
|
|
744
|
+
if (existingIdx >= 0) {
|
|
745
|
+
data.projects[existingIdx].lastSeen = now;
|
|
746
|
+
if (projectName) {
|
|
747
|
+
data.projects[existingIdx].name = name;
|
|
748
|
+
}
|
|
749
|
+
} else {
|
|
750
|
+
data.projects.push({ path: projectPath, name, lastSeen: now });
|
|
751
|
+
}
|
|
752
|
+
const cutoff = Date.now() - STALE_PROJECT_MS;
|
|
753
|
+
data.projects = data.projects.filter((p) => new Date(p.lastSeen).getTime() > cutoff);
|
|
754
|
+
writeKnownProjects(data);
|
|
755
|
+
} catch {
|
|
756
|
+
}
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
// ../../packages/shared/src/storage.ts
|
|
697
760
|
var STORAGE_DIR = ".keepgoing";
|
|
698
761
|
var META_FILE = "meta.json";
|
|
699
762
|
var SESSIONS_FILE = "sessions.json";
|
|
@@ -715,23 +778,23 @@ var KeepGoingWriter = class {
|
|
|
715
778
|
currentTasksFilePath;
|
|
716
779
|
constructor(workspacePath) {
|
|
717
780
|
const mainRoot = resolveStorageRoot(workspacePath);
|
|
718
|
-
this.storagePath =
|
|
719
|
-
this.sessionsFilePath =
|
|
720
|
-
this.stateFilePath =
|
|
721
|
-
this.metaFilePath =
|
|
722
|
-
this.currentTasksFilePath =
|
|
781
|
+
this.storagePath = path4.join(mainRoot, STORAGE_DIR);
|
|
782
|
+
this.sessionsFilePath = path4.join(this.storagePath, SESSIONS_FILE);
|
|
783
|
+
this.stateFilePath = path4.join(this.storagePath, STATE_FILE);
|
|
784
|
+
this.metaFilePath = path4.join(this.storagePath, META_FILE);
|
|
785
|
+
this.currentTasksFilePath = path4.join(this.storagePath, CURRENT_TASKS_FILE);
|
|
723
786
|
}
|
|
724
787
|
ensureDir() {
|
|
725
|
-
if (!
|
|
726
|
-
|
|
788
|
+
if (!fs2.existsSync(this.storagePath)) {
|
|
789
|
+
fs2.mkdirSync(this.storagePath, { recursive: true });
|
|
727
790
|
}
|
|
728
791
|
}
|
|
729
792
|
saveCheckpoint(checkpoint, projectName) {
|
|
730
793
|
this.ensureDir();
|
|
731
794
|
let sessionsData;
|
|
732
795
|
try {
|
|
733
|
-
if (
|
|
734
|
-
const raw = JSON.parse(
|
|
796
|
+
if (fs2.existsSync(this.sessionsFilePath)) {
|
|
797
|
+
const raw = JSON.parse(fs2.readFileSync(this.sessionsFilePath, "utf-8"));
|
|
735
798
|
if (Array.isArray(raw)) {
|
|
736
799
|
sessionsData = { version: 1, project: projectName, sessions: raw };
|
|
737
800
|
} else {
|
|
@@ -749,20 +812,22 @@ var KeepGoingWriter = class {
|
|
|
749
812
|
if (sessionsData.sessions.length > MAX_SESSIONS) {
|
|
750
813
|
sessionsData.sessions = sessionsData.sessions.slice(-MAX_SESSIONS);
|
|
751
814
|
}
|
|
752
|
-
|
|
815
|
+
fs2.writeFileSync(this.sessionsFilePath, JSON.stringify(sessionsData, null, 2), "utf-8");
|
|
753
816
|
const state = {
|
|
754
817
|
lastSessionId: checkpoint.id,
|
|
755
818
|
lastKnownBranch: checkpoint.gitBranch,
|
|
756
819
|
lastActivityAt: checkpoint.timestamp
|
|
757
820
|
};
|
|
758
|
-
|
|
821
|
+
fs2.writeFileSync(this.stateFilePath, JSON.stringify(state, null, 2), "utf-8");
|
|
759
822
|
this.updateMeta(checkpoint.timestamp);
|
|
823
|
+
const mainRoot = resolveStorageRoot(this.storagePath);
|
|
824
|
+
registerProject(mainRoot, projectName);
|
|
760
825
|
}
|
|
761
826
|
updateMeta(timestamp) {
|
|
762
827
|
let meta;
|
|
763
828
|
try {
|
|
764
|
-
if (
|
|
765
|
-
meta = JSON.parse(
|
|
829
|
+
if (fs2.existsSync(this.metaFilePath)) {
|
|
830
|
+
meta = JSON.parse(fs2.readFileSync(this.metaFilePath, "utf-8"));
|
|
766
831
|
meta.lastUpdated = timestamp;
|
|
767
832
|
} else {
|
|
768
833
|
meta = {
|
|
@@ -778,7 +843,7 @@ var KeepGoingWriter = class {
|
|
|
778
843
|
lastUpdated: timestamp
|
|
779
844
|
};
|
|
780
845
|
}
|
|
781
|
-
|
|
846
|
+
fs2.writeFileSync(this.metaFilePath, JSON.stringify(meta, null, 2), "utf-8");
|
|
782
847
|
}
|
|
783
848
|
// ---------------------------------------------------------------------------
|
|
784
849
|
// Multi-session API
|
|
@@ -786,8 +851,8 @@ var KeepGoingWriter = class {
|
|
|
786
851
|
/** Read all current tasks from current-tasks.json. Auto-prunes stale sessions. */
|
|
787
852
|
readCurrentTasks() {
|
|
788
853
|
try {
|
|
789
|
-
if (
|
|
790
|
-
const raw = JSON.parse(
|
|
854
|
+
if (fs2.existsSync(this.currentTasksFilePath)) {
|
|
855
|
+
const raw = JSON.parse(fs2.readFileSync(this.currentTasksFilePath, "utf-8"));
|
|
791
856
|
const tasks = Array.isArray(raw) ? raw : raw.tasks ?? [];
|
|
792
857
|
return this.pruneStale(tasks);
|
|
793
858
|
}
|
|
@@ -802,6 +867,8 @@ var KeepGoingWriter = class {
|
|
|
802
867
|
upsertSession(update) {
|
|
803
868
|
this.ensureDir();
|
|
804
869
|
this.upsertSessionCore(update);
|
|
870
|
+
const mainRoot = resolveStorageRoot(this.storagePath);
|
|
871
|
+
registerProject(mainRoot);
|
|
805
872
|
}
|
|
806
873
|
/** Core upsert logic: merges the update into current-tasks.json and returns the pruned task list. */
|
|
807
874
|
upsertSessionCore(update) {
|
|
@@ -840,8 +907,8 @@ var KeepGoingWriter = class {
|
|
|
840
907
|
// ---------------------------------------------------------------------------
|
|
841
908
|
readAllTasksRaw() {
|
|
842
909
|
try {
|
|
843
|
-
if (
|
|
844
|
-
const raw = JSON.parse(
|
|
910
|
+
if (fs2.existsSync(this.currentTasksFilePath)) {
|
|
911
|
+
const raw = JSON.parse(fs2.readFileSync(this.currentTasksFilePath, "utf-8"));
|
|
845
912
|
return Array.isArray(raw) ? [...raw] : [...raw.tasks ?? []];
|
|
846
913
|
}
|
|
847
914
|
} catch {
|
|
@@ -853,7 +920,7 @@ var KeepGoingWriter = class {
|
|
|
853
920
|
}
|
|
854
921
|
writeTasksFile(tasks) {
|
|
855
922
|
const data = { version: 1, tasks };
|
|
856
|
-
|
|
923
|
+
fs2.writeFileSync(this.currentTasksFilePath, JSON.stringify(data, null, 2), "utf-8");
|
|
857
924
|
}
|
|
858
925
|
};
|
|
859
926
|
function generateSessionId(context) {
|
|
@@ -1032,35 +1099,35 @@ function capitalize(s) {
|
|
|
1032
1099
|
}
|
|
1033
1100
|
|
|
1034
1101
|
// ../../packages/shared/src/decisionStorage.ts
|
|
1035
|
-
import
|
|
1036
|
-
import
|
|
1102
|
+
import fs4 from "fs";
|
|
1103
|
+
import path6 from "path";
|
|
1037
1104
|
|
|
1038
1105
|
// ../../packages/shared/src/license.ts
|
|
1039
1106
|
import crypto from "crypto";
|
|
1040
|
-
import
|
|
1041
|
-
import
|
|
1042
|
-
import
|
|
1107
|
+
import fs3 from "fs";
|
|
1108
|
+
import os2 from "os";
|
|
1109
|
+
import path5 from "path";
|
|
1043
1110
|
var LICENSE_FILE = "license.json";
|
|
1044
1111
|
var DEVICE_ID_FILE = "device-id";
|
|
1045
1112
|
function getGlobalLicenseDir() {
|
|
1046
|
-
return
|
|
1113
|
+
return path5.join(os2.homedir(), ".keepgoing");
|
|
1047
1114
|
}
|
|
1048
1115
|
function getGlobalLicensePath() {
|
|
1049
|
-
return
|
|
1116
|
+
return path5.join(getGlobalLicenseDir(), LICENSE_FILE);
|
|
1050
1117
|
}
|
|
1051
1118
|
function getDeviceId() {
|
|
1052
1119
|
const dir = getGlobalLicenseDir();
|
|
1053
|
-
const filePath =
|
|
1120
|
+
const filePath = path5.join(dir, DEVICE_ID_FILE);
|
|
1054
1121
|
try {
|
|
1055
|
-
const existing =
|
|
1122
|
+
const existing = fs3.readFileSync(filePath, "utf-8").trim();
|
|
1056
1123
|
if (existing) return existing;
|
|
1057
1124
|
} catch {
|
|
1058
1125
|
}
|
|
1059
1126
|
const id = crypto.randomUUID();
|
|
1060
|
-
if (!
|
|
1061
|
-
|
|
1127
|
+
if (!fs3.existsSync(dir)) {
|
|
1128
|
+
fs3.mkdirSync(dir, { recursive: true });
|
|
1062
1129
|
}
|
|
1063
|
-
|
|
1130
|
+
fs3.writeFileSync(filePath, id, "utf-8");
|
|
1064
1131
|
return id;
|
|
1065
1132
|
}
|
|
1066
1133
|
var DECISION_DETECTION_VARIANT_ID = 1361527;
|
|
@@ -1094,10 +1161,10 @@ function readLicenseStore() {
|
|
|
1094
1161
|
const licensePath = getGlobalLicensePath();
|
|
1095
1162
|
let store;
|
|
1096
1163
|
try {
|
|
1097
|
-
if (!
|
|
1164
|
+
if (!fs3.existsSync(licensePath)) {
|
|
1098
1165
|
store = { version: 2, licenses: [] };
|
|
1099
1166
|
} else {
|
|
1100
|
-
const raw =
|
|
1167
|
+
const raw = fs3.readFileSync(licensePath, "utf-8");
|
|
1101
1168
|
const data = JSON.parse(raw);
|
|
1102
1169
|
if (data?.version === 2 && Array.isArray(data.licenses)) {
|
|
1103
1170
|
store = data;
|
|
@@ -1114,11 +1181,11 @@ function readLicenseStore() {
|
|
|
1114
1181
|
}
|
|
1115
1182
|
function writeLicenseStore(store) {
|
|
1116
1183
|
const dirPath = getGlobalLicenseDir();
|
|
1117
|
-
if (!
|
|
1118
|
-
|
|
1184
|
+
if (!fs3.existsSync(dirPath)) {
|
|
1185
|
+
fs3.mkdirSync(dirPath, { recursive: true });
|
|
1119
1186
|
}
|
|
1120
|
-
const licensePath =
|
|
1121
|
-
|
|
1187
|
+
const licensePath = path5.join(dirPath, LICENSE_FILE);
|
|
1188
|
+
fs3.writeFileSync(licensePath, JSON.stringify(store, null, 2), "utf-8");
|
|
1122
1189
|
_cachedStore = store;
|
|
1123
1190
|
_cacheTimestamp = Date.now();
|
|
1124
1191
|
}
|
|
@@ -1169,23 +1236,23 @@ var DecisionStorage = class {
|
|
|
1169
1236
|
decisionsFilePath;
|
|
1170
1237
|
constructor(workspacePath) {
|
|
1171
1238
|
const mainRoot = resolveStorageRoot(workspacePath);
|
|
1172
|
-
this.storagePath =
|
|
1173
|
-
this.decisionsFilePath =
|
|
1239
|
+
this.storagePath = path6.join(mainRoot, STORAGE_DIR2);
|
|
1240
|
+
this.decisionsFilePath = path6.join(this.storagePath, DECISIONS_FILE);
|
|
1174
1241
|
}
|
|
1175
1242
|
ensureStorageDir() {
|
|
1176
|
-
if (!
|
|
1177
|
-
|
|
1243
|
+
if (!fs4.existsSync(this.storagePath)) {
|
|
1244
|
+
fs4.mkdirSync(this.storagePath, { recursive: true });
|
|
1178
1245
|
}
|
|
1179
1246
|
}
|
|
1180
1247
|
getProjectName() {
|
|
1181
|
-
return
|
|
1248
|
+
return path6.basename(path6.dirname(this.storagePath));
|
|
1182
1249
|
}
|
|
1183
1250
|
load() {
|
|
1184
1251
|
try {
|
|
1185
|
-
if (!
|
|
1252
|
+
if (!fs4.existsSync(this.decisionsFilePath)) {
|
|
1186
1253
|
return createEmptyProjectDecisions(this.getProjectName());
|
|
1187
1254
|
}
|
|
1188
|
-
const raw =
|
|
1255
|
+
const raw = fs4.readFileSync(this.decisionsFilePath, "utf-8");
|
|
1189
1256
|
const data = JSON.parse(raw);
|
|
1190
1257
|
return data;
|
|
1191
1258
|
} catch {
|
|
@@ -1195,7 +1262,7 @@ var DecisionStorage = class {
|
|
|
1195
1262
|
save(decisions) {
|
|
1196
1263
|
this.ensureStorageDir();
|
|
1197
1264
|
const content = JSON.stringify(decisions, null, 2);
|
|
1198
|
-
|
|
1265
|
+
fs4.writeFileSync(this.decisionsFilePath, content, "utf-8");
|
|
1199
1266
|
}
|
|
1200
1267
|
/**
|
|
1201
1268
|
* Save a decision record as a draft. Always persists regardless of Pro
|
|
@@ -1446,8 +1513,8 @@ function tryDetectDecision(opts) {
|
|
|
1446
1513
|
}
|
|
1447
1514
|
|
|
1448
1515
|
// ../../packages/shared/src/reader.ts
|
|
1449
|
-
import
|
|
1450
|
-
import
|
|
1516
|
+
import fs5 from "fs";
|
|
1517
|
+
import path7 from "path";
|
|
1451
1518
|
var STORAGE_DIR3 = ".keepgoing";
|
|
1452
1519
|
var META_FILE2 = "meta.json";
|
|
1453
1520
|
var SESSIONS_FILE2 = "sessions.json";
|
|
@@ -1469,16 +1536,16 @@ var KeepGoingReader = class {
|
|
|
1469
1536
|
this.workspacePath = workspacePath;
|
|
1470
1537
|
const mainRoot = resolveStorageRoot(workspacePath);
|
|
1471
1538
|
this._isWorktree = mainRoot !== workspacePath;
|
|
1472
|
-
this.storagePath =
|
|
1473
|
-
this.metaFilePath =
|
|
1474
|
-
this.sessionsFilePath =
|
|
1475
|
-
this.decisionsFilePath =
|
|
1476
|
-
this.stateFilePath =
|
|
1477
|
-
this.currentTasksFilePath =
|
|
1539
|
+
this.storagePath = path7.join(mainRoot, STORAGE_DIR3);
|
|
1540
|
+
this.metaFilePath = path7.join(this.storagePath, META_FILE2);
|
|
1541
|
+
this.sessionsFilePath = path7.join(this.storagePath, SESSIONS_FILE2);
|
|
1542
|
+
this.decisionsFilePath = path7.join(this.storagePath, DECISIONS_FILE2);
|
|
1543
|
+
this.stateFilePath = path7.join(this.storagePath, STATE_FILE2);
|
|
1544
|
+
this.currentTasksFilePath = path7.join(this.storagePath, CURRENT_TASKS_FILE2);
|
|
1478
1545
|
}
|
|
1479
1546
|
/** Check if .keepgoing/ directory exists. */
|
|
1480
1547
|
exists() {
|
|
1481
|
-
return
|
|
1548
|
+
return fs5.existsSync(this.storagePath);
|
|
1482
1549
|
}
|
|
1483
1550
|
/** Read state.json, returns undefined if missing or corrupt. */
|
|
1484
1551
|
getState() {
|
|
@@ -1716,10 +1783,10 @@ var KeepGoingReader = class {
|
|
|
1716
1783
|
}
|
|
1717
1784
|
readJsonFile(filePath) {
|
|
1718
1785
|
try {
|
|
1719
|
-
if (!
|
|
1786
|
+
if (!fs5.existsSync(filePath)) {
|
|
1720
1787
|
return void 0;
|
|
1721
1788
|
}
|
|
1722
|
-
const raw =
|
|
1789
|
+
const raw = fs5.readFileSync(filePath, "utf-8");
|
|
1723
1790
|
return JSON.parse(raw);
|
|
1724
1791
|
} catch {
|
|
1725
1792
|
return void 0;
|
|
@@ -1728,9 +1795,9 @@ var KeepGoingReader = class {
|
|
|
1728
1795
|
};
|
|
1729
1796
|
|
|
1730
1797
|
// ../../packages/shared/src/setup.ts
|
|
1731
|
-
import
|
|
1732
|
-
import
|
|
1733
|
-
import
|
|
1798
|
+
import fs6 from "fs";
|
|
1799
|
+
import os3 from "os";
|
|
1800
|
+
import path8 from "path";
|
|
1734
1801
|
var KEEPGOING_MARKER = "@keepgoingdev/mcp-server";
|
|
1735
1802
|
var SESSION_START_HOOK = {
|
|
1736
1803
|
matcher: "",
|
|
@@ -1759,36 +1826,55 @@ var POST_TOOL_USE_HOOK = {
|
|
|
1759
1826
|
}
|
|
1760
1827
|
]
|
|
1761
1828
|
};
|
|
1762
|
-
var
|
|
1829
|
+
var SESSION_END_HOOK = {
|
|
1830
|
+
matcher: "",
|
|
1831
|
+
hooks: [
|
|
1832
|
+
{
|
|
1833
|
+
type: "command",
|
|
1834
|
+
command: "npx -y @keepgoingdev/mcp-server --save-checkpoint"
|
|
1835
|
+
}
|
|
1836
|
+
]
|
|
1837
|
+
};
|
|
1838
|
+
var KEEPGOING_RULES_VERSION = 1;
|
|
1839
|
+
var KEEPGOING_RULES_CONTENT = `<!-- @keepgoingdev/mcp-server v${KEEPGOING_RULES_VERSION} -->
|
|
1763
1840
|
## KeepGoing
|
|
1764
1841
|
|
|
1765
1842
|
After completing a task or meaningful piece of work, call the \`save_checkpoint\` MCP tool with:
|
|
1766
|
-
- \`summary\`: What
|
|
1767
|
-
- \`nextStep\`: What
|
|
1843
|
+
- \`summary\`: 1-2 sentences. What changed and why \u2014 no file paths, no implementation details (those are captured from git).
|
|
1844
|
+
- \`nextStep\`: What to do next
|
|
1768
1845
|
- \`blocker\`: Any blocker (if applicable)
|
|
1769
1846
|
`;
|
|
1847
|
+
function getRulesFileVersion(content) {
|
|
1848
|
+
const match = content.match(/<!-- @keepgoingdev\/mcp-server v(\d+) -->/);
|
|
1849
|
+
return match ? parseInt(match[1], 10) : null;
|
|
1850
|
+
}
|
|
1770
1851
|
var STATUSLINE_CMD = "npx -y @keepgoingdev/mcp-server --statusline";
|
|
1852
|
+
function detectClaudeDir() {
|
|
1853
|
+
return process.env.CLAUDE_CONFIG_DIR || path8.join(os3.homedir(), ".claude");
|
|
1854
|
+
}
|
|
1771
1855
|
function hasKeepGoingHook(hookEntries) {
|
|
1772
1856
|
return hookEntries.some(
|
|
1773
1857
|
(entry) => entry?.hooks?.some((h) => typeof h?.command === "string" && h.command.includes(KEEPGOING_MARKER))
|
|
1774
1858
|
);
|
|
1775
1859
|
}
|
|
1776
|
-
function resolveScopePaths(scope, workspacePath) {
|
|
1860
|
+
function resolveScopePaths(scope, workspacePath, overrideClaudeDir) {
|
|
1777
1861
|
if (scope === "user") {
|
|
1778
|
-
const claudeDir2 =
|
|
1862
|
+
const claudeDir2 = overrideClaudeDir || detectClaudeDir();
|
|
1779
1863
|
return {
|
|
1780
1864
|
claudeDir: claudeDir2,
|
|
1781
|
-
settingsPath:
|
|
1782
|
-
claudeMdPath:
|
|
1865
|
+
settingsPath: path8.join(claudeDir2, "settings.json"),
|
|
1866
|
+
claudeMdPath: path8.join(claudeDir2, "CLAUDE.md"),
|
|
1867
|
+
rulesPath: path8.join(claudeDir2, "rules", "keepgoing.md")
|
|
1783
1868
|
};
|
|
1784
1869
|
}
|
|
1785
|
-
const claudeDir =
|
|
1786
|
-
const dotClaudeMdPath =
|
|
1787
|
-
const rootClaudeMdPath =
|
|
1870
|
+
const claudeDir = path8.join(workspacePath, ".claude");
|
|
1871
|
+
const dotClaudeMdPath = path8.join(workspacePath, ".claude", "CLAUDE.md");
|
|
1872
|
+
const rootClaudeMdPath = path8.join(workspacePath, "CLAUDE.md");
|
|
1788
1873
|
return {
|
|
1789
1874
|
claudeDir,
|
|
1790
|
-
settingsPath:
|
|
1791
|
-
claudeMdPath:
|
|
1875
|
+
settingsPath: path8.join(claudeDir, "settings.json"),
|
|
1876
|
+
claudeMdPath: fs6.existsSync(dotClaudeMdPath) ? dotClaudeMdPath : rootClaudeMdPath,
|
|
1877
|
+
rulesPath: path8.join(workspacePath, ".claude", "rules", "keepgoing.md")
|
|
1792
1878
|
};
|
|
1793
1879
|
}
|
|
1794
1880
|
function writeHooksToSettings(settings) {
|
|
@@ -1817,15 +1903,22 @@ function writeHooksToSettings(settings) {
|
|
|
1817
1903
|
settings.hooks.PostToolUse.push(POST_TOOL_USE_HOOK);
|
|
1818
1904
|
changed = true;
|
|
1819
1905
|
}
|
|
1906
|
+
if (!Array.isArray(settings.hooks.SessionEnd)) {
|
|
1907
|
+
settings.hooks.SessionEnd = [];
|
|
1908
|
+
}
|
|
1909
|
+
if (!hasKeepGoingHook(settings.hooks.SessionEnd)) {
|
|
1910
|
+
settings.hooks.SessionEnd.push(SESSION_END_HOOK);
|
|
1911
|
+
changed = true;
|
|
1912
|
+
}
|
|
1820
1913
|
return changed;
|
|
1821
1914
|
}
|
|
1822
1915
|
function checkHookConflict(scope, workspacePath) {
|
|
1823
1916
|
const otherPaths = resolveScopePaths(scope === "user" ? "project" : "user", workspacePath);
|
|
1824
|
-
if (!
|
|
1917
|
+
if (!fs6.existsSync(otherPaths.settingsPath)) {
|
|
1825
1918
|
return null;
|
|
1826
1919
|
}
|
|
1827
1920
|
try {
|
|
1828
|
-
const otherSettings = JSON.parse(
|
|
1921
|
+
const otherSettings = JSON.parse(fs6.readFileSync(otherPaths.settingsPath, "utf-8"));
|
|
1829
1922
|
const hooks = otherSettings?.hooks;
|
|
1830
1923
|
if (!hooks) return null;
|
|
1831
1924
|
const hasConflict = Array.isArray(hooks.SessionStart) && hasKeepGoingHook(hooks.SessionStart) || Array.isArray(hooks.Stop) && hasKeepGoingHook(hooks.Stop);
|
|
@@ -1844,15 +1937,20 @@ function setupProject(options) {
|
|
|
1844
1937
|
scope = "project",
|
|
1845
1938
|
sessionHooks = true,
|
|
1846
1939
|
claudeMd = true,
|
|
1940
|
+
claudeDir: claudeDirOverride,
|
|
1847
1941
|
statusline
|
|
1848
1942
|
} = options;
|
|
1849
1943
|
const messages = [];
|
|
1850
1944
|
let changed = false;
|
|
1851
|
-
const { claudeDir, settingsPath, claudeMdPath } = resolveScopePaths(
|
|
1852
|
-
|
|
1945
|
+
const { claudeDir, settingsPath, claudeMdPath, rulesPath } = resolveScopePaths(
|
|
1946
|
+
scope,
|
|
1947
|
+
workspacePath,
|
|
1948
|
+
claudeDirOverride
|
|
1949
|
+
);
|
|
1950
|
+
const scopeLabel = scope === "user" ? path8.join("~", path8.relative(os3.homedir(), claudeDir), "settings.json").replace(/\\/g, "/") : ".claude/settings.json";
|
|
1853
1951
|
let settings = {};
|
|
1854
|
-
if (
|
|
1855
|
-
settings = JSON.parse(
|
|
1952
|
+
if (fs6.existsSync(settingsPath)) {
|
|
1953
|
+
settings = JSON.parse(fs6.readFileSync(settingsPath, "utf-8"));
|
|
1856
1954
|
}
|
|
1857
1955
|
let settingsChanged = false;
|
|
1858
1956
|
if (sessionHooks) {
|
|
@@ -1883,29 +1981,45 @@ function setupProject(options) {
|
|
|
1883
1981
|
statusline?.cleanup?.();
|
|
1884
1982
|
}
|
|
1885
1983
|
if (settingsChanged) {
|
|
1886
|
-
if (!
|
|
1887
|
-
|
|
1984
|
+
if (!fs6.existsSync(claudeDir)) {
|
|
1985
|
+
fs6.mkdirSync(claudeDir, { recursive: true });
|
|
1888
1986
|
}
|
|
1889
|
-
|
|
1987
|
+
fs6.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
1890
1988
|
changed = true;
|
|
1891
1989
|
}
|
|
1892
1990
|
if (claudeMd) {
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1991
|
+
const rulesDir = path8.dirname(rulesPath);
|
|
1992
|
+
const rulesLabel = scope === "user" ? path8.join(path8.relative(os3.homedir(), path8.dirname(rulesPath)), "keepgoing.md").replace(/\\/g, "/") : ".claude/rules/keepgoing.md";
|
|
1993
|
+
if (fs6.existsSync(rulesPath)) {
|
|
1994
|
+
const existing = fs6.readFileSync(rulesPath, "utf-8");
|
|
1995
|
+
const existingVersion = getRulesFileVersion(existing);
|
|
1996
|
+
if (existingVersion === null) {
|
|
1997
|
+
messages.push(`Rules file: Custom file found at ${rulesLabel}, skipping`);
|
|
1998
|
+
} else if (existingVersion >= KEEPGOING_RULES_VERSION) {
|
|
1999
|
+
messages.push(`Rules file: Already up to date (v${existingVersion}), skipped`);
|
|
2000
|
+
} else {
|
|
2001
|
+
if (!fs6.existsSync(rulesDir)) {
|
|
2002
|
+
fs6.mkdirSync(rulesDir, { recursive: true });
|
|
2003
|
+
}
|
|
2004
|
+
fs6.writeFileSync(rulesPath, KEEPGOING_RULES_CONTENT);
|
|
2005
|
+
changed = true;
|
|
2006
|
+
messages.push(`Rules file: Updated v${existingVersion} \u2192 v${KEEPGOING_RULES_VERSION} at ${rulesLabel}`);
|
|
2007
|
+
}
|
|
1900
2008
|
} else {
|
|
1901
|
-
const
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
fs5.mkdirSync(mdDir, { recursive: true });
|
|
2009
|
+
const existingClaudeMd = fs6.existsSync(claudeMdPath) ? fs6.readFileSync(claudeMdPath, "utf-8") : "";
|
|
2010
|
+
if (!fs6.existsSync(rulesDir)) {
|
|
2011
|
+
fs6.mkdirSync(rulesDir, { recursive: true });
|
|
1905
2012
|
}
|
|
1906
|
-
|
|
2013
|
+
fs6.writeFileSync(rulesPath, KEEPGOING_RULES_CONTENT);
|
|
1907
2014
|
changed = true;
|
|
1908
|
-
|
|
2015
|
+
if (existingClaudeMd.includes("## KeepGoing")) {
|
|
2016
|
+
const mdLabel = scope === "user" ? "~/.claude/CLAUDE.md" : "CLAUDE.md";
|
|
2017
|
+
messages.push(
|
|
2018
|
+
`Rules file: Created ${rulesLabel} (you can now remove the ## KeepGoing section from ${mdLabel})`
|
|
2019
|
+
);
|
|
2020
|
+
} else {
|
|
2021
|
+
messages.push(`Rules file: Created ${rulesLabel}`);
|
|
2022
|
+
}
|
|
1909
2023
|
}
|
|
1910
2024
|
}
|
|
1911
2025
|
return { messages, changed };
|
|
@@ -2253,7 +2367,7 @@ function registerGetReentryBriefing(server, reader, workspacePath) {
|
|
|
2253
2367
|
}
|
|
2254
2368
|
|
|
2255
2369
|
// src/tools/saveCheckpoint.ts
|
|
2256
|
-
import
|
|
2370
|
+
import path9 from "path";
|
|
2257
2371
|
import { z as z4 } from "zod";
|
|
2258
2372
|
function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
2259
2373
|
server.tool(
|
|
@@ -2269,7 +2383,7 @@ function registerSaveCheckpoint(server, reader, workspacePath) {
|
|
|
2269
2383
|
const gitBranch = getCurrentBranch(workspacePath);
|
|
2270
2384
|
const touchedFiles = getTouchedFiles(workspacePath);
|
|
2271
2385
|
const commitHashes = getCommitsSince(workspacePath, lastSession?.timestamp);
|
|
2272
|
-
const projectName =
|
|
2386
|
+
const projectName = path9.basename(resolveStorageRoot(workspacePath));
|
|
2273
2387
|
const sessionId = generateSessionId({ workspaceRoot: workspacePath, branch: gitBranch ?? void 0, worktreePath: workspacePath });
|
|
2274
2388
|
const checkpoint = createCheckpoint({
|
|
2275
2389
|
summary,
|
|
@@ -2389,7 +2503,7 @@ function registerGetDecisions(server, reader) {
|
|
|
2389
2503
|
function registerGetCurrentTask(server, reader) {
|
|
2390
2504
|
server.tool(
|
|
2391
2505
|
"get_current_task",
|
|
2392
|
-
"Get
|
|
2506
|
+
"Get a bird's eye view of all active Claude sessions. See what each session is working on, which branch it is on, and when it last did something. Useful when running multiple parallel sessions across worktrees.",
|
|
2393
2507
|
{},
|
|
2394
2508
|
async () => {
|
|
2395
2509
|
if (!reader.exists()) {
|
|
@@ -2438,12 +2552,15 @@ function registerGetCurrentTask(server, reader) {
|
|
|
2438
2552
|
for (const task of [...activeTasks, ...finishedTasks]) {
|
|
2439
2553
|
const statusIcon = task.sessionActive ? "\u{1F7E2}" : "\u2705";
|
|
2440
2554
|
const statusLabel = task.sessionActive ? "Active" : "Finished";
|
|
2441
|
-
const sessionLabel = task.agentLabel || task.sessionId || "Session";
|
|
2555
|
+
const sessionLabel = task.sessionLabel || task.agentLabel || task.sessionId || "Session";
|
|
2442
2556
|
lines.push(`### ${statusIcon} ${sessionLabel} (${statusLabel})`);
|
|
2443
2557
|
lines.push(`- **Updated:** ${formatRelativeTime(task.updatedAt)}`);
|
|
2444
2558
|
if (task.branch) {
|
|
2445
2559
|
lines.push(`- **Branch:** ${task.branch}`);
|
|
2446
2560
|
}
|
|
2561
|
+
if (task.agentLabel && task.sessionLabel) {
|
|
2562
|
+
lines.push(`- **Agent:** ${task.agentLabel}`);
|
|
2563
|
+
}
|
|
2447
2564
|
if (task.taskSummary) {
|
|
2448
2565
|
lines.push(`- **Doing:** ${task.taskSummary}`);
|
|
2449
2566
|
}
|
|
@@ -2484,25 +2601,25 @@ function registerGetCurrentTask(server, reader) {
|
|
|
2484
2601
|
import { z as z6 } from "zod";
|
|
2485
2602
|
|
|
2486
2603
|
// src/cli/migrate.ts
|
|
2487
|
-
import
|
|
2488
|
-
import
|
|
2489
|
-
import
|
|
2604
|
+
import fs7 from "fs";
|
|
2605
|
+
import os4 from "os";
|
|
2606
|
+
import path10 from "path";
|
|
2490
2607
|
var STATUSLINE_CMD2 = "npx -y @keepgoingdev/mcp-server --statusline";
|
|
2491
2608
|
function isLegacyStatusline(command) {
|
|
2492
2609
|
return !command.includes("--statusline") && command.includes("keepgoing-statusline");
|
|
2493
2610
|
}
|
|
2494
2611
|
function migrateStatusline(wsPath) {
|
|
2495
|
-
const settingsPath =
|
|
2496
|
-
if (!
|
|
2612
|
+
const settingsPath = path10.join(wsPath, ".claude", "settings.json");
|
|
2613
|
+
if (!fs7.existsSync(settingsPath)) return void 0;
|
|
2497
2614
|
try {
|
|
2498
|
-
const settings = JSON.parse(
|
|
2615
|
+
const settings = JSON.parse(fs7.readFileSync(settingsPath, "utf-8"));
|
|
2499
2616
|
const cmd = settings.statusLine?.command;
|
|
2500
2617
|
if (!cmd || !isLegacyStatusline(cmd)) return void 0;
|
|
2501
2618
|
settings.statusLine = {
|
|
2502
2619
|
type: "command",
|
|
2503
2620
|
command: STATUSLINE_CMD2
|
|
2504
2621
|
};
|
|
2505
|
-
|
|
2622
|
+
fs7.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + "\n");
|
|
2506
2623
|
cleanupLegacyScript();
|
|
2507
2624
|
return "[KeepGoing] Migrated statusline to auto-updating command (restart Claude Code to apply)";
|
|
2508
2625
|
} catch {
|
|
@@ -2510,10 +2627,10 @@ function migrateStatusline(wsPath) {
|
|
|
2510
2627
|
}
|
|
2511
2628
|
}
|
|
2512
2629
|
function cleanupLegacyScript() {
|
|
2513
|
-
const legacyScript =
|
|
2514
|
-
if (
|
|
2630
|
+
const legacyScript = path10.join(os4.homedir(), ".claude", "keepgoing-statusline.sh");
|
|
2631
|
+
if (fs7.existsSync(legacyScript)) {
|
|
2515
2632
|
try {
|
|
2516
|
-
|
|
2633
|
+
fs7.unlinkSync(legacyScript);
|
|
2517
2634
|
} catch {
|
|
2518
2635
|
}
|
|
2519
2636
|
}
|
|
@@ -2526,15 +2643,17 @@ function registerSetupProject(server, workspacePath) {
|
|
|
2526
2643
|
'Set up KeepGoing hooks and instructions. Use scope "user" for global setup (all projects) or "project" for per-project setup.',
|
|
2527
2644
|
{
|
|
2528
2645
|
sessionHooks: z6.boolean().optional().default(true).describe("Add session hooks to settings.json"),
|
|
2529
|
-
claudeMd: z6.boolean().optional().default(true).describe("Add KeepGoing instructions to
|
|
2530
|
-
scope: z6.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)')
|
|
2646
|
+
claudeMd: z6.boolean().optional().default(true).describe("Add KeepGoing instructions to .claude/rules/keepgoing.md"),
|
|
2647
|
+
scope: z6.enum(["project", "user"]).optional().default("project").describe('Where to write config: "user" for global (~/.claude/), "project" for per-project (.claude/)'),
|
|
2648
|
+
claudeDir: z6.string().optional().describe("Override the Claude config directory for user scope (defaults to CLAUDE_CONFIG_DIR env var or ~/.claude)")
|
|
2531
2649
|
},
|
|
2532
|
-
async ({ sessionHooks, claudeMd, scope }) => {
|
|
2650
|
+
async ({ sessionHooks, claudeMd, scope, claudeDir }) => {
|
|
2533
2651
|
const result = setupProject({
|
|
2534
2652
|
workspacePath,
|
|
2535
2653
|
scope,
|
|
2536
2654
|
sessionHooks,
|
|
2537
2655
|
claudeMd,
|
|
2656
|
+
claudeDir,
|
|
2538
2657
|
statusline: {
|
|
2539
2658
|
isLegacy: isLegacyStatusline,
|
|
2540
2659
|
cleanup: cleanupLegacyScript
|
|
@@ -2909,7 +3028,7 @@ async function handlePrintCurrent() {
|
|
|
2909
3028
|
}
|
|
2910
3029
|
|
|
2911
3030
|
// src/cli/saveCheckpoint.ts
|
|
2912
|
-
import
|
|
3031
|
+
import path11 from "path";
|
|
2913
3032
|
async function handleSaveCheckpoint() {
|
|
2914
3033
|
const wsPath = resolveWsPath();
|
|
2915
3034
|
const reader = new KeepGoingReader(wsPath);
|
|
@@ -2937,9 +3056,9 @@ async function handleSaveCheckpoint() {
|
|
|
2937
3056
|
sessionStartTime: lastSession?.timestamp ?? now,
|
|
2938
3057
|
lastActivityTime: now
|
|
2939
3058
|
});
|
|
2940
|
-
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) =>
|
|
3059
|
+
const summary = buildSmartSummary(events) ?? `Worked on ${touchedFiles.slice(0, 5).map((f) => path11.basename(f)).join(", ")}`;
|
|
2941
3060
|
const nextStep = buildSmartNextStep(events);
|
|
2942
|
-
const projectName =
|
|
3061
|
+
const projectName = path11.basename(resolveStorageRoot(wsPath));
|
|
2943
3062
|
const sessionId = generateSessionId({ workspaceRoot: wsPath, branch: gitBranch ?? void 0, worktreePath: wsPath });
|
|
2944
3063
|
const checkpoint = createCheckpoint({
|
|
2945
3064
|
summary,
|
|
@@ -2960,16 +3079,19 @@ async function handleSaveCheckpoint() {
|
|
|
2960
3079
|
branch: gitBranch ?? void 0,
|
|
2961
3080
|
updatedAt: checkpoint.timestamp
|
|
2962
3081
|
});
|
|
2963
|
-
if (
|
|
2964
|
-
|
|
2965
|
-
|
|
3082
|
+
if (process.env.KEEPGOING_PRO_BYPASS === "1" || getLicenseForFeature("decisions")) {
|
|
3083
|
+
for (let i = 0; i < commitHashes.length; i++) {
|
|
3084
|
+
const hash = commitHashes[i];
|
|
3085
|
+
const message = commitMessages[i];
|
|
3086
|
+
if (!hash || !message) continue;
|
|
3087
|
+
const files = getFilesChangedInCommit(wsPath, hash);
|
|
2966
3088
|
const detected = tryDetectDecision({
|
|
2967
3089
|
workspacePath: wsPath,
|
|
2968
3090
|
checkpointId: checkpoint.id,
|
|
2969
3091
|
gitBranch,
|
|
2970
|
-
commitHash:
|
|
2971
|
-
commitMessage:
|
|
2972
|
-
filesChanged:
|
|
3092
|
+
commitHash: hash,
|
|
3093
|
+
commitMessage: message,
|
|
3094
|
+
filesChanged: files
|
|
2973
3095
|
});
|
|
2974
3096
|
if (detected) {
|
|
2975
3097
|
console.log(`[KeepGoing] Decision detected: ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);
|
|
@@ -2981,7 +3103,7 @@ async function handleSaveCheckpoint() {
|
|
|
2981
3103
|
}
|
|
2982
3104
|
|
|
2983
3105
|
// src/cli/transcriptUtils.ts
|
|
2984
|
-
import
|
|
3106
|
+
import fs8 from "fs";
|
|
2985
3107
|
var TAIL_READ_BYTES = 8192;
|
|
2986
3108
|
var TOOL_VERB_MAP = {
|
|
2987
3109
|
Edit: "editing",
|
|
@@ -3032,9 +3154,9 @@ function isAssistantEntry(entry) {
|
|
|
3032
3154
|
return entry.message?.role === "assistant";
|
|
3033
3155
|
}
|
|
3034
3156
|
function extractSessionLabel(transcriptPath) {
|
|
3035
|
-
if (!transcriptPath || !
|
|
3157
|
+
if (!transcriptPath || !fs8.existsSync(transcriptPath)) return null;
|
|
3036
3158
|
try {
|
|
3037
|
-
const raw =
|
|
3159
|
+
const raw = fs8.readFileSync(transcriptPath, "utf-8");
|
|
3038
3160
|
for (const line of raw.split("\n")) {
|
|
3039
3161
|
const trimmed = line.trim();
|
|
3040
3162
|
if (!trimmed) continue;
|
|
@@ -3063,19 +3185,19 @@ function extractSessionLabel(transcriptPath) {
|
|
|
3063
3185
|
return null;
|
|
3064
3186
|
}
|
|
3065
3187
|
function extractCurrentAction(transcriptPath) {
|
|
3066
|
-
if (!transcriptPath || !
|
|
3188
|
+
if (!transcriptPath || !fs8.existsSync(transcriptPath)) return null;
|
|
3067
3189
|
try {
|
|
3068
|
-
const stat =
|
|
3190
|
+
const stat = fs8.statSync(transcriptPath);
|
|
3069
3191
|
const fileSize = stat.size;
|
|
3070
3192
|
if (fileSize === 0) return null;
|
|
3071
3193
|
const readSize = Math.min(fileSize, TAIL_READ_BYTES);
|
|
3072
3194
|
const offset = fileSize - readSize;
|
|
3073
3195
|
const buf = Buffer.alloc(readSize);
|
|
3074
|
-
const fd =
|
|
3196
|
+
const fd = fs8.openSync(transcriptPath, "r");
|
|
3075
3197
|
try {
|
|
3076
|
-
|
|
3198
|
+
fs8.readSync(fd, buf, 0, readSize, offset);
|
|
3077
3199
|
} finally {
|
|
3078
|
-
|
|
3200
|
+
fs8.closeSync(fd);
|
|
3079
3201
|
}
|
|
3080
3202
|
const tail = buf.toString("utf-8");
|
|
3081
3203
|
const lines = tail.split("\n").reverse();
|
|
@@ -3177,8 +3299,8 @@ async function handleUpdateTaskFromHook() {
|
|
|
3177
3299
|
}
|
|
3178
3300
|
|
|
3179
3301
|
// src/cli/statusline.ts
|
|
3180
|
-
import
|
|
3181
|
-
import
|
|
3302
|
+
import fs9 from "fs";
|
|
3303
|
+
import path12 from "path";
|
|
3182
3304
|
var STDIN_TIMEOUT_MS2 = 3e3;
|
|
3183
3305
|
async function handleStatusline() {
|
|
3184
3306
|
const chunks = [];
|
|
@@ -3205,9 +3327,9 @@ async function handleStatusline() {
|
|
|
3205
3327
|
if (!label) {
|
|
3206
3328
|
try {
|
|
3207
3329
|
const gitRoot = findGitRoot(dir);
|
|
3208
|
-
const tasksFile =
|
|
3209
|
-
if (
|
|
3210
|
-
const data = JSON.parse(
|
|
3330
|
+
const tasksFile = path12.join(gitRoot, ".keepgoing", "current-tasks.json");
|
|
3331
|
+
if (fs9.existsSync(tasksFile)) {
|
|
3332
|
+
const data = JSON.parse(fs9.readFileSync(tasksFile, "utf-8"));
|
|
3211
3333
|
const tasks = pruneStaleTasks(data.tasks ?? []);
|
|
3212
3334
|
const match = sessionId ? tasks.find((t) => t.sessionId === sessionId) : void 0;
|
|
3213
3335
|
if (match?.sessionLabel) {
|
|
@@ -3254,6 +3376,35 @@ async function handleContinueOn() {
|
|
|
3254
3376
|
process.exit(0);
|
|
3255
3377
|
}
|
|
3256
3378
|
|
|
3379
|
+
// src/cli/detectDecisions.ts
|
|
3380
|
+
async function handleDetectDecisions() {
|
|
3381
|
+
const wsPath = resolveWsPath();
|
|
3382
|
+
if (!(process.env.KEEPGOING_PRO_BYPASS === "1" || getLicenseForFeature("decisions"))) {
|
|
3383
|
+
process.exit(0);
|
|
3384
|
+
}
|
|
3385
|
+
const reader = new KeepGoingReader(wsPath);
|
|
3386
|
+
if (!reader.exists()) {
|
|
3387
|
+
process.exit(0);
|
|
3388
|
+
}
|
|
3389
|
+
const gitBranch = getCurrentBranch(wsPath);
|
|
3390
|
+
const headHash = getHeadCommitHash(wsPath);
|
|
3391
|
+
if (!headHash) process.exit(0);
|
|
3392
|
+
const commitMessage = getCommitMessageByHash(wsPath, headHash);
|
|
3393
|
+
if (!commitMessage) process.exit(0);
|
|
3394
|
+
const files = getFilesChangedInCommit(wsPath, headHash);
|
|
3395
|
+
const detected = tryDetectDecision({
|
|
3396
|
+
workspacePath: wsPath,
|
|
3397
|
+
gitBranch,
|
|
3398
|
+
commitHash: headHash,
|
|
3399
|
+
commitMessage,
|
|
3400
|
+
filesChanged: files
|
|
3401
|
+
});
|
|
3402
|
+
if (detected) {
|
|
3403
|
+
console.log(`[KeepGoing] Decision detected: ${detected.category} (${(detected.confidence * 100).toFixed(0)}% confidence)`);
|
|
3404
|
+
}
|
|
3405
|
+
process.exit(0);
|
|
3406
|
+
}
|
|
3407
|
+
|
|
3257
3408
|
// src/index.ts
|
|
3258
3409
|
var CLI_HANDLERS = {
|
|
3259
3410
|
"--print-momentum": handlePrintMomentum,
|
|
@@ -3262,7 +3413,8 @@ var CLI_HANDLERS = {
|
|
|
3262
3413
|
"--update-task-from-hook": handleUpdateTaskFromHook,
|
|
3263
3414
|
"--print-current": handlePrintCurrent,
|
|
3264
3415
|
"--statusline": handleStatusline,
|
|
3265
|
-
"--continue-on": handleContinueOn
|
|
3416
|
+
"--continue-on": handleContinueOn,
|
|
3417
|
+
"--detect-decisions": handleDetectDecisions
|
|
3266
3418
|
};
|
|
3267
3419
|
var flag = process.argv.slice(2).find((a) => a in CLI_HANDLERS);
|
|
3268
3420
|
if (flag) {
|