@codebyplan/cli 3.2.0 → 3.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/cli.js +2829 -929
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -37,7 +37,7 @@ var VERSION, PACKAGE_NAME;
|
|
|
37
37
|
var init_version = __esm({
|
|
38
38
|
"src/lib/version.ts"() {
|
|
39
39
|
"use strict";
|
|
40
|
-
VERSION = "3.
|
|
40
|
+
VERSION = "3.4.0";
|
|
41
41
|
PACKAGE_NAME = "@codebyplan/cli";
|
|
42
42
|
}
|
|
43
43
|
});
|
|
@@ -71,13 +71,17 @@ async function validateConnectivity() {
|
|
|
71
71
|
);
|
|
72
72
|
}
|
|
73
73
|
if (!res.ok) {
|
|
74
|
-
console.error(
|
|
74
|
+
console.error(
|
|
75
|
+
`Warning: API returned status ${res.status} during connectivity check`
|
|
76
|
+
);
|
|
75
77
|
}
|
|
76
78
|
} catch (err) {
|
|
77
79
|
if (err instanceof Error && err.message.includes("Invalid API key")) {
|
|
78
80
|
throw err;
|
|
79
81
|
}
|
|
80
|
-
console.error(
|
|
82
|
+
console.error(
|
|
83
|
+
"Warning: Could not reach CodeByPlan API. Requests may fail."
|
|
84
|
+
);
|
|
81
85
|
}
|
|
82
86
|
}
|
|
83
87
|
function buildUrl(path, params) {
|
|
@@ -166,8 +170,8 @@ async function apiPut(path, body) {
|
|
|
166
170
|
async function apiPatch(path, body) {
|
|
167
171
|
return request("PATCH", path, { body });
|
|
168
172
|
}
|
|
169
|
-
async function apiDelete(path) {
|
|
170
|
-
await request("DELETE", path);
|
|
173
|
+
async function apiDelete(path, params) {
|
|
174
|
+
await request("DELETE", path, { params });
|
|
171
175
|
}
|
|
172
176
|
var API_KEY, BASE_URL, REQUEST_TIMEOUT_MS, MAX_RETRIES, BASE_DELAY_MS, ApiError;
|
|
173
177
|
var init_api = __esm({
|
|
@@ -175,10 +179,7 @@ var init_api = __esm({
|
|
|
175
179
|
"use strict";
|
|
176
180
|
init_version();
|
|
177
181
|
API_KEY = process.env.CODEBYPLAN_API_KEY ?? "";
|
|
178
|
-
BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com").replace(
|
|
179
|
-
/\/$/,
|
|
180
|
-
""
|
|
181
|
-
);
|
|
182
|
+
BASE_URL = (process.env.CODEBYPLAN_API_URL ?? "https://codebyplan.com").replace(/\/$/, "");
|
|
182
183
|
REQUEST_TIMEOUT_MS = 3e4;
|
|
183
184
|
MAX_RETRIES = 3;
|
|
184
185
|
BASE_DELAY_MS = 1e3;
|
|
@@ -665,7 +666,51 @@ async function executeSyncToLocal(options) {
|
|
|
665
666
|
byType[typeName] = result;
|
|
666
667
|
}
|
|
667
668
|
if (!dryRun) {
|
|
668
|
-
await
|
|
669
|
+
await apiPost("/sync/state", {
|
|
670
|
+
repo_id: repoId,
|
|
671
|
+
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
672
|
+
was_skipped: false,
|
|
673
|
+
files_synced_count: totals.created + totals.updated + totals.deleted + totals.unchanged,
|
|
674
|
+
files_pushed: 0,
|
|
675
|
+
files_pulled: totals.created + totals.updated,
|
|
676
|
+
files_deleted: totals.deleted,
|
|
677
|
+
files_skipped: 0
|
|
678
|
+
});
|
|
679
|
+
const fileRepoUpdates = [];
|
|
680
|
+
const syncTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
681
|
+
for (const [syncKey] of Object.entries(syncKeyToType)) {
|
|
682
|
+
const remoteFiles = syncData[syncKey] ?? [];
|
|
683
|
+
for (const file of remoteFiles) {
|
|
684
|
+
if (file.id) {
|
|
685
|
+
fileRepoUpdates.push({
|
|
686
|
+
claude_file_id: file.id,
|
|
687
|
+
last_synced_at: syncTimestamp,
|
|
688
|
+
sync_status: "synced"
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
}
|
|
693
|
+
for (const typeName of ["claude_md", "settings"]) {
|
|
694
|
+
const remoteFiles = syncData[typeName] ?? [];
|
|
695
|
+
for (const file of remoteFiles) {
|
|
696
|
+
if (file.id) {
|
|
697
|
+
fileRepoUpdates.push({
|
|
698
|
+
claude_file_id: file.id,
|
|
699
|
+
last_synced_at: syncTimestamp,
|
|
700
|
+
sync_status: "synced"
|
|
701
|
+
});
|
|
702
|
+
}
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
if (fileRepoUpdates.length > 0) {
|
|
706
|
+
try {
|
|
707
|
+
await apiPost("/sync/file-repos", {
|
|
708
|
+
repo_id: repoId,
|
|
709
|
+
file_repos: fileRepoUpdates
|
|
710
|
+
});
|
|
711
|
+
} catch {
|
|
712
|
+
}
|
|
713
|
+
}
|
|
669
714
|
}
|
|
670
715
|
return { byType, totals, dbOnlyFiles };
|
|
671
716
|
}
|
|
@@ -683,7 +728,8 @@ var init_sync_engine = __esm({
|
|
|
683
728
|
skill: { dir: "skills", ext: ".md", subfolder: "SKILL" },
|
|
684
729
|
rule: { dir: "rules", ext: ".md" },
|
|
685
730
|
hook: { dir: "hooks", ext: ".sh" },
|
|
686
|
-
template: { dir: "templates", ext: "" }
|
|
731
|
+
template: { dir: "templates", ext: "" },
|
|
732
|
+
context: { dir: "context", ext: ".md" }
|
|
687
733
|
};
|
|
688
734
|
syncKeyToType = {
|
|
689
735
|
commands: "command",
|
|
@@ -691,7 +737,8 @@ var init_sync_engine = __esm({
|
|
|
691
737
|
skills: "skill",
|
|
692
738
|
rules: "rule",
|
|
693
739
|
hooks: "hook",
|
|
694
|
-
templates: "template"
|
|
740
|
+
templates: "template",
|
|
741
|
+
contexts: "context"
|
|
695
742
|
};
|
|
696
743
|
}
|
|
697
744
|
});
|
|
@@ -1051,58 +1098,176 @@ var init_fileMapper = __esm({
|
|
|
1051
1098
|
});
|
|
1052
1099
|
|
|
1053
1100
|
// src/cli/confirm.ts
|
|
1101
|
+
var confirm_exports = {};
|
|
1102
|
+
__export(confirm_exports, {
|
|
1103
|
+
SyncCancelledError: () => SyncCancelledError,
|
|
1104
|
+
confirmEach: () => confirmEach,
|
|
1105
|
+
confirmProceed: () => confirmProceed,
|
|
1106
|
+
promptChoice: () => promptChoice,
|
|
1107
|
+
promptReviewMode: () => promptReviewMode,
|
|
1108
|
+
reviewFilesOneByOne: () => reviewFilesOneByOne,
|
|
1109
|
+
reviewFolder: () => reviewFolder
|
|
1110
|
+
});
|
|
1054
1111
|
import { createInterface as createInterface2 } from "node:readline/promises";
|
|
1055
1112
|
import { stdin as stdin2, stdout as stdout2 } from "node:process";
|
|
1113
|
+
function isAbortError(err) {
|
|
1114
|
+
return err instanceof Error && err.code === "ABORT_ERR";
|
|
1115
|
+
}
|
|
1056
1116
|
async function confirmProceed(message) {
|
|
1057
1117
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1058
1118
|
try {
|
|
1059
|
-
|
|
1119
|
+
while (true) {
|
|
1120
|
+
const answer = await rl.question(message ?? " Proceed? [Y/n] ");
|
|
1121
|
+
const a = answer.trim().toLowerCase();
|
|
1122
|
+
if (a === "" || a === "y" || a === "yes") return true;
|
|
1123
|
+
if (a === "n" || a === "no") return false;
|
|
1124
|
+
console.log(` Unknown option "${answer.trim()}". Valid: y/yes, n/no, or Enter for yes.`);
|
|
1125
|
+
}
|
|
1126
|
+
} catch (err) {
|
|
1127
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1128
|
+
throw err;
|
|
1129
|
+
} finally {
|
|
1130
|
+
rl.close();
|
|
1131
|
+
}
|
|
1132
|
+
}
|
|
1133
|
+
async function promptChoice(message, options) {
|
|
1134
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1135
|
+
try {
|
|
1136
|
+
const answer = await rl.question(message);
|
|
1060
1137
|
const a = answer.trim().toLowerCase();
|
|
1061
|
-
return a
|
|
1138
|
+
return options.includes(a) ? a : options[0];
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1141
|
+
throw err;
|
|
1142
|
+
} finally {
|
|
1143
|
+
rl.close();
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
async function confirmEach(items, label) {
|
|
1147
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1148
|
+
const accepted = [];
|
|
1149
|
+
try {
|
|
1150
|
+
for (const item of items) {
|
|
1151
|
+
const answer = await rl.question(` ${label(item)} \u2014 delete? [y/n/a] `);
|
|
1152
|
+
const a = answer.trim().toLowerCase();
|
|
1153
|
+
if (a === "a") {
|
|
1154
|
+
accepted.push(item, ...items.slice(items.indexOf(item) + 1));
|
|
1155
|
+
break;
|
|
1156
|
+
}
|
|
1157
|
+
if (a === "y" || a === "yes" || a === "") {
|
|
1158
|
+
accepted.push(item);
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1163
|
+
throw err;
|
|
1062
1164
|
} finally {
|
|
1063
1165
|
rl.close();
|
|
1064
1166
|
}
|
|
1167
|
+
return accepted;
|
|
1065
1168
|
}
|
|
1066
|
-
function parseReviewAction(input
|
|
1169
|
+
function parseReviewAction(input) {
|
|
1067
1170
|
const a = input.trim().toLowerCase();
|
|
1068
1171
|
switch (a) {
|
|
1069
1172
|
case "d":
|
|
1070
1173
|
case "delete":
|
|
1071
|
-
return { action: "delete", all: false };
|
|
1174
|
+
return { action: "delete", all: false, special: null };
|
|
1072
1175
|
case "p":
|
|
1073
1176
|
case "pull":
|
|
1074
|
-
return { action: "pull", all: false };
|
|
1177
|
+
return { action: "pull", all: false, special: null };
|
|
1075
1178
|
case "s":
|
|
1076
1179
|
case "push":
|
|
1077
|
-
return { action: "push", all: false };
|
|
1180
|
+
return { action: "push", all: false, special: null };
|
|
1078
1181
|
case "k":
|
|
1079
1182
|
case "skip":
|
|
1080
|
-
return { action: "skip", all: false };
|
|
1183
|
+
return { action: "skip", all: false, special: null };
|
|
1081
1184
|
case "da":
|
|
1082
|
-
return { action: "delete", all: true };
|
|
1185
|
+
return { action: "delete", all: true, special: null };
|
|
1083
1186
|
case "pa":
|
|
1084
|
-
return { action: "pull", all: true };
|
|
1187
|
+
return { action: "pull", all: true, special: null };
|
|
1085
1188
|
case "sa":
|
|
1086
|
-
return { action: "push", all: true };
|
|
1189
|
+
return { action: "push", all: true, special: null };
|
|
1087
1190
|
case "ka":
|
|
1088
|
-
return { action: "skip", all: true };
|
|
1191
|
+
return { action: "skip", all: true, special: null };
|
|
1192
|
+
case "v":
|
|
1193
|
+
case "view":
|
|
1194
|
+
return { action: null, all: false, special: "view" };
|
|
1195
|
+
case "r":
|
|
1196
|
+
case "recommended":
|
|
1197
|
+
return { action: null, all: false, special: "recommended" };
|
|
1089
1198
|
case "":
|
|
1090
|
-
return { action:
|
|
1199
|
+
return { action: null, all: false, special: "recommended" };
|
|
1200
|
+
// Enter = recommended
|
|
1091
1201
|
default:
|
|
1092
|
-
return { action:
|
|
1202
|
+
return { action: null, all: false, special: null };
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
function formatActionPrompt(recommended, includeView, includeRecommended) {
|
|
1206
|
+
const actions = [
|
|
1207
|
+
`[d]elete${recommended === "delete" ? "\u2605" : ""}`,
|
|
1208
|
+
`[p]ull${recommended === "pull" ? "\u2605" : ""}`,
|
|
1209
|
+
`pu[s]h${recommended === "push" ? "\u2605" : ""}`,
|
|
1210
|
+
`s[k]ip${recommended === "skip" ? "\u2605" : ""}`
|
|
1211
|
+
];
|
|
1212
|
+
if (includeView) actions.push("[v]iew");
|
|
1213
|
+
if (includeRecommended) actions.push("[r]ecommended");
|
|
1214
|
+
return actions.join(" ");
|
|
1215
|
+
}
|
|
1216
|
+
function showDiff(local, remote, displayPath) {
|
|
1217
|
+
console.log(`
|
|
1218
|
+
--- ${displayPath} (diff) ---`);
|
|
1219
|
+
if (local === null && remote !== null) {
|
|
1220
|
+
console.log(" (no local file \u2014 remote content below)");
|
|
1221
|
+
for (const line of remote.split("\n").slice(0, 30)) {
|
|
1222
|
+
console.log(` + ${line}`);
|
|
1223
|
+
}
|
|
1224
|
+
if (remote.split("\n").length > 30) console.log(" ... (truncated)");
|
|
1225
|
+
} else if (local !== null && remote === null) {
|
|
1226
|
+
console.log(" (no remote file \u2014 local content below)");
|
|
1227
|
+
for (const line of local.split("\n").slice(0, 30)) {
|
|
1228
|
+
console.log(` - ${line}`);
|
|
1229
|
+
}
|
|
1230
|
+
if (local.split("\n").length > 30) console.log(" ... (truncated)");
|
|
1231
|
+
} else if (local !== null && remote !== null) {
|
|
1232
|
+
const localLines = local.split("\n");
|
|
1233
|
+
const remoteLines = remote.split("\n");
|
|
1234
|
+
let shown = 0;
|
|
1235
|
+
const maxLines = 40;
|
|
1236
|
+
for (let i = 0; i < Math.max(localLines.length, remoteLines.length) && shown < maxLines; i++) {
|
|
1237
|
+
const l = localLines[i];
|
|
1238
|
+
const r = remoteLines[i];
|
|
1239
|
+
if (l === r) {
|
|
1240
|
+
console.log(` ${l ?? ""}`);
|
|
1241
|
+
} else {
|
|
1242
|
+
if (l !== void 0) console.log(` - ${l}`);
|
|
1243
|
+
if (r !== void 0) console.log(` + ${r}`);
|
|
1244
|
+
}
|
|
1245
|
+
shown++;
|
|
1246
|
+
}
|
|
1247
|
+
if (Math.max(localLines.length, remoteLines.length) > maxLines) {
|
|
1248
|
+
console.log(" ... (truncated)");
|
|
1249
|
+
}
|
|
1093
1250
|
}
|
|
1251
|
+
console.log();
|
|
1094
1252
|
}
|
|
1095
1253
|
async function promptReviewMode() {
|
|
1096
1254
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1097
1255
|
try {
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1256
|
+
while (true) {
|
|
1257
|
+
const answer = await rl.question(" Review [o]ne-by-one or [f]older-by-folder? ");
|
|
1258
|
+
const a = answer.trim().toLowerCase();
|
|
1259
|
+
if (a === "o" || a === "one-by-one" || a === "one" || a === "file") return "file";
|
|
1260
|
+
if (a === "f" || a === "folder") return "folder";
|
|
1261
|
+
console.log(` Unknown option "${answer.trim()}". Valid: o/one-by-one, f/folder`);
|
|
1262
|
+
}
|
|
1263
|
+
} catch (err) {
|
|
1264
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1265
|
+
throw err;
|
|
1101
1266
|
} finally {
|
|
1102
1267
|
rl.close();
|
|
1103
1268
|
}
|
|
1104
1269
|
}
|
|
1105
|
-
async function reviewFilesOneByOne(items, label, plannedAction) {
|
|
1270
|
+
async function reviewFilesOneByOne(items, label, plannedAction, recommendedAction, content) {
|
|
1106
1271
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1107
1272
|
const results = [];
|
|
1108
1273
|
try {
|
|
@@ -1113,49 +1278,112 @@ async function reviewFilesOneByOne(items, label, plannedAction) {
|
|
|
1113
1278
|
continue;
|
|
1114
1279
|
}
|
|
1115
1280
|
const planned = plannedAction(item);
|
|
1116
|
-
const
|
|
1117
|
-
|
|
1118
|
-
)
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1281
|
+
const rec = recommendedAction ? recommendedAction(item) : planned;
|
|
1282
|
+
const hasContent = content != null;
|
|
1283
|
+
const prompt = ` ${label(item)} (${planned}) \u2014 ${formatActionPrompt(rec, hasContent, false)}: `;
|
|
1284
|
+
while (true) {
|
|
1285
|
+
const answer = await rl.question(prompt);
|
|
1286
|
+
const result = parseReviewAction(answer);
|
|
1287
|
+
if (result.special === "view") {
|
|
1288
|
+
if (content) {
|
|
1289
|
+
showDiff(content.local(item), content.remote(item), label(item));
|
|
1290
|
+
} else {
|
|
1291
|
+
console.log(" No content available for diff.");
|
|
1292
|
+
}
|
|
1293
|
+
continue;
|
|
1294
|
+
}
|
|
1295
|
+
if (result.special === "recommended") {
|
|
1296
|
+
results.push(rec);
|
|
1297
|
+
break;
|
|
1298
|
+
}
|
|
1299
|
+
if (result.action === null) {
|
|
1300
|
+
console.log(` Unknown option "${answer.trim()}". Valid: ${formatActionPrompt(rec, hasContent, false)}`);
|
|
1301
|
+
continue;
|
|
1302
|
+
}
|
|
1303
|
+
results.push(result.action);
|
|
1304
|
+
if (result.all) applyAll = result.action;
|
|
1305
|
+
break;
|
|
1306
|
+
}
|
|
1122
1307
|
}
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1310
|
+
throw err;
|
|
1123
1311
|
} finally {
|
|
1124
1312
|
rl.close();
|
|
1125
1313
|
}
|
|
1126
1314
|
return results;
|
|
1127
1315
|
}
|
|
1128
|
-
async function reviewFolder(folderName, items, label, plannedAction) {
|
|
1316
|
+
async function reviewFolder(folderName, items, label, plannedAction, recommendedAction, content) {
|
|
1129
1317
|
console.log(`
|
|
1130
1318
|
${folderName} (${items.length} files):`);
|
|
1131
1319
|
for (const item of items) {
|
|
1132
|
-
|
|
1320
|
+
const rec = recommendedAction ? recommendedAction(item) : plannedAction(item);
|
|
1321
|
+
const actionLabel = plannedAction(item);
|
|
1322
|
+
const star = actionLabel === rec ? "\u2605" : "";
|
|
1323
|
+
console.log(` ${label(item)} (${actionLabel}${star})`);
|
|
1133
1324
|
}
|
|
1134
1325
|
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1135
|
-
let answer;
|
|
1136
1326
|
try {
|
|
1137
|
-
|
|
1138
|
-
` Action for all:
|
|
1139
|
-
|
|
1327
|
+
while (true) {
|
|
1328
|
+
const promptStr = ` Action for all: ${formatActionPrompt(
|
|
1329
|
+
recommendedAction ? recommendedAction(items[0]) : plannedAction(items[0]),
|
|
1330
|
+
false,
|
|
1331
|
+
true
|
|
1332
|
+
)} [o]ne-by-one: `;
|
|
1333
|
+
const answer = await rl.question(promptStr);
|
|
1334
|
+
const a = answer.trim().toLowerCase();
|
|
1335
|
+
if (a === "o" || a === "one-by-one") {
|
|
1336
|
+
rl.close();
|
|
1337
|
+
return reviewFilesOneByOne(items, label, plannedAction, recommendedAction, content);
|
|
1338
|
+
}
|
|
1339
|
+
if (a === "r" || a === "recommended") {
|
|
1340
|
+
return items.map(
|
|
1341
|
+
(item) => recommendedAction ? recommendedAction(item) : plannedAction(item)
|
|
1342
|
+
);
|
|
1343
|
+
}
|
|
1344
|
+
if (a === "v" || a === "view") {
|
|
1345
|
+
if (content) {
|
|
1346
|
+
for (const item of items) {
|
|
1347
|
+
showDiff(content.local(item), content.remote(item), label(item));
|
|
1348
|
+
}
|
|
1349
|
+
} else {
|
|
1350
|
+
console.log(" No content available for diff.");
|
|
1351
|
+
}
|
|
1352
|
+
continue;
|
|
1353
|
+
}
|
|
1354
|
+
const result = parseReviewAction(a);
|
|
1355
|
+
if (result.action !== null) {
|
|
1356
|
+
return items.map(() => result.action);
|
|
1357
|
+
}
|
|
1358
|
+
console.log(` Unknown option "${answer.trim()}". Valid: ${formatActionPrompt(
|
|
1359
|
+
recommendedAction ? recommendedAction(items[0]) : plannedAction(items[0]),
|
|
1360
|
+
false,
|
|
1361
|
+
true
|
|
1362
|
+
)} [o]ne-by-one`);
|
|
1363
|
+
}
|
|
1364
|
+
} catch (err) {
|
|
1365
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1366
|
+
throw err;
|
|
1140
1367
|
} finally {
|
|
1141
1368
|
rl.close();
|
|
1142
1369
|
}
|
|
1143
|
-
const a = answer.trim().toLowerCase();
|
|
1144
|
-
if (a === "o" || a === "one-by-one") {
|
|
1145
|
-
return reviewFilesOneByOne(items, label, plannedAction);
|
|
1146
|
-
}
|
|
1147
|
-
const { action } = parseReviewAction(a, "skip");
|
|
1148
|
-
return items.map(() => action);
|
|
1149
1370
|
}
|
|
1371
|
+
var SyncCancelledError;
|
|
1150
1372
|
var init_confirm = __esm({
|
|
1151
1373
|
"src/cli/confirm.ts"() {
|
|
1152
1374
|
"use strict";
|
|
1375
|
+
SyncCancelledError = class extends Error {
|
|
1376
|
+
constructor() {
|
|
1377
|
+
super("Sync cancelled");
|
|
1378
|
+
this.name = "SyncCancelledError";
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1153
1381
|
}
|
|
1154
1382
|
});
|
|
1155
1383
|
|
|
1156
1384
|
// src/lib/tech-detect.ts
|
|
1157
1385
|
import { readFile as readFile6, access, readdir as readdir4 } from "node:fs/promises";
|
|
1158
|
-
import { join as join6 } from "node:path";
|
|
1386
|
+
import { join as join6, relative } from "node:path";
|
|
1159
1387
|
async function fileExists(filePath) {
|
|
1160
1388
|
try {
|
|
1161
1389
|
await access(filePath);
|
|
@@ -1335,7 +1563,69 @@ function parseTechStackResult(raw) {
|
|
|
1335
1563
|
const flat = parseTechStack(raw);
|
|
1336
1564
|
return { repo: flat, apps: [], flat };
|
|
1337
1565
|
}
|
|
1338
|
-
|
|
1566
|
+
function categorizeDependency(depName) {
|
|
1567
|
+
const rule = PACKAGE_MAP[depName];
|
|
1568
|
+
if (rule) return rule.category;
|
|
1569
|
+
for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
|
|
1570
|
+
if (depName.startsWith(prefix)) return prefixRule.category;
|
|
1571
|
+
}
|
|
1572
|
+
return "other";
|
|
1573
|
+
}
|
|
1574
|
+
async function findPackageJsonFiles(dir, projectPath, depth = 0) {
|
|
1575
|
+
if (depth > 4) return [];
|
|
1576
|
+
const results = [];
|
|
1577
|
+
const pkgPath = join6(dir, "package.json");
|
|
1578
|
+
if (await fileExists(pkgPath)) {
|
|
1579
|
+
results.push(pkgPath);
|
|
1580
|
+
}
|
|
1581
|
+
try {
|
|
1582
|
+
const entries = await readdir4(dir, { withFileTypes: true });
|
|
1583
|
+
for (const entry of entries) {
|
|
1584
|
+
if (!entry.isDirectory() || SKIP_DIRS.has(entry.name)) continue;
|
|
1585
|
+
const subResults = await findPackageJsonFiles(
|
|
1586
|
+
join6(dir, entry.name),
|
|
1587
|
+
projectPath,
|
|
1588
|
+
depth + 1
|
|
1589
|
+
);
|
|
1590
|
+
results.push(...subResults);
|
|
1591
|
+
}
|
|
1592
|
+
} catch {
|
|
1593
|
+
}
|
|
1594
|
+
return results;
|
|
1595
|
+
}
|
|
1596
|
+
async function scanAllDependencies(projectPath) {
|
|
1597
|
+
const packageJsonPaths = await findPackageJsonFiles(projectPath, projectPath);
|
|
1598
|
+
const dependencies = [];
|
|
1599
|
+
for (const pkgPath of packageJsonPaths) {
|
|
1600
|
+
try {
|
|
1601
|
+
const raw = await readFile6(pkgPath, "utf-8");
|
|
1602
|
+
const pkg = JSON.parse(raw);
|
|
1603
|
+
const sourcePath = relative(projectPath, pkgPath);
|
|
1604
|
+
const depSections = [
|
|
1605
|
+
{ deps: pkg.dependencies, depType: "production", isDev: false },
|
|
1606
|
+
{ deps: pkg.devDependencies, depType: "dev", isDev: true },
|
|
1607
|
+
{ deps: pkg.peerDependencies, depType: "peer", isDev: false },
|
|
1608
|
+
{ deps: pkg.optionalDependencies, depType: "optional", isDev: false }
|
|
1609
|
+
];
|
|
1610
|
+
for (const { deps, depType, isDev } of depSections) {
|
|
1611
|
+
if (!deps) continue;
|
|
1612
|
+
for (const [name, version2] of Object.entries(deps)) {
|
|
1613
|
+
dependencies.push({
|
|
1614
|
+
name,
|
|
1615
|
+
version: version2,
|
|
1616
|
+
category: categorizeDependency(name),
|
|
1617
|
+
source_path: sourcePath,
|
|
1618
|
+
is_dev: isDev,
|
|
1619
|
+
dep_type: depType
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1622
|
+
}
|
|
1623
|
+
} catch {
|
|
1624
|
+
}
|
|
1625
|
+
}
|
|
1626
|
+
return { dependencies };
|
|
1627
|
+
}
|
|
1628
|
+
var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SKIP_DIRS;
|
|
1339
1629
|
var init_tech_detect = __esm({
|
|
1340
1630
|
"src/lib/tech-detect.ts"() {
|
|
1341
1631
|
"use strict";
|
|
@@ -1441,6 +1731,18 @@ var init_tech_detect = __esm({
|
|
|
1441
1731
|
{ file: "nx.json", rule: { name: "Nx", category: "build" } },
|
|
1442
1732
|
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } }
|
|
1443
1733
|
];
|
|
1734
|
+
SKIP_DIRS = /* @__PURE__ */ new Set([
|
|
1735
|
+
"node_modules",
|
|
1736
|
+
".next",
|
|
1737
|
+
"dist",
|
|
1738
|
+
".turbo",
|
|
1739
|
+
".git",
|
|
1740
|
+
"coverage",
|
|
1741
|
+
"build",
|
|
1742
|
+
"out",
|
|
1743
|
+
".vercel",
|
|
1744
|
+
".expo"
|
|
1745
|
+
]);
|
|
1444
1746
|
}
|
|
1445
1747
|
});
|
|
1446
1748
|
|
|
@@ -1449,8 +1751,12 @@ var sync_exports = {};
|
|
|
1449
1751
|
__export(sync_exports, {
|
|
1450
1752
|
runSync: () => runSync
|
|
1451
1753
|
});
|
|
1754
|
+
import { createHash } from "node:crypto";
|
|
1452
1755
|
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
|
|
1453
1756
|
import { join as join7, dirname as dirname2 } from "node:path";
|
|
1757
|
+
function contentHash(content) {
|
|
1758
|
+
return createHash("sha256").update(content).digest("hex");
|
|
1759
|
+
}
|
|
1454
1760
|
async function runSync() {
|
|
1455
1761
|
const flags = parseFlags(3);
|
|
1456
1762
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -1465,6 +1771,49 @@ async function runSync() {
|
|
|
1465
1771
|
if (dryRun) console.log(` Mode: dry-run`);
|
|
1466
1772
|
if (force) console.log(` Mode: force`);
|
|
1467
1773
|
console.log();
|
|
1774
|
+
if (!dryRun) {
|
|
1775
|
+
console.log(" Acquiring sync lock...");
|
|
1776
|
+
try {
|
|
1777
|
+
await apiPost("/sync/lock", {
|
|
1778
|
+
repo_id: repoId,
|
|
1779
|
+
locked_by: `cli-sync`,
|
|
1780
|
+
reason: "Bidirectional sync",
|
|
1781
|
+
ttl_minutes: 10
|
|
1782
|
+
});
|
|
1783
|
+
console.log(" Lock acquired.\n");
|
|
1784
|
+
} catch (lockErr) {
|
|
1785
|
+
const lockStatus = await apiGet("/sync/lock", { repo_id: repoId });
|
|
1786
|
+
if (lockStatus.data.locked && lockStatus.data.lock) {
|
|
1787
|
+
const lock = lockStatus.data.lock;
|
|
1788
|
+
console.log(` Sync locked by ${lock.locked_by} since ${lock.locked_at}.`);
|
|
1789
|
+
console.log(` Expires: ${lock.expires_at}`);
|
|
1790
|
+
console.log(` Use --force to override, or wait for lock to expire.
|
|
1791
|
+
`);
|
|
1792
|
+
if (!force) return;
|
|
1793
|
+
await apiPost("/sync/lock", {
|
|
1794
|
+
repo_id: repoId,
|
|
1795
|
+
locked_by: `cli-sync`,
|
|
1796
|
+
reason: "Bidirectional sync (forced)",
|
|
1797
|
+
ttl_minutes: 10
|
|
1798
|
+
});
|
|
1799
|
+
console.log(" Lock acquired (forced).\n");
|
|
1800
|
+
} else {
|
|
1801
|
+
throw lockErr;
|
|
1802
|
+
}
|
|
1803
|
+
}
|
|
1804
|
+
}
|
|
1805
|
+
try {
|
|
1806
|
+
await runSyncInner(repoId, projectPath, dryRun, force);
|
|
1807
|
+
} finally {
|
|
1808
|
+
if (!dryRun) {
|
|
1809
|
+
try {
|
|
1810
|
+
await apiDelete("/sync/lock", { repo_id: repoId });
|
|
1811
|
+
} catch {
|
|
1812
|
+
}
|
|
1813
|
+
}
|
|
1814
|
+
}
|
|
1815
|
+
}
|
|
1816
|
+
async function runSyncInner(repoId, projectPath, dryRun, force) {
|
|
1468
1817
|
console.log(" Reading local and remote state...");
|
|
1469
1818
|
const claudeDir = join7(projectPath, ".claude");
|
|
1470
1819
|
let localFiles = /* @__PURE__ */ new Map();
|
|
@@ -1472,14 +1821,27 @@ async function runSync() {
|
|
|
1472
1821
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
1473
1822
|
} catch {
|
|
1474
1823
|
}
|
|
1475
|
-
const [defaultsRes, repoSyncRes, repoRes] = await Promise.all([
|
|
1824
|
+
const [defaultsRes, repoSyncRes, repoRes, syncStateRes, fileReposRes] = await Promise.all([
|
|
1476
1825
|
apiGet("/sync/defaults"),
|
|
1477
1826
|
apiGet("/sync/files", { repo_id: repoId }),
|
|
1478
|
-
apiGet(`/repos/${repoId}`)
|
|
1827
|
+
apiGet(`/repos/${repoId}`),
|
|
1828
|
+
apiGet("/sync/state", { repo_id: repoId }),
|
|
1829
|
+
apiGet("/sync/file-repos", { repo_id: repoId })
|
|
1479
1830
|
]);
|
|
1831
|
+
const syncStartTime = Date.now();
|
|
1480
1832
|
const repoData = repoRes.data;
|
|
1481
1833
|
const remoteDefaults = flattenSyncData(defaultsRes.data);
|
|
1482
1834
|
const remoteRepoFiles = flattenSyncData(repoSyncRes.data);
|
|
1835
|
+
const syncState = syncStateRes.data;
|
|
1836
|
+
const fileRepoHashes = /* @__PURE__ */ new Map();
|
|
1837
|
+
const fileRepoByClaudeFileId = /* @__PURE__ */ new Map();
|
|
1838
|
+
for (const entry of fileReposRes.data ?? []) {
|
|
1839
|
+
if (entry.claude_files) {
|
|
1840
|
+
const key = compositeKey(entry.claude_files.type, entry.claude_files.name, entry.claude_files.category);
|
|
1841
|
+
fileRepoHashes.set(key, entry.last_synced_content_hash);
|
|
1842
|
+
}
|
|
1843
|
+
fileRepoByClaudeFileId.set(entry.claude_file_id, entry.last_synced_content_hash);
|
|
1844
|
+
}
|
|
1483
1845
|
const remoteFiles = new Map([...remoteDefaults, ...remoteRepoFiles]);
|
|
1484
1846
|
console.log(` Local: ${localFiles.size} files, Remote: ${remoteFiles.size} files
|
|
1485
1847
|
`);
|
|
@@ -1493,6 +1855,7 @@ async function runSync() {
|
|
|
1493
1855
|
key,
|
|
1494
1856
|
displayPath: `${local.type}/${local.category ? local.category + "/" : ""}${local.name}`,
|
|
1495
1857
|
action: "push",
|
|
1858
|
+
recommended: "push",
|
|
1496
1859
|
localContent: local.content,
|
|
1497
1860
|
remoteContent: null,
|
|
1498
1861
|
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
@@ -1500,14 +1863,18 @@ async function runSync() {
|
|
|
1500
1863
|
type: local.type,
|
|
1501
1864
|
name: local.name,
|
|
1502
1865
|
category: local.category,
|
|
1503
|
-
isHook: local.type === "hook"
|
|
1866
|
+
isHook: local.type === "hook",
|
|
1867
|
+
claudeFileId: null
|
|
1504
1868
|
});
|
|
1505
1869
|
} else if (!local && remote) {
|
|
1506
1870
|
const resolvedContent = substituteVariables(remote.content, repoData);
|
|
1871
|
+
const hadSyncedThisFile = remote.id ? fileRepoByClaudeFileId.has(remote.id) : fileRepoHashes.has(key);
|
|
1872
|
+
const recommended = hadSyncedThisFile ? "delete" : "pull";
|
|
1507
1873
|
plan.push({
|
|
1508
1874
|
key,
|
|
1509
1875
|
displayPath: `${remote.type}/${remote.category ? remote.category + "/" : ""}${remote.name}`,
|
|
1510
|
-
action:
|
|
1876
|
+
action: recommended,
|
|
1877
|
+
recommended,
|
|
1511
1878
|
localContent: null,
|
|
1512
1879
|
remoteContent: resolvedContent,
|
|
1513
1880
|
pushContent: null,
|
|
@@ -1515,28 +1882,38 @@ async function runSync() {
|
|
|
1515
1882
|
type: remote.type,
|
|
1516
1883
|
name: remote.name,
|
|
1517
1884
|
category: remote.category ?? null,
|
|
1518
|
-
isHook: remote.type === "hook"
|
|
1885
|
+
isHook: remote.type === "hook",
|
|
1886
|
+
claudeFileId: remote.id ?? null
|
|
1519
1887
|
});
|
|
1520
1888
|
} else if (local && remote) {
|
|
1521
1889
|
const resolvedRemote = substituteVariables(remote.content, repoData);
|
|
1522
1890
|
if (local.content === resolvedRemote) {
|
|
1523
1891
|
continue;
|
|
1524
1892
|
}
|
|
1525
|
-
const
|
|
1526
|
-
const
|
|
1527
|
-
const
|
|
1893
|
+
const localHash = contentHash(local.content);
|
|
1894
|
+
const lastSyncedHash = fileRepoHashes.get(key) ?? null;
|
|
1895
|
+
const localChanged = lastSyncedHash ? localHash !== lastSyncedHash : true;
|
|
1528
1896
|
let action;
|
|
1529
|
-
if (
|
|
1897
|
+
if (force) {
|
|
1530
1898
|
action = "pull";
|
|
1531
|
-
} else if (!
|
|
1532
|
-
action = "push";
|
|
1533
|
-
} else {
|
|
1899
|
+
} else if (!localChanged) {
|
|
1534
1900
|
action = "pull";
|
|
1901
|
+
} else if (lastSyncedHash === null) {
|
|
1902
|
+
action = "conflict";
|
|
1903
|
+
} else {
|
|
1904
|
+
const remoteDbHash = remote.content_hash ?? null;
|
|
1905
|
+
const remoteChanged = remoteDbHash ? remoteDbHash !== lastSyncedHash : true;
|
|
1906
|
+
if (remoteChanged) {
|
|
1907
|
+
action = "conflict";
|
|
1908
|
+
} else {
|
|
1909
|
+
action = "push";
|
|
1910
|
+
}
|
|
1535
1911
|
}
|
|
1536
1912
|
plan.push({
|
|
1537
1913
|
key,
|
|
1538
1914
|
displayPath: `${local.type}/${local.category ? local.category + "/" : ""}${local.name}`,
|
|
1539
1915
|
action,
|
|
1916
|
+
recommended: action === "conflict" ? "pull" : action,
|
|
1540
1917
|
localContent: local.content,
|
|
1541
1918
|
remoteContent: resolvedRemote,
|
|
1542
1919
|
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
@@ -1544,14 +1921,17 @@ async function runSync() {
|
|
|
1544
1921
|
type: local.type,
|
|
1545
1922
|
name: local.name,
|
|
1546
1923
|
category: local.category,
|
|
1547
|
-
isHook: local.type === "hook"
|
|
1924
|
+
isHook: local.type === "hook",
|
|
1925
|
+
claudeFileId: remote.id ?? null
|
|
1548
1926
|
});
|
|
1549
1927
|
}
|
|
1550
1928
|
}
|
|
1551
1929
|
const pulls = plan.filter((p) => p.action === "pull");
|
|
1552
1930
|
const pushes = plan.filter((p) => p.action === "push");
|
|
1553
|
-
const
|
|
1931
|
+
const conflicts = plan.filter((p) => p.action === "conflict");
|
|
1554
1932
|
const contentPulls = pulls.filter((p) => p.localContent !== null);
|
|
1933
|
+
const dbOnlyPull = plan.filter((p) => p.localContent === null && p.action === "pull");
|
|
1934
|
+
const dbOnlyDelete = plan.filter((p) => p.localContent === null && p.action === "delete");
|
|
1555
1935
|
if (contentPulls.length > 0) {
|
|
1556
1936
|
console.log(` Pull (DB \u2192 local): ${contentPulls.length}`);
|
|
1557
1937
|
for (const p of contentPulls) console.log(` \u2193 ${p.displayPath}`);
|
|
@@ -1560,12 +1940,22 @@ async function runSync() {
|
|
|
1560
1940
|
console.log(` Push (local \u2192 DB): ${pushes.length}`);
|
|
1561
1941
|
for (const p of pushes) console.log(` \u2191 ${p.displayPath}`);
|
|
1562
1942
|
}
|
|
1563
|
-
if (
|
|
1943
|
+
if (dbOnlyPull.length > 0) {
|
|
1944
|
+
console.log(`
|
|
1945
|
+
DB-only (new, will pull): ${dbOnlyPull.length}`);
|
|
1946
|
+
for (const p of dbOnlyPull) console.log(` \u2193 ${p.displayPath}`);
|
|
1947
|
+
}
|
|
1948
|
+
if (dbOnlyDelete.length > 0) {
|
|
1949
|
+
console.log(`
|
|
1950
|
+
DB-only (previously synced, will delete): ${dbOnlyDelete.length}`);
|
|
1951
|
+
for (const p of dbOnlyDelete) console.log(` \u2715 ${p.displayPath}`);
|
|
1952
|
+
}
|
|
1953
|
+
if (conflicts.length > 0) {
|
|
1564
1954
|
console.log(`
|
|
1565
|
-
|
|
1566
|
-
for (const p of
|
|
1955
|
+
Conflicts (both sides changed): ${conflicts.length}`);
|
|
1956
|
+
for (const p of conflicts) console.log(` \u26A0 ${p.displayPath}`);
|
|
1567
1957
|
}
|
|
1568
|
-
if (contentPulls.length === 0 && pushes.length === 0 &&
|
|
1958
|
+
if (contentPulls.length === 0 && pushes.length === 0 && dbOnlyPull.length === 0 && dbOnlyDelete.length === 0 && conflicts.length === 0) {
|
|
1569
1959
|
console.log(" All .claude/ files in sync.");
|
|
1570
1960
|
}
|
|
1571
1961
|
if (plan.length > 0 && !dryRun) {
|
|
@@ -1574,11 +1964,17 @@ async function runSync() {
|
|
|
1574
1964
|
Agree with sync? [Y/n] `);
|
|
1575
1965
|
if (!agreed) {
|
|
1576
1966
|
const mode = await promptReviewMode();
|
|
1967
|
+
const contentProvider = {
|
|
1968
|
+
local: (p) => p.localContent,
|
|
1969
|
+
remote: (p) => p.remoteContent
|
|
1970
|
+
};
|
|
1577
1971
|
if (mode === "file") {
|
|
1578
1972
|
const actions = await reviewFilesOneByOne(
|
|
1579
1973
|
plan,
|
|
1580
1974
|
(p) => p.displayPath,
|
|
1581
|
-
(p) => p.action
|
|
1975
|
+
(p) => p.action,
|
|
1976
|
+
(p) => p.recommended,
|
|
1977
|
+
contentProvider
|
|
1582
1978
|
);
|
|
1583
1979
|
for (let i = 0; i < plan.length; i++) {
|
|
1584
1980
|
plan[i].action = actions[i];
|
|
@@ -1590,7 +1986,9 @@ async function runSync() {
|
|
|
1590
1986
|
typeName,
|
|
1591
1987
|
items,
|
|
1592
1988
|
(p) => p.displayPath,
|
|
1593
|
-
(p) => p.action
|
|
1989
|
+
(p) => p.action,
|
|
1990
|
+
(p) => p.recommended,
|
|
1991
|
+
contentProvider
|
|
1594
1992
|
);
|
|
1595
1993
|
for (let i = 0; i < items.length; i++) {
|
|
1596
1994
|
items[i].action = actions[i];
|
|
@@ -1622,7 +2020,8 @@ async function runSync() {
|
|
|
1622
2020
|
if (toUpsert.length > 0) {
|
|
1623
2021
|
await apiPost("/sync/files", {
|
|
1624
2022
|
repo_id: repoId,
|
|
1625
|
-
files: toUpsert
|
|
2023
|
+
files: toUpsert,
|
|
2024
|
+
changed_by_repo_id: repoId
|
|
1626
2025
|
});
|
|
1627
2026
|
}
|
|
1628
2027
|
if (toDelete.length > 0) {
|
|
@@ -1644,7 +2043,75 @@ async function runSync() {
|
|
|
1644
2043
|
}
|
|
1645
2044
|
}
|
|
1646
2045
|
}
|
|
1647
|
-
|
|
2046
|
+
const unresolvedConflicts = plan.filter(
|
|
2047
|
+
(p) => p.action === "conflict" || p.action === "skip" && p.localContent !== null && p.remoteContent !== null
|
|
2048
|
+
);
|
|
2049
|
+
if (unresolvedConflicts.length > 0) {
|
|
2050
|
+
let stored = 0;
|
|
2051
|
+
for (const p of unresolvedConflicts) {
|
|
2052
|
+
if (p.claudeFileId) {
|
|
2053
|
+
try {
|
|
2054
|
+
await apiPost("/sync/conflicts", {
|
|
2055
|
+
repo_id: repoId,
|
|
2056
|
+
claude_file_id: p.claudeFileId,
|
|
2057
|
+
conflict_type: "both_modified",
|
|
2058
|
+
local_content: p.localContent,
|
|
2059
|
+
remote_content: p.remoteContent
|
|
2060
|
+
});
|
|
2061
|
+
stored++;
|
|
2062
|
+
} catch {
|
|
2063
|
+
}
|
|
2064
|
+
}
|
|
2065
|
+
}
|
|
2066
|
+
if (stored > 0) {
|
|
2067
|
+
console.log(`
|
|
2068
|
+
${stored} conflict(s) stored in DB for later resolution.`);
|
|
2069
|
+
}
|
|
2070
|
+
}
|
|
2071
|
+
const syncDurationMs = Date.now() - syncStartTime;
|
|
2072
|
+
await apiPost("/sync/state", {
|
|
2073
|
+
repo_id: repoId,
|
|
2074
|
+
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2075
|
+
was_skipped: skipped.length > 0,
|
|
2076
|
+
files_synced_count: toPull.length + toPush.length + toDelete.length,
|
|
2077
|
+
files_pushed: toPush.length,
|
|
2078
|
+
files_pulled: toPull.length,
|
|
2079
|
+
files_deleted: toDelete.length,
|
|
2080
|
+
files_skipped: skipped.length,
|
|
2081
|
+
sync_duration_ms: syncDurationMs,
|
|
2082
|
+
sync_version: getSyncVersion()
|
|
2083
|
+
});
|
|
2084
|
+
const syncTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2085
|
+
const fileRepoUpdates = [];
|
|
2086
|
+
for (const p of toPull) {
|
|
2087
|
+
if (p.claudeFileId && p.remoteContent !== null) {
|
|
2088
|
+
fileRepoUpdates.push({
|
|
2089
|
+
claude_file_id: p.claudeFileId,
|
|
2090
|
+
last_synced_at: syncTimestamp,
|
|
2091
|
+
last_synced_content_hash: contentHash(p.remoteContent),
|
|
2092
|
+
sync_status: "synced"
|
|
2093
|
+
});
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
for (const p of toPush) {
|
|
2097
|
+
if (p.claudeFileId && p.localContent !== null) {
|
|
2098
|
+
fileRepoUpdates.push({
|
|
2099
|
+
claude_file_id: p.claudeFileId,
|
|
2100
|
+
last_synced_at: syncTimestamp,
|
|
2101
|
+
last_synced_content_hash: contentHash(p.localContent),
|
|
2102
|
+
sync_status: "synced"
|
|
2103
|
+
});
|
|
2104
|
+
}
|
|
2105
|
+
}
|
|
2106
|
+
if (fileRepoUpdates.length > 0) {
|
|
2107
|
+
try {
|
|
2108
|
+
await apiPost("/sync/file-repos", {
|
|
2109
|
+
repo_id: repoId,
|
|
2110
|
+
file_repos: fileRepoUpdates
|
|
2111
|
+
});
|
|
2112
|
+
} catch {
|
|
2113
|
+
}
|
|
2114
|
+
}
|
|
1648
2115
|
console.log(
|
|
1649
2116
|
`
|
|
1650
2117
|
Applied: ${toPull.length} pulled, ${toPush.length} pushed, ${toDelete.length} deleted` + (skipped.length > 0 ? `, ${skipped.length} skipped` : "")
|
|
@@ -1723,14 +2190,18 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
1723
2190
|
let portAllocations = [];
|
|
1724
2191
|
try {
|
|
1725
2192
|
const portsRes = await apiGet(`/port-allocations`, { repo_id: repoId });
|
|
1726
|
-
|
|
2193
|
+
const allAllocations = portsRes.data ?? [];
|
|
2194
|
+
const worktreeId2 = currentConfig.worktree_id;
|
|
2195
|
+
portAllocations = worktreeId2 ? allAllocations.filter((a) => a.worktree_id === worktreeId2) : allAllocations.filter((a) => !a.worktree_id);
|
|
1727
2196
|
} catch {
|
|
1728
2197
|
}
|
|
2198
|
+
const worktreeId = currentConfig.worktree_id;
|
|
2199
|
+
const matchingAlloc = portAllocations[0];
|
|
1729
2200
|
const newConfig = {
|
|
1730
2201
|
repo_id: repoId,
|
|
1731
|
-
...
|
|
1732
|
-
server_port: repo.server_port,
|
|
1733
|
-
server_type: repo.server_type,
|
|
2202
|
+
...worktreeId ? { worktree_id: worktreeId } : {},
|
|
2203
|
+
server_port: worktreeId && matchingAlloc ? matchingAlloc.port : repo.server_port,
|
|
2204
|
+
server_type: worktreeId && matchingAlloc ? matchingAlloc.server_type : repo.server_type,
|
|
1734
2205
|
git_branch: repo.git_branch ?? "development",
|
|
1735
2206
|
auto_push_enabled: repo.auto_push_enabled,
|
|
1736
2207
|
...portAllocations.length > 0 ? { port_allocations: portAllocations } : {}
|
|
@@ -1750,23 +2221,33 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
1750
2221
|
}
|
|
1751
2222
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
1752
2223
|
try {
|
|
1753
|
-
const
|
|
1754
|
-
if (
|
|
1755
|
-
console.log(" No
|
|
2224
|
+
const { dependencies } = await scanAllDependencies(projectPath);
|
|
2225
|
+
if (dependencies.length === 0) {
|
|
2226
|
+
console.log(" No dependencies found.");
|
|
1756
2227
|
return;
|
|
1757
2228
|
}
|
|
1758
|
-
const
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
2229
|
+
const sourcePaths = new Set(dependencies.map((d) => d.source_path));
|
|
2230
|
+
console.log(` ${dependencies.length} dependencies from ${sourcePaths.size} package.json file${sourcePaths.size !== 1 ? "s" : ""}`);
|
|
2231
|
+
if (!dryRun) {
|
|
2232
|
+
const result = await apiPost(
|
|
2233
|
+
`/repos/${repoId}/tech-stack`,
|
|
2234
|
+
{ dependencies }
|
|
2235
|
+
);
|
|
2236
|
+
if (result.data.stale_removed > 0) {
|
|
2237
|
+
console.log(` ${result.data.stale_removed} stale dependencies removed`);
|
|
2238
|
+
}
|
|
1767
2239
|
}
|
|
1768
|
-
|
|
1769
|
-
|
|
2240
|
+
const detected = await detectTechStack(projectPath);
|
|
2241
|
+
if (detected.flat.length > 0) {
|
|
2242
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
2243
|
+
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
2244
|
+
const { merged, added } = mergeTechStack(remote, detected);
|
|
2245
|
+
if (added.length > 0) {
|
|
2246
|
+
console.log(` ${added.length} new tech entries`);
|
|
2247
|
+
if (!dryRun) {
|
|
2248
|
+
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
2249
|
+
}
|
|
2250
|
+
}
|
|
1770
2251
|
}
|
|
1771
2252
|
} catch {
|
|
1772
2253
|
console.log(" Tech stack detection skipped.");
|
|
@@ -1812,6 +2293,13 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
1812
2293
|
if (remote.type === "template") return join7(typeDir, remote.name);
|
|
1813
2294
|
return join7(typeDir, `${remote.name}${cfg.ext}`);
|
|
1814
2295
|
}
|
|
2296
|
+
function getSyncVersion() {
|
|
2297
|
+
try {
|
|
2298
|
+
return "3.4.0";
|
|
2299
|
+
} catch {
|
|
2300
|
+
return "unknown";
|
|
2301
|
+
}
|
|
2302
|
+
}
|
|
1815
2303
|
function flattenSyncData(data) {
|
|
1816
2304
|
const result = /* @__PURE__ */ new Map();
|
|
1817
2305
|
const typeMap = {
|
|
@@ -1828,11 +2316,13 @@ function flattenSyncData(data) {
|
|
|
1828
2316
|
for (const file of files) {
|
|
1829
2317
|
const key = compositeKey(typeName, file.name, file.category ?? null);
|
|
1830
2318
|
result.set(key, {
|
|
2319
|
+
id: file.id,
|
|
1831
2320
|
type: typeName,
|
|
1832
2321
|
name: file.name,
|
|
1833
2322
|
content: file.content,
|
|
1834
2323
|
category: file.category,
|
|
1835
|
-
updated_at: file.updated_at
|
|
2324
|
+
updated_at: file.updated_at,
|
|
2325
|
+
content_hash: file.content_hash
|
|
1836
2326
|
});
|
|
1837
2327
|
}
|
|
1838
2328
|
}
|
|
@@ -18588,49 +19078,49 @@ var require_fast_uri = __commonJS({
|
|
|
18588
19078
|
schemelessOptions.skipEscape = true;
|
|
18589
19079
|
return serialize(resolved, schemelessOptions);
|
|
18590
19080
|
}
|
|
18591
|
-
function resolveComponent(base,
|
|
19081
|
+
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
18592
19082
|
const target = {};
|
|
18593
19083
|
if (!skipNormalization) {
|
|
18594
19084
|
base = parse3(serialize(base, options), options);
|
|
18595
|
-
|
|
19085
|
+
relative2 = parse3(serialize(relative2, options), options);
|
|
18596
19086
|
}
|
|
18597
19087
|
options = options || {};
|
|
18598
|
-
if (!options.tolerant &&
|
|
18599
|
-
target.scheme =
|
|
18600
|
-
target.userinfo =
|
|
18601
|
-
target.host =
|
|
18602
|
-
target.port =
|
|
18603
|
-
target.path = removeDotSegments(
|
|
18604
|
-
target.query =
|
|
19088
|
+
if (!options.tolerant && relative2.scheme) {
|
|
19089
|
+
target.scheme = relative2.scheme;
|
|
19090
|
+
target.userinfo = relative2.userinfo;
|
|
19091
|
+
target.host = relative2.host;
|
|
19092
|
+
target.port = relative2.port;
|
|
19093
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
19094
|
+
target.query = relative2.query;
|
|
18605
19095
|
} else {
|
|
18606
|
-
if (
|
|
18607
|
-
target.userinfo =
|
|
18608
|
-
target.host =
|
|
18609
|
-
target.port =
|
|
18610
|
-
target.path = removeDotSegments(
|
|
18611
|
-
target.query =
|
|
19096
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
19097
|
+
target.userinfo = relative2.userinfo;
|
|
19098
|
+
target.host = relative2.host;
|
|
19099
|
+
target.port = relative2.port;
|
|
19100
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
19101
|
+
target.query = relative2.query;
|
|
18612
19102
|
} else {
|
|
18613
|
-
if (!
|
|
19103
|
+
if (!relative2.path) {
|
|
18614
19104
|
target.path = base.path;
|
|
18615
|
-
if (
|
|
18616
|
-
target.query =
|
|
19105
|
+
if (relative2.query !== void 0) {
|
|
19106
|
+
target.query = relative2.query;
|
|
18617
19107
|
} else {
|
|
18618
19108
|
target.query = base.query;
|
|
18619
19109
|
}
|
|
18620
19110
|
} else {
|
|
18621
|
-
if (
|
|
18622
|
-
target.path = removeDotSegments(
|
|
19111
|
+
if (relative2.path[0] === "/") {
|
|
19112
|
+
target.path = removeDotSegments(relative2.path);
|
|
18623
19113
|
} else {
|
|
18624
19114
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
18625
|
-
target.path = "/" +
|
|
19115
|
+
target.path = "/" + relative2.path;
|
|
18626
19116
|
} else if (!base.path) {
|
|
18627
|
-
target.path =
|
|
19117
|
+
target.path = relative2.path;
|
|
18628
19118
|
} else {
|
|
18629
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
19119
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
|
|
18630
19120
|
}
|
|
18631
19121
|
target.path = removeDotSegments(target.path);
|
|
18632
19122
|
}
|
|
18633
|
-
target.query =
|
|
19123
|
+
target.query = relative2.query;
|
|
18634
19124
|
}
|
|
18635
19125
|
target.userinfo = base.userinfo;
|
|
18636
19126
|
target.host = base.host;
|
|
@@ -18638,7 +19128,7 @@ var require_fast_uri = __commonJS({
|
|
|
18638
19128
|
}
|
|
18639
19129
|
target.scheme = base.scheme;
|
|
18640
19130
|
}
|
|
18641
|
-
target.fragment =
|
|
19131
|
+
target.fragment = relative2.fragment;
|
|
18642
19132
|
return target;
|
|
18643
19133
|
}
|
|
18644
19134
|
function equal(uriA, uriB, options) {
|
|
@@ -23388,258 +23878,689 @@ var init_stdio2 = __esm({
|
|
|
23388
23878
|
|
|
23389
23879
|
// src/tools/read.ts
|
|
23390
23880
|
function registerReadTools(server) {
|
|
23391
|
-
server.registerTool(
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
|
|
23400
|
-
|
|
23401
|
-
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
|
|
23413
|
-
|
|
23414
|
-
|
|
23415
|
-
});
|
|
23416
|
-
const plan = res.data[0] ?? null;
|
|
23417
|
-
if (!plan) {
|
|
23418
|
-
return { content: [{ type: "text", text: "Error: No work plan found for the specified repo, week, and year" }], isError: true };
|
|
23881
|
+
server.registerTool(
|
|
23882
|
+
"get_repos",
|
|
23883
|
+
{
|
|
23884
|
+
description: "List all repos.",
|
|
23885
|
+
inputSchema: {}
|
|
23886
|
+
},
|
|
23887
|
+
async () => {
|
|
23888
|
+
try {
|
|
23889
|
+
const res = await apiGet("/repos");
|
|
23890
|
+
return {
|
|
23891
|
+
content: [
|
|
23892
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
23893
|
+
]
|
|
23894
|
+
};
|
|
23895
|
+
} catch (err) {
|
|
23896
|
+
return {
|
|
23897
|
+
content: [
|
|
23898
|
+
{
|
|
23899
|
+
type: "text",
|
|
23900
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23901
|
+
}
|
|
23902
|
+
],
|
|
23903
|
+
isError: true
|
|
23904
|
+
};
|
|
23419
23905
|
}
|
|
23420
|
-
return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] };
|
|
23421
|
-
} catch (err) {
|
|
23422
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23423
|
-
}
|
|
23424
|
-
});
|
|
23425
|
-
server.registerTool("get_checkpoints", {
|
|
23426
|
-
description: "List checkpoints for a repo. Optionally filter by status and/or worktree assignment.",
|
|
23427
|
-
inputSchema: {
|
|
23428
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23429
|
-
status: external_exports.string().optional().describe("Filter by status (draft, pending, active, completed)"),
|
|
23430
|
-
worktree_id: external_exports.string().uuid().optional().describe("Filter by worktree UUID assignment")
|
|
23431
|
-
}
|
|
23432
|
-
}, async ({ repo_id, status, worktree_id }) => {
|
|
23433
|
-
try {
|
|
23434
|
-
const res = await apiGet("/checkpoints", { repo_id, status, worktree_id });
|
|
23435
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23436
|
-
} catch (err) {
|
|
23437
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23438
|
-
}
|
|
23439
|
-
});
|
|
23440
|
-
server.registerTool("get_tasks", {
|
|
23441
|
-
description: "List tasks for a checkpoint. Optionally filter by status.",
|
|
23442
|
-
inputSchema: {
|
|
23443
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
23444
|
-
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
23445
23906
|
}
|
|
23446
|
-
|
|
23447
|
-
|
|
23448
|
-
|
|
23449
|
-
|
|
23450
|
-
|
|
23451
|
-
|
|
23452
|
-
|
|
23453
|
-
|
|
23454
|
-
|
|
23455
|
-
description: "List rounds for a task. Optionally filter by status.",
|
|
23456
|
-
inputSchema: {
|
|
23457
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
23458
|
-
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
23459
|
-
}
|
|
23460
|
-
}, async ({ task_id, status }) => {
|
|
23461
|
-
try {
|
|
23462
|
-
const res = await apiGet("/rounds", { task_id, status });
|
|
23463
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23464
|
-
} catch (err) {
|
|
23465
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23466
|
-
}
|
|
23467
|
-
});
|
|
23468
|
-
server.registerTool("get_current_task", {
|
|
23469
|
-
description: "Get the current in-progress task for a repo. Finds the active checkpoint, then the in-progress task within it.",
|
|
23470
|
-
inputSchema: {
|
|
23471
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23472
|
-
worktree_id: external_exports.string().uuid().optional().describe("Optional worktree UUID to filter checkpoints by assignment")
|
|
23473
|
-
}
|
|
23474
|
-
}, async ({ repo_id, worktree_id }) => {
|
|
23475
|
-
try {
|
|
23476
|
-
const checkpointsRes = await apiGet("/checkpoints", {
|
|
23477
|
-
repo_id,
|
|
23478
|
-
status: "active"
|
|
23479
|
-
});
|
|
23480
|
-
let activeCheckpoints = checkpointsRes.data;
|
|
23481
|
-
if (worktree_id) {
|
|
23482
|
-
activeCheckpoints = activeCheckpoints.filter((c) => c.worktree_id === worktree_id);
|
|
23907
|
+
);
|
|
23908
|
+
server.registerTool(
|
|
23909
|
+
"get_work_plan",
|
|
23910
|
+
{
|
|
23911
|
+
description: "Get the work plan for a specific repo, week, and year.",
|
|
23912
|
+
inputSchema: {
|
|
23913
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23914
|
+
week_number: external_exports.number().int().min(1).max(53).describe("ISO week number"),
|
|
23915
|
+
year: external_exports.number().int().describe("Year (e.g. 2026)")
|
|
23483
23916
|
}
|
|
23484
|
-
|
|
23917
|
+
},
|
|
23918
|
+
async ({ repo_id, week_number, year }) => {
|
|
23919
|
+
try {
|
|
23920
|
+
const res = await apiGet("/work-plans", {
|
|
23921
|
+
repo_id,
|
|
23922
|
+
week_number: String(week_number),
|
|
23923
|
+
year: String(year)
|
|
23924
|
+
});
|
|
23925
|
+
const plan = res.data[0] ?? null;
|
|
23926
|
+
if (!plan) {
|
|
23927
|
+
return {
|
|
23928
|
+
content: [
|
|
23929
|
+
{
|
|
23930
|
+
type: "text",
|
|
23931
|
+
text: "Error: No work plan found for the specified repo, week, and year"
|
|
23932
|
+
}
|
|
23933
|
+
],
|
|
23934
|
+
isError: true
|
|
23935
|
+
};
|
|
23936
|
+
}
|
|
23485
23937
|
return {
|
|
23486
|
-
content: [
|
|
23938
|
+
content: [
|
|
23939
|
+
{ type: "text", text: JSON.stringify(plan, null, 2) }
|
|
23940
|
+
]
|
|
23487
23941
|
};
|
|
23488
|
-
}
|
|
23489
|
-
const checkpoint = activeCheckpoints[0];
|
|
23490
|
-
const tasksRes = await apiGet("/tasks", {
|
|
23491
|
-
checkpoint_id: checkpoint.id,
|
|
23492
|
-
status: "in_progress"
|
|
23493
|
-
});
|
|
23494
|
-
if (tasksRes.data.length === 0) {
|
|
23942
|
+
} catch (err) {
|
|
23495
23943
|
return {
|
|
23496
|
-
content: [
|
|
23497
|
-
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
|
|
23501
|
-
|
|
23502
|
-
|
|
23503
|
-
}]
|
|
23944
|
+
content: [
|
|
23945
|
+
{
|
|
23946
|
+
type: "text",
|
|
23947
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23948
|
+
}
|
|
23949
|
+
],
|
|
23950
|
+
isError: true
|
|
23504
23951
|
};
|
|
23505
23952
|
}
|
|
23506
|
-
return {
|
|
23507
|
-
content: [{
|
|
23508
|
-
type: "text",
|
|
23509
|
-
text: JSON.stringify({ checkpoint, task: tasksRes.data[0] }, null, 2)
|
|
23510
|
-
}]
|
|
23511
|
-
};
|
|
23512
|
-
} catch (err) {
|
|
23513
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23514
|
-
}
|
|
23515
|
-
});
|
|
23516
|
-
server.registerTool("get_launches", {
|
|
23517
|
-
description: "List launches for a repo. Optionally filter by status or type.",
|
|
23518
|
-
inputSchema: {
|
|
23519
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23520
|
-
status: external_exports.string().optional().describe("Filter by status"),
|
|
23521
|
-
type: external_exports.string().optional().describe("Filter by type")
|
|
23522
|
-
}
|
|
23523
|
-
}, async ({ repo_id, status, type }) => {
|
|
23524
|
-
try {
|
|
23525
|
-
const res = await apiGet("/launches", { repo_id, status, type });
|
|
23526
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23527
|
-
} catch (err) {
|
|
23528
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23529
23953
|
}
|
|
23530
|
-
|
|
23531
|
-
server.registerTool(
|
|
23532
|
-
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
|
|
23536
|
-
|
|
23537
|
-
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
}
|
|
23541
|
-
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
23553
|
-
|
|
23554
|
-
|
|
23555
|
-
|
|
23954
|
+
);
|
|
23955
|
+
server.registerTool(
|
|
23956
|
+
"get_checkpoints",
|
|
23957
|
+
{
|
|
23958
|
+
description: "List checkpoints for a repo. Optionally filter by status and/or worktree assignment.",
|
|
23959
|
+
inputSchema: {
|
|
23960
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23961
|
+
status: external_exports.string().optional().describe("Filter by status (draft, pending, active, completed)"),
|
|
23962
|
+
worktree_id: external_exports.string().uuid().optional().describe("Filter by worktree UUID assignment")
|
|
23963
|
+
}
|
|
23964
|
+
},
|
|
23965
|
+
async ({ repo_id, status, worktree_id }) => {
|
|
23966
|
+
try {
|
|
23967
|
+
const res = await apiGet("/checkpoints", {
|
|
23968
|
+
repo_id,
|
|
23969
|
+
status,
|
|
23970
|
+
worktree_id
|
|
23971
|
+
});
|
|
23972
|
+
return {
|
|
23973
|
+
content: [
|
|
23974
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
23975
|
+
]
|
|
23976
|
+
};
|
|
23977
|
+
} catch (err) {
|
|
23978
|
+
return {
|
|
23979
|
+
content: [
|
|
23980
|
+
{
|
|
23981
|
+
type: "text",
|
|
23982
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23983
|
+
}
|
|
23984
|
+
],
|
|
23985
|
+
isError: true
|
|
23986
|
+
};
|
|
23987
|
+
}
|
|
23556
23988
|
}
|
|
23557
|
-
|
|
23558
|
-
server.registerTool(
|
|
23559
|
-
|
|
23560
|
-
|
|
23561
|
-
|
|
23989
|
+
);
|
|
23990
|
+
server.registerTool(
|
|
23991
|
+
"get_tasks",
|
|
23992
|
+
{
|
|
23993
|
+
description: "List tasks for a checkpoint or repo. Filter by checkpoint_id for checkpoint tasks, or repo_id with standalone=true for standalone tasks.",
|
|
23994
|
+
inputSchema: {
|
|
23995
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("The checkpoint UUID (for checkpoint-bound tasks)"),
|
|
23996
|
+
repo_id: external_exports.string().uuid().optional().describe("The repo UUID (for standalone tasks)"),
|
|
23997
|
+
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)"),
|
|
23998
|
+
standalone: external_exports.boolean().optional().describe(
|
|
23999
|
+
"If true, only return tasks with no checkpoint (standalone)"
|
|
24000
|
+
)
|
|
24001
|
+
}
|
|
24002
|
+
},
|
|
24003
|
+
async ({ checkpoint_id, repo_id, status, standalone }) => {
|
|
24004
|
+
try {
|
|
24005
|
+
const params = {
|
|
24006
|
+
checkpoint_id,
|
|
24007
|
+
repo_id,
|
|
24008
|
+
status
|
|
24009
|
+
};
|
|
24010
|
+
if (standalone) params.standalone = "true";
|
|
24011
|
+
const res = await apiGet("/tasks", params);
|
|
24012
|
+
return {
|
|
24013
|
+
content: [
|
|
24014
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24015
|
+
]
|
|
24016
|
+
};
|
|
24017
|
+
} catch (err) {
|
|
24018
|
+
return {
|
|
24019
|
+
content: [
|
|
24020
|
+
{
|
|
24021
|
+
type: "text",
|
|
24022
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24023
|
+
}
|
|
24024
|
+
],
|
|
24025
|
+
isError: true
|
|
24026
|
+
};
|
|
24027
|
+
}
|
|
23562
24028
|
}
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
23566
|
-
|
|
23567
|
-
|
|
23568
|
-
|
|
24029
|
+
);
|
|
24030
|
+
server.registerTool(
|
|
24031
|
+
"get_rounds",
|
|
24032
|
+
{
|
|
24033
|
+
description: "List rounds for a task. Optionally filter by status.",
|
|
24034
|
+
inputSchema: {
|
|
24035
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24036
|
+
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
24037
|
+
}
|
|
24038
|
+
},
|
|
24039
|
+
async ({ task_id, status }) => {
|
|
24040
|
+
try {
|
|
24041
|
+
const res = await apiGet("/rounds", {
|
|
24042
|
+
task_id,
|
|
24043
|
+
status
|
|
24044
|
+
});
|
|
24045
|
+
return {
|
|
24046
|
+
content: [
|
|
24047
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24048
|
+
]
|
|
24049
|
+
};
|
|
24050
|
+
} catch (err) {
|
|
24051
|
+
return {
|
|
24052
|
+
content: [
|
|
24053
|
+
{
|
|
24054
|
+
type: "text",
|
|
24055
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24056
|
+
}
|
|
24057
|
+
],
|
|
24058
|
+
isError: true
|
|
24059
|
+
};
|
|
24060
|
+
}
|
|
23569
24061
|
}
|
|
23570
|
-
|
|
23571
|
-
server.registerTool(
|
|
23572
|
-
|
|
23573
|
-
|
|
23574
|
-
|
|
24062
|
+
);
|
|
24063
|
+
server.registerTool(
|
|
24064
|
+
"get_current_task",
|
|
24065
|
+
{
|
|
24066
|
+
description: "Get the current in-progress task for a repo. Finds the active checkpoint, then the in-progress task within it.",
|
|
24067
|
+
inputSchema: {
|
|
24068
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24069
|
+
worktree_id: external_exports.string().uuid().optional().describe(
|
|
24070
|
+
"Optional worktree UUID to filter checkpoints by assignment"
|
|
24071
|
+
)
|
|
24072
|
+
}
|
|
24073
|
+
},
|
|
24074
|
+
async ({ repo_id, worktree_id }) => {
|
|
24075
|
+
try {
|
|
24076
|
+
const checkpointsRes = await apiGet(
|
|
24077
|
+
"/checkpoints",
|
|
24078
|
+
{
|
|
24079
|
+
repo_id,
|
|
24080
|
+
status: "active"
|
|
24081
|
+
}
|
|
24082
|
+
);
|
|
24083
|
+
let activeCheckpoints = checkpointsRes.data;
|
|
24084
|
+
if (worktree_id) {
|
|
24085
|
+
activeCheckpoints = activeCheckpoints.filter(
|
|
24086
|
+
(c) => c.worktree_id === worktree_id
|
|
24087
|
+
);
|
|
24088
|
+
}
|
|
24089
|
+
if (activeCheckpoints.length === 0) {
|
|
24090
|
+
return {
|
|
24091
|
+
content: [
|
|
24092
|
+
{
|
|
24093
|
+
type: "text",
|
|
24094
|
+
text: "No active checkpoint found for this repo."
|
|
24095
|
+
}
|
|
24096
|
+
]
|
|
24097
|
+
};
|
|
24098
|
+
}
|
|
24099
|
+
const checkpoint = activeCheckpoints[0];
|
|
24100
|
+
const tasksRes = await apiGet("/tasks", {
|
|
24101
|
+
checkpoint_id: checkpoint.id,
|
|
24102
|
+
status: "in_progress"
|
|
24103
|
+
});
|
|
24104
|
+
if (tasksRes.data.length === 0) {
|
|
24105
|
+
return {
|
|
24106
|
+
content: [
|
|
24107
|
+
{
|
|
24108
|
+
type: "text",
|
|
24109
|
+
text: JSON.stringify(
|
|
24110
|
+
{
|
|
24111
|
+
checkpoint,
|
|
24112
|
+
task: null,
|
|
24113
|
+
message: "Active checkpoint found but no in-progress task."
|
|
24114
|
+
},
|
|
24115
|
+
null,
|
|
24116
|
+
2
|
|
24117
|
+
)
|
|
24118
|
+
}
|
|
24119
|
+
]
|
|
24120
|
+
};
|
|
24121
|
+
}
|
|
24122
|
+
return {
|
|
24123
|
+
content: [
|
|
24124
|
+
{
|
|
24125
|
+
type: "text",
|
|
24126
|
+
text: JSON.stringify(
|
|
24127
|
+
{ checkpoint, task: tasksRes.data[0] },
|
|
24128
|
+
null,
|
|
24129
|
+
2
|
|
24130
|
+
)
|
|
24131
|
+
}
|
|
24132
|
+
]
|
|
24133
|
+
};
|
|
24134
|
+
} catch (err) {
|
|
24135
|
+
return {
|
|
24136
|
+
content: [
|
|
24137
|
+
{
|
|
24138
|
+
type: "text",
|
|
24139
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24140
|
+
}
|
|
24141
|
+
],
|
|
24142
|
+
isError: true
|
|
24143
|
+
};
|
|
24144
|
+
}
|
|
23575
24145
|
}
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
23581
|
-
|
|
23582
|
-
|
|
23583
|
-
|
|
24146
|
+
);
|
|
24147
|
+
server.registerTool(
|
|
24148
|
+
"get_launches",
|
|
24149
|
+
{
|
|
24150
|
+
description: "List launches for a repo. Optionally filter by status or type.",
|
|
24151
|
+
inputSchema: {
|
|
24152
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24153
|
+
status: external_exports.string().optional().describe("Filter by status"),
|
|
24154
|
+
type: external_exports.string().optional().describe("Filter by type")
|
|
24155
|
+
}
|
|
24156
|
+
},
|
|
24157
|
+
async ({ repo_id, status, type }) => {
|
|
24158
|
+
try {
|
|
24159
|
+
const res = await apiGet("/launches", {
|
|
24160
|
+
repo_id,
|
|
24161
|
+
status,
|
|
24162
|
+
type
|
|
24163
|
+
});
|
|
24164
|
+
return {
|
|
24165
|
+
content: [
|
|
24166
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24167
|
+
]
|
|
24168
|
+
};
|
|
24169
|
+
} catch (err) {
|
|
24170
|
+
return {
|
|
24171
|
+
content: [
|
|
24172
|
+
{
|
|
24173
|
+
type: "text",
|
|
24174
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24175
|
+
}
|
|
24176
|
+
],
|
|
24177
|
+
isError: true
|
|
24178
|
+
};
|
|
24179
|
+
}
|
|
23584
24180
|
}
|
|
23585
|
-
|
|
23586
|
-
server.registerTool(
|
|
23587
|
-
|
|
23588
|
-
|
|
23589
|
-
|
|
23590
|
-
|
|
24181
|
+
);
|
|
24182
|
+
server.registerTool(
|
|
24183
|
+
"get_launch",
|
|
24184
|
+
{
|
|
24185
|
+
description: "Get a single launch by ID.",
|
|
24186
|
+
inputSchema: {
|
|
24187
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID")
|
|
24188
|
+
}
|
|
24189
|
+
},
|
|
24190
|
+
async ({ launch_id }) => {
|
|
24191
|
+
try {
|
|
24192
|
+
const res = await apiGet(
|
|
24193
|
+
`/launches/${launch_id}`
|
|
24194
|
+
);
|
|
24195
|
+
return {
|
|
24196
|
+
content: [
|
|
24197
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24198
|
+
]
|
|
24199
|
+
};
|
|
24200
|
+
} catch (err) {
|
|
24201
|
+
return {
|
|
24202
|
+
content: [
|
|
24203
|
+
{
|
|
24204
|
+
type: "text",
|
|
24205
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24206
|
+
}
|
|
24207
|
+
],
|
|
24208
|
+
isError: true
|
|
24209
|
+
};
|
|
24210
|
+
}
|
|
23591
24211
|
}
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
|
|
23596
|
-
|
|
23597
|
-
|
|
23598
|
-
|
|
23599
|
-
|
|
24212
|
+
);
|
|
24213
|
+
server.registerTool(
|
|
24214
|
+
"get_session_logs",
|
|
24215
|
+
{
|
|
24216
|
+
description: "List session logs for a repo. Optionally filter by date.",
|
|
24217
|
+
inputSchema: {
|
|
24218
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24219
|
+
session_date: external_exports.string().optional().describe("Filter by session date (YYYY-MM-DD)")
|
|
24220
|
+
}
|
|
24221
|
+
},
|
|
24222
|
+
async ({ repo_id, session_date }) => {
|
|
24223
|
+
try {
|
|
24224
|
+
const res = await apiGet("/session-logs", {
|
|
24225
|
+
repo_id,
|
|
24226
|
+
session_date
|
|
24227
|
+
});
|
|
24228
|
+
return {
|
|
24229
|
+
content: [
|
|
24230
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24231
|
+
]
|
|
24232
|
+
};
|
|
24233
|
+
} catch (err) {
|
|
24234
|
+
return {
|
|
24235
|
+
content: [
|
|
24236
|
+
{
|
|
24237
|
+
type: "text",
|
|
24238
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24239
|
+
}
|
|
24240
|
+
],
|
|
24241
|
+
isError: true
|
|
24242
|
+
};
|
|
24243
|
+
}
|
|
23600
24244
|
}
|
|
23601
|
-
|
|
23602
|
-
server.registerTool(
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
23608
|
-
|
|
23609
|
-
}
|
|
23610
|
-
|
|
24245
|
+
);
|
|
24246
|
+
server.registerTool(
|
|
24247
|
+
"get_session_log",
|
|
24248
|
+
{
|
|
24249
|
+
description: "Get a single session log by ID.",
|
|
24250
|
+
inputSchema: {
|
|
24251
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID")
|
|
24252
|
+
}
|
|
24253
|
+
},
|
|
24254
|
+
async ({ session_log_id }) => {
|
|
24255
|
+
try {
|
|
24256
|
+
const res = await apiGet(
|
|
24257
|
+
`/session-logs/${session_log_id}`
|
|
24258
|
+
);
|
|
24259
|
+
return {
|
|
24260
|
+
content: [
|
|
24261
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24262
|
+
]
|
|
24263
|
+
};
|
|
24264
|
+
} catch (err) {
|
|
24265
|
+
return {
|
|
24266
|
+
content: [
|
|
24267
|
+
{
|
|
24268
|
+
type: "text",
|
|
24269
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24270
|
+
}
|
|
24271
|
+
],
|
|
24272
|
+
isError: true
|
|
24273
|
+
};
|
|
24274
|
+
}
|
|
23611
24275
|
}
|
|
23612
|
-
|
|
23613
|
-
server.registerTool(
|
|
23614
|
-
|
|
23615
|
-
|
|
23616
|
-
|
|
23617
|
-
|
|
24276
|
+
);
|
|
24277
|
+
server.registerTool(
|
|
24278
|
+
"get_session_state",
|
|
24279
|
+
{
|
|
24280
|
+
description: "Get session state for a repo (active, paused, inactive, needs_refresh, last_session_at).",
|
|
24281
|
+
inputSchema: {
|
|
24282
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
24283
|
+
}
|
|
24284
|
+
},
|
|
24285
|
+
async ({ repo_id }) => {
|
|
24286
|
+
try {
|
|
24287
|
+
const res = await apiGet(`/repos/${repo_id}`);
|
|
24288
|
+
const {
|
|
24289
|
+
id,
|
|
24290
|
+
name,
|
|
24291
|
+
is_session_active,
|
|
24292
|
+
session_status,
|
|
24293
|
+
needs_refresh,
|
|
24294
|
+
last_session_at
|
|
24295
|
+
} = res.data;
|
|
24296
|
+
const data = {
|
|
24297
|
+
id,
|
|
24298
|
+
name,
|
|
24299
|
+
is_session_active,
|
|
24300
|
+
session_status,
|
|
24301
|
+
needs_refresh,
|
|
24302
|
+
last_session_at
|
|
24303
|
+
};
|
|
24304
|
+
return {
|
|
24305
|
+
content: [
|
|
24306
|
+
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
24307
|
+
]
|
|
24308
|
+
};
|
|
24309
|
+
} catch (err) {
|
|
24310
|
+
return {
|
|
24311
|
+
content: [
|
|
24312
|
+
{
|
|
24313
|
+
type: "text",
|
|
24314
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24315
|
+
}
|
|
24316
|
+
],
|
|
24317
|
+
isError: true
|
|
24318
|
+
};
|
|
24319
|
+
}
|
|
23618
24320
|
}
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
23624
|
-
|
|
23625
|
-
|
|
23626
|
-
|
|
24321
|
+
);
|
|
24322
|
+
server.registerTool(
|
|
24323
|
+
"get_server_config",
|
|
24324
|
+
{
|
|
24325
|
+
description: "Get server configuration for a repo (port, type, active servers).",
|
|
24326
|
+
inputSchema: {
|
|
24327
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24328
|
+
worktree_id: external_exports.string().uuid().optional().describe(
|
|
24329
|
+
"Optional worktree UUID to filter port allocations by worktree"
|
|
24330
|
+
)
|
|
24331
|
+
}
|
|
24332
|
+
},
|
|
24333
|
+
async ({ repo_id, worktree_id }) => {
|
|
24334
|
+
try {
|
|
24335
|
+
const params = {};
|
|
24336
|
+
if (worktree_id) params.worktree_id = worktree_id;
|
|
24337
|
+
const res = await apiGet(
|
|
24338
|
+
`/repos/${repo_id}/server`,
|
|
24339
|
+
params
|
|
24340
|
+
);
|
|
24341
|
+
return {
|
|
24342
|
+
content: [
|
|
24343
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24344
|
+
]
|
|
24345
|
+
};
|
|
24346
|
+
} catch (err) {
|
|
24347
|
+
return {
|
|
24348
|
+
content: [
|
|
24349
|
+
{
|
|
24350
|
+
type: "text",
|
|
24351
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24352
|
+
}
|
|
24353
|
+
],
|
|
24354
|
+
isError: true
|
|
24355
|
+
};
|
|
24356
|
+
}
|
|
23627
24357
|
}
|
|
23628
|
-
|
|
23629
|
-
server.registerTool(
|
|
23630
|
-
|
|
23631
|
-
|
|
23632
|
-
|
|
23633
|
-
|
|
24358
|
+
);
|
|
24359
|
+
server.registerTool(
|
|
24360
|
+
"get_sync_status",
|
|
24361
|
+
{
|
|
24362
|
+
description: "Get cross-repo sync status. Shows which repos need a claude files sync based on latest updates.",
|
|
24363
|
+
inputSchema: {}
|
|
24364
|
+
},
|
|
24365
|
+
async () => {
|
|
24366
|
+
try {
|
|
24367
|
+
const res = await apiGet("/sync/status");
|
|
24368
|
+
return {
|
|
24369
|
+
content: [
|
|
24370
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24371
|
+
]
|
|
24372
|
+
};
|
|
24373
|
+
} catch (err) {
|
|
24374
|
+
return {
|
|
24375
|
+
content: [
|
|
24376
|
+
{
|
|
24377
|
+
type: "text",
|
|
24378
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24379
|
+
}
|
|
24380
|
+
],
|
|
24381
|
+
isError: true
|
|
24382
|
+
};
|
|
24383
|
+
}
|
|
23634
24384
|
}
|
|
23635
|
-
|
|
23636
|
-
|
|
23637
|
-
|
|
23638
|
-
|
|
23639
|
-
|
|
23640
|
-
|
|
24385
|
+
);
|
|
24386
|
+
server.registerTool(
|
|
24387
|
+
"get_next_action",
|
|
24388
|
+
{
|
|
24389
|
+
description: "Compute the next action for a repo based on current workflow state. Returns command, instructions, state, and context.",
|
|
24390
|
+
inputSchema: {
|
|
24391
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24392
|
+
worktree_id: external_exports.string().uuid().optional().describe("Optional worktree UUID to filter by assignment")
|
|
24393
|
+
}
|
|
24394
|
+
},
|
|
24395
|
+
async ({ repo_id, worktree_id }) => {
|
|
24396
|
+
try {
|
|
24397
|
+
const params = {};
|
|
24398
|
+
if (worktree_id) params.worktree_id = worktree_id;
|
|
24399
|
+
const res = await apiGet(
|
|
24400
|
+
`/repos/${repo_id}/next-action`,
|
|
24401
|
+
params
|
|
24402
|
+
);
|
|
24403
|
+
return {
|
|
24404
|
+
content: [
|
|
24405
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24406
|
+
]
|
|
24407
|
+
};
|
|
24408
|
+
} catch (err) {
|
|
24409
|
+
return {
|
|
24410
|
+
content: [
|
|
24411
|
+
{
|
|
24412
|
+
type: "text",
|
|
24413
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24414
|
+
}
|
|
24415
|
+
],
|
|
24416
|
+
isError: true
|
|
24417
|
+
};
|
|
24418
|
+
}
|
|
23641
24419
|
}
|
|
23642
|
-
|
|
24420
|
+
);
|
|
24421
|
+
server.registerTool(
|
|
24422
|
+
"get_worktrees",
|
|
24423
|
+
{
|
|
24424
|
+
description: "List worktrees for a repo. Optionally filter by status.",
|
|
24425
|
+
inputSchema: {
|
|
24426
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24427
|
+
status: external_exports.string().optional().describe("Filter by status (active, inactive, archived)")
|
|
24428
|
+
}
|
|
24429
|
+
},
|
|
24430
|
+
async ({ repo_id, status }) => {
|
|
24431
|
+
try {
|
|
24432
|
+
const res = await apiGet("/worktrees", {
|
|
24433
|
+
repo_id,
|
|
24434
|
+
status
|
|
24435
|
+
});
|
|
24436
|
+
return {
|
|
24437
|
+
content: [
|
|
24438
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24439
|
+
]
|
|
24440
|
+
};
|
|
24441
|
+
} catch (err) {
|
|
24442
|
+
return {
|
|
24443
|
+
content: [
|
|
24444
|
+
{
|
|
24445
|
+
type: "text",
|
|
24446
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24447
|
+
}
|
|
24448
|
+
],
|
|
24449
|
+
isError: true
|
|
24450
|
+
};
|
|
24451
|
+
}
|
|
24452
|
+
}
|
|
24453
|
+
);
|
|
24454
|
+
server.registerTool(
|
|
24455
|
+
"get_sync_lock_status",
|
|
24456
|
+
{
|
|
24457
|
+
description: "Check if a sync lock is currently held for a repo. Cleans up expired locks.",
|
|
24458
|
+
inputSchema: {
|
|
24459
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
24460
|
+
}
|
|
24461
|
+
},
|
|
24462
|
+
async ({ repo_id }) => {
|
|
24463
|
+
try {
|
|
24464
|
+
const res = await apiGet("/sync/lock", { repo_id });
|
|
24465
|
+
return {
|
|
24466
|
+
content: [
|
|
24467
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24468
|
+
]
|
|
24469
|
+
};
|
|
24470
|
+
} catch (err) {
|
|
24471
|
+
return {
|
|
24472
|
+
content: [
|
|
24473
|
+
{
|
|
24474
|
+
type: "text",
|
|
24475
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24476
|
+
}
|
|
24477
|
+
],
|
|
24478
|
+
isError: true
|
|
24479
|
+
};
|
|
24480
|
+
}
|
|
24481
|
+
}
|
|
24482
|
+
);
|
|
24483
|
+
server.registerTool(
|
|
24484
|
+
"get_sync_conflicts",
|
|
24485
|
+
{
|
|
24486
|
+
description: "List sync conflicts for a repo. Optionally filter by status.",
|
|
24487
|
+
inputSchema: {
|
|
24488
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24489
|
+
status: external_exports.string().optional().describe("Filter by status (unresolved, resolved, skipped)")
|
|
24490
|
+
}
|
|
24491
|
+
},
|
|
24492
|
+
async ({ repo_id, status }) => {
|
|
24493
|
+
try {
|
|
24494
|
+
const res = await apiGet("/sync/conflicts", {
|
|
24495
|
+
repo_id,
|
|
24496
|
+
status
|
|
24497
|
+
});
|
|
24498
|
+
return {
|
|
24499
|
+
content: [
|
|
24500
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24501
|
+
]
|
|
24502
|
+
};
|
|
24503
|
+
} catch (err) {
|
|
24504
|
+
return {
|
|
24505
|
+
content: [
|
|
24506
|
+
{
|
|
24507
|
+
type: "text",
|
|
24508
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24509
|
+
}
|
|
24510
|
+
],
|
|
24511
|
+
isError: true
|
|
24512
|
+
};
|
|
24513
|
+
}
|
|
24514
|
+
}
|
|
24515
|
+
);
|
|
24516
|
+
server.registerTool(
|
|
24517
|
+
"get_file_changes",
|
|
24518
|
+
{
|
|
24519
|
+
description: "List file changes for a repo. Filter by checkpoint, task, round, file path, or source. Use aggregate='task' with task_id to get latest entry per file (task-level view).",
|
|
24520
|
+
inputSchema: {
|
|
24521
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24522
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("Filter by checkpoint"),
|
|
24523
|
+
task_id: external_exports.string().uuid().optional().describe("Filter by task"),
|
|
24524
|
+
round_id: external_exports.string().uuid().optional().describe("Filter by round"),
|
|
24525
|
+
file_path: external_exports.string().optional().describe("Filter by exact file path"),
|
|
24526
|
+
source: external_exports.enum(["round", "fix"]).optional().describe("Filter by source (round or fix)"),
|
|
24527
|
+
aggregate: external_exports.enum(["task"]).optional().describe(
|
|
24528
|
+
"Aggregation mode. 'task' returns latest entry per file_path for a task (requires task_id)"
|
|
24529
|
+
)
|
|
24530
|
+
}
|
|
24531
|
+
},
|
|
24532
|
+
async ({ repo_id, checkpoint_id, task_id, round_id, file_path, source, aggregate }) => {
|
|
24533
|
+
try {
|
|
24534
|
+
const res = await apiGet(
|
|
24535
|
+
"/file-changes",
|
|
24536
|
+
{
|
|
24537
|
+
repo_id,
|
|
24538
|
+
checkpoint_id,
|
|
24539
|
+
task_id,
|
|
24540
|
+
round_id,
|
|
24541
|
+
file_path,
|
|
24542
|
+
source,
|
|
24543
|
+
aggregate
|
|
24544
|
+
}
|
|
24545
|
+
);
|
|
24546
|
+
return {
|
|
24547
|
+
content: [
|
|
24548
|
+
{ type: "text", text: JSON.stringify(res, null, 2) }
|
|
24549
|
+
]
|
|
24550
|
+
};
|
|
24551
|
+
} catch (err) {
|
|
24552
|
+
return {
|
|
24553
|
+
content: [
|
|
24554
|
+
{
|
|
24555
|
+
type: "text",
|
|
24556
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24557
|
+
}
|
|
24558
|
+
],
|
|
24559
|
+
isError: true
|
|
24560
|
+
};
|
|
24561
|
+
}
|
|
24562
|
+
}
|
|
24563
|
+
);
|
|
23643
24564
|
}
|
|
23644
24565
|
var init_read = __esm({
|
|
23645
24566
|
"src/tools/read.ts"() {
|
|
@@ -23917,621 +24838,1591 @@ var init_promotion = __esm({
|
|
|
23917
24838
|
|
|
23918
24839
|
// src/tools/write.ts
|
|
23919
24840
|
function registerWriteTools(server) {
|
|
23920
|
-
server.registerTool(
|
|
23921
|
-
|
|
23922
|
-
|
|
23923
|
-
|
|
23924
|
-
|
|
23925
|
-
|
|
23926
|
-
|
|
23927
|
-
|
|
23928
|
-
|
|
23929
|
-
|
|
23930
|
-
|
|
23931
|
-
|
|
23932
|
-
|
|
23933
|
-
|
|
23934
|
-
|
|
23935
|
-
|
|
23936
|
-
|
|
23937
|
-
|
|
23938
|
-
|
|
23939
|
-
|
|
23940
|
-
|
|
23941
|
-
|
|
23942
|
-
|
|
23943
|
-
|
|
23944
|
-
|
|
23945
|
-
|
|
23946
|
-
|
|
23947
|
-
|
|
23948
|
-
|
|
23949
|
-
|
|
23950
|
-
|
|
23951
|
-
|
|
23952
|
-
|
|
23953
|
-
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23954
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23955
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23956
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
23957
|
-
}
|
|
23958
|
-
}, async ({ repo_id, title, number: number3, goal, deadline, status, launch_id, ideas, context, research, qa }) => {
|
|
23959
|
-
try {
|
|
23960
|
-
const body = {
|
|
23961
|
-
repo_id,
|
|
23962
|
-
title: title ?? null,
|
|
23963
|
-
number: number3,
|
|
23964
|
-
goal: goal ?? null,
|
|
23965
|
-
deadline: deadline ?? null,
|
|
23966
|
-
status: status ?? "pending",
|
|
23967
|
-
launch_id: launch_id ?? null
|
|
23968
|
-
};
|
|
23969
|
-
if (ideas !== void 0) body.ideas = ideas;
|
|
23970
|
-
if (context !== void 0) body.context = context;
|
|
23971
|
-
if (research !== void 0) body.research = research;
|
|
23972
|
-
if (qa !== void 0) body.qa = qa;
|
|
23973
|
-
const res = await apiPost("/checkpoints", body);
|
|
23974
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23975
|
-
} catch (err) {
|
|
23976
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23977
|
-
}
|
|
23978
|
-
});
|
|
23979
|
-
server.registerTool("update_checkpoint", {
|
|
23980
|
-
description: "Update an existing checkpoint. Can connect or disconnect a launch via launch_id.",
|
|
23981
|
-
inputSchema: {
|
|
23982
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
23983
|
-
title: external_exports.string().nullable().optional().describe("New title (or null to clear)"),
|
|
23984
|
-
goal: external_exports.string().optional().describe("New goal (max 300 chars, brief overview)"),
|
|
23985
|
-
status: external_exports.string().optional().describe("New status (draft, pending, active, completed)"),
|
|
23986
|
-
deadline: external_exports.string().optional().describe("New deadline (ISO format)"),
|
|
23987
|
-
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
23988
|
-
launch_id: external_exports.string().uuid().nullable().optional().describe("Launch UUID to connect (or null to disconnect)"),
|
|
23989
|
-
worktree_id: external_exports.string().uuid().nullable().optional().describe("Worktree UUID to assign (or null to unassign)"),
|
|
23990
|
-
assigned_to: external_exports.string().nullable().optional().describe("Who/what claimed this checkpoint"),
|
|
23991
|
-
branch_name: external_exports.string().nullable().optional().describe("Git branch name for this checkpoint (e.g. feat/CHK-061-git-overhaul)"),
|
|
23992
|
-
ideas: external_exports.array(external_exports.object({
|
|
23993
|
-
description: external_exports.string().describe("Idea description"),
|
|
23994
|
-
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
23995
|
-
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
23996
|
-
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23997
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23998
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23999
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
24000
|
-
}
|
|
24001
|
-
}, async ({ checkpoint_id, title, goal, status, deadline, completed_at, launch_id, worktree_id, assigned_to, branch_name, ideas, context, research, qa }) => {
|
|
24002
|
-
const update = {};
|
|
24003
|
-
if (title !== void 0) update.title = title;
|
|
24004
|
-
if (goal !== void 0) update.goal = goal;
|
|
24005
|
-
if (status !== void 0) update.status = status;
|
|
24006
|
-
if (deadline !== void 0) update.deadline = deadline;
|
|
24007
|
-
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
24008
|
-
if (launch_id !== void 0) update.launch_id = launch_id;
|
|
24009
|
-
if (worktree_id !== void 0) update.worktree_id = worktree_id;
|
|
24010
|
-
if (assigned_to !== void 0) update.assigned_to = assigned_to;
|
|
24011
|
-
if (branch_name !== void 0) update.branch_name = branch_name;
|
|
24012
|
-
if (ideas !== void 0) update.ideas = ideas;
|
|
24013
|
-
if (context !== void 0) update.context = context;
|
|
24014
|
-
if (research !== void 0) update.research = research;
|
|
24015
|
-
if (qa !== void 0) update.qa = qa;
|
|
24016
|
-
if (Object.keys(update).length === 0) {
|
|
24017
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24018
|
-
}
|
|
24019
|
-
try {
|
|
24020
|
-
const res = await apiPut(`/checkpoints/${checkpoint_id}`, update);
|
|
24021
|
-
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
|
|
24022
|
-
} catch (err) {
|
|
24023
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24024
|
-
}
|
|
24025
|
-
});
|
|
24026
|
-
server.registerTool("complete_checkpoint", {
|
|
24027
|
-
description: "Mark a checkpoint as completed. Sets status to 'completed', completed_at to now, and triggers promotion (creates PR from feat branch to development).",
|
|
24028
|
-
inputSchema: {
|
|
24029
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
24030
|
-
}
|
|
24031
|
-
}, async ({ checkpoint_id }) => {
|
|
24032
|
-
try {
|
|
24033
|
-
const res = await apiPut(`/checkpoints/${checkpoint_id}`, {
|
|
24034
|
-
status: "completed",
|
|
24035
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
24036
|
-
});
|
|
24037
|
-
const checkpoint = res.data;
|
|
24038
|
-
const featToDevResult = await promoteCheckpoint(checkpoint_id);
|
|
24039
|
-
let devToMainResult = null;
|
|
24040
|
-
const repoRes = await apiGet(`/repos/${checkpoint.repo_id}`);
|
|
24041
|
-
if (repoRes.data.auto_push_enabled) {
|
|
24042
|
-
devToMainResult = await promoteToMain(checkpoint.repo_id);
|
|
24043
|
-
}
|
|
24044
|
-
return { content: [{ type: "text", text: JSON.stringify({ checkpoint, promotion: { feat_to_development: featToDevResult, development_to_main: devToMainResult } }, null, 2) }] };
|
|
24045
|
-
} catch (err) {
|
|
24046
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24047
|
-
}
|
|
24048
|
-
});
|
|
24049
|
-
server.registerTool("create_task", {
|
|
24050
|
-
description: "Create a new task within a checkpoint.",
|
|
24051
|
-
inputSchema: {
|
|
24052
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
24053
|
-
title: external_exports.string().describe("Task title"),
|
|
24054
|
-
number: external_exports.number().int().describe("Task number (e.g. 1 for TASK-1)"),
|
|
24055
|
-
requirements: external_exports.string().optional().describe("Task requirements text"),
|
|
24056
|
-
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
24057
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints)"),
|
|
24058
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24059
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)")
|
|
24060
|
-
}
|
|
24061
|
-
}, async ({ checkpoint_id, title, number: number3, requirements, status, context, qa, research }) => {
|
|
24062
|
-
try {
|
|
24063
|
-
const body = {
|
|
24064
|
-
checkpoint_id,
|
|
24065
|
-
title,
|
|
24066
|
-
number: number3,
|
|
24067
|
-
requirements: requirements ?? null,
|
|
24068
|
-
status: status ?? "pending"
|
|
24069
|
-
};
|
|
24070
|
-
if (context !== void 0) body.context = context;
|
|
24071
|
-
if (qa !== void 0) body.qa = qa;
|
|
24072
|
-
if (research !== void 0) body.research = research;
|
|
24073
|
-
const res = await apiPost("/tasks", body);
|
|
24074
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24075
|
-
} catch (err) {
|
|
24076
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24077
|
-
}
|
|
24078
|
-
});
|
|
24079
|
-
server.registerTool("update_task", {
|
|
24080
|
-
description: "Update an existing task. Use to update status, requirements, or file tracking. Pass claim_worktree_id when setting status to in_progress to auto-claim the parent checkpoint for the worktree.",
|
|
24081
|
-
inputSchema: {
|
|
24082
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24083
|
-
title: external_exports.string().optional().describe("New title"),
|
|
24084
|
-
requirements: external_exports.string().optional().describe("New requirements text"),
|
|
24085
|
-
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
24086
|
-
files_changed: external_exports.array(external_exports.object({
|
|
24087
|
-
path: external_exports.string().describe("File path relative to repo root"),
|
|
24088
|
-
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
24089
|
-
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
24090
|
-
claude_approved: external_exports.boolean().optional().describe("Whether Claude's automated checks passed for this file"),
|
|
24091
|
-
user_approved: external_exports.boolean().optional().describe("Whether the user has approved this file (via git add or web UI)")
|
|
24092
|
-
})).optional().describe("Files changed across all rounds"),
|
|
24093
|
-
claim_worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID to auto-claim the parent checkpoint when setting status to in_progress"),
|
|
24094
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints)"),
|
|
24095
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24096
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)")
|
|
24097
|
-
}
|
|
24098
|
-
}, async ({ task_id, title, requirements, status, files_changed, claim_worktree_id, context, qa, research }) => {
|
|
24099
|
-
const update = {};
|
|
24100
|
-
if (title !== void 0) update.title = title;
|
|
24101
|
-
if (requirements !== void 0) update.requirements = requirements;
|
|
24102
|
-
if (status !== void 0) update.status = status;
|
|
24103
|
-
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
24104
|
-
if (claim_worktree_id !== void 0) update.claim_worktree_id = claim_worktree_id;
|
|
24105
|
-
if (context !== void 0) update.context = context;
|
|
24106
|
-
if (qa !== void 0) update.qa = qa;
|
|
24107
|
-
if (research !== void 0) update.research = research;
|
|
24108
|
-
if (Object.keys(update).length === 0) {
|
|
24109
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24110
|
-
}
|
|
24111
|
-
try {
|
|
24112
|
-
const res = await apiPut(`/tasks/${task_id}`, update);
|
|
24113
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24114
|
-
} catch (err) {
|
|
24115
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24116
|
-
}
|
|
24117
|
-
});
|
|
24118
|
-
server.registerTool("complete_task", {
|
|
24119
|
-
description: "Mark a task as completed. Sets status to 'completed' and completed_at to now.",
|
|
24120
|
-
inputSchema: {
|
|
24121
|
-
task_id: external_exports.string().uuid().describe("The task UUID")
|
|
24122
|
-
}
|
|
24123
|
-
}, async ({ task_id }) => {
|
|
24124
|
-
try {
|
|
24125
|
-
const res = await apiPut(`/tasks/${task_id}`, {
|
|
24126
|
-
status: "completed",
|
|
24127
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
24128
|
-
});
|
|
24129
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24130
|
-
} catch (err) {
|
|
24131
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24132
|
-
}
|
|
24133
|
-
});
|
|
24134
|
-
server.registerTool("add_round", {
|
|
24135
|
-
description: "Add a round to a task.",
|
|
24136
|
-
inputSchema: {
|
|
24137
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24138
|
-
number: external_exports.number().int().describe("Round number"),
|
|
24139
|
-
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
24140
|
-
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
24141
|
-
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
24142
|
-
context: external_exports.any().optional().describe("Context JSONB"),
|
|
24143
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
24144
|
-
}
|
|
24145
|
-
}, async ({ task_id, number: number3, requirements, status, started_at, context, qa }) => {
|
|
24146
|
-
try {
|
|
24147
|
-
const body = {
|
|
24148
|
-
task_id,
|
|
24149
|
-
number: number3,
|
|
24150
|
-
requirements: requirements ?? null,
|
|
24151
|
-
status: status ?? "pending",
|
|
24152
|
-
started_at: started_at ?? null
|
|
24153
|
-
};
|
|
24154
|
-
if (context !== void 0) body.context = context;
|
|
24155
|
-
if (qa !== void 0) body.qa = qa;
|
|
24156
|
-
const res = await apiPost("/rounds", body);
|
|
24157
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24158
|
-
} catch (err) {
|
|
24159
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24160
|
-
}
|
|
24161
|
-
});
|
|
24162
|
-
server.registerTool("update_round", {
|
|
24163
|
-
description: "Update an existing round.",
|
|
24164
|
-
inputSchema: {
|
|
24165
|
-
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
24166
|
-
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
24167
|
-
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
24168
|
-
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
24169
|
-
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
24170
|
-
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes"),
|
|
24171
|
-
files_changed: external_exports.array(external_exports.object({
|
|
24172
|
-
path: external_exports.string().describe("File path relative to repo root"),
|
|
24173
|
-
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
24174
|
-
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
24175
|
-
claude_approved: external_exports.boolean().optional().describe("Whether Claude's automated checks passed for this file"),
|
|
24176
|
-
user_approved: external_exports.boolean().optional().describe("Whether the user has approved this file (via git add or web UI)")
|
|
24177
|
-
})).optional().describe("Files changed in this round with approval status"),
|
|
24178
|
-
context: external_exports.any().optional().describe("Context JSONB"),
|
|
24179
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
24180
|
-
}
|
|
24181
|
-
}, async ({ round_id, requirements, status, started_at, completed_at, duration_minutes, files_changed, context, qa }) => {
|
|
24182
|
-
const update = {};
|
|
24183
|
-
if (requirements !== void 0) update.requirements = requirements;
|
|
24184
|
-
if (status !== void 0) update.status = status;
|
|
24185
|
-
if (started_at !== void 0) update.started_at = started_at;
|
|
24186
|
-
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
24187
|
-
if (duration_minutes !== void 0) update.duration_minutes = duration_minutes;
|
|
24188
|
-
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
24189
|
-
if (context !== void 0) update.context = context;
|
|
24190
|
-
if (qa !== void 0) update.qa = qa;
|
|
24191
|
-
if (Object.keys(update).length === 0) {
|
|
24192
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24193
|
-
}
|
|
24194
|
-
try {
|
|
24195
|
-
const res = await apiPut(`/rounds/${round_id}`, update);
|
|
24196
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24197
|
-
} catch (err) {
|
|
24198
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24841
|
+
server.registerTool(
|
|
24842
|
+
"create_repo",
|
|
24843
|
+
{
|
|
24844
|
+
description: "Create a new repo entry.",
|
|
24845
|
+
inputSchema: {
|
|
24846
|
+
name: external_exports.string().describe("Repo name (must be unique)"),
|
|
24847
|
+
path: external_exports.string().optional().describe("Local filesystem path to the repo"),
|
|
24848
|
+
git_branch: external_exports.string().optional().describe("Default git branch (default: development)")
|
|
24849
|
+
}
|
|
24850
|
+
},
|
|
24851
|
+
async ({ name, path, git_branch }) => {
|
|
24852
|
+
try {
|
|
24853
|
+
const res = await apiPost("/repos", {
|
|
24854
|
+
name,
|
|
24855
|
+
path: path ?? null,
|
|
24856
|
+
git_branch: git_branch ?? "development"
|
|
24857
|
+
});
|
|
24858
|
+
return {
|
|
24859
|
+
content: [
|
|
24860
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24861
|
+
]
|
|
24862
|
+
};
|
|
24863
|
+
} catch (err) {
|
|
24864
|
+
return {
|
|
24865
|
+
content: [
|
|
24866
|
+
{
|
|
24867
|
+
type: "text",
|
|
24868
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24869
|
+
}
|
|
24870
|
+
],
|
|
24871
|
+
isError: true
|
|
24872
|
+
};
|
|
24873
|
+
}
|
|
24199
24874
|
}
|
|
24200
|
-
|
|
24201
|
-
server.registerTool(
|
|
24202
|
-
|
|
24203
|
-
|
|
24204
|
-
|
|
24205
|
-
|
|
24875
|
+
);
|
|
24876
|
+
server.registerTool(
|
|
24877
|
+
"create_checkpoint",
|
|
24878
|
+
{
|
|
24879
|
+
description: "Create a new checkpoint for a repo. Optionally connect it to a launch via launch_id.",
|
|
24880
|
+
inputSchema: {
|
|
24881
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24882
|
+
title: external_exports.string().optional().describe(
|
|
24883
|
+
"Checkpoint title (optional \u2014 Claude can generate if missing)"
|
|
24884
|
+
),
|
|
24885
|
+
number: external_exports.number().int().describe("Checkpoint number (e.g. 1 for CHK-001)"),
|
|
24886
|
+
goal: external_exports.string().optional().describe(
|
|
24887
|
+
"Checkpoint goal description (max 300 chars, brief overview)"
|
|
24888
|
+
),
|
|
24889
|
+
deadline: external_exports.string().optional().describe("Deadline date (ISO format)"),
|
|
24890
|
+
status: external_exports.string().optional().describe(
|
|
24891
|
+
"Initial status (default: pending). Use 'draft' for checkpoints not ready for development."
|
|
24892
|
+
),
|
|
24893
|
+
launch_id: external_exports.string().uuid().optional().describe("Optional launch UUID to connect this checkpoint to"),
|
|
24894
|
+
ideas: external_exports.array(
|
|
24895
|
+
external_exports.object({
|
|
24896
|
+
description: external_exports.string().describe("Idea description"),
|
|
24897
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
24898
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
24899
|
+
})
|
|
24900
|
+
).optional().describe(
|
|
24901
|
+
"Ideas array \u2014 each idea has description, requirements[], images[]"
|
|
24902
|
+
),
|
|
24903
|
+
context: external_exports.any().optional().describe(
|
|
24904
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"
|
|
24905
|
+
),
|
|
24906
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
24907
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24908
|
+
plan: external_exports.any().optional().describe("Plan JSONB (steps with title, description, scope)"),
|
|
24909
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
24910
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
24911
|
+
context_development: external_exports.any().optional().describe(
|
|
24912
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
24913
|
+
),
|
|
24914
|
+
resources: external_exports.any().optional().describe(
|
|
24915
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
24916
|
+
)
|
|
24917
|
+
}
|
|
24918
|
+
},
|
|
24919
|
+
async ({
|
|
24920
|
+
repo_id,
|
|
24921
|
+
title,
|
|
24922
|
+
number: number3,
|
|
24923
|
+
goal,
|
|
24924
|
+
deadline,
|
|
24925
|
+
status,
|
|
24926
|
+
launch_id,
|
|
24927
|
+
ideas,
|
|
24928
|
+
context,
|
|
24929
|
+
research,
|
|
24930
|
+
qa,
|
|
24931
|
+
plan,
|
|
24932
|
+
user_context,
|
|
24933
|
+
is_claude_written,
|
|
24934
|
+
context_development,
|
|
24935
|
+
resources
|
|
24936
|
+
}) => {
|
|
24937
|
+
try {
|
|
24938
|
+
const body = {
|
|
24939
|
+
repo_id,
|
|
24940
|
+
title: title ?? null,
|
|
24941
|
+
number: number3,
|
|
24942
|
+
goal: goal ?? null,
|
|
24943
|
+
deadline: deadline ?? null,
|
|
24944
|
+
status: status ?? "pending",
|
|
24945
|
+
launch_id: launch_id ?? null
|
|
24946
|
+
};
|
|
24947
|
+
if (ideas !== void 0) body.ideas = ideas;
|
|
24948
|
+
if (context !== void 0) body.context = context;
|
|
24949
|
+
if (research !== void 0) body.research = research;
|
|
24950
|
+
if (qa !== void 0) body.qa = qa;
|
|
24951
|
+
if (plan !== void 0) body.plan = plan;
|
|
24952
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
24953
|
+
if (is_claude_written !== void 0)
|
|
24954
|
+
body.is_claude_written = is_claude_written;
|
|
24955
|
+
if (context_development !== void 0)
|
|
24956
|
+
body.context_development = context_development;
|
|
24957
|
+
if (resources !== void 0) body.resources = resources;
|
|
24958
|
+
const res = await apiPost(
|
|
24959
|
+
"/checkpoints",
|
|
24960
|
+
body
|
|
24961
|
+
);
|
|
24962
|
+
return {
|
|
24963
|
+
content: [
|
|
24964
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24965
|
+
]
|
|
24966
|
+
};
|
|
24967
|
+
} catch (err) {
|
|
24968
|
+
return {
|
|
24969
|
+
content: [
|
|
24970
|
+
{
|
|
24971
|
+
type: "text",
|
|
24972
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24973
|
+
}
|
|
24974
|
+
],
|
|
24975
|
+
isError: true
|
|
24976
|
+
};
|
|
24977
|
+
}
|
|
24206
24978
|
}
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
24214
|
-
|
|
24215
|
-
|
|
24216
|
-
|
|
24217
|
-
|
|
24979
|
+
);
|
|
24980
|
+
server.registerTool(
|
|
24981
|
+
"update_checkpoint",
|
|
24982
|
+
{
|
|
24983
|
+
description: "Update an existing checkpoint. Can connect or disconnect a launch via launch_id.",
|
|
24984
|
+
inputSchema: {
|
|
24985
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
24986
|
+
title: external_exports.string().nullable().optional().describe("New title (or null to clear)"),
|
|
24987
|
+
goal: external_exports.string().optional().describe("New goal (max 300 chars, brief overview)"),
|
|
24988
|
+
status: external_exports.string().optional().describe("New status (draft, pending, active, completed)"),
|
|
24989
|
+
deadline: external_exports.string().optional().describe("New deadline (ISO format)"),
|
|
24990
|
+
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
24991
|
+
launch_id: external_exports.string().uuid().nullable().optional().describe("Launch UUID to connect (or null to disconnect)"),
|
|
24992
|
+
worktree_id: external_exports.string().uuid().nullable().optional().describe("Worktree UUID to assign (or null to unassign)"),
|
|
24993
|
+
assigned_to: external_exports.string().nullable().optional().describe("Who/what claimed this checkpoint"),
|
|
24994
|
+
branch_name: external_exports.string().nullable().optional().describe(
|
|
24995
|
+
"Git branch name for this checkpoint (e.g. feat/CHK-061-git-overhaul)"
|
|
24996
|
+
),
|
|
24997
|
+
ideas: external_exports.array(
|
|
24998
|
+
external_exports.object({
|
|
24999
|
+
description: external_exports.string().describe("Idea description"),
|
|
25000
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
25001
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
25002
|
+
})
|
|
25003
|
+
).optional().describe(
|
|
25004
|
+
"Ideas array \u2014 each idea has description, requirements[], images[]"
|
|
25005
|
+
),
|
|
25006
|
+
context: external_exports.any().optional().describe(
|
|
25007
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"
|
|
25008
|
+
),
|
|
25009
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
25010
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25011
|
+
plan: external_exports.any().optional().describe("Plan JSONB (steps with title, description, scope)"),
|
|
25012
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25013
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25014
|
+
context_development: external_exports.any().optional().describe(
|
|
25015
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25016
|
+
),
|
|
25017
|
+
resources: external_exports.any().optional().describe(
|
|
25018
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25019
|
+
)
|
|
25020
|
+
}
|
|
25021
|
+
},
|
|
25022
|
+
async ({
|
|
25023
|
+
checkpoint_id,
|
|
25024
|
+
title,
|
|
25025
|
+
goal,
|
|
25026
|
+
status,
|
|
25027
|
+
deadline,
|
|
25028
|
+
completed_at,
|
|
25029
|
+
launch_id,
|
|
25030
|
+
worktree_id,
|
|
25031
|
+
assigned_to,
|
|
25032
|
+
branch_name,
|
|
25033
|
+
ideas,
|
|
25034
|
+
context,
|
|
25035
|
+
research,
|
|
25036
|
+
qa,
|
|
25037
|
+
plan,
|
|
25038
|
+
user_context,
|
|
25039
|
+
is_claude_written,
|
|
25040
|
+
context_development,
|
|
25041
|
+
resources
|
|
25042
|
+
}) => {
|
|
25043
|
+
const update = {};
|
|
25044
|
+
if (title !== void 0) update.title = title;
|
|
25045
|
+
if (goal !== void 0) update.goal = goal;
|
|
25046
|
+
if (status !== void 0) update.status = status;
|
|
25047
|
+
if (deadline !== void 0) update.deadline = deadline;
|
|
25048
|
+
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
25049
|
+
if (launch_id !== void 0) update.launch_id = launch_id;
|
|
25050
|
+
if (worktree_id !== void 0) update.worktree_id = worktree_id;
|
|
25051
|
+
if (assigned_to !== void 0) update.assigned_to = assigned_to;
|
|
25052
|
+
if (branch_name !== void 0) update.branch_name = branch_name;
|
|
25053
|
+
if (ideas !== void 0) update.ideas = ideas;
|
|
25054
|
+
if (context !== void 0) update.context = context;
|
|
25055
|
+
if (research !== void 0) update.research = research;
|
|
25056
|
+
if (qa !== void 0) update.qa = qa;
|
|
25057
|
+
if (plan !== void 0) update.plan = plan;
|
|
25058
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25059
|
+
if (is_claude_written !== void 0)
|
|
25060
|
+
update.is_claude_written = is_claude_written;
|
|
25061
|
+
if (context_development !== void 0)
|
|
25062
|
+
update.context_development = context_development;
|
|
25063
|
+
if (resources !== void 0) update.resources = resources;
|
|
25064
|
+
if (Object.keys(update).length === 0) {
|
|
25065
|
+
return {
|
|
25066
|
+
content: [
|
|
25067
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25068
|
+
],
|
|
25069
|
+
isError: true
|
|
25070
|
+
};
|
|
25071
|
+
}
|
|
25072
|
+
try {
|
|
25073
|
+
const res = await apiPut(`/checkpoints/${checkpoint_id}`, update);
|
|
25074
|
+
return {
|
|
25075
|
+
content: [
|
|
25076
|
+
{ type: "text", text: JSON.stringify(res, null, 2) }
|
|
25077
|
+
]
|
|
25078
|
+
};
|
|
25079
|
+
} catch (err) {
|
|
25080
|
+
return {
|
|
25081
|
+
content: [
|
|
25082
|
+
{
|
|
25083
|
+
type: "text",
|
|
25084
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25085
|
+
}
|
|
25086
|
+
],
|
|
25087
|
+
isError: true
|
|
25088
|
+
};
|
|
25089
|
+
}
|
|
24218
25090
|
}
|
|
24219
|
-
|
|
24220
|
-
server.registerTool(
|
|
24221
|
-
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
|
|
24226
|
-
|
|
24227
|
-
|
|
24228
|
-
|
|
25091
|
+
);
|
|
25092
|
+
server.registerTool(
|
|
25093
|
+
"complete_checkpoint",
|
|
25094
|
+
{
|
|
25095
|
+
description: "Mark a checkpoint as completed. Sets status to 'completed', completed_at to now, and triggers promotion (creates PR from feat branch to development).",
|
|
25096
|
+
inputSchema: {
|
|
25097
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
25098
|
+
}
|
|
25099
|
+
},
|
|
25100
|
+
async ({ checkpoint_id }) => {
|
|
25101
|
+
try {
|
|
25102
|
+
const res = await apiPut(
|
|
25103
|
+
`/checkpoints/${checkpoint_id}`,
|
|
25104
|
+
{
|
|
25105
|
+
status: "completed",
|
|
25106
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25107
|
+
}
|
|
25108
|
+
);
|
|
25109
|
+
const checkpoint = res.data;
|
|
25110
|
+
const featToDevResult = await promoteCheckpoint(checkpoint_id);
|
|
25111
|
+
let devToMainResult = null;
|
|
25112
|
+
const repoRes = await apiGet(
|
|
25113
|
+
`/repos/${checkpoint.repo_id}`
|
|
25114
|
+
);
|
|
25115
|
+
if (repoRes.data.auto_push_enabled) {
|
|
25116
|
+
devToMainResult = await promoteToMain(checkpoint.repo_id);
|
|
25117
|
+
}
|
|
25118
|
+
return {
|
|
25119
|
+
content: [
|
|
25120
|
+
{
|
|
25121
|
+
type: "text",
|
|
25122
|
+
text: JSON.stringify(
|
|
25123
|
+
{
|
|
25124
|
+
checkpoint,
|
|
25125
|
+
promotion: {
|
|
25126
|
+
feat_to_development: featToDevResult,
|
|
25127
|
+
development_to_main: devToMainResult
|
|
25128
|
+
}
|
|
25129
|
+
},
|
|
25130
|
+
null,
|
|
25131
|
+
2
|
|
25132
|
+
)
|
|
25133
|
+
}
|
|
25134
|
+
]
|
|
25135
|
+
};
|
|
25136
|
+
} catch (err) {
|
|
25137
|
+
return {
|
|
25138
|
+
content: [
|
|
25139
|
+
{
|
|
25140
|
+
type: "text",
|
|
25141
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25142
|
+
}
|
|
25143
|
+
],
|
|
25144
|
+
isError: true
|
|
25145
|
+
};
|
|
25146
|
+
}
|
|
24229
25147
|
}
|
|
24230
|
-
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
24240
|
-
|
|
24241
|
-
|
|
24242
|
-
|
|
25148
|
+
);
|
|
25149
|
+
server.registerTool(
|
|
25150
|
+
"create_task",
|
|
25151
|
+
{
|
|
25152
|
+
description: "Create a new task within a checkpoint or as a standalone task. Provide checkpoint_id for checkpoint tasks, or repo_id for standalone tasks.",
|
|
25153
|
+
inputSchema: {
|
|
25154
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("The checkpoint UUID (for checkpoint-bound tasks)"),
|
|
25155
|
+
repo_id: external_exports.string().uuid().optional().describe("The repo UUID (for standalone tasks without checkpoint)"),
|
|
25156
|
+
title: external_exports.string().describe("Task title"),
|
|
25157
|
+
number: external_exports.number().int().optional().describe(
|
|
25158
|
+
"Task number (e.g. 1 for TASK-1). Auto-generated for standalone tasks if omitted."
|
|
25159
|
+
),
|
|
25160
|
+
requirements: external_exports.string().optional().describe("Task requirements text"),
|
|
25161
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25162
|
+
context: external_exports.any().optional().describe(
|
|
25163
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints)"
|
|
25164
|
+
),
|
|
25165
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25166
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
25167
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25168
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25169
|
+
context_development: external_exports.any().optional().describe(
|
|
25170
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25171
|
+
),
|
|
25172
|
+
resources: external_exports.any().optional().describe(
|
|
25173
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25174
|
+
)
|
|
25175
|
+
}
|
|
25176
|
+
},
|
|
25177
|
+
async ({
|
|
25178
|
+
checkpoint_id,
|
|
25179
|
+
repo_id,
|
|
25180
|
+
title,
|
|
25181
|
+
number: number3,
|
|
25182
|
+
requirements,
|
|
25183
|
+
status,
|
|
25184
|
+
context,
|
|
25185
|
+
qa,
|
|
25186
|
+
research,
|
|
25187
|
+
user_context,
|
|
25188
|
+
is_claude_written,
|
|
25189
|
+
context_development,
|
|
25190
|
+
resources
|
|
25191
|
+
}) => {
|
|
25192
|
+
try {
|
|
25193
|
+
const body = {
|
|
25194
|
+
title,
|
|
25195
|
+
requirements: requirements ?? null,
|
|
25196
|
+
status: status ?? "pending"
|
|
25197
|
+
};
|
|
25198
|
+
if (checkpoint_id) body.checkpoint_id = checkpoint_id;
|
|
25199
|
+
if (repo_id) body.repo_id = repo_id;
|
|
25200
|
+
if (number3 !== void 0) body.number = number3;
|
|
25201
|
+
if (context !== void 0) body.context = context;
|
|
25202
|
+
if (qa !== void 0) body.qa = qa;
|
|
25203
|
+
if (research !== void 0) body.research = research;
|
|
25204
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
25205
|
+
if (is_claude_written !== void 0)
|
|
25206
|
+
body.is_claude_written = is_claude_written;
|
|
25207
|
+
if (context_development !== void 0)
|
|
25208
|
+
body.context_development = context_development;
|
|
25209
|
+
if (resources !== void 0) body.resources = resources;
|
|
25210
|
+
const res = await apiPost("/tasks", body);
|
|
25211
|
+
return {
|
|
25212
|
+
content: [
|
|
25213
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25214
|
+
]
|
|
25215
|
+
};
|
|
25216
|
+
} catch (err) {
|
|
25217
|
+
return {
|
|
25218
|
+
content: [
|
|
25219
|
+
{
|
|
25220
|
+
type: "text",
|
|
25221
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25222
|
+
}
|
|
25223
|
+
],
|
|
25224
|
+
isError: true
|
|
25225
|
+
};
|
|
25226
|
+
}
|
|
24243
25227
|
}
|
|
24244
|
-
|
|
24245
|
-
server.registerTool(
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
24261
|
-
|
|
24262
|
-
|
|
24263
|
-
|
|
25228
|
+
);
|
|
25229
|
+
server.registerTool(
|
|
25230
|
+
"update_task",
|
|
25231
|
+
{
|
|
25232
|
+
description: "Update an existing task. Use to update status, requirements, or file tracking. Pass claim_worktree_id when setting status to in_progress to auto-claim the parent checkpoint for the worktree.",
|
|
25233
|
+
inputSchema: {
|
|
25234
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
25235
|
+
title: external_exports.string().optional().describe("New title"),
|
|
25236
|
+
requirements: external_exports.string().optional().describe("New requirements text"),
|
|
25237
|
+
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
25238
|
+
files_changed: external_exports.array(
|
|
25239
|
+
external_exports.object({
|
|
25240
|
+
path: external_exports.string().describe("File path relative to repo root"),
|
|
25241
|
+
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
25242
|
+
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
25243
|
+
claude_approved: external_exports.boolean().optional().describe(
|
|
25244
|
+
"Whether Claude's automated checks passed for this file"
|
|
25245
|
+
),
|
|
25246
|
+
user_approved: external_exports.boolean().optional().describe(
|
|
25247
|
+
"Whether the user has approved this file (via git add or web UI)"
|
|
25248
|
+
),
|
|
25249
|
+
app_approved: external_exports.boolean().optional().describe(
|
|
25250
|
+
"Whether the app has approved this file (via automated checks)"
|
|
25251
|
+
)
|
|
25252
|
+
})
|
|
25253
|
+
).optional().describe("Files changed across all rounds"),
|
|
25254
|
+
claim_worktree_id: external_exports.string().uuid().optional().describe(
|
|
25255
|
+
"Worktree UUID to auto-claim the parent checkpoint when setting status to in_progress"
|
|
25256
|
+
),
|
|
25257
|
+
context: external_exports.any().optional().describe(
|
|
25258
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints)"
|
|
25259
|
+
),
|
|
25260
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25261
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
25262
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25263
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25264
|
+
context_development: external_exports.any().optional().describe(
|
|
25265
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25266
|
+
),
|
|
25267
|
+
resources: external_exports.any().optional().describe(
|
|
25268
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25269
|
+
),
|
|
25270
|
+
app_file_approval_by_user: external_exports.boolean().optional().describe(
|
|
25271
|
+
"Whether user interacted with file approvals via web UI. CLI resets to false after processing."
|
|
25272
|
+
)
|
|
25273
|
+
}
|
|
25274
|
+
},
|
|
25275
|
+
async ({
|
|
25276
|
+
task_id,
|
|
25277
|
+
title,
|
|
25278
|
+
requirements,
|
|
25279
|
+
status,
|
|
25280
|
+
files_changed,
|
|
25281
|
+
claim_worktree_id,
|
|
25282
|
+
context,
|
|
25283
|
+
qa,
|
|
25284
|
+
research,
|
|
25285
|
+
user_context,
|
|
25286
|
+
is_claude_written,
|
|
25287
|
+
context_development,
|
|
25288
|
+
resources,
|
|
25289
|
+
app_file_approval_by_user
|
|
25290
|
+
}) => {
|
|
25291
|
+
const update = {};
|
|
25292
|
+
if (title !== void 0) update.title = title;
|
|
25293
|
+
if (requirements !== void 0) update.requirements = requirements;
|
|
25294
|
+
if (status !== void 0) update.status = status;
|
|
25295
|
+
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
25296
|
+
if (claim_worktree_id !== void 0)
|
|
25297
|
+
update.claim_worktree_id = claim_worktree_id;
|
|
25298
|
+
if (context !== void 0) update.context = context;
|
|
25299
|
+
if (qa !== void 0) update.qa = qa;
|
|
25300
|
+
if (research !== void 0) update.research = research;
|
|
25301
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25302
|
+
if (is_claude_written !== void 0)
|
|
25303
|
+
update.is_claude_written = is_claude_written;
|
|
25304
|
+
if (context_development !== void 0)
|
|
25305
|
+
update.context_development = context_development;
|
|
25306
|
+
if (resources !== void 0) update.resources = resources;
|
|
25307
|
+
if (app_file_approval_by_user !== void 0)
|
|
25308
|
+
update.app_file_approval_by_user = app_file_approval_by_user;
|
|
25309
|
+
if (Object.keys(update).length === 0) {
|
|
25310
|
+
return {
|
|
25311
|
+
content: [
|
|
25312
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25313
|
+
],
|
|
25314
|
+
isError: true
|
|
25315
|
+
};
|
|
25316
|
+
}
|
|
25317
|
+
try {
|
|
25318
|
+
const res = await apiPut(
|
|
25319
|
+
`/tasks/${task_id}`,
|
|
25320
|
+
update
|
|
25321
|
+
);
|
|
25322
|
+
return {
|
|
25323
|
+
content: [
|
|
25324
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25325
|
+
]
|
|
25326
|
+
};
|
|
25327
|
+
} catch (err) {
|
|
25328
|
+
return {
|
|
25329
|
+
content: [
|
|
25330
|
+
{
|
|
25331
|
+
type: "text",
|
|
25332
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25333
|
+
}
|
|
25334
|
+
],
|
|
25335
|
+
isError: true
|
|
25336
|
+
};
|
|
25337
|
+
}
|
|
24264
25338
|
}
|
|
24265
|
-
|
|
24266
|
-
|
|
24267
|
-
|
|
24268
|
-
|
|
24269
|
-
|
|
25339
|
+
);
|
|
25340
|
+
server.registerTool(
|
|
25341
|
+
"complete_task",
|
|
25342
|
+
{
|
|
25343
|
+
description: "Mark a task as completed. Sets status to 'completed' and completed_at to now.",
|
|
25344
|
+
inputSchema: {
|
|
25345
|
+
task_id: external_exports.string().uuid().describe("The task UUID")
|
|
25346
|
+
}
|
|
25347
|
+
},
|
|
25348
|
+
async ({ task_id }) => {
|
|
25349
|
+
try {
|
|
25350
|
+
let hasUnapproved = false;
|
|
25351
|
+
let unapprovedCount = 0;
|
|
25352
|
+
let resolvedRepoId = null;
|
|
25353
|
+
try {
|
|
25354
|
+
const taskRes = await apiGet(
|
|
25355
|
+
`/tasks/${task_id}`
|
|
25356
|
+
);
|
|
25357
|
+
const checkpointRes = await apiGet(
|
|
25358
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25359
|
+
);
|
|
25360
|
+
resolvedRepoId = checkpointRes.data.repo_id;
|
|
25361
|
+
const fileChangesRes = await apiGet("/file-changes", {
|
|
25362
|
+
repo_id: resolvedRepoId,
|
|
25363
|
+
task_id
|
|
25364
|
+
});
|
|
25365
|
+
const unapproved = (fileChangesRes.data ?? []).filter(
|
|
25366
|
+
(f) => !f.user_approved
|
|
25367
|
+
);
|
|
25368
|
+
unapprovedCount = unapproved.length;
|
|
25369
|
+
hasUnapproved = unapprovedCount > 0;
|
|
25370
|
+
} catch {
|
|
25371
|
+
}
|
|
25372
|
+
if (hasUnapproved) {
|
|
25373
|
+
return {
|
|
25374
|
+
content: [
|
|
25375
|
+
{
|
|
25376
|
+
type: "text",
|
|
25377
|
+
text: `Error: Cannot complete task \u2014 ${unapprovedCount} file(s) are not approved. All files must be user_approved before task completion.`
|
|
25378
|
+
}
|
|
25379
|
+
],
|
|
25380
|
+
isError: true
|
|
25381
|
+
};
|
|
25382
|
+
}
|
|
25383
|
+
const res = await apiPut(`/tasks/${task_id}`, {
|
|
25384
|
+
status: "completed",
|
|
25385
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25386
|
+
});
|
|
25387
|
+
if (resolvedRepoId) {
|
|
25388
|
+
try {
|
|
25389
|
+
await apiPatch("/file-changes", {
|
|
25390
|
+
action: "lock_task",
|
|
25391
|
+
task_id,
|
|
25392
|
+
repo_id: resolvedRepoId
|
|
25393
|
+
});
|
|
25394
|
+
} catch {
|
|
25395
|
+
}
|
|
25396
|
+
}
|
|
25397
|
+
return {
|
|
25398
|
+
content: [
|
|
25399
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25400
|
+
]
|
|
25401
|
+
};
|
|
25402
|
+
} catch (err) {
|
|
25403
|
+
return {
|
|
25404
|
+
content: [
|
|
25405
|
+
{
|
|
25406
|
+
type: "text",
|
|
25407
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25408
|
+
}
|
|
25409
|
+
],
|
|
25410
|
+
isError: true
|
|
25411
|
+
};
|
|
25412
|
+
}
|
|
24270
25413
|
}
|
|
24271
|
-
|
|
24272
|
-
server.registerTool(
|
|
24273
|
-
|
|
24274
|
-
|
|
24275
|
-
|
|
25414
|
+
);
|
|
25415
|
+
server.registerTool(
|
|
25416
|
+
"add_round",
|
|
25417
|
+
{
|
|
25418
|
+
description: "Add a round to a task.",
|
|
25419
|
+
inputSchema: {
|
|
25420
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
25421
|
+
number: external_exports.number().int().describe("Round number"),
|
|
25422
|
+
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
25423
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25424
|
+
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
25425
|
+
triggered_by: external_exports.enum(["user", "claude"]).optional().describe("Who triggered the round (user or claude)"),
|
|
25426
|
+
context: external_exports.any().optional().describe("Context JSONB"),
|
|
25427
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25428
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25429
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25430
|
+
context_development: external_exports.any().optional().describe(
|
|
25431
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25432
|
+
),
|
|
25433
|
+
resources: external_exports.any().optional().describe(
|
|
25434
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25435
|
+
)
|
|
25436
|
+
}
|
|
25437
|
+
},
|
|
25438
|
+
async ({
|
|
25439
|
+
task_id,
|
|
25440
|
+
number: number3,
|
|
25441
|
+
requirements,
|
|
25442
|
+
status,
|
|
25443
|
+
started_at,
|
|
25444
|
+
triggered_by,
|
|
25445
|
+
context,
|
|
25446
|
+
qa,
|
|
25447
|
+
user_context,
|
|
25448
|
+
is_claude_written,
|
|
25449
|
+
context_development,
|
|
25450
|
+
resources
|
|
25451
|
+
}) => {
|
|
25452
|
+
try {
|
|
25453
|
+
const body = {
|
|
25454
|
+
task_id,
|
|
25455
|
+
number: number3,
|
|
25456
|
+
requirements: requirements ?? null,
|
|
25457
|
+
status: status ?? "pending",
|
|
25458
|
+
started_at: started_at ?? null
|
|
25459
|
+
};
|
|
25460
|
+
if (triggered_by !== void 0) body.triggered_by = triggered_by;
|
|
25461
|
+
if (context !== void 0) body.context = context;
|
|
25462
|
+
if (qa !== void 0) body.qa = qa;
|
|
25463
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
25464
|
+
if (is_claude_written !== void 0)
|
|
25465
|
+
body.is_claude_written = is_claude_written;
|
|
25466
|
+
if (context_development !== void 0)
|
|
25467
|
+
body.context_development = context_development;
|
|
25468
|
+
if (resources !== void 0) body.resources = resources;
|
|
25469
|
+
const res = await apiPost("/rounds", body);
|
|
25470
|
+
return {
|
|
25471
|
+
content: [
|
|
25472
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25473
|
+
]
|
|
25474
|
+
};
|
|
25475
|
+
} catch (err) {
|
|
25476
|
+
return {
|
|
25477
|
+
content: [
|
|
25478
|
+
{
|
|
25479
|
+
type: "text",
|
|
25480
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25481
|
+
}
|
|
25482
|
+
],
|
|
25483
|
+
isError: true
|
|
25484
|
+
};
|
|
25485
|
+
}
|
|
24276
25486
|
}
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
24280
|
-
|
|
24281
|
-
|
|
24282
|
-
|
|
25487
|
+
);
|
|
25488
|
+
server.registerTool(
|
|
25489
|
+
"update_round",
|
|
25490
|
+
{
|
|
25491
|
+
description: "Update an existing round.",
|
|
25492
|
+
inputSchema: {
|
|
25493
|
+
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
25494
|
+
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
25495
|
+
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
25496
|
+
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
25497
|
+
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
25498
|
+
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes"),
|
|
25499
|
+
files_changed: external_exports.array(
|
|
25500
|
+
external_exports.object({
|
|
25501
|
+
path: external_exports.string().describe("File path relative to repo root"),
|
|
25502
|
+
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
25503
|
+
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
25504
|
+
claude_approved: external_exports.boolean().optional().describe(
|
|
25505
|
+
"Whether Claude's automated checks passed for this file"
|
|
25506
|
+
),
|
|
25507
|
+
user_approved: external_exports.boolean().optional().describe(
|
|
25508
|
+
"Whether the user has approved this file (via git add or web UI)"
|
|
25509
|
+
),
|
|
25510
|
+
app_approved: external_exports.boolean().optional().describe(
|
|
25511
|
+
"Whether the app has approved this file (via automated checks)"
|
|
25512
|
+
)
|
|
25513
|
+
})
|
|
25514
|
+
).optional().describe("Files changed in this round with approval status"),
|
|
25515
|
+
context: external_exports.any().optional().describe("Context JSONB"),
|
|
25516
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25517
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25518
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25519
|
+
context_development: external_exports.any().optional().describe(
|
|
25520
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25521
|
+
),
|
|
25522
|
+
resources: external_exports.any().optional().describe(
|
|
25523
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25524
|
+
)
|
|
25525
|
+
}
|
|
25526
|
+
},
|
|
25527
|
+
async ({
|
|
25528
|
+
round_id,
|
|
25529
|
+
requirements,
|
|
25530
|
+
status,
|
|
25531
|
+
started_at,
|
|
25532
|
+
completed_at,
|
|
25533
|
+
duration_minutes,
|
|
25534
|
+
files_changed,
|
|
25535
|
+
context,
|
|
25536
|
+
qa,
|
|
25537
|
+
user_context,
|
|
25538
|
+
is_claude_written,
|
|
25539
|
+
context_development,
|
|
25540
|
+
resources
|
|
25541
|
+
}) => {
|
|
25542
|
+
const update = {};
|
|
25543
|
+
if (requirements !== void 0) update.requirements = requirements;
|
|
25544
|
+
if (status !== void 0) update.status = status;
|
|
25545
|
+
if (started_at !== void 0) update.started_at = started_at;
|
|
25546
|
+
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
25547
|
+
if (duration_minutes !== void 0)
|
|
25548
|
+
update.duration_minutes = duration_minutes;
|
|
25549
|
+
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
25550
|
+
if (context !== void 0) update.context = context;
|
|
25551
|
+
if (qa !== void 0) update.qa = qa;
|
|
25552
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25553
|
+
if (is_claude_written !== void 0)
|
|
25554
|
+
update.is_claude_written = is_claude_written;
|
|
25555
|
+
if (context_development !== void 0)
|
|
25556
|
+
update.context_development = context_development;
|
|
25557
|
+
if (resources !== void 0) update.resources = resources;
|
|
25558
|
+
if (Object.keys(update).length === 0) {
|
|
25559
|
+
return {
|
|
25560
|
+
content: [
|
|
25561
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25562
|
+
],
|
|
25563
|
+
isError: true
|
|
25564
|
+
};
|
|
25565
|
+
}
|
|
25566
|
+
try {
|
|
25567
|
+
const res = await apiPut(
|
|
25568
|
+
`/rounds/${round_id}`,
|
|
25569
|
+
update
|
|
25570
|
+
);
|
|
25571
|
+
if (files_changed && files_changed.length > 0) {
|
|
25572
|
+
try {
|
|
25573
|
+
const taskRes = await apiGet(
|
|
25574
|
+
`/tasks/${res.data.task_id}`
|
|
25575
|
+
);
|
|
25576
|
+
const checkpointRes = await apiGet(
|
|
25577
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25578
|
+
);
|
|
25579
|
+
await apiPatch("/file-changes", {
|
|
25580
|
+
action: "replace_round_files",
|
|
25581
|
+
round_id,
|
|
25582
|
+
repo_id: checkpointRes.data.repo_id,
|
|
25583
|
+
checkpoint_id: taskRes.data.checkpoint_id,
|
|
25584
|
+
task_id: res.data.task_id,
|
|
25585
|
+
source: "round",
|
|
25586
|
+
files: files_changed.map(
|
|
25587
|
+
(f) => ({
|
|
25588
|
+
file_path: f.path,
|
|
25589
|
+
action: f.action,
|
|
25590
|
+
claude_approved: f.claude_approved ?? false,
|
|
25591
|
+
user_approved: f.user_approved ?? false
|
|
25592
|
+
})
|
|
25593
|
+
)
|
|
25594
|
+
});
|
|
25595
|
+
} catch {
|
|
25596
|
+
}
|
|
25597
|
+
}
|
|
25598
|
+
return {
|
|
25599
|
+
content: [
|
|
25600
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25601
|
+
]
|
|
25602
|
+
};
|
|
25603
|
+
} catch (err) {
|
|
25604
|
+
return {
|
|
25605
|
+
content: [
|
|
25606
|
+
{
|
|
25607
|
+
type: "text",
|
|
25608
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25609
|
+
}
|
|
25610
|
+
],
|
|
25611
|
+
isError: true
|
|
25612
|
+
};
|
|
25613
|
+
}
|
|
24283
25614
|
}
|
|
24284
|
-
|
|
24285
|
-
server.registerTool(
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
|
|
24289
|
-
|
|
24290
|
-
|
|
24291
|
-
|
|
24292
|
-
|
|
24293
|
-
|
|
25615
|
+
);
|
|
25616
|
+
server.registerTool(
|
|
25617
|
+
"complete_round",
|
|
25618
|
+
{
|
|
25619
|
+
description: "Mark a round as completed. Sets status to 'completed' and completed_at to now.",
|
|
25620
|
+
inputSchema: {
|
|
25621
|
+
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
25622
|
+
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes")
|
|
25623
|
+
}
|
|
25624
|
+
},
|
|
25625
|
+
async ({ round_id, duration_minutes }) => {
|
|
25626
|
+
try {
|
|
25627
|
+
const update = {
|
|
25628
|
+
status: "completed",
|
|
25629
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25630
|
+
};
|
|
25631
|
+
if (duration_minutes !== void 0)
|
|
25632
|
+
update.duration_minutes = duration_minutes;
|
|
25633
|
+
const res = await apiPut(
|
|
25634
|
+
`/rounds/${round_id}`,
|
|
25635
|
+
update
|
|
25636
|
+
);
|
|
25637
|
+
let unapprovedFiles = [];
|
|
25638
|
+
try {
|
|
25639
|
+
const taskRes = await apiGet(
|
|
25640
|
+
`/tasks/${res.data.task_id}`
|
|
25641
|
+
);
|
|
25642
|
+
const checkpointRes = await apiGet(
|
|
25643
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25644
|
+
);
|
|
25645
|
+
const repoId = checkpointRes.data.repo_id;
|
|
25646
|
+
await apiPatch("/file-changes", {
|
|
25647
|
+
action: "lock_round",
|
|
25648
|
+
round_id,
|
|
25649
|
+
repo_id: repoId
|
|
25650
|
+
});
|
|
25651
|
+
const fileChangesRes = await apiGet("/file-changes", {
|
|
25652
|
+
repo_id: repoId,
|
|
25653
|
+
round_id
|
|
25654
|
+
});
|
|
25655
|
+
unapprovedFiles = (fileChangesRes.data ?? []).filter((f) => !f.user_approved).map((f) => ({ file_path: f.file_path, action: f.action }));
|
|
25656
|
+
} catch {
|
|
25657
|
+
}
|
|
25658
|
+
const result = {
|
|
25659
|
+
...res.data,
|
|
25660
|
+
unapproved_files: unapprovedFiles,
|
|
25661
|
+
unapproved_count: unapprovedFiles.length
|
|
25662
|
+
};
|
|
25663
|
+
return {
|
|
25664
|
+
content: [
|
|
25665
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
25666
|
+
]
|
|
25667
|
+
};
|
|
25668
|
+
} catch (err) {
|
|
25669
|
+
return {
|
|
25670
|
+
content: [
|
|
25671
|
+
{
|
|
25672
|
+
type: "text",
|
|
25673
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25674
|
+
}
|
|
25675
|
+
],
|
|
25676
|
+
isError: true
|
|
25677
|
+
};
|
|
25678
|
+
}
|
|
24294
25679
|
}
|
|
24295
|
-
|
|
24296
|
-
|
|
24297
|
-
|
|
24298
|
-
|
|
24299
|
-
|
|
24300
|
-
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
24306
|
-
|
|
24307
|
-
|
|
24308
|
-
|
|
25680
|
+
);
|
|
25681
|
+
server.registerTool(
|
|
25682
|
+
"create_launch",
|
|
25683
|
+
{
|
|
25684
|
+
description: "Create a new launch for a repo.",
|
|
25685
|
+
inputSchema: {
|
|
25686
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
25687
|
+
title: external_exports.string().describe("Launch title"),
|
|
25688
|
+
type: external_exports.string().describe("Launch type"),
|
|
25689
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25690
|
+
version: external_exports.string().optional().describe("Version string"),
|
|
25691
|
+
user_requirements: external_exports.any().optional().describe("User requirements (JSON)")
|
|
25692
|
+
}
|
|
25693
|
+
},
|
|
25694
|
+
async ({ repo_id, title, type, status, version: version2, user_requirements }) => {
|
|
25695
|
+
try {
|
|
25696
|
+
const res = await apiPost("/launches", {
|
|
25697
|
+
repo_id,
|
|
25698
|
+
title,
|
|
25699
|
+
type,
|
|
25700
|
+
status: status ?? "pending",
|
|
25701
|
+
version: version2 ?? null,
|
|
25702
|
+
user_requirements: user_requirements ?? null
|
|
25703
|
+
});
|
|
25704
|
+
return {
|
|
25705
|
+
content: [
|
|
25706
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25707
|
+
]
|
|
25708
|
+
};
|
|
25709
|
+
} catch (err) {
|
|
25710
|
+
return {
|
|
25711
|
+
content: [
|
|
25712
|
+
{
|
|
25713
|
+
type: "text",
|
|
25714
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25715
|
+
}
|
|
25716
|
+
],
|
|
25717
|
+
isError: true
|
|
25718
|
+
};
|
|
25719
|
+
}
|
|
24309
25720
|
}
|
|
24310
|
-
|
|
24311
|
-
server.registerTool(
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24315
|
-
|
|
24316
|
-
|
|
24317
|
-
|
|
24318
|
-
|
|
24319
|
-
|
|
24320
|
-
|
|
24321
|
-
|
|
24322
|
-
|
|
24323
|
-
|
|
24324
|
-
|
|
24325
|
-
|
|
24326
|
-
|
|
24327
|
-
|
|
25721
|
+
);
|
|
25722
|
+
server.registerTool(
|
|
25723
|
+
"update_launch",
|
|
25724
|
+
{
|
|
25725
|
+
description: "Update an existing launch.",
|
|
25726
|
+
inputSchema: {
|
|
25727
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID"),
|
|
25728
|
+
title: external_exports.string().optional().describe("New title"),
|
|
25729
|
+
type: external_exports.string().optional().describe("New type"),
|
|
25730
|
+
status: external_exports.string().optional().describe("New status"),
|
|
25731
|
+
version: external_exports.string().optional().describe("New version"),
|
|
25732
|
+
user_requirements: external_exports.any().optional().describe("New user requirements (JSON)")
|
|
25733
|
+
}
|
|
25734
|
+
},
|
|
25735
|
+
async ({ launch_id, title, type, status, version: version2, user_requirements }) => {
|
|
25736
|
+
const update = {};
|
|
25737
|
+
if (title !== void 0) update.title = title;
|
|
25738
|
+
if (type !== void 0) update.type = type;
|
|
25739
|
+
if (status !== void 0) update.status = status;
|
|
25740
|
+
if (version2 !== void 0) update.version = version2;
|
|
25741
|
+
if (user_requirements !== void 0)
|
|
25742
|
+
update.user_requirements = user_requirements;
|
|
25743
|
+
if (Object.keys(update).length === 0) {
|
|
25744
|
+
return {
|
|
25745
|
+
content: [
|
|
25746
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25747
|
+
],
|
|
25748
|
+
isError: true
|
|
25749
|
+
};
|
|
25750
|
+
}
|
|
25751
|
+
try {
|
|
25752
|
+
const res = await apiPut(
|
|
25753
|
+
`/launches/${launch_id}`,
|
|
25754
|
+
update
|
|
25755
|
+
);
|
|
25756
|
+
return {
|
|
25757
|
+
content: [
|
|
25758
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25759
|
+
]
|
|
25760
|
+
};
|
|
25761
|
+
} catch (err) {
|
|
25762
|
+
return {
|
|
25763
|
+
content: [
|
|
25764
|
+
{
|
|
25765
|
+
type: "text",
|
|
25766
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25767
|
+
}
|
|
25768
|
+
],
|
|
25769
|
+
isError: true
|
|
25770
|
+
};
|
|
25771
|
+
}
|
|
24328
25772
|
}
|
|
24329
|
-
|
|
24330
|
-
|
|
24331
|
-
|
|
24332
|
-
|
|
24333
|
-
|
|
25773
|
+
);
|
|
25774
|
+
server.registerTool(
|
|
25775
|
+
"delete_launch",
|
|
25776
|
+
{
|
|
25777
|
+
description: "Delete a launch by ID.",
|
|
25778
|
+
inputSchema: {
|
|
25779
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID")
|
|
25780
|
+
}
|
|
25781
|
+
},
|
|
25782
|
+
async ({ launch_id }) => {
|
|
25783
|
+
try {
|
|
25784
|
+
await apiDelete(`/launches/${launch_id}`);
|
|
25785
|
+
return {
|
|
25786
|
+
content: [
|
|
25787
|
+
{ type: "text", text: "Launch deleted successfully" }
|
|
25788
|
+
]
|
|
25789
|
+
};
|
|
25790
|
+
} catch (err) {
|
|
25791
|
+
return {
|
|
25792
|
+
content: [
|
|
25793
|
+
{
|
|
25794
|
+
type: "text",
|
|
25795
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25796
|
+
}
|
|
25797
|
+
],
|
|
25798
|
+
isError: true
|
|
25799
|
+
};
|
|
25800
|
+
}
|
|
24334
25801
|
}
|
|
24335
|
-
|
|
24336
|
-
server.registerTool(
|
|
24337
|
-
|
|
24338
|
-
|
|
24339
|
-
|
|
25802
|
+
);
|
|
25803
|
+
server.registerTool(
|
|
25804
|
+
"create_session_log",
|
|
25805
|
+
{
|
|
25806
|
+
description: "Create a new session log for a repo.",
|
|
25807
|
+
inputSchema: {
|
|
25808
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
25809
|
+
session_date: external_exports.string().describe("Session date (YYYY-MM-DD)"),
|
|
25810
|
+
day_number: external_exports.number().int().describe("Day number (e.g. 41 for D-41)"),
|
|
25811
|
+
session_number: external_exports.number().int().optional().describe("Session number within the day (default: 1)"),
|
|
25812
|
+
content: external_exports.any().optional().describe("Session log content (JSON)"),
|
|
25813
|
+
worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID that created this session log")
|
|
25814
|
+
}
|
|
25815
|
+
},
|
|
25816
|
+
async ({
|
|
25817
|
+
repo_id,
|
|
25818
|
+
session_date,
|
|
25819
|
+
day_number,
|
|
25820
|
+
session_number,
|
|
25821
|
+
content,
|
|
25822
|
+
worktree_id
|
|
25823
|
+
}) => {
|
|
25824
|
+
try {
|
|
25825
|
+
const body = {
|
|
25826
|
+
repo_id,
|
|
25827
|
+
session_date,
|
|
25828
|
+
day_number,
|
|
25829
|
+
session_number: session_number ?? 1,
|
|
25830
|
+
content: content ?? null
|
|
25831
|
+
};
|
|
25832
|
+
if (worktree_id) body.worktree_id = worktree_id;
|
|
25833
|
+
const res = await apiPost(
|
|
25834
|
+
"/session-logs",
|
|
25835
|
+
body
|
|
25836
|
+
);
|
|
25837
|
+
return {
|
|
25838
|
+
content: [
|
|
25839
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25840
|
+
]
|
|
25841
|
+
};
|
|
25842
|
+
} catch (err) {
|
|
25843
|
+
return {
|
|
25844
|
+
content: [
|
|
25845
|
+
{
|
|
25846
|
+
type: "text",
|
|
25847
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25848
|
+
}
|
|
25849
|
+
],
|
|
25850
|
+
isError: true
|
|
25851
|
+
};
|
|
25852
|
+
}
|
|
24340
25853
|
}
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
|
|
24344
|
-
|
|
24345
|
-
|
|
24346
|
-
|
|
25854
|
+
);
|
|
25855
|
+
server.registerTool(
|
|
25856
|
+
"update_session_log",
|
|
25857
|
+
{
|
|
25858
|
+
description: "Update an existing session log.",
|
|
25859
|
+
inputSchema: {
|
|
25860
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID"),
|
|
25861
|
+
session_date: external_exports.string().optional().describe("New session date"),
|
|
25862
|
+
day_number: external_exports.number().int().optional().describe("New day number"),
|
|
25863
|
+
session_number: external_exports.number().int().optional().describe("New session number"),
|
|
25864
|
+
content: external_exports.any().optional().describe("New content (JSON)")
|
|
25865
|
+
}
|
|
25866
|
+
},
|
|
25867
|
+
async ({
|
|
25868
|
+
session_log_id,
|
|
25869
|
+
session_date,
|
|
25870
|
+
day_number,
|
|
25871
|
+
session_number,
|
|
25872
|
+
content
|
|
25873
|
+
}) => {
|
|
25874
|
+
const update = {};
|
|
25875
|
+
if (session_date !== void 0) update.session_date = session_date;
|
|
25876
|
+
if (day_number !== void 0) update.day_number = day_number;
|
|
25877
|
+
if (session_number !== void 0) update.session_number = session_number;
|
|
25878
|
+
if (content !== void 0) update.content = content;
|
|
25879
|
+
if (Object.keys(update).length === 0) {
|
|
25880
|
+
return {
|
|
25881
|
+
content: [
|
|
25882
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25883
|
+
],
|
|
25884
|
+
isError: true
|
|
25885
|
+
};
|
|
25886
|
+
}
|
|
25887
|
+
try {
|
|
25888
|
+
const res = await apiPut(
|
|
25889
|
+
`/session-logs/${session_log_id}`,
|
|
25890
|
+
update
|
|
25891
|
+
);
|
|
25892
|
+
return {
|
|
25893
|
+
content: [
|
|
25894
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25895
|
+
]
|
|
25896
|
+
};
|
|
25897
|
+
} catch (err) {
|
|
25898
|
+
return {
|
|
25899
|
+
content: [
|
|
25900
|
+
{
|
|
25901
|
+
type: "text",
|
|
25902
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25903
|
+
}
|
|
25904
|
+
],
|
|
25905
|
+
isError: true
|
|
25906
|
+
};
|
|
25907
|
+
}
|
|
24347
25908
|
}
|
|
24348
|
-
|
|
24349
|
-
server.registerTool(
|
|
24350
|
-
|
|
24351
|
-
|
|
24352
|
-
|
|
24353
|
-
|
|
25909
|
+
);
|
|
25910
|
+
server.registerTool(
|
|
25911
|
+
"delete_session_log",
|
|
25912
|
+
{
|
|
25913
|
+
description: "Delete a session log by ID.",
|
|
25914
|
+
inputSchema: {
|
|
25915
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID")
|
|
25916
|
+
}
|
|
25917
|
+
},
|
|
25918
|
+
async ({ session_log_id }) => {
|
|
25919
|
+
try {
|
|
25920
|
+
await apiDelete(`/session-logs/${session_log_id}`);
|
|
25921
|
+
return {
|
|
25922
|
+
content: [
|
|
25923
|
+
{ type: "text", text: "Session log deleted successfully" }
|
|
25924
|
+
]
|
|
25925
|
+
};
|
|
25926
|
+
} catch (err) {
|
|
25927
|
+
return {
|
|
25928
|
+
content: [
|
|
25929
|
+
{
|
|
25930
|
+
type: "text",
|
|
25931
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25932
|
+
}
|
|
25933
|
+
],
|
|
25934
|
+
isError: true
|
|
25935
|
+
};
|
|
25936
|
+
}
|
|
24354
25937
|
}
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
}
|
|
24364
|
-
|
|
24365
|
-
|
|
24366
|
-
|
|
25938
|
+
);
|
|
25939
|
+
server.registerTool(
|
|
25940
|
+
"sync_claude_files",
|
|
25941
|
+
{
|
|
25942
|
+
description: "Sync .claude infrastructure from CodeByPlan DB to local project. Uses aggregated defaults (latest version across all repos) for shared files (commands, agents, skills, rules, hooks, templates, stack docs). Repo-specific files (CLAUDE.md, settings) are not overwritten.",
|
|
25943
|
+
inputSchema: {
|
|
25944
|
+
repo_id: external_exports.string().uuid().describe("Repository ID to sync files for"),
|
|
25945
|
+
project_path: external_exports.string().describe("Absolute path to the project root directory")
|
|
25946
|
+
}
|
|
25947
|
+
},
|
|
25948
|
+
async ({ repo_id, project_path }) => {
|
|
25949
|
+
try {
|
|
25950
|
+
const syncResult = await executeSyncToLocal({
|
|
25951
|
+
repoId: repo_id,
|
|
25952
|
+
projectPath: project_path
|
|
25953
|
+
});
|
|
25954
|
+
const { byType, totals, dbOnlyFiles } = syncResult;
|
|
25955
|
+
const summary = {
|
|
25956
|
+
...byType,
|
|
25957
|
+
totals: {
|
|
25958
|
+
created: totals.created,
|
|
25959
|
+
updated: totals.updated,
|
|
25960
|
+
deleted: totals.deleted
|
|
25961
|
+
},
|
|
25962
|
+
message: totals.created + totals.updated + totals.deleted === 0 ? "All files up to date" : `Synced: ${totals.created} created, ${totals.updated} updated, ${totals.deleted} deleted`
|
|
25963
|
+
};
|
|
25964
|
+
if (dbOnlyFiles.length > 0) {
|
|
25965
|
+
summary.db_only_files = dbOnlyFiles;
|
|
25966
|
+
summary.message += `
|
|
24367
25967
|
|
|
24368
25968
|
${dbOnlyFiles.length} file(s) exist in DB but were missing locally (recreated). Use delete_claude_files to remove them from DB if they were intentionally deleted.`;
|
|
25969
|
+
}
|
|
25970
|
+
return {
|
|
25971
|
+
content: [
|
|
25972
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) }
|
|
25973
|
+
]
|
|
25974
|
+
};
|
|
25975
|
+
} catch (err) {
|
|
25976
|
+
return {
|
|
25977
|
+
content: [
|
|
25978
|
+
{
|
|
25979
|
+
type: "text",
|
|
25980
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25981
|
+
}
|
|
25982
|
+
],
|
|
25983
|
+
isError: true
|
|
25984
|
+
};
|
|
24369
25985
|
}
|
|
24370
|
-
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
24371
|
-
} catch (err) {
|
|
24372
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24373
|
-
}
|
|
24374
|
-
});
|
|
24375
|
-
server.registerTool("delete_claude_files", {
|
|
24376
|
-
description: "Soft-delete claude files from the CodeByPlan DB. Use after sync_claude_files reports db_only_files that should be removed. Each file is identified by type, name, and optional category.",
|
|
24377
|
-
inputSchema: {
|
|
24378
|
-
repo_id: external_exports.string().uuid().describe("Repository ID"),
|
|
24379
|
-
files: external_exports.array(external_exports.object({
|
|
24380
|
-
type: external_exports.string().describe("File type: command, agent, skill, rule, hook, template, docs_stack"),
|
|
24381
|
-
name: external_exports.string().describe("File name"),
|
|
24382
|
-
category: external_exports.string().nullable().optional().describe("Category (for commands: e.g. 'development/checkpoint')")
|
|
24383
|
-
})).describe("Files to soft-delete from DB")
|
|
24384
|
-
}
|
|
24385
|
-
}, async ({ repo_id, files }) => {
|
|
24386
|
-
try {
|
|
24387
|
-
const res = await apiPost("/sync/files", {
|
|
24388
|
-
repo_id,
|
|
24389
|
-
delete_keys: files
|
|
24390
|
-
});
|
|
24391
|
-
return {
|
|
24392
|
-
content: [{
|
|
24393
|
-
type: "text",
|
|
24394
|
-
text: JSON.stringify({
|
|
24395
|
-
deleted: res.data.deleted,
|
|
24396
|
-
message: `Soft-deleted ${res.data.deleted} file(s) from DB. Run sync_claude_files to clean up local copies.`
|
|
24397
|
-
}, null, 2)
|
|
24398
|
-
}]
|
|
24399
|
-
};
|
|
24400
|
-
} catch (err) {
|
|
24401
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24402
25986
|
}
|
|
24403
|
-
|
|
24404
|
-
server.registerTool(
|
|
24405
|
-
|
|
24406
|
-
|
|
24407
|
-
|
|
24408
|
-
|
|
24409
|
-
|
|
24410
|
-
|
|
24411
|
-
|
|
24412
|
-
|
|
24413
|
-
|
|
24414
|
-
|
|
24415
|
-
|
|
24416
|
-
|
|
24417
|
-
|
|
24418
|
-
|
|
24419
|
-
|
|
24420
|
-
|
|
24421
|
-
|
|
24422
|
-
|
|
24423
|
-
|
|
24424
|
-
|
|
24425
|
-
|
|
24426
|
-
|
|
24427
|
-
|
|
24428
|
-
|
|
24429
|
-
|
|
24430
|
-
|
|
24431
|
-
|
|
24432
|
-
|
|
24433
|
-
|
|
24434
|
-
|
|
24435
|
-
|
|
24436
|
-
|
|
24437
|
-
|
|
24438
|
-
|
|
25987
|
+
);
|
|
25988
|
+
server.registerTool(
|
|
25989
|
+
"delete_claude_files",
|
|
25990
|
+
{
|
|
25991
|
+
description: "Soft-delete claude files from the CodeByPlan DB. Use after sync_claude_files reports db_only_files that should be removed. Each file is identified by type, name, and optional category.",
|
|
25992
|
+
inputSchema: {
|
|
25993
|
+
repo_id: external_exports.string().uuid().describe("Repository ID"),
|
|
25994
|
+
files: external_exports.array(
|
|
25995
|
+
external_exports.object({
|
|
25996
|
+
type: external_exports.string().describe(
|
|
25997
|
+
"File type: command, agent, skill, rule, hook, template, docs_stack, context"
|
|
25998
|
+
),
|
|
25999
|
+
name: external_exports.string().describe("File name"),
|
|
26000
|
+
category: external_exports.string().nullable().optional().describe(
|
|
26001
|
+
"Category (for commands: e.g. 'development/checkpoint')"
|
|
26002
|
+
)
|
|
26003
|
+
})
|
|
26004
|
+
).describe("Files to soft-delete from DB")
|
|
26005
|
+
}
|
|
26006
|
+
},
|
|
26007
|
+
async ({ repo_id, files }) => {
|
|
26008
|
+
try {
|
|
26009
|
+
const res = await apiPost("/sync/files", {
|
|
26010
|
+
repo_id,
|
|
26011
|
+
delete_keys: files
|
|
26012
|
+
});
|
|
26013
|
+
return {
|
|
26014
|
+
content: [
|
|
26015
|
+
{
|
|
26016
|
+
type: "text",
|
|
26017
|
+
text: JSON.stringify(
|
|
26018
|
+
{
|
|
26019
|
+
deleted: res.data.deleted,
|
|
26020
|
+
message: `Soft-deleted ${res.data.deleted} file(s) from DB. Run sync_claude_files to clean up local copies.`
|
|
26021
|
+
},
|
|
26022
|
+
null,
|
|
26023
|
+
2
|
|
26024
|
+
)
|
|
26025
|
+
}
|
|
26026
|
+
]
|
|
26027
|
+
};
|
|
26028
|
+
} catch (err) {
|
|
26029
|
+
return {
|
|
26030
|
+
content: [
|
|
26031
|
+
{
|
|
26032
|
+
type: "text",
|
|
26033
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26034
|
+
}
|
|
26035
|
+
],
|
|
26036
|
+
isError: true
|
|
26037
|
+
};
|
|
26038
|
+
}
|
|
24439
26039
|
}
|
|
24440
|
-
|
|
24441
|
-
server.registerTool(
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
|
|
24445
|
-
|
|
24446
|
-
|
|
24447
|
-
|
|
26040
|
+
);
|
|
26041
|
+
server.registerTool(
|
|
26042
|
+
"update_session_state",
|
|
26043
|
+
{
|
|
26044
|
+
description: "Update session state for a repo. Actions: activate (deactivates other repos), deactivate, pause, refresh, clear_refresh.",
|
|
26045
|
+
inputSchema: {
|
|
26046
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26047
|
+
action: external_exports.enum(["activate", "deactivate", "pause", "refresh", "clear_refresh"]).describe("Session action to perform")
|
|
26048
|
+
}
|
|
26049
|
+
},
|
|
26050
|
+
async ({ repo_id, action }) => {
|
|
26051
|
+
try {
|
|
26052
|
+
const res = await apiPatch(
|
|
26053
|
+
`/repos/${repo_id}/session`,
|
|
26054
|
+
{ action }
|
|
26055
|
+
);
|
|
26056
|
+
return {
|
|
26057
|
+
content: [
|
|
26058
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26059
|
+
]
|
|
26060
|
+
};
|
|
26061
|
+
} catch (err) {
|
|
26062
|
+
return {
|
|
26063
|
+
content: [
|
|
26064
|
+
{
|
|
26065
|
+
type: "text",
|
|
26066
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26067
|
+
}
|
|
26068
|
+
],
|
|
26069
|
+
isError: true
|
|
26070
|
+
};
|
|
26071
|
+
}
|
|
24448
26072
|
}
|
|
24449
|
-
|
|
24450
|
-
|
|
24451
|
-
|
|
24452
|
-
|
|
24453
|
-
|
|
24454
|
-
|
|
24455
|
-
|
|
24456
|
-
|
|
24457
|
-
|
|
24458
|
-
|
|
24459
|
-
|
|
26073
|
+
);
|
|
26074
|
+
server.registerTool(
|
|
26075
|
+
"update_server_config",
|
|
26076
|
+
{
|
|
26077
|
+
description: "Update server configuration for a repo (port, type, active servers). Active server entries can include worktree_id to track which worktree is running the server.",
|
|
26078
|
+
inputSchema: {
|
|
26079
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26080
|
+
server_port: external_exports.number().int().optional().describe("Server port number"),
|
|
26081
|
+
server_type: external_exports.string().optional().describe("Server type"),
|
|
26082
|
+
active_servers: external_exports.any().optional().describe("Active servers (JSON array)")
|
|
26083
|
+
}
|
|
26084
|
+
},
|
|
26085
|
+
async ({ repo_id, server_port, server_type, active_servers }) => {
|
|
26086
|
+
const body = {};
|
|
26087
|
+
if (server_port !== void 0) body.server_port = server_port;
|
|
26088
|
+
if (server_type !== void 0) body.server_type = server_type;
|
|
26089
|
+
if (active_servers !== void 0) body.active_servers = active_servers;
|
|
26090
|
+
if (Object.keys(body).length === 0) {
|
|
26091
|
+
return {
|
|
26092
|
+
content: [
|
|
26093
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
26094
|
+
],
|
|
26095
|
+
isError: true
|
|
26096
|
+
};
|
|
26097
|
+
}
|
|
26098
|
+
try {
|
|
26099
|
+
const res = await apiPatch(
|
|
26100
|
+
`/repos/${repo_id}/server`,
|
|
26101
|
+
body
|
|
26102
|
+
);
|
|
26103
|
+
return {
|
|
26104
|
+
content: [
|
|
26105
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26106
|
+
]
|
|
26107
|
+
};
|
|
26108
|
+
} catch (err) {
|
|
26109
|
+
return {
|
|
26110
|
+
content: [
|
|
26111
|
+
{
|
|
26112
|
+
type: "text",
|
|
26113
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26114
|
+
}
|
|
26115
|
+
],
|
|
26116
|
+
isError: true
|
|
26117
|
+
};
|
|
26118
|
+
}
|
|
24460
26119
|
}
|
|
24461
|
-
|
|
24462
|
-
server.registerTool(
|
|
24463
|
-
|
|
24464
|
-
|
|
24465
|
-
|
|
26120
|
+
);
|
|
26121
|
+
server.registerTool(
|
|
26122
|
+
"create_worktree",
|
|
26123
|
+
{
|
|
26124
|
+
description: "Create a new worktree for a repo.",
|
|
26125
|
+
inputSchema: {
|
|
26126
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26127
|
+
name: external_exports.string().describe("Worktree name (unique per repo)"),
|
|
26128
|
+
path: external_exports.string().optional().describe("Local filesystem path"),
|
|
26129
|
+
status: external_exports.string().optional().describe("Initial status (default: active)")
|
|
26130
|
+
}
|
|
26131
|
+
},
|
|
26132
|
+
async ({ repo_id, name, path, status }) => {
|
|
26133
|
+
try {
|
|
26134
|
+
const res = await apiPost("/worktrees", {
|
|
26135
|
+
repo_id,
|
|
26136
|
+
name,
|
|
26137
|
+
path: path ?? null,
|
|
26138
|
+
status: status ?? "active"
|
|
26139
|
+
});
|
|
26140
|
+
return {
|
|
26141
|
+
content: [
|
|
26142
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26143
|
+
]
|
|
26144
|
+
};
|
|
26145
|
+
} catch (err) {
|
|
26146
|
+
return {
|
|
26147
|
+
content: [
|
|
26148
|
+
{
|
|
26149
|
+
type: "text",
|
|
26150
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26151
|
+
}
|
|
26152
|
+
],
|
|
26153
|
+
isError: true
|
|
26154
|
+
};
|
|
26155
|
+
}
|
|
24466
26156
|
}
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
24470
|
-
|
|
24471
|
-
|
|
24472
|
-
|
|
26157
|
+
);
|
|
26158
|
+
server.registerTool(
|
|
26159
|
+
"delete_worktree",
|
|
26160
|
+
{
|
|
26161
|
+
description: "Delete a worktree by ID.",
|
|
26162
|
+
inputSchema: {
|
|
26163
|
+
worktree_id: external_exports.string().uuid().describe("The worktree UUID")
|
|
26164
|
+
}
|
|
26165
|
+
},
|
|
26166
|
+
async ({ worktree_id }) => {
|
|
26167
|
+
try {
|
|
26168
|
+
await apiDelete(`/worktrees/${worktree_id}`);
|
|
26169
|
+
return {
|
|
26170
|
+
content: [
|
|
26171
|
+
{ type: "text", text: "Worktree deleted successfully" }
|
|
26172
|
+
]
|
|
26173
|
+
};
|
|
26174
|
+
} catch (err) {
|
|
26175
|
+
return {
|
|
26176
|
+
content: [
|
|
26177
|
+
{
|
|
26178
|
+
type: "text",
|
|
26179
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26180
|
+
}
|
|
26181
|
+
],
|
|
26182
|
+
isError: true
|
|
26183
|
+
};
|
|
26184
|
+
}
|
|
24473
26185
|
}
|
|
24474
|
-
|
|
24475
|
-
server.registerTool(
|
|
24476
|
-
|
|
24477
|
-
|
|
24478
|
-
|
|
24479
|
-
|
|
24480
|
-
|
|
24481
|
-
|
|
24482
|
-
|
|
26186
|
+
);
|
|
26187
|
+
server.registerTool(
|
|
26188
|
+
"create_pr",
|
|
26189
|
+
{
|
|
26190
|
+
description: "Create a GitHub PR for a repo. Uses gh CLI.",
|
|
26191
|
+
inputSchema: {
|
|
26192
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26193
|
+
head: external_exports.string().describe("Source branch name"),
|
|
26194
|
+
base: external_exports.string().describe("Target branch name"),
|
|
26195
|
+
title: external_exports.string().describe("PR title"),
|
|
26196
|
+
body: external_exports.string().optional().describe("PR description body")
|
|
26197
|
+
}
|
|
26198
|
+
},
|
|
26199
|
+
async ({ repo_id, head, base, title, body }) => {
|
|
26200
|
+
try {
|
|
26201
|
+
const repoRes = await apiGet(`/repos/${repo_id}`);
|
|
26202
|
+
const repo = repoRes.data;
|
|
26203
|
+
if (!repo.path) {
|
|
26204
|
+
return {
|
|
26205
|
+
content: [
|
|
26206
|
+
{
|
|
26207
|
+
type: "text",
|
|
26208
|
+
text: "Error: Repo path is not configured."
|
|
26209
|
+
}
|
|
26210
|
+
],
|
|
26211
|
+
isError: true
|
|
26212
|
+
};
|
|
26213
|
+
}
|
|
26214
|
+
const result = await createPR({
|
|
26215
|
+
repoPath: repo.path,
|
|
26216
|
+
head,
|
|
26217
|
+
base,
|
|
26218
|
+
title,
|
|
26219
|
+
body: body ?? ""
|
|
26220
|
+
});
|
|
26221
|
+
return {
|
|
26222
|
+
content: [
|
|
26223
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
26224
|
+
]
|
|
26225
|
+
};
|
|
26226
|
+
} catch (err) {
|
|
26227
|
+
return {
|
|
26228
|
+
content: [
|
|
26229
|
+
{
|
|
26230
|
+
type: "text",
|
|
26231
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26232
|
+
}
|
|
26233
|
+
],
|
|
26234
|
+
isError: true
|
|
26235
|
+
};
|
|
26236
|
+
}
|
|
24483
26237
|
}
|
|
24484
|
-
|
|
24485
|
-
|
|
24486
|
-
|
|
24487
|
-
|
|
24488
|
-
|
|
24489
|
-
|
|
26238
|
+
);
|
|
26239
|
+
server.registerTool(
|
|
26240
|
+
"get_pr_status",
|
|
26241
|
+
{
|
|
26242
|
+
description: "Get the status of a GitHub PR by number.",
|
|
26243
|
+
inputSchema: {
|
|
26244
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26245
|
+
pr_number: external_exports.number().int().describe("The PR number")
|
|
26246
|
+
}
|
|
26247
|
+
},
|
|
26248
|
+
async ({ repo_id, pr_number }) => {
|
|
26249
|
+
try {
|
|
26250
|
+
const repoRes = await apiGet(`/repos/${repo_id}`);
|
|
26251
|
+
const repo = repoRes.data;
|
|
26252
|
+
if (!repo.path) {
|
|
26253
|
+
return {
|
|
26254
|
+
content: [
|
|
26255
|
+
{
|
|
26256
|
+
type: "text",
|
|
26257
|
+
text: "Error: Repo path is not configured."
|
|
26258
|
+
}
|
|
26259
|
+
],
|
|
26260
|
+
isError: true
|
|
26261
|
+
};
|
|
26262
|
+
}
|
|
26263
|
+
const status = await getPRStatus(repo.path, pr_number);
|
|
26264
|
+
return {
|
|
26265
|
+
content: [
|
|
26266
|
+
{ type: "text", text: JSON.stringify(status, null, 2) }
|
|
26267
|
+
]
|
|
26268
|
+
};
|
|
26269
|
+
} catch (err) {
|
|
26270
|
+
return {
|
|
26271
|
+
content: [
|
|
26272
|
+
{
|
|
26273
|
+
type: "text",
|
|
26274
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26275
|
+
}
|
|
26276
|
+
],
|
|
26277
|
+
isError: true
|
|
26278
|
+
};
|
|
24490
26279
|
}
|
|
24491
|
-
const result = await createPR({
|
|
24492
|
-
repoPath: repo.path,
|
|
24493
|
-
head,
|
|
24494
|
-
base,
|
|
24495
|
-
title,
|
|
24496
|
-
body: body ?? ""
|
|
24497
|
-
});
|
|
24498
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
24499
|
-
} catch (err) {
|
|
24500
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24501
26280
|
}
|
|
24502
|
-
|
|
24503
|
-
server.registerTool(
|
|
24504
|
-
|
|
24505
|
-
|
|
24506
|
-
|
|
24507
|
-
|
|
26281
|
+
);
|
|
26282
|
+
server.registerTool(
|
|
26283
|
+
"promote_checkpoint",
|
|
26284
|
+
{
|
|
26285
|
+
description: "Trigger full promotion flow for a checkpoint. Creates merge checklist from templates and GitHub PR (feat branch \u2192 development).",
|
|
26286
|
+
inputSchema: {
|
|
26287
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
26288
|
+
}
|
|
26289
|
+
},
|
|
26290
|
+
async ({ checkpoint_id }) => {
|
|
26291
|
+
try {
|
|
26292
|
+
const result = await promoteCheckpoint(checkpoint_id);
|
|
26293
|
+
return {
|
|
26294
|
+
content: [
|
|
26295
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
26296
|
+
]
|
|
26297
|
+
};
|
|
26298
|
+
} catch (err) {
|
|
26299
|
+
return {
|
|
26300
|
+
content: [
|
|
26301
|
+
{
|
|
26302
|
+
type: "text",
|
|
26303
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26304
|
+
}
|
|
26305
|
+
],
|
|
26306
|
+
isError: true
|
|
26307
|
+
};
|
|
26308
|
+
}
|
|
24508
26309
|
}
|
|
24509
|
-
|
|
24510
|
-
|
|
24511
|
-
|
|
24512
|
-
|
|
24513
|
-
if
|
|
24514
|
-
|
|
26310
|
+
);
|
|
26311
|
+
server.registerTool(
|
|
26312
|
+
"acquire_sync_lock",
|
|
26313
|
+
{
|
|
26314
|
+
description: "Acquire a sync lock for a repo. User-scoped: only one active lock per user. Returns conflict if lock already held on a different repo.",
|
|
26315
|
+
inputSchema: {
|
|
26316
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26317
|
+
locked_by: external_exports.string().describe(
|
|
26318
|
+
"Identifier of the client acquiring the lock (e.g. repo name)"
|
|
26319
|
+
),
|
|
26320
|
+
reason: external_exports.string().optional().describe("Why the lock is being acquired"),
|
|
26321
|
+
ttl_minutes: external_exports.number().int().optional().describe("Lock TTL in minutes (default 10)"),
|
|
26322
|
+
worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID if syncing from a worktree")
|
|
26323
|
+
}
|
|
26324
|
+
},
|
|
26325
|
+
async ({ repo_id, locked_by, reason, ttl_minutes, worktree_id }) => {
|
|
26326
|
+
try {
|
|
26327
|
+
const res = await apiPost("/sync/lock", {
|
|
26328
|
+
repo_id,
|
|
26329
|
+
locked_by,
|
|
26330
|
+
reason: reason ?? void 0,
|
|
26331
|
+
ttl_minutes: ttl_minutes ?? 10,
|
|
26332
|
+
worktree_id: worktree_id ?? void 0
|
|
26333
|
+
});
|
|
26334
|
+
return {
|
|
26335
|
+
content: [
|
|
26336
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26337
|
+
]
|
|
26338
|
+
};
|
|
26339
|
+
} catch (err) {
|
|
26340
|
+
return {
|
|
26341
|
+
content: [
|
|
26342
|
+
{
|
|
26343
|
+
type: "text",
|
|
26344
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26345
|
+
}
|
|
26346
|
+
],
|
|
26347
|
+
isError: true
|
|
26348
|
+
};
|
|
24515
26349
|
}
|
|
24516
|
-
const status = await getPRStatus(repo.path, pr_number);
|
|
24517
|
-
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
24518
|
-
} catch (err) {
|
|
24519
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24520
26350
|
}
|
|
24521
|
-
|
|
24522
|
-
server.registerTool(
|
|
24523
|
-
|
|
24524
|
-
|
|
24525
|
-
|
|
26351
|
+
);
|
|
26352
|
+
server.registerTool(
|
|
26353
|
+
"release_sync_lock",
|
|
26354
|
+
{
|
|
26355
|
+
description: "Release a sync lock for a repo.",
|
|
26356
|
+
inputSchema: {
|
|
26357
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
26358
|
+
}
|
|
26359
|
+
},
|
|
26360
|
+
async ({ repo_id }) => {
|
|
26361
|
+
try {
|
|
26362
|
+
await apiDelete("/sync/lock", { repo_id });
|
|
26363
|
+
return {
|
|
26364
|
+
content: [
|
|
26365
|
+
{
|
|
26366
|
+
type: "text",
|
|
26367
|
+
text: JSON.stringify({ released: true }, null, 2)
|
|
26368
|
+
}
|
|
26369
|
+
]
|
|
26370
|
+
};
|
|
26371
|
+
} catch (err) {
|
|
26372
|
+
return {
|
|
26373
|
+
content: [
|
|
26374
|
+
{
|
|
26375
|
+
type: "text",
|
|
26376
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26377
|
+
}
|
|
26378
|
+
],
|
|
26379
|
+
isError: true
|
|
26380
|
+
};
|
|
26381
|
+
}
|
|
24526
26382
|
}
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
24530
|
-
|
|
24531
|
-
|
|
24532
|
-
|
|
26383
|
+
);
|
|
26384
|
+
server.registerTool(
|
|
26385
|
+
"resolve_sync_conflict",
|
|
26386
|
+
{
|
|
26387
|
+
description: "Resolve a sync conflict. Choose use_local, use_remote, merge (with resolved_content), or skip.",
|
|
26388
|
+
inputSchema: {
|
|
26389
|
+
conflict_id: external_exports.string().uuid().describe("The conflict UUID"),
|
|
26390
|
+
resolution_type: external_exports.enum(["use_local", "use_remote", "merge", "skip"]).describe("Resolution strategy"),
|
|
26391
|
+
resolved_content: external_exports.string().optional().describe("Merged content (required when resolution_type is merge)"),
|
|
26392
|
+
resolved_by_repo_id: external_exports.string().uuid().optional().describe("Repo UUID that resolved the conflict")
|
|
26393
|
+
}
|
|
26394
|
+
},
|
|
26395
|
+
async ({
|
|
26396
|
+
conflict_id,
|
|
26397
|
+
resolution_type,
|
|
26398
|
+
resolved_content,
|
|
26399
|
+
resolved_by_repo_id
|
|
26400
|
+
}) => {
|
|
26401
|
+
try {
|
|
26402
|
+
const res = await apiPatch("/sync/conflicts", {
|
|
26403
|
+
conflict_id,
|
|
26404
|
+
resolution_type,
|
|
26405
|
+
resolved_content: resolved_content ?? void 0,
|
|
26406
|
+
resolved_by_repo_id: resolved_by_repo_id ?? void 0
|
|
26407
|
+
});
|
|
26408
|
+
return {
|
|
26409
|
+
content: [
|
|
26410
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26411
|
+
]
|
|
26412
|
+
};
|
|
26413
|
+
} catch (err) {
|
|
26414
|
+
return {
|
|
26415
|
+
content: [
|
|
26416
|
+
{
|
|
26417
|
+
type: "text",
|
|
26418
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26419
|
+
}
|
|
26420
|
+
],
|
|
26421
|
+
isError: true
|
|
26422
|
+
};
|
|
26423
|
+
}
|
|
24533
26424
|
}
|
|
24534
|
-
|
|
26425
|
+
);
|
|
24535
26426
|
}
|
|
24536
26427
|
var init_write = __esm({
|
|
24537
26428
|
"src/tools/write.ts"() {
|
|
@@ -24715,7 +26606,16 @@ if (arg === "setup") {
|
|
|
24715
26606
|
}
|
|
24716
26607
|
if (arg === "sync") {
|
|
24717
26608
|
const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
24718
|
-
await
|
|
26609
|
+
const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
|
|
26610
|
+
try {
|
|
26611
|
+
await runSync2();
|
|
26612
|
+
} catch (err) {
|
|
26613
|
+
if (err instanceof SyncCancelledError2) {
|
|
26614
|
+
console.log("\n Sync cancelled.\n");
|
|
26615
|
+
process.exit(0);
|
|
26616
|
+
}
|
|
26617
|
+
throw err;
|
|
26618
|
+
}
|
|
24719
26619
|
process.exit(0);
|
|
24720
26620
|
}
|
|
24721
26621
|
if (arg === "help" || arg === "--help" || arg === "-h") {
|