@codebyplan/cli 3.1.0 → 3.3.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 +3096 -1226
- 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.3.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,14 +1098,34 @@ 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
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
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;
|
|
1062
1129
|
} finally {
|
|
1063
1130
|
rl.close();
|
|
1064
1131
|
}
|
|
@@ -1069,6 +1136,9 @@ async function promptChoice(message, options) {
|
|
|
1069
1136
|
const answer = await rl.question(message);
|
|
1070
1137
|
const a = answer.trim().toLowerCase();
|
|
1071
1138
|
return options.includes(a) ? a : options[0];
|
|
1139
|
+
} catch (err) {
|
|
1140
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1141
|
+
throw err;
|
|
1072
1142
|
} finally {
|
|
1073
1143
|
rl.close();
|
|
1074
1144
|
}
|
|
@@ -1088,20 +1158,232 @@ async function confirmEach(items, label) {
|
|
|
1088
1158
|
accepted.push(item);
|
|
1089
1159
|
}
|
|
1090
1160
|
}
|
|
1161
|
+
} catch (err) {
|
|
1162
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1163
|
+
throw err;
|
|
1091
1164
|
} finally {
|
|
1092
1165
|
rl.close();
|
|
1093
1166
|
}
|
|
1094
1167
|
return accepted;
|
|
1095
1168
|
}
|
|
1169
|
+
function parseReviewAction(input) {
|
|
1170
|
+
const a = input.trim().toLowerCase();
|
|
1171
|
+
switch (a) {
|
|
1172
|
+
case "d":
|
|
1173
|
+
case "delete":
|
|
1174
|
+
return { action: "delete", all: false, special: null };
|
|
1175
|
+
case "p":
|
|
1176
|
+
case "pull":
|
|
1177
|
+
return { action: "pull", all: false, special: null };
|
|
1178
|
+
case "s":
|
|
1179
|
+
case "push":
|
|
1180
|
+
return { action: "push", all: false, special: null };
|
|
1181
|
+
case "k":
|
|
1182
|
+
case "skip":
|
|
1183
|
+
return { action: "skip", all: false, special: null };
|
|
1184
|
+
case "da":
|
|
1185
|
+
return { action: "delete", all: true, special: null };
|
|
1186
|
+
case "pa":
|
|
1187
|
+
return { action: "pull", all: true, special: null };
|
|
1188
|
+
case "sa":
|
|
1189
|
+
return { action: "push", all: true, special: null };
|
|
1190
|
+
case "ka":
|
|
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" };
|
|
1198
|
+
case "":
|
|
1199
|
+
return { action: null, all: false, special: "recommended" };
|
|
1200
|
+
// Enter = recommended
|
|
1201
|
+
default:
|
|
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
|
+
}
|
|
1250
|
+
}
|
|
1251
|
+
console.log();
|
|
1252
|
+
}
|
|
1253
|
+
async function promptReviewMode() {
|
|
1254
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1255
|
+
try {
|
|
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;
|
|
1266
|
+
} finally {
|
|
1267
|
+
rl.close();
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
async function reviewFilesOneByOne(items, label, plannedAction, recommendedAction, content) {
|
|
1271
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1272
|
+
const results = [];
|
|
1273
|
+
try {
|
|
1274
|
+
let applyAll = null;
|
|
1275
|
+
for (const item of items) {
|
|
1276
|
+
if (applyAll) {
|
|
1277
|
+
results.push(applyAll);
|
|
1278
|
+
continue;
|
|
1279
|
+
}
|
|
1280
|
+
const planned = plannedAction(item);
|
|
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
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
} catch (err) {
|
|
1309
|
+
if (isAbortError(err)) throw new SyncCancelledError();
|
|
1310
|
+
throw err;
|
|
1311
|
+
} finally {
|
|
1312
|
+
rl.close();
|
|
1313
|
+
}
|
|
1314
|
+
return results;
|
|
1315
|
+
}
|
|
1316
|
+
async function reviewFolder(folderName, items, label, plannedAction, recommendedAction, content) {
|
|
1317
|
+
console.log(`
|
|
1318
|
+
${folderName} (${items.length} files):`);
|
|
1319
|
+
for (const item of items) {
|
|
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})`);
|
|
1324
|
+
}
|
|
1325
|
+
const rl = createInterface2({ input: stdin2, output: stdout2 });
|
|
1326
|
+
try {
|
|
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;
|
|
1367
|
+
} finally {
|
|
1368
|
+
rl.close();
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
var SyncCancelledError;
|
|
1096
1372
|
var init_confirm = __esm({
|
|
1097
1373
|
"src/cli/confirm.ts"() {
|
|
1098
1374
|
"use strict";
|
|
1375
|
+
SyncCancelledError = class extends Error {
|
|
1376
|
+
constructor() {
|
|
1377
|
+
super("Sync cancelled");
|
|
1378
|
+
this.name = "SyncCancelledError";
|
|
1379
|
+
}
|
|
1380
|
+
};
|
|
1099
1381
|
}
|
|
1100
1382
|
});
|
|
1101
1383
|
|
|
1102
1384
|
// src/lib/tech-detect.ts
|
|
1103
|
-
import { readFile as readFile6, readdir as readdir4
|
|
1104
|
-
import { join as join6 } from "node:path";
|
|
1385
|
+
import { readFile as readFile6, access, readdir as readdir4 } from "node:fs/promises";
|
|
1386
|
+
import { join as join6, relative } from "node:path";
|
|
1105
1387
|
async function fileExists(filePath) {
|
|
1106
1388
|
try {
|
|
1107
1389
|
await access(filePath);
|
|
@@ -1110,14 +1392,56 @@ async function fileExists(filePath) {
|
|
|
1110
1392
|
return false;
|
|
1111
1393
|
}
|
|
1112
1394
|
}
|
|
1113
|
-
async function
|
|
1395
|
+
async function discoverMonorepoApps(projectPath) {
|
|
1396
|
+
const apps = [];
|
|
1397
|
+
const patterns = [];
|
|
1398
|
+
try {
|
|
1399
|
+
const raw = await readFile6(join6(projectPath, "pnpm-workspace.yaml"), "utf-8");
|
|
1400
|
+
const matches = raw.match(/^\s*-\s*['"]?([^'"#\n]+)['"]?/gm);
|
|
1401
|
+
if (matches) {
|
|
1402
|
+
for (const m of matches) {
|
|
1403
|
+
const pattern = m.replace(/^\s*-\s*['"]?/, "").replace(/['"]?\s*$/, "").trim();
|
|
1404
|
+
if (pattern) patterns.push(pattern);
|
|
1405
|
+
}
|
|
1406
|
+
}
|
|
1407
|
+
} catch {
|
|
1408
|
+
}
|
|
1409
|
+
if (patterns.length === 0) {
|
|
1410
|
+
try {
|
|
1411
|
+
const raw = await readFile6(join6(projectPath, "package.json"), "utf-8");
|
|
1412
|
+
const pkg = JSON.parse(raw);
|
|
1413
|
+
const ws = Array.isArray(pkg.workspaces) ? pkg.workspaces : pkg.workspaces?.packages;
|
|
1414
|
+
if (ws) patterns.push(...ws);
|
|
1415
|
+
} catch {
|
|
1416
|
+
}
|
|
1417
|
+
}
|
|
1418
|
+
for (const pattern of patterns) {
|
|
1419
|
+
if (pattern.endsWith("/*")) {
|
|
1420
|
+
const dir = pattern.slice(0, -2);
|
|
1421
|
+
const absDir = join6(projectPath, dir);
|
|
1422
|
+
try {
|
|
1423
|
+
const entries = await readdir4(absDir, { withFileTypes: true });
|
|
1424
|
+
for (const entry of entries) {
|
|
1425
|
+
if (entry.isDirectory()) {
|
|
1426
|
+
const relPath = join6(dir, entry.name);
|
|
1427
|
+
const absPath = join6(absDir, entry.name);
|
|
1428
|
+
if (await fileExists(join6(absPath, "package.json"))) {
|
|
1429
|
+
apps.push({ name: entry.name, path: relPath, absPath });
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
} catch {
|
|
1434
|
+
}
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1437
|
+
return apps;
|
|
1438
|
+
}
|
|
1439
|
+
async function detectFromDirectory(dirPath) {
|
|
1440
|
+
const seen = /* @__PURE__ */ new Map();
|
|
1114
1441
|
try {
|
|
1115
|
-
const raw = await readFile6(
|
|
1442
|
+
const raw = await readFile6(join6(dirPath, "package.json"), "utf-8");
|
|
1116
1443
|
const pkg = JSON.parse(raw);
|
|
1117
|
-
const allDeps = {
|
|
1118
|
-
...pkg.dependencies,
|
|
1119
|
-
...pkg.devDependencies
|
|
1120
|
-
};
|
|
1444
|
+
const allDeps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
1121
1445
|
for (const depName of Object.keys(allDeps)) {
|
|
1122
1446
|
const rule = PACKAGE_MAP[depName];
|
|
1123
1447
|
if (rule) {
|
|
@@ -1125,79 +1449,183 @@ async function scanPackageJson(pkgPath, seen) {
|
|
|
1125
1449
|
if (!seen.has(key)) {
|
|
1126
1450
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1127
1451
|
}
|
|
1452
|
+
continue;
|
|
1453
|
+
}
|
|
1454
|
+
for (const { prefix, rule: prefixRule } of PACKAGE_PREFIX_MAP) {
|
|
1455
|
+
if (depName.startsWith(prefix)) {
|
|
1456
|
+
const key = prefixRule.name.toLowerCase();
|
|
1457
|
+
if (!seen.has(key)) {
|
|
1458
|
+
seen.set(key, { name: prefixRule.name, category: prefixRule.category });
|
|
1459
|
+
}
|
|
1460
|
+
break;
|
|
1461
|
+
}
|
|
1128
1462
|
}
|
|
1129
1463
|
}
|
|
1130
1464
|
} catch {
|
|
1131
1465
|
}
|
|
1132
|
-
}
|
|
1133
|
-
async function scanConfigFiles(dir, seen) {
|
|
1134
1466
|
for (const { file, rule } of CONFIG_FILE_MAP) {
|
|
1135
1467
|
const key = rule.name.toLowerCase();
|
|
1136
|
-
if (!seen.has(key) && await fileExists(join6(
|
|
1468
|
+
if (!seen.has(key) && await fileExists(join6(dirPath, file))) {
|
|
1137
1469
|
seen.set(key, { name: rule.name, category: rule.category });
|
|
1138
1470
|
}
|
|
1139
1471
|
}
|
|
1472
|
+
return Array.from(seen.values()).sort((a, b) => {
|
|
1473
|
+
const catCmp = a.category.localeCompare(b.category);
|
|
1474
|
+
if (catCmp !== 0) return catCmp;
|
|
1475
|
+
return a.name.localeCompare(b.name);
|
|
1476
|
+
});
|
|
1140
1477
|
}
|
|
1141
|
-
async function
|
|
1142
|
-
const
|
|
1143
|
-
const
|
|
1144
|
-
|
|
1145
|
-
const
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
for (const entry of entries) {
|
|
1150
|
-
if (entry.isDirectory()) {
|
|
1151
|
-
dirs.push(join6(projectPath, parent, entry.name));
|
|
1152
|
-
}
|
|
1153
|
-
}
|
|
1154
|
-
} catch {
|
|
1478
|
+
async function detectTechStack(projectPath) {
|
|
1479
|
+
const repo = await detectFromDirectory(projectPath);
|
|
1480
|
+
const discoveredApps = await discoverMonorepoApps(projectPath);
|
|
1481
|
+
const apps = [];
|
|
1482
|
+
for (const app of discoveredApps) {
|
|
1483
|
+
const stack = await detectFromDirectory(app.absPath);
|
|
1484
|
+
if (stack.length > 0) {
|
|
1485
|
+
apps.push({ name: app.name, path: app.path, stack });
|
|
1155
1486
|
}
|
|
1156
1487
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
const seen = /* @__PURE__ */ new Map();
|
|
1161
|
-
await scanPackageJson(join6(projectPath, "package.json"), seen);
|
|
1162
|
-
await scanConfigFiles(projectPath, seen);
|
|
1163
|
-
const workspaceDirs = await detectWorkspaceDirs(projectPath);
|
|
1164
|
-
for (const dir of workspaceDirs) {
|
|
1165
|
-
await scanPackageJson(join6(dir, "package.json"), seen);
|
|
1166
|
-
await scanConfigFiles(dir, seen);
|
|
1488
|
+
const flatMap = /* @__PURE__ */ new Map();
|
|
1489
|
+
for (const entry of repo) {
|
|
1490
|
+
flatMap.set(entry.name.toLowerCase(), entry);
|
|
1167
1491
|
}
|
|
1168
|
-
|
|
1492
|
+
for (const app of apps) {
|
|
1493
|
+
for (const entry of app.stack) {
|
|
1494
|
+
const key = entry.name.toLowerCase();
|
|
1495
|
+
if (!flatMap.has(key)) {
|
|
1496
|
+
flatMap.set(key, entry);
|
|
1497
|
+
}
|
|
1498
|
+
}
|
|
1499
|
+
}
|
|
1500
|
+
const flat = Array.from(flatMap.values()).sort((a, b) => {
|
|
1169
1501
|
const catCmp = a.category.localeCompare(b.category);
|
|
1170
1502
|
if (catCmp !== 0) return catCmp;
|
|
1171
1503
|
return a.name.localeCompare(b.name);
|
|
1172
1504
|
});
|
|
1505
|
+
return { repo, apps, flat };
|
|
1173
1506
|
}
|
|
1174
1507
|
function mergeTechStack(remote, detected) {
|
|
1508
|
+
const remoteResult = Array.isArray(remote) ? { repo: remote, apps: [], flat: remote } : remote;
|
|
1175
1509
|
const seen = /* @__PURE__ */ new Map();
|
|
1176
|
-
for (const entry of
|
|
1510
|
+
for (const entry of remoteResult.flat) {
|
|
1177
1511
|
seen.set(entry.name.toLowerCase(), entry);
|
|
1178
1512
|
}
|
|
1179
1513
|
const added = [];
|
|
1180
|
-
for (const entry of detected) {
|
|
1514
|
+
for (const entry of detected.flat) {
|
|
1181
1515
|
const key = entry.name.toLowerCase();
|
|
1182
1516
|
if (!seen.has(key)) {
|
|
1183
1517
|
seen.set(key, entry);
|
|
1184
1518
|
added.push(entry);
|
|
1185
1519
|
}
|
|
1186
1520
|
}
|
|
1187
|
-
const
|
|
1521
|
+
const flat = Array.from(seen.values()).sort((a, b) => {
|
|
1188
1522
|
const catCmp = a.category.localeCompare(b.category);
|
|
1189
1523
|
if (catCmp !== 0) return catCmp;
|
|
1190
1524
|
return a.name.localeCompare(b.name);
|
|
1191
1525
|
});
|
|
1526
|
+
const merged = {
|
|
1527
|
+
repo: detected.repo,
|
|
1528
|
+
apps: detected.apps,
|
|
1529
|
+
flat
|
|
1530
|
+
};
|
|
1192
1531
|
return { merged, added };
|
|
1193
1532
|
}
|
|
1194
1533
|
function parseTechStack(raw) {
|
|
1534
|
+
if (Array.isArray(raw)) {
|
|
1535
|
+
return raw.filter(
|
|
1536
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.category === "string"
|
|
1537
|
+
);
|
|
1538
|
+
}
|
|
1539
|
+
if (typeof raw === "object" && raw !== null && "flat" in raw) {
|
|
1540
|
+
return parseTechStack(raw.flat);
|
|
1541
|
+
}
|
|
1542
|
+
return [];
|
|
1543
|
+
}
|
|
1544
|
+
function parseAppTechStacks(raw) {
|
|
1195
1545
|
if (!Array.isArray(raw)) return [];
|
|
1196
1546
|
return raw.filter(
|
|
1197
|
-
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.
|
|
1198
|
-
)
|
|
1547
|
+
(item) => typeof item === "object" && item !== null && typeof item.name === "string" && typeof item.path === "string" && Array.isArray(item.stack)
|
|
1548
|
+
).map((item) => ({
|
|
1549
|
+
name: item.name,
|
|
1550
|
+
path: item.path,
|
|
1551
|
+
stack: parseTechStack(item.stack)
|
|
1552
|
+
}));
|
|
1553
|
+
}
|
|
1554
|
+
function parseTechStackResult(raw) {
|
|
1555
|
+
if (typeof raw === "object" && raw !== null && !Array.isArray(raw) && "flat" in raw) {
|
|
1556
|
+
const obj = raw;
|
|
1557
|
+
return {
|
|
1558
|
+
repo: parseTechStack(obj.repo),
|
|
1559
|
+
apps: parseAppTechStacks(obj.apps),
|
|
1560
|
+
flat: parseTechStack(obj.flat)
|
|
1561
|
+
};
|
|
1562
|
+
}
|
|
1563
|
+
const flat = parseTechStack(raw);
|
|
1564
|
+
return { repo: flat, apps: [], flat };
|
|
1565
|
+
}
|
|
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 };
|
|
1199
1627
|
}
|
|
1200
|
-
var PACKAGE_MAP, CONFIG_FILE_MAP;
|
|
1628
|
+
var PACKAGE_MAP, PACKAGE_PREFIX_MAP, CONFIG_FILE_MAP, SKIP_DIRS;
|
|
1201
1629
|
var init_tech_detect = __esm({
|
|
1202
1630
|
"src/lib/tech-detect.ts"() {
|
|
1203
1631
|
"use strict";
|
|
@@ -1240,17 +1668,50 @@ var init_tech_detect = __esm({
|
|
|
1240
1668
|
playwright: { name: "Playwright", category: "testing" },
|
|
1241
1669
|
"@playwright/test": { name: "Playwright", category: "testing" },
|
|
1242
1670
|
cypress: { name: "Cypress", category: "testing" },
|
|
1671
|
+
supertest: { name: "Supertest", category: "testing" },
|
|
1243
1672
|
// Build tools
|
|
1244
1673
|
turbo: { name: "Turborepo", category: "build" },
|
|
1245
1674
|
vite: { name: "Vite", category: "build" },
|
|
1246
1675
|
webpack: { name: "Webpack", category: "build" },
|
|
1247
1676
|
esbuild: { name: "esbuild", category: "build" },
|
|
1248
1677
|
rollup: { name: "Rollup", category: "build" },
|
|
1678
|
+
nx: { name: "Nx", category: "build" },
|
|
1679
|
+
lerna: { name: "Lerna", category: "build" },
|
|
1680
|
+
tsup: { name: "tsup", category: "build" },
|
|
1681
|
+
"@swc/core": { name: "SWC", category: "build" },
|
|
1682
|
+
parcel: { name: "Parcel", category: "build" },
|
|
1249
1683
|
// Tools
|
|
1250
1684
|
eslint: { name: "ESLint", category: "tool" },
|
|
1251
1685
|
prettier: { name: "Prettier", category: "tool" },
|
|
1252
|
-
"@biomejs/biome": { name: "Biome", category: "tool" }
|
|
1253
|
-
|
|
1686
|
+
"@biomejs/biome": { name: "Biome", category: "tool" },
|
|
1687
|
+
storybook: { name: "Storybook", category: "tool" },
|
|
1688
|
+
// Component libs
|
|
1689
|
+
"@mui/material": { name: "MUI", category: "component-lib" },
|
|
1690
|
+
"@chakra-ui/react": { name: "Chakra UI", category: "component-lib" },
|
|
1691
|
+
"@mantine/core": { name: "Mantine", category: "component-lib" },
|
|
1692
|
+
// GraphQL
|
|
1693
|
+
graphql: { name: "GraphQL", category: "graphql" },
|
|
1694
|
+
"@apollo/client": { name: "Apollo Client", category: "graphql" },
|
|
1695
|
+
urql: { name: "urql", category: "graphql" },
|
|
1696
|
+
"graphql-request": { name: "graphql-request", category: "graphql" },
|
|
1697
|
+
// Documentation
|
|
1698
|
+
typedoc: { name: "TypeDoc", category: "documentation" },
|
|
1699
|
+
"@docusaurus/core": { name: "Docusaurus", category: "documentation" },
|
|
1700
|
+
vitepress: { name: "VitePress", category: "documentation" },
|
|
1701
|
+
// Code quality
|
|
1702
|
+
husky: { name: "Husky", category: "quality" },
|
|
1703
|
+
"lint-staged": { name: "lint-staged", category: "quality" },
|
|
1704
|
+
commitlint: { name: "commitlint", category: "quality" },
|
|
1705
|
+
"@commitlint/cli": { name: "commitlint", category: "quality" },
|
|
1706
|
+
// Mobile
|
|
1707
|
+
"react-native": { name: "React Native", category: "mobile" },
|
|
1708
|
+
expo: { name: "Expo", category: "mobile" }
|
|
1709
|
+
};
|
|
1710
|
+
PACKAGE_PREFIX_MAP = [
|
|
1711
|
+
{ prefix: "@radix-ui/", rule: { name: "Radix UI", category: "component-lib" } },
|
|
1712
|
+
{ prefix: "@storybook/", rule: { name: "Storybook", category: "tool" } },
|
|
1713
|
+
{ prefix: "@testing-library/", rule: { name: "Testing Library", category: "testing" } }
|
|
1714
|
+
];
|
|
1254
1715
|
CONFIG_FILE_MAP = [
|
|
1255
1716
|
{ file: "tsconfig.json", rule: { name: "TypeScript", category: "language" } },
|
|
1256
1717
|
{ file: "next.config.js", rule: { name: "Next.js", category: "framework" } },
|
|
@@ -1262,120 +1723,26 @@ var init_tech_detect = __esm({
|
|
|
1262
1723
|
{ file: "docker-compose.yml", rule: { name: "Docker", category: "deployment" } },
|
|
1263
1724
|
{ file: "docker-compose.yaml", rule: { name: "Docker", category: "deployment" } },
|
|
1264
1725
|
{ file: "Dockerfile", rule: { name: "Docker", category: "deployment" } },
|
|
1265
|
-
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } }
|
|
1726
|
+
{ file: "vercel.json", rule: { name: "Vercel", category: "deployment" } },
|
|
1727
|
+
{ file: ".storybook/main.js", rule: { name: "Storybook", category: "tool" } },
|
|
1728
|
+
{ file: ".storybook/main.ts", rule: { name: "Storybook", category: "tool" } },
|
|
1729
|
+
{ file: ".storybook/main.mjs", rule: { name: "Storybook", category: "tool" } },
|
|
1730
|
+
{ file: "components.json", rule: { name: "shadcn/ui", category: "component-lib" } },
|
|
1731
|
+
{ file: "nx.json", rule: { name: "Nx", category: "build" } },
|
|
1732
|
+
{ file: "lerna.json", rule: { name: "Lerna", category: "build" } }
|
|
1266
1733
|
];
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1274
|
-
|
|
1275
|
-
|
|
1276
|
-
|
|
1277
|
-
|
|
1278
|
-
|
|
1279
|
-
}
|
|
1280
|
-
}
|
|
1281
|
-
function detectPackageManager(dir) {
|
|
1282
|
-
return (async () => {
|
|
1283
|
-
if (await fileExists2(join7(dir, "pnpm-lock.yaml"))) return "pnpm";
|
|
1284
|
-
if (await fileExists2(join7(dir, "yarn.lock"))) return "yarn";
|
|
1285
|
-
return "npm";
|
|
1286
|
-
})();
|
|
1287
|
-
}
|
|
1288
|
-
function detectFramework(pkg) {
|
|
1289
|
-
const deps = pkg.dependencies ?? {};
|
|
1290
|
-
const devDeps = pkg.devDependencies ?? {};
|
|
1291
|
-
const hasDep = (name) => name in deps || name in devDeps;
|
|
1292
|
-
if (hasDep("next")) return "nextjs";
|
|
1293
|
-
if (hasDep("@tauri-apps/api") || hasDep("@tauri-apps/cli")) return "tauri";
|
|
1294
|
-
if (hasDep("expo")) return "expo";
|
|
1295
|
-
if (hasDep("vite")) return "vite";
|
|
1296
|
-
if (hasDep("express")) return "express";
|
|
1297
|
-
if (hasDep("@nestjs/core")) return "nestjs";
|
|
1298
|
-
return "custom";
|
|
1299
|
-
}
|
|
1300
|
-
function detectPortFromScripts(pkg) {
|
|
1301
|
-
const scripts = pkg.scripts;
|
|
1302
|
-
if (!scripts?.dev) return null;
|
|
1303
|
-
const parts = scripts.dev.split(/\s+/);
|
|
1304
|
-
for (let i = 0; i < parts.length - 1; i++) {
|
|
1305
|
-
if (parts[i] === "--port" || parts[i] === "-p") {
|
|
1306
|
-
const next = parts[i + 1];
|
|
1307
|
-
if (next) {
|
|
1308
|
-
const port = parseInt(next, 10);
|
|
1309
|
-
if (!isNaN(port)) return port;
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
return null;
|
|
1314
|
-
}
|
|
1315
|
-
async function isMonorepo(dir) {
|
|
1316
|
-
return await fileExists2(join7(dir, "turbo.json")) || await fileExists2(join7(dir, "pnpm-workspace.yaml"));
|
|
1317
|
-
}
|
|
1318
|
-
async function detectServers(projectPath) {
|
|
1319
|
-
let pkg;
|
|
1320
|
-
try {
|
|
1321
|
-
const raw = await readFile7(join7(projectPath, "package.json"), "utf-8");
|
|
1322
|
-
pkg = JSON.parse(raw);
|
|
1323
|
-
} catch {
|
|
1324
|
-
return {
|
|
1325
|
-
name: "unknown",
|
|
1326
|
-
isMonorepo: false,
|
|
1327
|
-
packageManager: "npm",
|
|
1328
|
-
servers: []
|
|
1329
|
-
};
|
|
1330
|
-
}
|
|
1331
|
-
const rawName = pkg.name ?? "unknown";
|
|
1332
|
-
const name = rawName.startsWith("@") ? projectPath.split("/").pop() ?? rawName : rawName;
|
|
1333
|
-
const pkgManager = await detectPackageManager(projectPath);
|
|
1334
|
-
const mono = await isMonorepo(projectPath);
|
|
1335
|
-
const servers = [];
|
|
1336
|
-
if (mono) {
|
|
1337
|
-
const appsDir = join7(projectPath, "apps");
|
|
1338
|
-
try {
|
|
1339
|
-
const entries = await readdir5(appsDir, { withFileTypes: true });
|
|
1340
|
-
const sorted = [...entries].sort((a, b) => a.name.localeCompare(b.name));
|
|
1341
|
-
for (const entry of sorted) {
|
|
1342
|
-
if (!entry.isDirectory()) continue;
|
|
1343
|
-
const appPkgPath = join7(appsDir, entry.name, "package.json");
|
|
1344
|
-
try {
|
|
1345
|
-
const appRaw = await readFile7(appPkgPath, "utf-8");
|
|
1346
|
-
const appPkg = JSON.parse(appRaw);
|
|
1347
|
-
const appName = entry.name;
|
|
1348
|
-
const framework = detectFramework(appPkg);
|
|
1349
|
-
const port = detectPortFromScripts(appPkg);
|
|
1350
|
-
let command;
|
|
1351
|
-
switch (pkgManager) {
|
|
1352
|
-
case "pnpm":
|
|
1353
|
-
command = `pnpm --filter ${appName} dev`;
|
|
1354
|
-
break;
|
|
1355
|
-
case "yarn":
|
|
1356
|
-
command = `yarn workspace ${appName} dev`;
|
|
1357
|
-
break;
|
|
1358
|
-
default:
|
|
1359
|
-
command = `npm run dev -w apps/${appName}`;
|
|
1360
|
-
break;
|
|
1361
|
-
}
|
|
1362
|
-
servers.push({ label: appName, port, command, server_type: framework });
|
|
1363
|
-
} catch {
|
|
1364
|
-
}
|
|
1365
|
-
}
|
|
1366
|
-
} catch {
|
|
1367
|
-
}
|
|
1368
|
-
} else {
|
|
1369
|
-
const framework = detectFramework(pkg);
|
|
1370
|
-
const port = detectPortFromScripts(pkg);
|
|
1371
|
-
const command = `${pkgManager} run dev`;
|
|
1372
|
-
servers.push({ label: "dev", port, command, server_type: framework });
|
|
1373
|
-
}
|
|
1374
|
-
return { name, isMonorepo: mono, packageManager: pkgManager, servers };
|
|
1375
|
-
}
|
|
1376
|
-
var init_server_detect = __esm({
|
|
1377
|
-
"src/lib/server-detect.ts"() {
|
|
1378
|
-
"use strict";
|
|
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
|
+
]);
|
|
1379
1746
|
}
|
|
1380
1747
|
});
|
|
1381
1748
|
|
|
@@ -1384,9 +1751,8 @@ var sync_exports = {};
|
|
|
1384
1751
|
__export(sync_exports, {
|
|
1385
1752
|
runSync: () => runSync
|
|
1386
1753
|
});
|
|
1387
|
-
import { readFile as
|
|
1388
|
-
import {
|
|
1389
|
-
import { join as join8, dirname as dirname2 } from "node:path";
|
|
1754
|
+
import { readFile as readFile7, writeFile as writeFile3, mkdir as mkdir2, chmod as chmod2, unlink as unlink2 } from "node:fs/promises";
|
|
1755
|
+
import { join as join7, dirname as dirname2 } from "node:path";
|
|
1390
1756
|
async function runSync() {
|
|
1391
1757
|
const flags = parseFlags(3);
|
|
1392
1758
|
const dryRun = hasFlag("dry-run", 3);
|
|
@@ -1401,21 +1767,67 @@ async function runSync() {
|
|
|
1401
1767
|
if (dryRun) console.log(` Mode: dry-run`);
|
|
1402
1768
|
if (force) console.log(` Mode: force`);
|
|
1403
1769
|
console.log();
|
|
1770
|
+
if (!dryRun) {
|
|
1771
|
+
console.log(" Acquiring sync lock...");
|
|
1772
|
+
try {
|
|
1773
|
+
await apiPost("/sync/lock", {
|
|
1774
|
+
repo_id: repoId,
|
|
1775
|
+
locked_by: `cli-sync`,
|
|
1776
|
+
reason: "Bidirectional sync",
|
|
1777
|
+
ttl_minutes: 10
|
|
1778
|
+
});
|
|
1779
|
+
console.log(" Lock acquired.\n");
|
|
1780
|
+
} catch (lockErr) {
|
|
1781
|
+
const lockStatus = await apiGet("/sync/lock", { repo_id: repoId });
|
|
1782
|
+
if (lockStatus.data.locked && lockStatus.data.lock) {
|
|
1783
|
+
const lock = lockStatus.data.lock;
|
|
1784
|
+
console.log(` Sync locked by ${lock.locked_by} since ${lock.locked_at}.`);
|
|
1785
|
+
console.log(` Expires: ${lock.expires_at}`);
|
|
1786
|
+
console.log(` Use --force to override, or wait for lock to expire.
|
|
1787
|
+
`);
|
|
1788
|
+
if (!force) return;
|
|
1789
|
+
await apiPost("/sync/lock", {
|
|
1790
|
+
repo_id: repoId,
|
|
1791
|
+
locked_by: `cli-sync`,
|
|
1792
|
+
reason: "Bidirectional sync (forced)",
|
|
1793
|
+
ttl_minutes: 10
|
|
1794
|
+
});
|
|
1795
|
+
console.log(" Lock acquired (forced).\n");
|
|
1796
|
+
} else {
|
|
1797
|
+
throw lockErr;
|
|
1798
|
+
}
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
try {
|
|
1802
|
+
await runSyncInner(repoId, projectPath, dryRun, force);
|
|
1803
|
+
} finally {
|
|
1804
|
+
if (!dryRun) {
|
|
1805
|
+
try {
|
|
1806
|
+
await apiDelete("/sync/lock", { repo_id: repoId });
|
|
1807
|
+
} catch {
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
}
|
|
1811
|
+
}
|
|
1812
|
+
async function runSyncInner(repoId, projectPath, dryRun, force) {
|
|
1404
1813
|
console.log(" Reading local and remote state...");
|
|
1405
|
-
const claudeDir =
|
|
1814
|
+
const claudeDir = join7(projectPath, ".claude");
|
|
1406
1815
|
let localFiles = /* @__PURE__ */ new Map();
|
|
1407
1816
|
try {
|
|
1408
1817
|
localFiles = await scanLocalFiles(claudeDir, projectPath);
|
|
1409
1818
|
} catch {
|
|
1410
1819
|
}
|
|
1411
|
-
const [defaultsRes, repoSyncRes, repoRes] = await Promise.all([
|
|
1820
|
+
const [defaultsRes, repoSyncRes, repoRes, syncStateRes] = await Promise.all([
|
|
1412
1821
|
apiGet("/sync/defaults"),
|
|
1413
1822
|
apiGet("/sync/files", { repo_id: repoId }),
|
|
1414
|
-
apiGet(`/repos/${repoId}`)
|
|
1823
|
+
apiGet(`/repos/${repoId}`),
|
|
1824
|
+
apiGet("/sync/state", { repo_id: repoId })
|
|
1415
1825
|
]);
|
|
1826
|
+
const syncStartTime = Date.now();
|
|
1416
1827
|
const repoData = repoRes.data;
|
|
1417
1828
|
const remoteDefaults = flattenSyncData(defaultsRes.data);
|
|
1418
1829
|
const remoteRepoFiles = flattenSyncData(repoSyncRes.data);
|
|
1830
|
+
const syncState = syncStateRes.data;
|
|
1419
1831
|
const remoteFiles = new Map([...remoteDefaults, ...remoteRepoFiles]);
|
|
1420
1832
|
console.log(` Local: ${localFiles.size} files, Remote: ${remoteFiles.size} files
|
|
1421
1833
|
`);
|
|
@@ -1429,21 +1841,27 @@ async function runSync() {
|
|
|
1429
1841
|
key,
|
|
1430
1842
|
displayPath: `${local.type}/${local.category ? local.category + "/" : ""}${local.name}`,
|
|
1431
1843
|
action: "push",
|
|
1844
|
+
recommended: "push",
|
|
1432
1845
|
localContent: local.content,
|
|
1433
1846
|
remoteContent: null,
|
|
1434
1847
|
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
1435
|
-
filePath:
|
|
1848
|
+
filePath: getLocalFilePath(claudeDir, projectPath, { type: local.type, name: local.name, category: local.category }),
|
|
1436
1849
|
type: local.type,
|
|
1437
1850
|
name: local.name,
|
|
1438
1851
|
category: local.category,
|
|
1439
|
-
isHook: local.type === "hook"
|
|
1852
|
+
isHook: local.type === "hook",
|
|
1853
|
+
claudeFileId: null
|
|
1440
1854
|
});
|
|
1441
1855
|
} else if (!local && remote) {
|
|
1442
1856
|
const resolvedContent = substituteVariables(remote.content, repoData);
|
|
1857
|
+
const isDefaultOnly = remoteDefaults.has(key) && !remoteRepoFiles.has(key);
|
|
1858
|
+
const hasSyncedBefore = syncState?.last_synced_at != null;
|
|
1859
|
+
const recommended = !isDefaultOnly && hasSyncedBefore ? "delete" : "pull";
|
|
1443
1860
|
plan.push({
|
|
1444
1861
|
key,
|
|
1445
1862
|
displayPath: `${remote.type}/${remote.category ? remote.category + "/" : ""}${remote.name}`,
|
|
1446
|
-
action:
|
|
1863
|
+
action: recommended,
|
|
1864
|
+
recommended,
|
|
1447
1865
|
localContent: null,
|
|
1448
1866
|
remoteContent: resolvedContent,
|
|
1449
1867
|
pushContent: null,
|
|
@@ -1451,43 +1869,48 @@ async function runSync() {
|
|
|
1451
1869
|
type: remote.type,
|
|
1452
1870
|
name: remote.name,
|
|
1453
1871
|
category: remote.category ?? null,
|
|
1454
|
-
isHook: remote.type === "hook"
|
|
1872
|
+
isHook: remote.type === "hook",
|
|
1873
|
+
claudeFileId: remote.id ?? null
|
|
1455
1874
|
});
|
|
1456
1875
|
} else if (local && remote) {
|
|
1457
1876
|
const resolvedRemote = substituteVariables(remote.content, repoData);
|
|
1458
1877
|
if (local.content === resolvedRemote) {
|
|
1459
1878
|
continue;
|
|
1460
1879
|
}
|
|
1461
|
-
const
|
|
1880
|
+
const lastSyncedAt = syncState?.last_synced_at;
|
|
1462
1881
|
const remoteUpdatedAt = remote.updated_at;
|
|
1463
|
-
const remoteChanged = remoteUpdatedAt &&
|
|
1882
|
+
const remoteChanged = remoteUpdatedAt && lastSyncedAt ? new Date(remoteUpdatedAt) > new Date(lastSyncedAt) : true;
|
|
1464
1883
|
let action;
|
|
1465
1884
|
if (remoteChanged && force) {
|
|
1466
1885
|
action = "pull";
|
|
1467
1886
|
} else if (!remoteChanged) {
|
|
1468
1887
|
action = "push";
|
|
1469
1888
|
} else {
|
|
1470
|
-
action = "
|
|
1889
|
+
action = "conflict";
|
|
1471
1890
|
}
|
|
1472
1891
|
plan.push({
|
|
1473
1892
|
key,
|
|
1474
1893
|
displayPath: `${local.type}/${local.category ? local.category + "/" : ""}${local.name}`,
|
|
1475
1894
|
action,
|
|
1895
|
+
recommended: action === "conflict" ? "pull" : action,
|
|
1476
1896
|
localContent: local.content,
|
|
1477
1897
|
remoteContent: resolvedRemote,
|
|
1478
|
-
pushContent:
|
|
1898
|
+
pushContent: reverseSubstituteVariables(local.content, repoData),
|
|
1479
1899
|
filePath: getLocalFilePath(claudeDir, projectPath, remote),
|
|
1480
1900
|
type: local.type,
|
|
1481
1901
|
name: local.name,
|
|
1482
1902
|
category: local.category,
|
|
1483
|
-
isHook: local.type === "hook"
|
|
1903
|
+
isHook: local.type === "hook",
|
|
1904
|
+
claudeFileId: remote.id ?? null
|
|
1484
1905
|
});
|
|
1485
1906
|
}
|
|
1486
1907
|
}
|
|
1487
1908
|
const pulls = plan.filter((p) => p.action === "pull");
|
|
1488
1909
|
const pushes = plan.filter((p) => p.action === "push");
|
|
1489
|
-
const
|
|
1910
|
+
const conflicts = plan.filter((p) => p.action === "conflict");
|
|
1490
1911
|
const contentPulls = pulls.filter((p) => p.localContent !== null);
|
|
1912
|
+
const dbOnlyPull = plan.filter((p) => p.localContent === null && p.action === "pull");
|
|
1913
|
+
const dbOnlyDelete = plan.filter((p) => p.localContent === null && p.action === "delete");
|
|
1491
1914
|
if (contentPulls.length > 0) {
|
|
1492
1915
|
console.log(` Pull (DB \u2192 local): ${contentPulls.length}`);
|
|
1493
1916
|
for (const p of contentPulls) console.log(` \u2193 ${p.displayPath}`);
|
|
@@ -1496,99 +1919,186 @@ async function runSync() {
|
|
|
1496
1919
|
console.log(` Push (local \u2192 DB): ${pushes.length}`);
|
|
1497
1920
|
for (const p of pushes) console.log(` \u2191 ${p.displayPath}`);
|
|
1498
1921
|
}
|
|
1499
|
-
if (
|
|
1922
|
+
if (dbOnlyPull.length > 0) {
|
|
1923
|
+
console.log(`
|
|
1924
|
+
DB-only (new, will pull): ${dbOnlyPull.length}`);
|
|
1925
|
+
for (const p of dbOnlyPull) console.log(` \u2193 ${p.displayPath}`);
|
|
1926
|
+
}
|
|
1927
|
+
if (dbOnlyDelete.length > 0) {
|
|
1928
|
+
console.log(`
|
|
1929
|
+
DB-only (previously synced, will delete): ${dbOnlyDelete.length}`);
|
|
1930
|
+
for (const p of dbOnlyDelete) console.log(` \u2715 ${p.displayPath}`);
|
|
1931
|
+
}
|
|
1932
|
+
if (conflicts.length > 0) {
|
|
1500
1933
|
console.log(`
|
|
1501
|
-
|
|
1502
|
-
for (const p of
|
|
1934
|
+
Conflicts (both sides changed): ${conflicts.length}`);
|
|
1935
|
+
for (const p of conflicts) console.log(` \u26A0 ${p.displayPath}`);
|
|
1503
1936
|
}
|
|
1504
|
-
if (contentPulls.length === 0 && pushes.length === 0 &&
|
|
1937
|
+
if (contentPulls.length === 0 && pushes.length === 0 && dbOnlyPull.length === 0 && dbOnlyDelete.length === 0 && conflicts.length === 0) {
|
|
1505
1938
|
console.log(" All .claude/ files in sync.");
|
|
1506
1939
|
}
|
|
1507
1940
|
if (plan.length > 0 && !dryRun) {
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1941
|
+
if (!force) {
|
|
1942
|
+
const agreed = await confirmProceed(`
|
|
1943
|
+
Agree with sync? [Y/n] `);
|
|
1944
|
+
if (!agreed) {
|
|
1945
|
+
const mode = await promptReviewMode();
|
|
1946
|
+
const contentProvider = {
|
|
1947
|
+
local: (p) => p.localContent,
|
|
1948
|
+
remote: (p) => p.remoteContent
|
|
1949
|
+
};
|
|
1950
|
+
if (mode === "file") {
|
|
1951
|
+
const actions = await reviewFilesOneByOne(
|
|
1952
|
+
plan,
|
|
1953
|
+
(p) => p.displayPath,
|
|
1954
|
+
(p) => p.action,
|
|
1955
|
+
(p) => p.recommended,
|
|
1956
|
+
contentProvider
|
|
1957
|
+
);
|
|
1958
|
+
for (let i = 0; i < plan.length; i++) {
|
|
1959
|
+
plan[i].action = actions[i];
|
|
1960
|
+
}
|
|
1961
|
+
} else {
|
|
1962
|
+
const groups = groupByType(plan);
|
|
1963
|
+
for (const [typeName, items] of groups) {
|
|
1964
|
+
const actions = await reviewFolder(
|
|
1965
|
+
typeName,
|
|
1966
|
+
items,
|
|
1967
|
+
(p) => p.displayPath,
|
|
1968
|
+
(p) => p.action,
|
|
1969
|
+
(p) => p.recommended,
|
|
1970
|
+
contentProvider
|
|
1971
|
+
);
|
|
1972
|
+
for (let i = 0; i < items.length; i++) {
|
|
1973
|
+
items[i].action = actions[i];
|
|
1974
|
+
}
|
|
1975
|
+
}
|
|
1540
1976
|
}
|
|
1541
1977
|
}
|
|
1542
1978
|
}
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1979
|
+
const toPull = plan.filter((p) => p.action === "pull");
|
|
1980
|
+
const toPush = plan.filter((p) => p.action === "push");
|
|
1981
|
+
const toDelete = plan.filter((p) => p.action === "delete");
|
|
1982
|
+
const skipped = plan.filter((p) => p.action === "skip");
|
|
1983
|
+
if (toPull.length + toPush.length + toDelete.length === 0) {
|
|
1984
|
+
console.log("\n All items skipped \u2014 no changes applied.");
|
|
1985
|
+
} else {
|
|
1986
|
+
for (const p of toPull) {
|
|
1987
|
+
if (p.filePath && p.remoteContent !== null) {
|
|
1988
|
+
await mkdir2(dirname2(p.filePath), { recursive: true });
|
|
1989
|
+
await writeFile3(p.filePath, p.remoteContent, "utf-8");
|
|
1990
|
+
if (p.isHook) await chmod2(p.filePath, 493);
|
|
1991
|
+
}
|
|
1548
1992
|
}
|
|
1549
|
-
|
|
1550
|
-
const toUpsert = pushes.filter((p) => p.pushContent !== null).map((p) => ({
|
|
1551
|
-
type: p.type,
|
|
1552
|
-
name: p.name,
|
|
1553
|
-
category: p.category,
|
|
1554
|
-
content: p.pushContent
|
|
1555
|
-
}));
|
|
1556
|
-
if (toUpsert.length > 0) {
|
|
1557
|
-
await apiPost("/sync/files", {
|
|
1558
|
-
repo_id: repoId,
|
|
1559
|
-
files: toUpsert
|
|
1560
|
-
});
|
|
1561
|
-
}
|
|
1562
|
-
if (toDelete.length > 0) {
|
|
1563
|
-
const deleteKeys = toDelete.map((p) => ({
|
|
1993
|
+
const toUpsert = toPush.filter((p) => p.pushContent !== null).map((p) => ({
|
|
1564
1994
|
type: p.type,
|
|
1565
1995
|
name: p.name,
|
|
1566
|
-
category: p.category
|
|
1996
|
+
category: p.category,
|
|
1997
|
+
content: p.pushContent
|
|
1567
1998
|
}));
|
|
1568
|
-
|
|
1569
|
-
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
|
|
1580
|
-
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1999
|
+
if (toUpsert.length > 0) {
|
|
2000
|
+
await apiPost("/sync/files", {
|
|
2001
|
+
repo_id: repoId,
|
|
2002
|
+
files: toUpsert,
|
|
2003
|
+
changed_by_repo_id: repoId
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
if (toDelete.length > 0) {
|
|
2007
|
+
const deleteKeys = toDelete.map((p) => ({
|
|
2008
|
+
type: p.type,
|
|
2009
|
+
name: p.name,
|
|
2010
|
+
category: p.category
|
|
2011
|
+
}));
|
|
2012
|
+
await apiPost("/sync/files", {
|
|
2013
|
+
repo_id: repoId,
|
|
2014
|
+
delete_keys: deleteKeys
|
|
2015
|
+
});
|
|
2016
|
+
for (const p of toDelete) {
|
|
2017
|
+
if (p.filePath) {
|
|
2018
|
+
try {
|
|
2019
|
+
await unlink2(p.filePath);
|
|
2020
|
+
} catch {
|
|
2021
|
+
}
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
}
|
|
2025
|
+
const unresolvedConflicts = plan.filter(
|
|
2026
|
+
(p) => p.action === "conflict" || p.action === "skip" && p.localContent !== null && p.remoteContent !== null
|
|
2027
|
+
);
|
|
2028
|
+
if (unresolvedConflicts.length > 0) {
|
|
2029
|
+
let stored = 0;
|
|
2030
|
+
for (const p of unresolvedConflicts) {
|
|
2031
|
+
if (p.claudeFileId) {
|
|
2032
|
+
try {
|
|
2033
|
+
await apiPost("/sync/conflicts", {
|
|
2034
|
+
repo_id: repoId,
|
|
2035
|
+
claude_file_id: p.claudeFileId,
|
|
2036
|
+
conflict_type: "both_modified",
|
|
2037
|
+
local_content: p.localContent,
|
|
2038
|
+
remote_content: p.remoteContent
|
|
2039
|
+
});
|
|
2040
|
+
stored++;
|
|
2041
|
+
} catch {
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
}
|
|
2045
|
+
if (stored > 0) {
|
|
2046
|
+
console.log(`
|
|
2047
|
+
${stored} conflict(s) stored in DB for later resolution.`);
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
2050
|
+
const syncDurationMs = Date.now() - syncStartTime;
|
|
2051
|
+
await apiPost("/sync/state", {
|
|
2052
|
+
repo_id: repoId,
|
|
2053
|
+
last_synced_at: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2054
|
+
was_skipped: skipped.length > 0,
|
|
2055
|
+
files_synced_count: toPull.length + toPush.length + toDelete.length,
|
|
2056
|
+
files_pushed: toPush.length,
|
|
2057
|
+
files_pulled: toPull.length,
|
|
2058
|
+
files_deleted: toDelete.length,
|
|
2059
|
+
files_skipped: skipped.length,
|
|
2060
|
+
sync_duration_ms: syncDurationMs,
|
|
2061
|
+
sync_version: getSyncVersion()
|
|
2062
|
+
});
|
|
2063
|
+
const syncTimestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2064
|
+
const fileRepoUpdates = [];
|
|
2065
|
+
for (const p of [...toPull, ...toPush]) {
|
|
2066
|
+
if (p.claudeFileId) {
|
|
2067
|
+
fileRepoUpdates.push({
|
|
2068
|
+
claude_file_id: p.claudeFileId,
|
|
2069
|
+
last_synced_at: syncTimestamp,
|
|
2070
|
+
sync_status: "synced"
|
|
2071
|
+
});
|
|
2072
|
+
}
|
|
2073
|
+
}
|
|
2074
|
+
if (fileRepoUpdates.length > 0) {
|
|
2075
|
+
try {
|
|
2076
|
+
await apiPost("/sync/file-repos", {
|
|
2077
|
+
repo_id: repoId,
|
|
2078
|
+
file_repos: fileRepoUpdates
|
|
2079
|
+
});
|
|
2080
|
+
} catch {
|
|
2081
|
+
}
|
|
2082
|
+
}
|
|
2083
|
+
console.log(
|
|
2084
|
+
`
|
|
2085
|
+
Applied: ${toPull.length} pulled, ${toPush.length} pushed, ${toDelete.length} deleted` + (skipped.length > 0 ? `, ${skipped.length} skipped` : "")
|
|
2086
|
+
);
|
|
2087
|
+
}
|
|
2088
|
+
} else if (dryRun) {
|
|
2089
|
+
console.log("\n (dry-run \u2014 no changes)");
|
|
2090
|
+
}
|
|
2091
|
+
console.log("\n Settings sync...");
|
|
2092
|
+
await syncSettings(claudeDir, projectPath, defaultsRes.data, repoData, dryRun);
|
|
2093
|
+
console.log(" Config sync...");
|
|
2094
|
+
await syncConfig(repoId, projectPath, dryRun);
|
|
2095
|
+
console.log(" Tech stack...");
|
|
2096
|
+
await syncTechStack(repoId, projectPath, dryRun);
|
|
2097
|
+
console.log("\n Sync complete.\n");
|
|
2098
|
+
}
|
|
2099
|
+
async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun) {
|
|
2100
|
+
const settingsPath = join7(claudeDir, "settings.json");
|
|
2101
|
+
const globalSettingsFiles = syncData.global_settings ?? [];
|
|
1592
2102
|
let globalSettings = {};
|
|
1593
2103
|
for (const gf of globalSettingsFiles) {
|
|
1594
2104
|
const parsed = JSON.parse(substituteVariables(gf.content, repoData));
|
|
@@ -1600,11 +2110,11 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1600
2110
|
repoSettings = JSON.parse(substituteVariables(rf.content, repoData));
|
|
1601
2111
|
}
|
|
1602
2112
|
const combinedTemplate = mergeGlobalAndRepoSettings(globalSettings, repoSettings);
|
|
1603
|
-
const hooksDir =
|
|
2113
|
+
const hooksDir = join7(projectPath, ".claude", "hooks");
|
|
1604
2114
|
const discovered = await discoverHooks(hooksDir);
|
|
1605
2115
|
let localSettings = {};
|
|
1606
2116
|
try {
|
|
1607
|
-
const raw = await
|
|
2117
|
+
const raw = await readFile7(settingsPath, "utf-8");
|
|
1608
2118
|
localSettings = JSON.parse(raw);
|
|
1609
2119
|
} catch {
|
|
1610
2120
|
}
|
|
@@ -1619,7 +2129,7 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1619
2129
|
const mergedContent = JSON.stringify(merged, null, 2) + "\n";
|
|
1620
2130
|
let currentContent = "";
|
|
1621
2131
|
try {
|
|
1622
|
-
currentContent = await
|
|
2132
|
+
currentContent = await readFile7(settingsPath, "utf-8");
|
|
1623
2133
|
} catch {
|
|
1624
2134
|
}
|
|
1625
2135
|
if (currentContent === mergedContent) {
|
|
@@ -1635,10 +2145,10 @@ async function syncSettings(claudeDir, projectPath, syncData, repoData, dryRun)
|
|
|
1635
2145
|
console.log(" Updated settings.json");
|
|
1636
2146
|
}
|
|
1637
2147
|
async function syncConfig(repoId, projectPath, dryRun) {
|
|
1638
|
-
const configPath =
|
|
2148
|
+
const configPath = join7(projectPath, ".codebyplan.json");
|
|
1639
2149
|
let currentConfig = {};
|
|
1640
2150
|
try {
|
|
1641
|
-
const raw = await
|
|
2151
|
+
const raw = await readFile7(configPath, "utf-8");
|
|
1642
2152
|
currentConfig = JSON.parse(raw);
|
|
1643
2153
|
} catch {
|
|
1644
2154
|
currentConfig = { repo_id: repoId };
|
|
@@ -1648,14 +2158,18 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
1648
2158
|
let portAllocations = [];
|
|
1649
2159
|
try {
|
|
1650
2160
|
const portsRes = await apiGet(`/port-allocations`, { repo_id: repoId });
|
|
1651
|
-
|
|
2161
|
+
const allAllocations = portsRes.data ?? [];
|
|
2162
|
+
const worktreeId2 = currentConfig.worktree_id;
|
|
2163
|
+
portAllocations = worktreeId2 ? allAllocations.filter((a) => a.worktree_id === worktreeId2) : allAllocations.filter((a) => !a.worktree_id);
|
|
1652
2164
|
} catch {
|
|
1653
2165
|
}
|
|
2166
|
+
const worktreeId = currentConfig.worktree_id;
|
|
2167
|
+
const matchingAlloc = portAllocations[0];
|
|
1654
2168
|
const newConfig = {
|
|
1655
2169
|
repo_id: repoId,
|
|
1656
|
-
...
|
|
1657
|
-
server_port: repo.server_port,
|
|
1658
|
-
server_type: repo.server_type,
|
|
2170
|
+
...worktreeId ? { worktree_id: worktreeId } : {},
|
|
2171
|
+
server_port: worktreeId && matchingAlloc ? matchingAlloc.port : repo.server_port,
|
|
2172
|
+
server_type: worktreeId && matchingAlloc ? matchingAlloc.server_type : repo.server_type,
|
|
1659
2173
|
git_branch: repo.git_branch ?? "development",
|
|
1660
2174
|
auto_push_enabled: repo.auto_push_enabled,
|
|
1661
2175
|
...portAllocations.length > 0 ? { port_allocations: portAllocations } : {}
|
|
@@ -1675,25 +2189,57 @@ async function syncConfig(repoId, projectPath, dryRun) {
|
|
|
1675
2189
|
}
|
|
1676
2190
|
async function syncTechStack(repoId, projectPath, dryRun) {
|
|
1677
2191
|
try {
|
|
1678
|
-
const
|
|
1679
|
-
if (
|
|
1680
|
-
console.log(" No
|
|
2192
|
+
const { dependencies } = await scanAllDependencies(projectPath);
|
|
2193
|
+
if (dependencies.length === 0) {
|
|
2194
|
+
console.log(" No dependencies found.");
|
|
1681
2195
|
return;
|
|
1682
2196
|
}
|
|
1683
|
-
const
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
2197
|
+
const sourcePaths = new Set(dependencies.map((d) => d.source_path));
|
|
2198
|
+
console.log(` ${dependencies.length} dependencies from ${sourcePaths.size} package.json file${sourcePaths.size !== 1 ? "s" : ""}`);
|
|
2199
|
+
if (!dryRun) {
|
|
2200
|
+
const result = await apiPost(
|
|
2201
|
+
`/repos/${repoId}/tech-stack`,
|
|
2202
|
+
{ dependencies }
|
|
2203
|
+
);
|
|
2204
|
+
if (result.data.stale_removed > 0) {
|
|
2205
|
+
console.log(` ${result.data.stale_removed} stale dependencies removed`);
|
|
2206
|
+
}
|
|
1689
2207
|
}
|
|
1690
|
-
|
|
1691
|
-
|
|
2208
|
+
const detected = await detectTechStack(projectPath);
|
|
2209
|
+
if (detected.flat.length > 0) {
|
|
2210
|
+
const repoRes = await apiGet(`/repos/${repoId}`);
|
|
2211
|
+
const remote = parseTechStackResult(repoRes.data.tech_stack);
|
|
2212
|
+
const { merged, added } = mergeTechStack(remote, detected);
|
|
2213
|
+
if (added.length > 0) {
|
|
2214
|
+
console.log(` ${added.length} new tech entries`);
|
|
2215
|
+
if (!dryRun) {
|
|
2216
|
+
await apiPut(`/repos/${repoId}`, { tech_stack: merged });
|
|
2217
|
+
}
|
|
2218
|
+
}
|
|
1692
2219
|
}
|
|
1693
2220
|
} catch {
|
|
1694
2221
|
console.log(" Tech stack detection skipped.");
|
|
1695
2222
|
}
|
|
1696
2223
|
}
|
|
2224
|
+
function groupByType(items) {
|
|
2225
|
+
const groups = /* @__PURE__ */ new Map();
|
|
2226
|
+
const typeLabels = {
|
|
2227
|
+
command: "Commands",
|
|
2228
|
+
agent: "Agents",
|
|
2229
|
+
skill: "Skills",
|
|
2230
|
+
rule: "Rules",
|
|
2231
|
+
hook: "Hooks",
|
|
2232
|
+
template: "Templates",
|
|
2233
|
+
settings: "Settings"
|
|
2234
|
+
};
|
|
2235
|
+
for (const item of items) {
|
|
2236
|
+
const label = typeLabels[item.type] ?? item.type;
|
|
2237
|
+
const group = groups.get(label) ?? [];
|
|
2238
|
+
group.push(item);
|
|
2239
|
+
groups.set(label, group);
|
|
2240
|
+
}
|
|
2241
|
+
return groups;
|
|
2242
|
+
}
|
|
1697
2243
|
function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
1698
2244
|
const typeConfig2 = {
|
|
1699
2245
|
command: { dir: "commands", ext: ".md" },
|
|
@@ -1705,15 +2251,22 @@ function getLocalFilePath(claudeDir, projectPath, remote) {
|
|
|
1705
2251
|
claude_md: { dir: "", ext: "" },
|
|
1706
2252
|
settings: { dir: "", ext: "" }
|
|
1707
2253
|
};
|
|
1708
|
-
if (remote.type === "claude_md") return
|
|
1709
|
-
if (remote.type === "settings") return
|
|
2254
|
+
if (remote.type === "claude_md") return join7(projectPath, "CLAUDE.md");
|
|
2255
|
+
if (remote.type === "settings") return join7(claudeDir, "settings.json");
|
|
1710
2256
|
const cfg = typeConfig2[remote.type];
|
|
1711
|
-
if (!cfg) return
|
|
1712
|
-
const typeDir = remote.type === "command" ?
|
|
1713
|
-
if (cfg.subfolder) return
|
|
1714
|
-
if (remote.type === "command" && remote.category) return
|
|
1715
|
-
if (remote.type === "template") return
|
|
1716
|
-
return
|
|
2257
|
+
if (!cfg) return join7(claudeDir, remote.name);
|
|
2258
|
+
const typeDir = remote.type === "command" ? join7(claudeDir, cfg.dir, "cbp") : join7(claudeDir, cfg.dir);
|
|
2259
|
+
if (cfg.subfolder) return join7(typeDir, remote.name, `${cfg.subfolder}${cfg.ext}`);
|
|
2260
|
+
if (remote.type === "command" && remote.category) return join7(typeDir, remote.category, `${remote.name}${cfg.ext}`);
|
|
2261
|
+
if (remote.type === "template") return join7(typeDir, remote.name);
|
|
2262
|
+
return join7(typeDir, `${remote.name}${cfg.ext}`);
|
|
2263
|
+
}
|
|
2264
|
+
function getSyncVersion() {
|
|
2265
|
+
try {
|
|
2266
|
+
return "3.3.0";
|
|
2267
|
+
} catch {
|
|
2268
|
+
return "unknown";
|
|
2269
|
+
}
|
|
1717
2270
|
}
|
|
1718
2271
|
function flattenSyncData(data) {
|
|
1719
2272
|
const result = /* @__PURE__ */ new Map();
|
|
@@ -1731,6 +2284,7 @@ function flattenSyncData(data) {
|
|
|
1731
2284
|
for (const file of files) {
|
|
1732
2285
|
const key = compositeKey(typeName, file.name, file.category ?? null);
|
|
1733
2286
|
result.set(key, {
|
|
2287
|
+
id: file.id,
|
|
1734
2288
|
type: typeName,
|
|
1735
2289
|
name: file.name,
|
|
1736
2290
|
content: file.content,
|
|
@@ -1741,99 +2295,6 @@ function flattenSyncData(data) {
|
|
|
1741
2295
|
}
|
|
1742
2296
|
return result;
|
|
1743
2297
|
}
|
|
1744
|
-
async function syncDesktopConfigs(repoId, projectPath, repoData, dryRun) {
|
|
1745
|
-
try {
|
|
1746
|
-
const cbpDir = join8(homedir2(), ".codebyplan");
|
|
1747
|
-
const configsPath = join8(cbpDir, "server-configs.json");
|
|
1748
|
-
let configFile = { repos: [] };
|
|
1749
|
-
try {
|
|
1750
|
-
const raw = await readFile8(configsPath, "utf-8");
|
|
1751
|
-
configFile = JSON.parse(raw);
|
|
1752
|
-
} catch {
|
|
1753
|
-
}
|
|
1754
|
-
const detection = await detectServers(projectPath);
|
|
1755
|
-
let portAllocations = [];
|
|
1756
|
-
try {
|
|
1757
|
-
const localConfigRaw = await readFile8(join8(projectPath, ".codebyplan.json"), "utf-8");
|
|
1758
|
-
const localConfig = JSON.parse(localConfigRaw);
|
|
1759
|
-
portAllocations = localConfig.port_allocations ?? [];
|
|
1760
|
-
} catch {
|
|
1761
|
-
}
|
|
1762
|
-
const rootAllocations = portAllocations.filter((a) => !a.worktree_id);
|
|
1763
|
-
const worktreeAllocations = portAllocations.filter((a) => a.worktree_id);
|
|
1764
|
-
const matchDetected = (alloc) => {
|
|
1765
|
-
let matched = detection.servers.find(
|
|
1766
|
-
(s) => alloc.label.toLowerCase().includes(s.label.toLowerCase())
|
|
1767
|
-
);
|
|
1768
|
-
if (!matched) {
|
|
1769
|
-
matched = detection.servers.find(
|
|
1770
|
-
(s) => s.server_type === alloc.server_type
|
|
1771
|
-
);
|
|
1772
|
-
}
|
|
1773
|
-
return {
|
|
1774
|
-
label: alloc.label,
|
|
1775
|
-
port: alloc.port,
|
|
1776
|
-
command: matched?.command ?? "",
|
|
1777
|
-
server_type: alloc.server_type,
|
|
1778
|
-
auto_start: alloc.auto_start
|
|
1779
|
-
};
|
|
1780
|
-
};
|
|
1781
|
-
let servers;
|
|
1782
|
-
if (rootAllocations.length > 0) {
|
|
1783
|
-
servers = rootAllocations.map(matchDetected);
|
|
1784
|
-
} else {
|
|
1785
|
-
servers = detection.servers.map((s) => ({
|
|
1786
|
-
label: s.label,
|
|
1787
|
-
port: s.port,
|
|
1788
|
-
command: s.command,
|
|
1789
|
-
server_type: s.server_type,
|
|
1790
|
-
auto_start: "off"
|
|
1791
|
-
}));
|
|
1792
|
-
}
|
|
1793
|
-
const worktreeGroups = /* @__PURE__ */ new Map();
|
|
1794
|
-
for (const alloc of worktreeAllocations) {
|
|
1795
|
-
const wId = alloc.worktree_id;
|
|
1796
|
-
if (!worktreeGroups.has(wId)) worktreeGroups.set(wId, []);
|
|
1797
|
-
worktreeGroups.get(wId).push(alloc);
|
|
1798
|
-
}
|
|
1799
|
-
const worktrees = Array.from(worktreeGroups.entries()).map(
|
|
1800
|
-
([worktreeId, allocs]) => {
|
|
1801
|
-
const firstLabel = allocs[0]?.label ?? "";
|
|
1802
|
-
const parenMatch = firstLabel.match(/\(([^)]+)\)/);
|
|
1803
|
-
const worktreeName = parenMatch?.[1] ?? worktreeId;
|
|
1804
|
-
return {
|
|
1805
|
-
name: worktreeName,
|
|
1806
|
-
path: "",
|
|
1807
|
-
// Path is managed by the desktop app
|
|
1808
|
-
cloud_id: worktreeId,
|
|
1809
|
-
servers: allocs.map(matchDetected)
|
|
1810
|
-
};
|
|
1811
|
-
}
|
|
1812
|
-
);
|
|
1813
|
-
const repoEntry = {
|
|
1814
|
-
name: repoData.name,
|
|
1815
|
-
path: projectPath,
|
|
1816
|
-
servers,
|
|
1817
|
-
cloud_id: repoId,
|
|
1818
|
-
...worktrees.length > 0 ? { worktrees } : {}
|
|
1819
|
-
};
|
|
1820
|
-
const existingIndex = configFile.repos.findIndex((r) => r.cloud_id === repoId);
|
|
1821
|
-
if (existingIndex >= 0) {
|
|
1822
|
-
configFile.repos[existingIndex] = repoEntry;
|
|
1823
|
-
} else {
|
|
1824
|
-
configFile.repos.push(repoEntry);
|
|
1825
|
-
}
|
|
1826
|
-
if (dryRun) {
|
|
1827
|
-
console.log(" Desktop server configs would be updated (dry-run).");
|
|
1828
|
-
return;
|
|
1829
|
-
}
|
|
1830
|
-
await mkdir2(cbpDir, { recursive: true });
|
|
1831
|
-
await writeFile3(configsPath, JSON.stringify(configFile, null, 2) + "\n", "utf-8");
|
|
1832
|
-
console.log(` Updated server-configs.json (${servers.length} server(s), ${worktrees.length} worktree(s))`);
|
|
1833
|
-
} catch {
|
|
1834
|
-
console.log(" Desktop server config sync skipped.");
|
|
1835
|
-
}
|
|
1836
|
-
}
|
|
1837
2298
|
var init_sync = __esm({
|
|
1838
2299
|
"src/cli/sync.ts"() {
|
|
1839
2300
|
"use strict";
|
|
@@ -1843,7 +2304,6 @@ var init_sync = __esm({
|
|
|
1843
2304
|
init_api();
|
|
1844
2305
|
init_variables();
|
|
1845
2306
|
init_tech_detect();
|
|
1846
|
-
init_server_detect();
|
|
1847
2307
|
init_settings_merge();
|
|
1848
2308
|
init_hook_registry();
|
|
1849
2309
|
}
|
|
@@ -18585,49 +19045,49 @@ var require_fast_uri = __commonJS({
|
|
|
18585
19045
|
schemelessOptions.skipEscape = true;
|
|
18586
19046
|
return serialize(resolved, schemelessOptions);
|
|
18587
19047
|
}
|
|
18588
|
-
function resolveComponent(base,
|
|
19048
|
+
function resolveComponent(base, relative2, options, skipNormalization) {
|
|
18589
19049
|
const target = {};
|
|
18590
19050
|
if (!skipNormalization) {
|
|
18591
19051
|
base = parse3(serialize(base, options), options);
|
|
18592
|
-
|
|
19052
|
+
relative2 = parse3(serialize(relative2, options), options);
|
|
18593
19053
|
}
|
|
18594
19054
|
options = options || {};
|
|
18595
|
-
if (!options.tolerant &&
|
|
18596
|
-
target.scheme =
|
|
18597
|
-
target.userinfo =
|
|
18598
|
-
target.host =
|
|
18599
|
-
target.port =
|
|
18600
|
-
target.path = removeDotSegments(
|
|
18601
|
-
target.query =
|
|
19055
|
+
if (!options.tolerant && relative2.scheme) {
|
|
19056
|
+
target.scheme = relative2.scheme;
|
|
19057
|
+
target.userinfo = relative2.userinfo;
|
|
19058
|
+
target.host = relative2.host;
|
|
19059
|
+
target.port = relative2.port;
|
|
19060
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
19061
|
+
target.query = relative2.query;
|
|
18602
19062
|
} else {
|
|
18603
|
-
if (
|
|
18604
|
-
target.userinfo =
|
|
18605
|
-
target.host =
|
|
18606
|
-
target.port =
|
|
18607
|
-
target.path = removeDotSegments(
|
|
18608
|
-
target.query =
|
|
19063
|
+
if (relative2.userinfo !== void 0 || relative2.host !== void 0 || relative2.port !== void 0) {
|
|
19064
|
+
target.userinfo = relative2.userinfo;
|
|
19065
|
+
target.host = relative2.host;
|
|
19066
|
+
target.port = relative2.port;
|
|
19067
|
+
target.path = removeDotSegments(relative2.path || "");
|
|
19068
|
+
target.query = relative2.query;
|
|
18609
19069
|
} else {
|
|
18610
|
-
if (!
|
|
19070
|
+
if (!relative2.path) {
|
|
18611
19071
|
target.path = base.path;
|
|
18612
|
-
if (
|
|
18613
|
-
target.query =
|
|
19072
|
+
if (relative2.query !== void 0) {
|
|
19073
|
+
target.query = relative2.query;
|
|
18614
19074
|
} else {
|
|
18615
19075
|
target.query = base.query;
|
|
18616
19076
|
}
|
|
18617
19077
|
} else {
|
|
18618
|
-
if (
|
|
18619
|
-
target.path = removeDotSegments(
|
|
19078
|
+
if (relative2.path[0] === "/") {
|
|
19079
|
+
target.path = removeDotSegments(relative2.path);
|
|
18620
19080
|
} else {
|
|
18621
19081
|
if ((base.userinfo !== void 0 || base.host !== void 0 || base.port !== void 0) && !base.path) {
|
|
18622
|
-
target.path = "/" +
|
|
19082
|
+
target.path = "/" + relative2.path;
|
|
18623
19083
|
} else if (!base.path) {
|
|
18624
|
-
target.path =
|
|
19084
|
+
target.path = relative2.path;
|
|
18625
19085
|
} else {
|
|
18626
|
-
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) +
|
|
19086
|
+
target.path = base.path.slice(0, base.path.lastIndexOf("/") + 1) + relative2.path;
|
|
18627
19087
|
}
|
|
18628
19088
|
target.path = removeDotSegments(target.path);
|
|
18629
19089
|
}
|
|
18630
|
-
target.query =
|
|
19090
|
+
target.query = relative2.query;
|
|
18631
19091
|
}
|
|
18632
19092
|
target.userinfo = base.userinfo;
|
|
18633
19093
|
target.host = base.host;
|
|
@@ -18635,7 +19095,7 @@ var require_fast_uri = __commonJS({
|
|
|
18635
19095
|
}
|
|
18636
19096
|
target.scheme = base.scheme;
|
|
18637
19097
|
}
|
|
18638
|
-
target.fragment =
|
|
19098
|
+
target.fragment = relative2.fragment;
|
|
18639
19099
|
return target;
|
|
18640
19100
|
}
|
|
18641
19101
|
function equal(uriA, uriB, options) {
|
|
@@ -23385,258 +23845,689 @@ var init_stdio2 = __esm({
|
|
|
23385
23845
|
|
|
23386
23846
|
// src/tools/read.ts
|
|
23387
23847
|
function registerReadTools(server) {
|
|
23388
|
-
server.registerTool(
|
|
23389
|
-
|
|
23390
|
-
|
|
23391
|
-
|
|
23392
|
-
|
|
23393
|
-
|
|
23394
|
-
|
|
23395
|
-
|
|
23396
|
-
|
|
23397
|
-
|
|
23398
|
-
|
|
23399
|
-
|
|
23400
|
-
|
|
23401
|
-
|
|
23402
|
-
|
|
23403
|
-
|
|
23404
|
-
|
|
23405
|
-
|
|
23406
|
-
|
|
23407
|
-
|
|
23408
|
-
|
|
23409
|
-
|
|
23410
|
-
|
|
23411
|
-
|
|
23412
|
-
});
|
|
23413
|
-
const plan = res.data[0] ?? null;
|
|
23414
|
-
if (!plan) {
|
|
23415
|
-
return { content: [{ type: "text", text: "Error: No work plan found for the specified repo, week, and year" }], isError: true };
|
|
23848
|
+
server.registerTool(
|
|
23849
|
+
"get_repos",
|
|
23850
|
+
{
|
|
23851
|
+
description: "List all repos.",
|
|
23852
|
+
inputSchema: {}
|
|
23853
|
+
},
|
|
23854
|
+
async () => {
|
|
23855
|
+
try {
|
|
23856
|
+
const res = await apiGet("/repos");
|
|
23857
|
+
return {
|
|
23858
|
+
content: [
|
|
23859
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
23860
|
+
]
|
|
23861
|
+
};
|
|
23862
|
+
} catch (err) {
|
|
23863
|
+
return {
|
|
23864
|
+
content: [
|
|
23865
|
+
{
|
|
23866
|
+
type: "text",
|
|
23867
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23868
|
+
}
|
|
23869
|
+
],
|
|
23870
|
+
isError: true
|
|
23871
|
+
};
|
|
23416
23872
|
}
|
|
23417
|
-
return { content: [{ type: "text", text: JSON.stringify(plan, null, 2) }] };
|
|
23418
|
-
} catch (err) {
|
|
23419
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23420
|
-
}
|
|
23421
|
-
});
|
|
23422
|
-
server.registerTool("get_checkpoints", {
|
|
23423
|
-
description: "List checkpoints for a repo. Optionally filter by status and/or worktree assignment.",
|
|
23424
|
-
inputSchema: {
|
|
23425
|
-
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23426
|
-
status: external_exports.string().optional().describe("Filter by status (draft, pending, active, completed)"),
|
|
23427
|
-
worktree_id: external_exports.string().uuid().optional().describe("Filter by worktree UUID assignment")
|
|
23428
|
-
}
|
|
23429
|
-
}, async ({ repo_id, status, worktree_id }) => {
|
|
23430
|
-
try {
|
|
23431
|
-
const res = await apiGet("/checkpoints", { repo_id, status, worktree_id });
|
|
23432
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23433
|
-
} catch (err) {
|
|
23434
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23435
|
-
}
|
|
23436
|
-
});
|
|
23437
|
-
server.registerTool("get_tasks", {
|
|
23438
|
-
description: "List tasks for a checkpoint. Optionally filter by status.",
|
|
23439
|
-
inputSchema: {
|
|
23440
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
23441
|
-
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
23442
|
-
}
|
|
23443
|
-
}, async ({ checkpoint_id, status }) => {
|
|
23444
|
-
try {
|
|
23445
|
-
const res = await apiGet("/tasks", { checkpoint_id, status });
|
|
23446
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23447
|
-
} catch (err) {
|
|
23448
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23449
|
-
}
|
|
23450
|
-
});
|
|
23451
|
-
server.registerTool("get_rounds", {
|
|
23452
|
-
description: "List rounds for a task. Optionally filter by status.",
|
|
23453
|
-
inputSchema: {
|
|
23454
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
23455
|
-
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
23456
23873
|
}
|
|
23457
|
-
|
|
23458
|
-
|
|
23459
|
-
|
|
23460
|
-
|
|
23461
|
-
|
|
23462
|
-
|
|
23874
|
+
);
|
|
23875
|
+
server.registerTool(
|
|
23876
|
+
"get_work_plan",
|
|
23877
|
+
{
|
|
23878
|
+
description: "Get the work plan for a specific repo, week, and year.",
|
|
23879
|
+
inputSchema: {
|
|
23880
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23881
|
+
week_number: external_exports.number().int().min(1).max(53).describe("ISO week number"),
|
|
23882
|
+
year: external_exports.number().int().describe("Year (e.g. 2026)")
|
|
23883
|
+
}
|
|
23884
|
+
},
|
|
23885
|
+
async ({ repo_id, week_number, year }) => {
|
|
23886
|
+
try {
|
|
23887
|
+
const res = await apiGet("/work-plans", {
|
|
23888
|
+
repo_id,
|
|
23889
|
+
week_number: String(week_number),
|
|
23890
|
+
year: String(year)
|
|
23891
|
+
});
|
|
23892
|
+
const plan = res.data[0] ?? null;
|
|
23893
|
+
if (!plan) {
|
|
23894
|
+
return {
|
|
23895
|
+
content: [
|
|
23896
|
+
{
|
|
23897
|
+
type: "text",
|
|
23898
|
+
text: "Error: No work plan found for the specified repo, week, and year"
|
|
23899
|
+
}
|
|
23900
|
+
],
|
|
23901
|
+
isError: true
|
|
23902
|
+
};
|
|
23903
|
+
}
|
|
23904
|
+
return {
|
|
23905
|
+
content: [
|
|
23906
|
+
{ type: "text", text: JSON.stringify(plan, null, 2) }
|
|
23907
|
+
]
|
|
23908
|
+
};
|
|
23909
|
+
} catch (err) {
|
|
23910
|
+
return {
|
|
23911
|
+
content: [
|
|
23912
|
+
{
|
|
23913
|
+
type: "text",
|
|
23914
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23915
|
+
}
|
|
23916
|
+
],
|
|
23917
|
+
isError: true
|
|
23918
|
+
};
|
|
23919
|
+
}
|
|
23463
23920
|
}
|
|
23464
|
-
|
|
23465
|
-
server.registerTool(
|
|
23466
|
-
|
|
23467
|
-
|
|
23468
|
-
|
|
23469
|
-
|
|
23921
|
+
);
|
|
23922
|
+
server.registerTool(
|
|
23923
|
+
"get_checkpoints",
|
|
23924
|
+
{
|
|
23925
|
+
description: "List checkpoints for a repo. Optionally filter by status and/or worktree assignment.",
|
|
23926
|
+
inputSchema: {
|
|
23927
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
23928
|
+
status: external_exports.string().optional().describe("Filter by status (draft, pending, active, completed)"),
|
|
23929
|
+
worktree_id: external_exports.string().uuid().optional().describe("Filter by worktree UUID assignment")
|
|
23930
|
+
}
|
|
23931
|
+
},
|
|
23932
|
+
async ({ repo_id, status, worktree_id }) => {
|
|
23933
|
+
try {
|
|
23934
|
+
const res = await apiGet("/checkpoints", {
|
|
23935
|
+
repo_id,
|
|
23936
|
+
status,
|
|
23937
|
+
worktree_id
|
|
23938
|
+
});
|
|
23939
|
+
return {
|
|
23940
|
+
content: [
|
|
23941
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
23942
|
+
]
|
|
23943
|
+
};
|
|
23944
|
+
} catch (err) {
|
|
23945
|
+
return {
|
|
23946
|
+
content: [
|
|
23947
|
+
{
|
|
23948
|
+
type: "text",
|
|
23949
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23950
|
+
}
|
|
23951
|
+
],
|
|
23952
|
+
isError: true
|
|
23953
|
+
};
|
|
23954
|
+
}
|
|
23470
23955
|
}
|
|
23471
|
-
|
|
23472
|
-
|
|
23473
|
-
|
|
23474
|
-
|
|
23475
|
-
|
|
23476
|
-
|
|
23477
|
-
|
|
23478
|
-
|
|
23479
|
-
|
|
23956
|
+
);
|
|
23957
|
+
server.registerTool(
|
|
23958
|
+
"get_tasks",
|
|
23959
|
+
{
|
|
23960
|
+
description: "List tasks for a checkpoint or repo. Filter by checkpoint_id for checkpoint tasks, or repo_id with standalone=true for standalone tasks.",
|
|
23961
|
+
inputSchema: {
|
|
23962
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("The checkpoint UUID (for checkpoint-bound tasks)"),
|
|
23963
|
+
repo_id: external_exports.string().uuid().optional().describe("The repo UUID (for standalone tasks)"),
|
|
23964
|
+
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)"),
|
|
23965
|
+
standalone: external_exports.boolean().optional().describe(
|
|
23966
|
+
"If true, only return tasks with no checkpoint (standalone)"
|
|
23967
|
+
)
|
|
23480
23968
|
}
|
|
23481
|
-
|
|
23969
|
+
},
|
|
23970
|
+
async ({ checkpoint_id, repo_id, status, standalone }) => {
|
|
23971
|
+
try {
|
|
23972
|
+
const params = {
|
|
23973
|
+
checkpoint_id,
|
|
23974
|
+
repo_id,
|
|
23975
|
+
status
|
|
23976
|
+
};
|
|
23977
|
+
if (standalone) params.standalone = "true";
|
|
23978
|
+
const res = await apiGet("/tasks", params);
|
|
23979
|
+
return {
|
|
23980
|
+
content: [
|
|
23981
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
23982
|
+
]
|
|
23983
|
+
};
|
|
23984
|
+
} catch (err) {
|
|
23482
23985
|
return {
|
|
23483
|
-
content: [
|
|
23986
|
+
content: [
|
|
23987
|
+
{
|
|
23988
|
+
type: "text",
|
|
23989
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
23990
|
+
}
|
|
23991
|
+
],
|
|
23992
|
+
isError: true
|
|
23484
23993
|
};
|
|
23485
23994
|
}
|
|
23486
|
-
|
|
23487
|
-
|
|
23488
|
-
|
|
23489
|
-
|
|
23490
|
-
|
|
23491
|
-
|
|
23995
|
+
}
|
|
23996
|
+
);
|
|
23997
|
+
server.registerTool(
|
|
23998
|
+
"get_rounds",
|
|
23999
|
+
{
|
|
24000
|
+
description: "List rounds for a task. Optionally filter by status.",
|
|
24001
|
+
inputSchema: {
|
|
24002
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24003
|
+
status: external_exports.string().optional().describe("Filter by status (pending, in_progress, completed)")
|
|
24004
|
+
}
|
|
24005
|
+
},
|
|
24006
|
+
async ({ task_id, status }) => {
|
|
24007
|
+
try {
|
|
24008
|
+
const res = await apiGet("/rounds", {
|
|
24009
|
+
task_id,
|
|
24010
|
+
status
|
|
24011
|
+
});
|
|
24012
|
+
return {
|
|
24013
|
+
content: [
|
|
24014
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24015
|
+
]
|
|
24016
|
+
};
|
|
24017
|
+
} catch (err) {
|
|
23492
24018
|
return {
|
|
23493
|
-
content: [
|
|
23494
|
-
|
|
23495
|
-
|
|
23496
|
-
|
|
23497
|
-
|
|
23498
|
-
|
|
23499
|
-
|
|
23500
|
-
}]
|
|
24019
|
+
content: [
|
|
24020
|
+
{
|
|
24021
|
+
type: "text",
|
|
24022
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24023
|
+
}
|
|
24024
|
+
],
|
|
24025
|
+
isError: true
|
|
23501
24026
|
};
|
|
23502
24027
|
}
|
|
23503
|
-
return {
|
|
23504
|
-
content: [{
|
|
23505
|
-
type: "text",
|
|
23506
|
-
text: JSON.stringify({ checkpoint, task: tasksRes.data[0] }, null, 2)
|
|
23507
|
-
}]
|
|
23508
|
-
};
|
|
23509
|
-
} catch (err) {
|
|
23510
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23511
24028
|
}
|
|
23512
|
-
|
|
23513
|
-
server.registerTool(
|
|
23514
|
-
|
|
23515
|
-
|
|
23516
|
-
|
|
23517
|
-
|
|
23518
|
-
|
|
24029
|
+
);
|
|
24030
|
+
server.registerTool(
|
|
24031
|
+
"get_current_task",
|
|
24032
|
+
{
|
|
24033
|
+
description: "Get the current in-progress task for a repo. Finds the active checkpoint, then the in-progress task within it.",
|
|
24034
|
+
inputSchema: {
|
|
24035
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24036
|
+
worktree_id: external_exports.string().uuid().optional().describe(
|
|
24037
|
+
"Optional worktree UUID to filter checkpoints by assignment"
|
|
24038
|
+
)
|
|
24039
|
+
}
|
|
24040
|
+
},
|
|
24041
|
+
async ({ repo_id, worktree_id }) => {
|
|
24042
|
+
try {
|
|
24043
|
+
const checkpointsRes = await apiGet(
|
|
24044
|
+
"/checkpoints",
|
|
24045
|
+
{
|
|
24046
|
+
repo_id,
|
|
24047
|
+
status: "active"
|
|
24048
|
+
}
|
|
24049
|
+
);
|
|
24050
|
+
let activeCheckpoints = checkpointsRes.data;
|
|
24051
|
+
if (worktree_id) {
|
|
24052
|
+
activeCheckpoints = activeCheckpoints.filter(
|
|
24053
|
+
(c) => c.worktree_id === worktree_id
|
|
24054
|
+
);
|
|
24055
|
+
}
|
|
24056
|
+
if (activeCheckpoints.length === 0) {
|
|
24057
|
+
return {
|
|
24058
|
+
content: [
|
|
24059
|
+
{
|
|
24060
|
+
type: "text",
|
|
24061
|
+
text: "No active checkpoint found for this repo."
|
|
24062
|
+
}
|
|
24063
|
+
]
|
|
24064
|
+
};
|
|
24065
|
+
}
|
|
24066
|
+
const checkpoint = activeCheckpoints[0];
|
|
24067
|
+
const tasksRes = await apiGet("/tasks", {
|
|
24068
|
+
checkpoint_id: checkpoint.id,
|
|
24069
|
+
status: "in_progress"
|
|
24070
|
+
});
|
|
24071
|
+
if (tasksRes.data.length === 0) {
|
|
24072
|
+
return {
|
|
24073
|
+
content: [
|
|
24074
|
+
{
|
|
24075
|
+
type: "text",
|
|
24076
|
+
text: JSON.stringify(
|
|
24077
|
+
{
|
|
24078
|
+
checkpoint,
|
|
24079
|
+
task: null,
|
|
24080
|
+
message: "Active checkpoint found but no in-progress task."
|
|
24081
|
+
},
|
|
24082
|
+
null,
|
|
24083
|
+
2
|
|
24084
|
+
)
|
|
24085
|
+
}
|
|
24086
|
+
]
|
|
24087
|
+
};
|
|
24088
|
+
}
|
|
24089
|
+
return {
|
|
24090
|
+
content: [
|
|
24091
|
+
{
|
|
24092
|
+
type: "text",
|
|
24093
|
+
text: JSON.stringify(
|
|
24094
|
+
{ checkpoint, task: tasksRes.data[0] },
|
|
24095
|
+
null,
|
|
24096
|
+
2
|
|
24097
|
+
)
|
|
24098
|
+
}
|
|
24099
|
+
]
|
|
24100
|
+
};
|
|
24101
|
+
} catch (err) {
|
|
24102
|
+
return {
|
|
24103
|
+
content: [
|
|
24104
|
+
{
|
|
24105
|
+
type: "text",
|
|
24106
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24107
|
+
}
|
|
24108
|
+
],
|
|
24109
|
+
isError: true
|
|
24110
|
+
};
|
|
24111
|
+
}
|
|
23519
24112
|
}
|
|
23520
|
-
|
|
23521
|
-
|
|
23522
|
-
|
|
23523
|
-
|
|
23524
|
-
|
|
23525
|
-
|
|
23526
|
-
|
|
23527
|
-
|
|
23528
|
-
|
|
23529
|
-
|
|
23530
|
-
|
|
23531
|
-
|
|
23532
|
-
|
|
23533
|
-
|
|
23534
|
-
|
|
23535
|
-
|
|
23536
|
-
|
|
23537
|
-
|
|
23538
|
-
|
|
23539
|
-
|
|
23540
|
-
|
|
23541
|
-
|
|
23542
|
-
|
|
23543
|
-
|
|
23544
|
-
|
|
23545
|
-
|
|
23546
|
-
|
|
23547
|
-
|
|
23548
|
-
|
|
23549
|
-
|
|
23550
|
-
|
|
23551
|
-
|
|
23552
|
-
|
|
24113
|
+
);
|
|
24114
|
+
server.registerTool(
|
|
24115
|
+
"get_launches",
|
|
24116
|
+
{
|
|
24117
|
+
description: "List launches for a repo. Optionally filter by status or type.",
|
|
24118
|
+
inputSchema: {
|
|
24119
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24120
|
+
status: external_exports.string().optional().describe("Filter by status"),
|
|
24121
|
+
type: external_exports.string().optional().describe("Filter by type")
|
|
24122
|
+
}
|
|
24123
|
+
},
|
|
24124
|
+
async ({ repo_id, status, type }) => {
|
|
24125
|
+
try {
|
|
24126
|
+
const res = await apiGet("/launches", {
|
|
24127
|
+
repo_id,
|
|
24128
|
+
status,
|
|
24129
|
+
type
|
|
24130
|
+
});
|
|
24131
|
+
return {
|
|
24132
|
+
content: [
|
|
24133
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24134
|
+
]
|
|
24135
|
+
};
|
|
24136
|
+
} catch (err) {
|
|
24137
|
+
return {
|
|
24138
|
+
content: [
|
|
24139
|
+
{
|
|
24140
|
+
type: "text",
|
|
24141
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24142
|
+
}
|
|
24143
|
+
],
|
|
24144
|
+
isError: true
|
|
24145
|
+
};
|
|
24146
|
+
}
|
|
23553
24147
|
}
|
|
23554
|
-
|
|
23555
|
-
server.registerTool(
|
|
23556
|
-
|
|
23557
|
-
|
|
23558
|
-
|
|
24148
|
+
);
|
|
24149
|
+
server.registerTool(
|
|
24150
|
+
"get_launch",
|
|
24151
|
+
{
|
|
24152
|
+
description: "Get a single launch by ID.",
|
|
24153
|
+
inputSchema: {
|
|
24154
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID")
|
|
24155
|
+
}
|
|
24156
|
+
},
|
|
24157
|
+
async ({ launch_id }) => {
|
|
24158
|
+
try {
|
|
24159
|
+
const res = await apiGet(
|
|
24160
|
+
`/launches/${launch_id}`
|
|
24161
|
+
);
|
|
24162
|
+
return {
|
|
24163
|
+
content: [
|
|
24164
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24165
|
+
]
|
|
24166
|
+
};
|
|
24167
|
+
} catch (err) {
|
|
24168
|
+
return {
|
|
24169
|
+
content: [
|
|
24170
|
+
{
|
|
24171
|
+
type: "text",
|
|
24172
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24173
|
+
}
|
|
24174
|
+
],
|
|
24175
|
+
isError: true
|
|
24176
|
+
};
|
|
24177
|
+
}
|
|
23559
24178
|
}
|
|
23560
|
-
|
|
23561
|
-
|
|
23562
|
-
|
|
23563
|
-
|
|
23564
|
-
|
|
23565
|
-
|
|
24179
|
+
);
|
|
24180
|
+
server.registerTool(
|
|
24181
|
+
"get_session_logs",
|
|
24182
|
+
{
|
|
24183
|
+
description: "List session logs for a repo. Optionally filter by date.",
|
|
24184
|
+
inputSchema: {
|
|
24185
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24186
|
+
session_date: external_exports.string().optional().describe("Filter by session date (YYYY-MM-DD)")
|
|
24187
|
+
}
|
|
24188
|
+
},
|
|
24189
|
+
async ({ repo_id, session_date }) => {
|
|
24190
|
+
try {
|
|
24191
|
+
const res = await apiGet("/session-logs", {
|
|
24192
|
+
repo_id,
|
|
24193
|
+
session_date
|
|
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
|
+
}
|
|
23566
24211
|
}
|
|
23567
|
-
|
|
23568
|
-
server.registerTool(
|
|
23569
|
-
|
|
23570
|
-
|
|
23571
|
-
|
|
24212
|
+
);
|
|
24213
|
+
server.registerTool(
|
|
24214
|
+
"get_session_log",
|
|
24215
|
+
{
|
|
24216
|
+
description: "Get a single session log by ID.",
|
|
24217
|
+
inputSchema: {
|
|
24218
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID")
|
|
24219
|
+
}
|
|
24220
|
+
},
|
|
24221
|
+
async ({ session_log_id }) => {
|
|
24222
|
+
try {
|
|
24223
|
+
const res = await apiGet(
|
|
24224
|
+
`/session-logs/${session_log_id}`
|
|
24225
|
+
);
|
|
24226
|
+
return {
|
|
24227
|
+
content: [
|
|
24228
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24229
|
+
]
|
|
24230
|
+
};
|
|
24231
|
+
} catch (err) {
|
|
24232
|
+
return {
|
|
24233
|
+
content: [
|
|
24234
|
+
{
|
|
24235
|
+
type: "text",
|
|
24236
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24237
|
+
}
|
|
24238
|
+
],
|
|
24239
|
+
isError: true
|
|
24240
|
+
};
|
|
24241
|
+
}
|
|
23572
24242
|
}
|
|
23573
|
-
|
|
23574
|
-
|
|
23575
|
-
|
|
23576
|
-
|
|
23577
|
-
|
|
23578
|
-
|
|
23579
|
-
|
|
23580
|
-
|
|
24243
|
+
);
|
|
24244
|
+
server.registerTool(
|
|
24245
|
+
"get_session_state",
|
|
24246
|
+
{
|
|
24247
|
+
description: "Get session state for a repo (active, paused, inactive, needs_refresh, last_session_at).",
|
|
24248
|
+
inputSchema: {
|
|
24249
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
24250
|
+
}
|
|
24251
|
+
},
|
|
24252
|
+
async ({ repo_id }) => {
|
|
24253
|
+
try {
|
|
24254
|
+
const res = await apiGet(`/repos/${repo_id}`);
|
|
24255
|
+
const {
|
|
24256
|
+
id,
|
|
24257
|
+
name,
|
|
24258
|
+
is_session_active,
|
|
24259
|
+
session_status,
|
|
24260
|
+
needs_refresh,
|
|
24261
|
+
last_session_at
|
|
24262
|
+
} = res.data;
|
|
24263
|
+
const data = {
|
|
24264
|
+
id,
|
|
24265
|
+
name,
|
|
24266
|
+
is_session_active,
|
|
24267
|
+
session_status,
|
|
24268
|
+
needs_refresh,
|
|
24269
|
+
last_session_at
|
|
24270
|
+
};
|
|
24271
|
+
return {
|
|
24272
|
+
content: [
|
|
24273
|
+
{ type: "text", text: JSON.stringify(data, null, 2) }
|
|
24274
|
+
]
|
|
24275
|
+
};
|
|
24276
|
+
} catch (err) {
|
|
24277
|
+
return {
|
|
24278
|
+
content: [
|
|
24279
|
+
{
|
|
24280
|
+
type: "text",
|
|
24281
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24282
|
+
}
|
|
24283
|
+
],
|
|
24284
|
+
isError: true
|
|
24285
|
+
};
|
|
24286
|
+
}
|
|
23581
24287
|
}
|
|
23582
|
-
|
|
23583
|
-
server.registerTool(
|
|
23584
|
-
|
|
23585
|
-
|
|
23586
|
-
|
|
23587
|
-
|
|
24288
|
+
);
|
|
24289
|
+
server.registerTool(
|
|
24290
|
+
"get_server_config",
|
|
24291
|
+
{
|
|
24292
|
+
description: "Get server configuration for a repo (port, type, active servers).",
|
|
24293
|
+
inputSchema: {
|
|
24294
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24295
|
+
worktree_id: external_exports.string().uuid().optional().describe(
|
|
24296
|
+
"Optional worktree UUID to filter port allocations by worktree"
|
|
24297
|
+
)
|
|
24298
|
+
}
|
|
24299
|
+
},
|
|
24300
|
+
async ({ repo_id, worktree_id }) => {
|
|
24301
|
+
try {
|
|
24302
|
+
const params = {};
|
|
24303
|
+
if (worktree_id) params.worktree_id = worktree_id;
|
|
24304
|
+
const res = await apiGet(
|
|
24305
|
+
`/repos/${repo_id}/server`,
|
|
24306
|
+
params
|
|
24307
|
+
);
|
|
24308
|
+
return {
|
|
24309
|
+
content: [
|
|
24310
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24311
|
+
]
|
|
24312
|
+
};
|
|
24313
|
+
} catch (err) {
|
|
24314
|
+
return {
|
|
24315
|
+
content: [
|
|
24316
|
+
{
|
|
24317
|
+
type: "text",
|
|
24318
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24319
|
+
}
|
|
24320
|
+
],
|
|
24321
|
+
isError: true
|
|
24322
|
+
};
|
|
24323
|
+
}
|
|
23588
24324
|
}
|
|
23589
|
-
|
|
23590
|
-
|
|
23591
|
-
|
|
23592
|
-
|
|
23593
|
-
|
|
23594
|
-
|
|
23595
|
-
}
|
|
23596
|
-
|
|
24325
|
+
);
|
|
24326
|
+
server.registerTool(
|
|
24327
|
+
"get_sync_status",
|
|
24328
|
+
{
|
|
24329
|
+
description: "Get cross-repo sync status. Shows which repos need a claude files sync based on latest updates.",
|
|
24330
|
+
inputSchema: {}
|
|
24331
|
+
},
|
|
24332
|
+
async () => {
|
|
24333
|
+
try {
|
|
24334
|
+
const res = await apiGet("/sync/status");
|
|
24335
|
+
return {
|
|
24336
|
+
content: [
|
|
24337
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24338
|
+
]
|
|
24339
|
+
};
|
|
24340
|
+
} catch (err) {
|
|
24341
|
+
return {
|
|
24342
|
+
content: [
|
|
24343
|
+
{
|
|
24344
|
+
type: "text",
|
|
24345
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24346
|
+
}
|
|
24347
|
+
],
|
|
24348
|
+
isError: true
|
|
24349
|
+
};
|
|
24350
|
+
}
|
|
23597
24351
|
}
|
|
23598
|
-
|
|
23599
|
-
server.registerTool(
|
|
23600
|
-
|
|
23601
|
-
|
|
23602
|
-
|
|
23603
|
-
|
|
23604
|
-
|
|
23605
|
-
|
|
23606
|
-
|
|
23607
|
-
|
|
24352
|
+
);
|
|
24353
|
+
server.registerTool(
|
|
24354
|
+
"get_next_action",
|
|
24355
|
+
{
|
|
24356
|
+
description: "Compute the next action for a repo based on current workflow state. Returns command, instructions, state, and context.",
|
|
24357
|
+
inputSchema: {
|
|
24358
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24359
|
+
worktree_id: external_exports.string().uuid().optional().describe("Optional worktree UUID to filter by assignment")
|
|
24360
|
+
}
|
|
24361
|
+
},
|
|
24362
|
+
async ({ repo_id, worktree_id }) => {
|
|
24363
|
+
try {
|
|
24364
|
+
const params = {};
|
|
24365
|
+
if (worktree_id) params.worktree_id = worktree_id;
|
|
24366
|
+
const res = await apiGet(
|
|
24367
|
+
`/repos/${repo_id}/next-action`,
|
|
24368
|
+
params
|
|
24369
|
+
);
|
|
24370
|
+
return {
|
|
24371
|
+
content: [
|
|
24372
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24373
|
+
]
|
|
24374
|
+
};
|
|
24375
|
+
} catch (err) {
|
|
24376
|
+
return {
|
|
24377
|
+
content: [
|
|
24378
|
+
{
|
|
24379
|
+
type: "text",
|
|
24380
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24381
|
+
}
|
|
24382
|
+
],
|
|
24383
|
+
isError: true
|
|
24384
|
+
};
|
|
24385
|
+
}
|
|
23608
24386
|
}
|
|
23609
|
-
|
|
23610
|
-
server.registerTool(
|
|
23611
|
-
|
|
23612
|
-
|
|
23613
|
-
|
|
23614
|
-
|
|
24387
|
+
);
|
|
24388
|
+
server.registerTool(
|
|
24389
|
+
"get_worktrees",
|
|
24390
|
+
{
|
|
24391
|
+
description: "List worktrees for a repo. Optionally filter by status.",
|
|
24392
|
+
inputSchema: {
|
|
24393
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24394
|
+
status: external_exports.string().optional().describe("Filter by status (active, inactive, archived)")
|
|
24395
|
+
}
|
|
24396
|
+
},
|
|
24397
|
+
async ({ repo_id, status }) => {
|
|
24398
|
+
try {
|
|
24399
|
+
const res = await apiGet("/worktrees", {
|
|
24400
|
+
repo_id,
|
|
24401
|
+
status
|
|
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
|
+
}
|
|
23615
24419
|
}
|
|
23616
|
-
|
|
23617
|
-
|
|
23618
|
-
|
|
23619
|
-
|
|
23620
|
-
|
|
23621
|
-
|
|
23622
|
-
|
|
23623
|
-
|
|
24420
|
+
);
|
|
24421
|
+
server.registerTool(
|
|
24422
|
+
"get_sync_lock_status",
|
|
24423
|
+
{
|
|
24424
|
+
description: "Check if a sync lock is currently held for a repo. Cleans up expired locks.",
|
|
24425
|
+
inputSchema: {
|
|
24426
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
24427
|
+
}
|
|
24428
|
+
},
|
|
24429
|
+
async ({ repo_id }) => {
|
|
24430
|
+
try {
|
|
24431
|
+
const res = await apiGet("/sync/lock", { repo_id });
|
|
24432
|
+
return {
|
|
24433
|
+
content: [
|
|
24434
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24435
|
+
]
|
|
24436
|
+
};
|
|
24437
|
+
} catch (err) {
|
|
24438
|
+
return {
|
|
24439
|
+
content: [
|
|
24440
|
+
{
|
|
24441
|
+
type: "text",
|
|
24442
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24443
|
+
}
|
|
24444
|
+
],
|
|
24445
|
+
isError: true
|
|
24446
|
+
};
|
|
24447
|
+
}
|
|
23624
24448
|
}
|
|
23625
|
-
|
|
23626
|
-
server.registerTool(
|
|
23627
|
-
|
|
23628
|
-
|
|
23629
|
-
|
|
23630
|
-
|
|
24449
|
+
);
|
|
24450
|
+
server.registerTool(
|
|
24451
|
+
"get_sync_conflicts",
|
|
24452
|
+
{
|
|
24453
|
+
description: "List sync conflicts for a repo. Optionally filter by status.",
|
|
24454
|
+
inputSchema: {
|
|
24455
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24456
|
+
status: external_exports.string().optional().describe("Filter by status (unresolved, resolved, skipped)")
|
|
24457
|
+
}
|
|
24458
|
+
},
|
|
24459
|
+
async ({ repo_id, status }) => {
|
|
24460
|
+
try {
|
|
24461
|
+
const res = await apiGet("/sync/conflicts", {
|
|
24462
|
+
repo_id,
|
|
24463
|
+
status
|
|
24464
|
+
});
|
|
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
|
+
}
|
|
23631
24481
|
}
|
|
23632
|
-
|
|
23633
|
-
|
|
23634
|
-
|
|
23635
|
-
|
|
23636
|
-
|
|
23637
|
-
|
|
24482
|
+
);
|
|
24483
|
+
server.registerTool(
|
|
24484
|
+
"get_file_changes",
|
|
24485
|
+
{
|
|
24486
|
+
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).",
|
|
24487
|
+
inputSchema: {
|
|
24488
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24489
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("Filter by checkpoint"),
|
|
24490
|
+
task_id: external_exports.string().uuid().optional().describe("Filter by task"),
|
|
24491
|
+
round_id: external_exports.string().uuid().optional().describe("Filter by round"),
|
|
24492
|
+
file_path: external_exports.string().optional().describe("Filter by exact file path"),
|
|
24493
|
+
source: external_exports.enum(["round", "fix"]).optional().describe("Filter by source (round or fix)"),
|
|
24494
|
+
aggregate: external_exports.enum(["task"]).optional().describe(
|
|
24495
|
+
"Aggregation mode. 'task' returns latest entry per file_path for a task (requires task_id)"
|
|
24496
|
+
)
|
|
24497
|
+
}
|
|
24498
|
+
},
|
|
24499
|
+
async ({ repo_id, checkpoint_id, task_id, round_id, file_path, source, aggregate }) => {
|
|
24500
|
+
try {
|
|
24501
|
+
const res = await apiGet(
|
|
24502
|
+
"/file-changes",
|
|
24503
|
+
{
|
|
24504
|
+
repo_id,
|
|
24505
|
+
checkpoint_id,
|
|
24506
|
+
task_id,
|
|
24507
|
+
round_id,
|
|
24508
|
+
file_path,
|
|
24509
|
+
source,
|
|
24510
|
+
aggregate
|
|
24511
|
+
}
|
|
24512
|
+
);
|
|
24513
|
+
return {
|
|
24514
|
+
content: [
|
|
24515
|
+
{ type: "text", text: JSON.stringify(res, null, 2) }
|
|
24516
|
+
]
|
|
24517
|
+
};
|
|
24518
|
+
} catch (err) {
|
|
24519
|
+
return {
|
|
24520
|
+
content: [
|
|
24521
|
+
{
|
|
24522
|
+
type: "text",
|
|
24523
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24524
|
+
}
|
|
24525
|
+
],
|
|
24526
|
+
isError: true
|
|
24527
|
+
};
|
|
24528
|
+
}
|
|
23638
24529
|
}
|
|
23639
|
-
|
|
24530
|
+
);
|
|
23640
24531
|
}
|
|
23641
24532
|
var init_read = __esm({
|
|
23642
24533
|
"src/tools/read.ts"() {
|
|
@@ -23914,621 +24805,1591 @@ var init_promotion = __esm({
|
|
|
23914
24805
|
|
|
23915
24806
|
// src/tools/write.ts
|
|
23916
24807
|
function registerWriteTools(server) {
|
|
23917
|
-
server.registerTool(
|
|
23918
|
-
|
|
23919
|
-
|
|
23920
|
-
|
|
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
|
-
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23951
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23952
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23953
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
23954
|
-
}
|
|
23955
|
-
}, async ({ repo_id, title, number: number3, goal, deadline, status, launch_id, ideas, context, research, qa }) => {
|
|
23956
|
-
try {
|
|
23957
|
-
const body = {
|
|
23958
|
-
repo_id,
|
|
23959
|
-
title: title ?? null,
|
|
23960
|
-
number: number3,
|
|
23961
|
-
goal: goal ?? null,
|
|
23962
|
-
deadline: deadline ?? null,
|
|
23963
|
-
status: status ?? "pending",
|
|
23964
|
-
launch_id: launch_id ?? null
|
|
23965
|
-
};
|
|
23966
|
-
if (ideas !== void 0) body.ideas = ideas;
|
|
23967
|
-
if (context !== void 0) body.context = context;
|
|
23968
|
-
if (research !== void 0) body.research = research;
|
|
23969
|
-
if (qa !== void 0) body.qa = qa;
|
|
23970
|
-
const res = await apiPost("/checkpoints", body);
|
|
23971
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
23972
|
-
} catch (err) {
|
|
23973
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
23974
|
-
}
|
|
23975
|
-
});
|
|
23976
|
-
server.registerTool("update_checkpoint", {
|
|
23977
|
-
description: "Update an existing checkpoint. Can connect or disconnect a launch via launch_id.",
|
|
23978
|
-
inputSchema: {
|
|
23979
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
23980
|
-
title: external_exports.string().nullable().optional().describe("New title (or null to clear)"),
|
|
23981
|
-
goal: external_exports.string().optional().describe("New goal (max 300 chars, brief overview)"),
|
|
23982
|
-
status: external_exports.string().optional().describe("New status (draft, pending, active, completed)"),
|
|
23983
|
-
deadline: external_exports.string().optional().describe("New deadline (ISO format)"),
|
|
23984
|
-
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
23985
|
-
launch_id: external_exports.string().uuid().nullable().optional().describe("Launch UUID to connect (or null to disconnect)"),
|
|
23986
|
-
worktree_id: external_exports.string().uuid().nullable().optional().describe("Worktree UUID to assign (or null to unassign)"),
|
|
23987
|
-
assigned_to: external_exports.string().nullable().optional().describe("Who/what claimed this checkpoint"),
|
|
23988
|
-
branch_name: external_exports.string().nullable().optional().describe("Git branch name for this checkpoint (e.g. feat/CHK-061-git-overhaul)"),
|
|
23989
|
-
ideas: external_exports.array(external_exports.object({
|
|
23990
|
-
description: external_exports.string().describe("Idea description"),
|
|
23991
|
-
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
23992
|
-
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
23993
|
-
})).optional().describe("Ideas array \u2014 each idea has description, requirements[], images[]"),
|
|
23994
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"),
|
|
23995
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
23996
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
23997
|
-
}
|
|
23998
|
-
}, async ({ checkpoint_id, title, goal, status, deadline, completed_at, launch_id, worktree_id, assigned_to, branch_name, ideas, context, research, qa }) => {
|
|
23999
|
-
const update = {};
|
|
24000
|
-
if (title !== void 0) update.title = title;
|
|
24001
|
-
if (goal !== void 0) update.goal = goal;
|
|
24002
|
-
if (status !== void 0) update.status = status;
|
|
24003
|
-
if (deadline !== void 0) update.deadline = deadline;
|
|
24004
|
-
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
24005
|
-
if (launch_id !== void 0) update.launch_id = launch_id;
|
|
24006
|
-
if (worktree_id !== void 0) update.worktree_id = worktree_id;
|
|
24007
|
-
if (assigned_to !== void 0) update.assigned_to = assigned_to;
|
|
24008
|
-
if (branch_name !== void 0) update.branch_name = branch_name;
|
|
24009
|
-
if (ideas !== void 0) update.ideas = ideas;
|
|
24010
|
-
if (context !== void 0) update.context = context;
|
|
24011
|
-
if (research !== void 0) update.research = research;
|
|
24012
|
-
if (qa !== void 0) update.qa = qa;
|
|
24013
|
-
if (Object.keys(update).length === 0) {
|
|
24014
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24015
|
-
}
|
|
24016
|
-
try {
|
|
24017
|
-
const res = await apiPut(`/checkpoints/${checkpoint_id}`, update);
|
|
24018
|
-
return { content: [{ type: "text", text: JSON.stringify(res, null, 2) }] };
|
|
24019
|
-
} catch (err) {
|
|
24020
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24021
|
-
}
|
|
24022
|
-
});
|
|
24023
|
-
server.registerTool("complete_checkpoint", {
|
|
24024
|
-
description: "Mark a checkpoint as completed. Sets status to 'completed', completed_at to now, and triggers promotion (creates PR from feat branch to development).",
|
|
24025
|
-
inputSchema: {
|
|
24026
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
24027
|
-
}
|
|
24028
|
-
}, async ({ checkpoint_id }) => {
|
|
24029
|
-
try {
|
|
24030
|
-
const res = await apiPut(`/checkpoints/${checkpoint_id}`, {
|
|
24031
|
-
status: "completed",
|
|
24032
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
24033
|
-
});
|
|
24034
|
-
const checkpoint = res.data;
|
|
24035
|
-
const featToDevResult = await promoteCheckpoint(checkpoint_id);
|
|
24036
|
-
let devToMainResult = null;
|
|
24037
|
-
const repoRes = await apiGet(`/repos/${checkpoint.repo_id}`);
|
|
24038
|
-
if (repoRes.data.auto_push_enabled) {
|
|
24039
|
-
devToMainResult = await promoteToMain(checkpoint.repo_id);
|
|
24040
|
-
}
|
|
24041
|
-
return { content: [{ type: "text", text: JSON.stringify({ checkpoint, promotion: { feat_to_development: featToDevResult, development_to_main: devToMainResult } }, null, 2) }] };
|
|
24042
|
-
} catch (err) {
|
|
24043
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24044
|
-
}
|
|
24045
|
-
});
|
|
24046
|
-
server.registerTool("create_task", {
|
|
24047
|
-
description: "Create a new task within a checkpoint.",
|
|
24048
|
-
inputSchema: {
|
|
24049
|
-
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
24050
|
-
title: external_exports.string().describe("Task title"),
|
|
24051
|
-
number: external_exports.number().int().describe("Task number (e.g. 1 for TASK-1)"),
|
|
24052
|
-
requirements: external_exports.string().optional().describe("Task requirements text"),
|
|
24053
|
-
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
24054
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints)"),
|
|
24055
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24056
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)")
|
|
24057
|
-
}
|
|
24058
|
-
}, async ({ checkpoint_id, title, number: number3, requirements, status, context, qa, research }) => {
|
|
24059
|
-
try {
|
|
24060
|
-
const body = {
|
|
24061
|
-
checkpoint_id,
|
|
24062
|
-
title,
|
|
24063
|
-
number: number3,
|
|
24064
|
-
requirements: requirements ?? null,
|
|
24065
|
-
status: status ?? "pending"
|
|
24066
|
-
};
|
|
24067
|
-
if (context !== void 0) body.context = context;
|
|
24068
|
-
if (qa !== void 0) body.qa = qa;
|
|
24069
|
-
if (research !== void 0) body.research = research;
|
|
24070
|
-
const res = await apiPost("/tasks", body);
|
|
24071
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24072
|
-
} catch (err) {
|
|
24073
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24074
|
-
}
|
|
24075
|
-
});
|
|
24076
|
-
server.registerTool("update_task", {
|
|
24077
|
-
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.",
|
|
24078
|
-
inputSchema: {
|
|
24079
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24080
|
-
title: external_exports.string().optional().describe("New title"),
|
|
24081
|
-
requirements: external_exports.string().optional().describe("New requirements text"),
|
|
24082
|
-
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
24083
|
-
files_changed: external_exports.array(external_exports.object({
|
|
24084
|
-
path: external_exports.string().describe("File path relative to repo root"),
|
|
24085
|
-
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
24086
|
-
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
24087
|
-
claude_approved: external_exports.boolean().optional().describe("Whether Claude's automated checks passed for this file"),
|
|
24088
|
-
user_approved: external_exports.boolean().optional().describe("Whether the user has approved this file (via git add or web UI)")
|
|
24089
|
-
})).optional().describe("Files changed across all rounds"),
|
|
24090
|
-
claim_worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID to auto-claim the parent checkpoint when setting status to in_progress"),
|
|
24091
|
-
context: external_exports.any().optional().describe("Context JSONB (decisions, discoveries, dependencies, constraints)"),
|
|
24092
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24093
|
-
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)")
|
|
24094
|
-
}
|
|
24095
|
-
}, async ({ task_id, title, requirements, status, files_changed, claim_worktree_id, context, qa, research }) => {
|
|
24096
|
-
const update = {};
|
|
24097
|
-
if (title !== void 0) update.title = title;
|
|
24098
|
-
if (requirements !== void 0) update.requirements = requirements;
|
|
24099
|
-
if (status !== void 0) update.status = status;
|
|
24100
|
-
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
24101
|
-
if (claim_worktree_id !== void 0) update.claim_worktree_id = claim_worktree_id;
|
|
24102
|
-
if (context !== void 0) update.context = context;
|
|
24103
|
-
if (qa !== void 0) update.qa = qa;
|
|
24104
|
-
if (research !== void 0) update.research = research;
|
|
24105
|
-
if (Object.keys(update).length === 0) {
|
|
24106
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24107
|
-
}
|
|
24108
|
-
try {
|
|
24109
|
-
const res = await apiPut(`/tasks/${task_id}`, update);
|
|
24110
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24111
|
-
} catch (err) {
|
|
24112
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24113
|
-
}
|
|
24114
|
-
});
|
|
24115
|
-
server.registerTool("complete_task", {
|
|
24116
|
-
description: "Mark a task as completed. Sets status to 'completed' and completed_at to now.",
|
|
24117
|
-
inputSchema: {
|
|
24118
|
-
task_id: external_exports.string().uuid().describe("The task UUID")
|
|
24119
|
-
}
|
|
24120
|
-
}, async ({ task_id }) => {
|
|
24121
|
-
try {
|
|
24122
|
-
const res = await apiPut(`/tasks/${task_id}`, {
|
|
24123
|
-
status: "completed",
|
|
24124
|
-
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
24125
|
-
});
|
|
24126
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24127
|
-
} catch (err) {
|
|
24128
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24129
|
-
}
|
|
24130
|
-
});
|
|
24131
|
-
server.registerTool("add_round", {
|
|
24132
|
-
description: "Add a round to a task.",
|
|
24133
|
-
inputSchema: {
|
|
24134
|
-
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
24135
|
-
number: external_exports.number().int().describe("Round number"),
|
|
24136
|
-
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
24137
|
-
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
24138
|
-
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
24139
|
-
context: external_exports.any().optional().describe("Context JSONB"),
|
|
24140
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
24141
|
-
}
|
|
24142
|
-
}, async ({ task_id, number: number3, requirements, status, started_at, context, qa }) => {
|
|
24143
|
-
try {
|
|
24144
|
-
const body = {
|
|
24145
|
-
task_id,
|
|
24146
|
-
number: number3,
|
|
24147
|
-
requirements: requirements ?? null,
|
|
24148
|
-
status: status ?? "pending",
|
|
24149
|
-
started_at: started_at ?? null
|
|
24150
|
-
};
|
|
24151
|
-
if (context !== void 0) body.context = context;
|
|
24152
|
-
if (qa !== void 0) body.qa = qa;
|
|
24153
|
-
const res = await apiPost("/rounds", body);
|
|
24154
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24155
|
-
} catch (err) {
|
|
24156
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24157
|
-
}
|
|
24158
|
-
});
|
|
24159
|
-
server.registerTool("update_round", {
|
|
24160
|
-
description: "Update an existing round.",
|
|
24161
|
-
inputSchema: {
|
|
24162
|
-
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
24163
|
-
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
24164
|
-
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
24165
|
-
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
24166
|
-
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
24167
|
-
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes"),
|
|
24168
|
-
files_changed: external_exports.array(external_exports.object({
|
|
24169
|
-
path: external_exports.string().describe("File path relative to repo root"),
|
|
24170
|
-
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
24171
|
-
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
24172
|
-
claude_approved: external_exports.boolean().optional().describe("Whether Claude's automated checks passed for this file"),
|
|
24173
|
-
user_approved: external_exports.boolean().optional().describe("Whether the user has approved this file (via git add or web UI)")
|
|
24174
|
-
})).optional().describe("Files changed in this round with approval status"),
|
|
24175
|
-
context: external_exports.any().optional().describe("Context JSONB"),
|
|
24176
|
-
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)")
|
|
24177
|
-
}
|
|
24178
|
-
}, async ({ round_id, requirements, status, started_at, completed_at, duration_minutes, files_changed, context, qa }) => {
|
|
24179
|
-
const update = {};
|
|
24180
|
-
if (requirements !== void 0) update.requirements = requirements;
|
|
24181
|
-
if (status !== void 0) update.status = status;
|
|
24182
|
-
if (started_at !== void 0) update.started_at = started_at;
|
|
24183
|
-
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
24184
|
-
if (duration_minutes !== void 0) update.duration_minutes = duration_minutes;
|
|
24185
|
-
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
24186
|
-
if (context !== void 0) update.context = context;
|
|
24187
|
-
if (qa !== void 0) update.qa = qa;
|
|
24188
|
-
if (Object.keys(update).length === 0) {
|
|
24189
|
-
return { content: [{ type: "text", text: "Error: No fields to update" }], isError: true };
|
|
24190
|
-
}
|
|
24191
|
-
try {
|
|
24192
|
-
const res = await apiPut(`/rounds/${round_id}`, update);
|
|
24193
|
-
return { content: [{ type: "text", text: JSON.stringify(res.data, null, 2) }] };
|
|
24194
|
-
} catch (err) {
|
|
24195
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24808
|
+
server.registerTool(
|
|
24809
|
+
"create_repo",
|
|
24810
|
+
{
|
|
24811
|
+
description: "Create a new repo entry.",
|
|
24812
|
+
inputSchema: {
|
|
24813
|
+
name: external_exports.string().describe("Repo name (must be unique)"),
|
|
24814
|
+
path: external_exports.string().optional().describe("Local filesystem path to the repo"),
|
|
24815
|
+
git_branch: external_exports.string().optional().describe("Default git branch (default: development)")
|
|
24816
|
+
}
|
|
24817
|
+
},
|
|
24818
|
+
async ({ name, path, git_branch }) => {
|
|
24819
|
+
try {
|
|
24820
|
+
const res = await apiPost("/repos", {
|
|
24821
|
+
name,
|
|
24822
|
+
path: path ?? null,
|
|
24823
|
+
git_branch: git_branch ?? "development"
|
|
24824
|
+
});
|
|
24825
|
+
return {
|
|
24826
|
+
content: [
|
|
24827
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24828
|
+
]
|
|
24829
|
+
};
|
|
24830
|
+
} catch (err) {
|
|
24831
|
+
return {
|
|
24832
|
+
content: [
|
|
24833
|
+
{
|
|
24834
|
+
type: "text",
|
|
24835
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24836
|
+
}
|
|
24837
|
+
],
|
|
24838
|
+
isError: true
|
|
24839
|
+
};
|
|
24840
|
+
}
|
|
24196
24841
|
}
|
|
24197
|
-
|
|
24198
|
-
server.registerTool(
|
|
24199
|
-
|
|
24200
|
-
|
|
24201
|
-
|
|
24202
|
-
|
|
24842
|
+
);
|
|
24843
|
+
server.registerTool(
|
|
24844
|
+
"create_checkpoint",
|
|
24845
|
+
{
|
|
24846
|
+
description: "Create a new checkpoint for a repo. Optionally connect it to a launch via launch_id.",
|
|
24847
|
+
inputSchema: {
|
|
24848
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
24849
|
+
title: external_exports.string().optional().describe(
|
|
24850
|
+
"Checkpoint title (optional \u2014 Claude can generate if missing)"
|
|
24851
|
+
),
|
|
24852
|
+
number: external_exports.number().int().describe("Checkpoint number (e.g. 1 for CHK-001)"),
|
|
24853
|
+
goal: external_exports.string().optional().describe(
|
|
24854
|
+
"Checkpoint goal description (max 300 chars, brief overview)"
|
|
24855
|
+
),
|
|
24856
|
+
deadline: external_exports.string().optional().describe("Deadline date (ISO format)"),
|
|
24857
|
+
status: external_exports.string().optional().describe(
|
|
24858
|
+
"Initial status (default: pending). Use 'draft' for checkpoints not ready for development."
|
|
24859
|
+
),
|
|
24860
|
+
launch_id: external_exports.string().uuid().optional().describe("Optional launch UUID to connect this checkpoint to"),
|
|
24861
|
+
ideas: external_exports.array(
|
|
24862
|
+
external_exports.object({
|
|
24863
|
+
description: external_exports.string().describe("Idea description"),
|
|
24864
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
24865
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
24866
|
+
})
|
|
24867
|
+
).optional().describe(
|
|
24868
|
+
"Ideas array \u2014 each idea has description, requirements[], images[]"
|
|
24869
|
+
),
|
|
24870
|
+
context: external_exports.any().optional().describe(
|
|
24871
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"
|
|
24872
|
+
),
|
|
24873
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
24874
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24875
|
+
plan: external_exports.any().optional().describe("Plan JSONB (steps with title, description, scope)"),
|
|
24876
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
24877
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
24878
|
+
context_development: external_exports.any().optional().describe(
|
|
24879
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
24880
|
+
),
|
|
24881
|
+
resources: external_exports.any().optional().describe(
|
|
24882
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
24883
|
+
)
|
|
24884
|
+
}
|
|
24885
|
+
},
|
|
24886
|
+
async ({
|
|
24887
|
+
repo_id,
|
|
24888
|
+
title,
|
|
24889
|
+
number: number3,
|
|
24890
|
+
goal,
|
|
24891
|
+
deadline,
|
|
24892
|
+
status,
|
|
24893
|
+
launch_id,
|
|
24894
|
+
ideas,
|
|
24895
|
+
context,
|
|
24896
|
+
research,
|
|
24897
|
+
qa,
|
|
24898
|
+
plan,
|
|
24899
|
+
user_context,
|
|
24900
|
+
is_claude_written,
|
|
24901
|
+
context_development,
|
|
24902
|
+
resources
|
|
24903
|
+
}) => {
|
|
24904
|
+
try {
|
|
24905
|
+
const body = {
|
|
24906
|
+
repo_id,
|
|
24907
|
+
title: title ?? null,
|
|
24908
|
+
number: number3,
|
|
24909
|
+
goal: goal ?? null,
|
|
24910
|
+
deadline: deadline ?? null,
|
|
24911
|
+
status: status ?? "pending",
|
|
24912
|
+
launch_id: launch_id ?? null
|
|
24913
|
+
};
|
|
24914
|
+
if (ideas !== void 0) body.ideas = ideas;
|
|
24915
|
+
if (context !== void 0) body.context = context;
|
|
24916
|
+
if (research !== void 0) body.research = research;
|
|
24917
|
+
if (qa !== void 0) body.qa = qa;
|
|
24918
|
+
if (plan !== void 0) body.plan = plan;
|
|
24919
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
24920
|
+
if (is_claude_written !== void 0)
|
|
24921
|
+
body.is_claude_written = is_claude_written;
|
|
24922
|
+
if (context_development !== void 0)
|
|
24923
|
+
body.context_development = context_development;
|
|
24924
|
+
if (resources !== void 0) body.resources = resources;
|
|
24925
|
+
const res = await apiPost(
|
|
24926
|
+
"/checkpoints",
|
|
24927
|
+
body
|
|
24928
|
+
);
|
|
24929
|
+
return {
|
|
24930
|
+
content: [
|
|
24931
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
24932
|
+
]
|
|
24933
|
+
};
|
|
24934
|
+
} catch (err) {
|
|
24935
|
+
return {
|
|
24936
|
+
content: [
|
|
24937
|
+
{
|
|
24938
|
+
type: "text",
|
|
24939
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
24940
|
+
}
|
|
24941
|
+
],
|
|
24942
|
+
isError: true
|
|
24943
|
+
};
|
|
24944
|
+
}
|
|
24203
24945
|
}
|
|
24204
|
-
|
|
24205
|
-
|
|
24206
|
-
|
|
24207
|
-
|
|
24208
|
-
|
|
24209
|
-
|
|
24210
|
-
|
|
24211
|
-
|
|
24212
|
-
|
|
24213
|
-
|
|
24214
|
-
|
|
24946
|
+
);
|
|
24947
|
+
server.registerTool(
|
|
24948
|
+
"update_checkpoint",
|
|
24949
|
+
{
|
|
24950
|
+
description: "Update an existing checkpoint. Can connect or disconnect a launch via launch_id.",
|
|
24951
|
+
inputSchema: {
|
|
24952
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID"),
|
|
24953
|
+
title: external_exports.string().nullable().optional().describe("New title (or null to clear)"),
|
|
24954
|
+
goal: external_exports.string().optional().describe("New goal (max 300 chars, brief overview)"),
|
|
24955
|
+
status: external_exports.string().optional().describe("New status (draft, pending, active, completed)"),
|
|
24956
|
+
deadline: external_exports.string().optional().describe("New deadline (ISO format)"),
|
|
24957
|
+
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
24958
|
+
launch_id: external_exports.string().uuid().nullable().optional().describe("Launch UUID to connect (or null to disconnect)"),
|
|
24959
|
+
worktree_id: external_exports.string().uuid().nullable().optional().describe("Worktree UUID to assign (or null to unassign)"),
|
|
24960
|
+
assigned_to: external_exports.string().nullable().optional().describe("Who/what claimed this checkpoint"),
|
|
24961
|
+
branch_name: external_exports.string().nullable().optional().describe(
|
|
24962
|
+
"Git branch name for this checkpoint (e.g. feat/CHK-061-git-overhaul)"
|
|
24963
|
+
),
|
|
24964
|
+
ideas: external_exports.array(
|
|
24965
|
+
external_exports.object({
|
|
24966
|
+
description: external_exports.string().describe("Idea description"),
|
|
24967
|
+
requirements: external_exports.array(external_exports.string()).optional().describe("List of requirements for this idea"),
|
|
24968
|
+
images: external_exports.array(external_exports.string()).optional().describe("Image URLs for this idea")
|
|
24969
|
+
})
|
|
24970
|
+
).optional().describe(
|
|
24971
|
+
"Ideas array \u2014 each idea has description, requirements[], images[]"
|
|
24972
|
+
),
|
|
24973
|
+
context: external_exports.any().optional().describe(
|
|
24974
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints, qa_answers)"
|
|
24975
|
+
),
|
|
24976
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
24977
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
24978
|
+
plan: external_exports.any().optional().describe("Plan JSONB (steps with title, description, scope)"),
|
|
24979
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
24980
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
24981
|
+
context_development: external_exports.any().optional().describe(
|
|
24982
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
24983
|
+
),
|
|
24984
|
+
resources: external_exports.any().optional().describe(
|
|
24985
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
24986
|
+
)
|
|
24987
|
+
}
|
|
24988
|
+
},
|
|
24989
|
+
async ({
|
|
24990
|
+
checkpoint_id,
|
|
24991
|
+
title,
|
|
24992
|
+
goal,
|
|
24993
|
+
status,
|
|
24994
|
+
deadline,
|
|
24995
|
+
completed_at,
|
|
24996
|
+
launch_id,
|
|
24997
|
+
worktree_id,
|
|
24998
|
+
assigned_to,
|
|
24999
|
+
branch_name,
|
|
25000
|
+
ideas,
|
|
25001
|
+
context,
|
|
25002
|
+
research,
|
|
25003
|
+
qa,
|
|
25004
|
+
plan,
|
|
25005
|
+
user_context,
|
|
25006
|
+
is_claude_written,
|
|
25007
|
+
context_development,
|
|
25008
|
+
resources
|
|
25009
|
+
}) => {
|
|
25010
|
+
const update = {};
|
|
25011
|
+
if (title !== void 0) update.title = title;
|
|
25012
|
+
if (goal !== void 0) update.goal = goal;
|
|
25013
|
+
if (status !== void 0) update.status = status;
|
|
25014
|
+
if (deadline !== void 0) update.deadline = deadline;
|
|
25015
|
+
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
25016
|
+
if (launch_id !== void 0) update.launch_id = launch_id;
|
|
25017
|
+
if (worktree_id !== void 0) update.worktree_id = worktree_id;
|
|
25018
|
+
if (assigned_to !== void 0) update.assigned_to = assigned_to;
|
|
25019
|
+
if (branch_name !== void 0) update.branch_name = branch_name;
|
|
25020
|
+
if (ideas !== void 0) update.ideas = ideas;
|
|
25021
|
+
if (context !== void 0) update.context = context;
|
|
25022
|
+
if (research !== void 0) update.research = research;
|
|
25023
|
+
if (qa !== void 0) update.qa = qa;
|
|
25024
|
+
if (plan !== void 0) update.plan = plan;
|
|
25025
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25026
|
+
if (is_claude_written !== void 0)
|
|
25027
|
+
update.is_claude_written = is_claude_written;
|
|
25028
|
+
if (context_development !== void 0)
|
|
25029
|
+
update.context_development = context_development;
|
|
25030
|
+
if (resources !== void 0) update.resources = resources;
|
|
25031
|
+
if (Object.keys(update).length === 0) {
|
|
25032
|
+
return {
|
|
25033
|
+
content: [
|
|
25034
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25035
|
+
],
|
|
25036
|
+
isError: true
|
|
25037
|
+
};
|
|
25038
|
+
}
|
|
25039
|
+
try {
|
|
25040
|
+
const res = await apiPut(`/checkpoints/${checkpoint_id}`, update);
|
|
25041
|
+
return {
|
|
25042
|
+
content: [
|
|
25043
|
+
{ type: "text", text: JSON.stringify(res, null, 2) }
|
|
25044
|
+
]
|
|
25045
|
+
};
|
|
25046
|
+
} catch (err) {
|
|
25047
|
+
return {
|
|
25048
|
+
content: [
|
|
25049
|
+
{
|
|
25050
|
+
type: "text",
|
|
25051
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25052
|
+
}
|
|
25053
|
+
],
|
|
25054
|
+
isError: true
|
|
25055
|
+
};
|
|
25056
|
+
}
|
|
24215
25057
|
}
|
|
24216
|
-
|
|
24217
|
-
server.registerTool(
|
|
24218
|
-
|
|
24219
|
-
|
|
24220
|
-
|
|
24221
|
-
|
|
24222
|
-
|
|
24223
|
-
|
|
24224
|
-
|
|
24225
|
-
|
|
25058
|
+
);
|
|
25059
|
+
server.registerTool(
|
|
25060
|
+
"complete_checkpoint",
|
|
25061
|
+
{
|
|
25062
|
+
description: "Mark a checkpoint as completed. Sets status to 'completed', completed_at to now, and triggers promotion (creates PR from feat branch to development).",
|
|
25063
|
+
inputSchema: {
|
|
25064
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
25065
|
+
}
|
|
25066
|
+
},
|
|
25067
|
+
async ({ checkpoint_id }) => {
|
|
25068
|
+
try {
|
|
25069
|
+
const res = await apiPut(
|
|
25070
|
+
`/checkpoints/${checkpoint_id}`,
|
|
25071
|
+
{
|
|
25072
|
+
status: "completed",
|
|
25073
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25074
|
+
}
|
|
25075
|
+
);
|
|
25076
|
+
const checkpoint = res.data;
|
|
25077
|
+
const featToDevResult = await promoteCheckpoint(checkpoint_id);
|
|
25078
|
+
let devToMainResult = null;
|
|
25079
|
+
const repoRes = await apiGet(
|
|
25080
|
+
`/repos/${checkpoint.repo_id}`
|
|
25081
|
+
);
|
|
25082
|
+
if (repoRes.data.auto_push_enabled) {
|
|
25083
|
+
devToMainResult = await promoteToMain(checkpoint.repo_id);
|
|
25084
|
+
}
|
|
25085
|
+
return {
|
|
25086
|
+
content: [
|
|
25087
|
+
{
|
|
25088
|
+
type: "text",
|
|
25089
|
+
text: JSON.stringify(
|
|
25090
|
+
{
|
|
25091
|
+
checkpoint,
|
|
25092
|
+
promotion: {
|
|
25093
|
+
feat_to_development: featToDevResult,
|
|
25094
|
+
development_to_main: devToMainResult
|
|
25095
|
+
}
|
|
25096
|
+
},
|
|
25097
|
+
null,
|
|
25098
|
+
2
|
|
25099
|
+
)
|
|
25100
|
+
}
|
|
25101
|
+
]
|
|
25102
|
+
};
|
|
25103
|
+
} catch (err) {
|
|
25104
|
+
return {
|
|
25105
|
+
content: [
|
|
25106
|
+
{
|
|
25107
|
+
type: "text",
|
|
25108
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25109
|
+
}
|
|
25110
|
+
],
|
|
25111
|
+
isError: true
|
|
25112
|
+
};
|
|
25113
|
+
}
|
|
24226
25114
|
}
|
|
24227
|
-
|
|
24228
|
-
|
|
24229
|
-
|
|
24230
|
-
|
|
24231
|
-
|
|
24232
|
-
|
|
24233
|
-
|
|
24234
|
-
|
|
24235
|
-
|
|
24236
|
-
|
|
24237
|
-
|
|
24238
|
-
|
|
24239
|
-
|
|
25115
|
+
);
|
|
25116
|
+
server.registerTool(
|
|
25117
|
+
"create_task",
|
|
25118
|
+
{
|
|
25119
|
+
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.",
|
|
25120
|
+
inputSchema: {
|
|
25121
|
+
checkpoint_id: external_exports.string().uuid().optional().describe("The checkpoint UUID (for checkpoint-bound tasks)"),
|
|
25122
|
+
repo_id: external_exports.string().uuid().optional().describe("The repo UUID (for standalone tasks without checkpoint)"),
|
|
25123
|
+
title: external_exports.string().describe("Task title"),
|
|
25124
|
+
number: external_exports.number().int().optional().describe(
|
|
25125
|
+
"Task number (e.g. 1 for TASK-1). Auto-generated for standalone tasks if omitted."
|
|
25126
|
+
),
|
|
25127
|
+
requirements: external_exports.string().optional().describe("Task requirements text"),
|
|
25128
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25129
|
+
context: external_exports.any().optional().describe(
|
|
25130
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints)"
|
|
25131
|
+
),
|
|
25132
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25133
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
25134
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25135
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25136
|
+
context_development: external_exports.any().optional().describe(
|
|
25137
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25138
|
+
),
|
|
25139
|
+
resources: external_exports.any().optional().describe(
|
|
25140
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25141
|
+
)
|
|
25142
|
+
}
|
|
25143
|
+
},
|
|
25144
|
+
async ({
|
|
25145
|
+
checkpoint_id,
|
|
25146
|
+
repo_id,
|
|
25147
|
+
title,
|
|
25148
|
+
number: number3,
|
|
25149
|
+
requirements,
|
|
25150
|
+
status,
|
|
25151
|
+
context,
|
|
25152
|
+
qa,
|
|
25153
|
+
research,
|
|
25154
|
+
user_context,
|
|
25155
|
+
is_claude_written,
|
|
25156
|
+
context_development,
|
|
25157
|
+
resources
|
|
25158
|
+
}) => {
|
|
25159
|
+
try {
|
|
25160
|
+
const body = {
|
|
25161
|
+
title,
|
|
25162
|
+
requirements: requirements ?? null,
|
|
25163
|
+
status: status ?? "pending"
|
|
25164
|
+
};
|
|
25165
|
+
if (checkpoint_id) body.checkpoint_id = checkpoint_id;
|
|
25166
|
+
if (repo_id) body.repo_id = repo_id;
|
|
25167
|
+
if (number3 !== void 0) body.number = number3;
|
|
25168
|
+
if (context !== void 0) body.context = context;
|
|
25169
|
+
if (qa !== void 0) body.qa = qa;
|
|
25170
|
+
if (research !== void 0) body.research = research;
|
|
25171
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
25172
|
+
if (is_claude_written !== void 0)
|
|
25173
|
+
body.is_claude_written = is_claude_written;
|
|
25174
|
+
if (context_development !== void 0)
|
|
25175
|
+
body.context_development = context_development;
|
|
25176
|
+
if (resources !== void 0) body.resources = resources;
|
|
25177
|
+
const res = await apiPost("/tasks", body);
|
|
25178
|
+
return {
|
|
25179
|
+
content: [
|
|
25180
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25181
|
+
]
|
|
25182
|
+
};
|
|
25183
|
+
} catch (err) {
|
|
25184
|
+
return {
|
|
25185
|
+
content: [
|
|
25186
|
+
{
|
|
25187
|
+
type: "text",
|
|
25188
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25189
|
+
}
|
|
25190
|
+
],
|
|
25191
|
+
isError: true
|
|
25192
|
+
};
|
|
25193
|
+
}
|
|
24240
25194
|
}
|
|
24241
|
-
|
|
24242
|
-
server.registerTool(
|
|
24243
|
-
|
|
24244
|
-
|
|
24245
|
-
|
|
24246
|
-
|
|
24247
|
-
|
|
24248
|
-
|
|
24249
|
-
|
|
24250
|
-
|
|
24251
|
-
|
|
24252
|
-
|
|
24253
|
-
|
|
24254
|
-
|
|
24255
|
-
|
|
24256
|
-
|
|
24257
|
-
|
|
24258
|
-
|
|
24259
|
-
|
|
24260
|
-
|
|
25195
|
+
);
|
|
25196
|
+
server.registerTool(
|
|
25197
|
+
"update_task",
|
|
25198
|
+
{
|
|
25199
|
+
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.",
|
|
25200
|
+
inputSchema: {
|
|
25201
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
25202
|
+
title: external_exports.string().optional().describe("New title"),
|
|
25203
|
+
requirements: external_exports.string().optional().describe("New requirements text"),
|
|
25204
|
+
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
25205
|
+
files_changed: external_exports.array(
|
|
25206
|
+
external_exports.object({
|
|
25207
|
+
path: external_exports.string().describe("File path relative to repo root"),
|
|
25208
|
+
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
25209
|
+
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
25210
|
+
claude_approved: external_exports.boolean().optional().describe(
|
|
25211
|
+
"Whether Claude's automated checks passed for this file"
|
|
25212
|
+
),
|
|
25213
|
+
user_approved: external_exports.boolean().optional().describe(
|
|
25214
|
+
"Whether the user has approved this file (via git add or web UI)"
|
|
25215
|
+
),
|
|
25216
|
+
app_approved: external_exports.boolean().optional().describe(
|
|
25217
|
+
"Whether the app has approved this file (via automated checks)"
|
|
25218
|
+
)
|
|
25219
|
+
})
|
|
25220
|
+
).optional().describe("Files changed across all rounds"),
|
|
25221
|
+
claim_worktree_id: external_exports.string().uuid().optional().describe(
|
|
25222
|
+
"Worktree UUID to auto-claim the parent checkpoint when setting status to in_progress"
|
|
25223
|
+
),
|
|
25224
|
+
context: external_exports.any().optional().describe(
|
|
25225
|
+
"Context JSONB (decisions, discoveries, dependencies, constraints)"
|
|
25226
|
+
),
|
|
25227
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25228
|
+
research: external_exports.any().optional().describe("Research JSONB (topics with findings and sources)"),
|
|
25229
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25230
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25231
|
+
context_development: external_exports.any().optional().describe(
|
|
25232
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25233
|
+
),
|
|
25234
|
+
resources: external_exports.any().optional().describe(
|
|
25235
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25236
|
+
),
|
|
25237
|
+
app_file_approval_by_user: external_exports.boolean().optional().describe(
|
|
25238
|
+
"Whether user interacted with file approvals via web UI. CLI resets to false after processing."
|
|
25239
|
+
)
|
|
25240
|
+
}
|
|
25241
|
+
},
|
|
25242
|
+
async ({
|
|
25243
|
+
task_id,
|
|
25244
|
+
title,
|
|
25245
|
+
requirements,
|
|
25246
|
+
status,
|
|
25247
|
+
files_changed,
|
|
25248
|
+
claim_worktree_id,
|
|
25249
|
+
context,
|
|
25250
|
+
qa,
|
|
25251
|
+
research,
|
|
25252
|
+
user_context,
|
|
25253
|
+
is_claude_written,
|
|
25254
|
+
context_development,
|
|
25255
|
+
resources,
|
|
25256
|
+
app_file_approval_by_user
|
|
25257
|
+
}) => {
|
|
25258
|
+
const update = {};
|
|
25259
|
+
if (title !== void 0) update.title = title;
|
|
25260
|
+
if (requirements !== void 0) update.requirements = requirements;
|
|
25261
|
+
if (status !== void 0) update.status = status;
|
|
25262
|
+
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
25263
|
+
if (claim_worktree_id !== void 0)
|
|
25264
|
+
update.claim_worktree_id = claim_worktree_id;
|
|
25265
|
+
if (context !== void 0) update.context = context;
|
|
25266
|
+
if (qa !== void 0) update.qa = qa;
|
|
25267
|
+
if (research !== void 0) update.research = research;
|
|
25268
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25269
|
+
if (is_claude_written !== void 0)
|
|
25270
|
+
update.is_claude_written = is_claude_written;
|
|
25271
|
+
if (context_development !== void 0)
|
|
25272
|
+
update.context_development = context_development;
|
|
25273
|
+
if (resources !== void 0) update.resources = resources;
|
|
25274
|
+
if (app_file_approval_by_user !== void 0)
|
|
25275
|
+
update.app_file_approval_by_user = app_file_approval_by_user;
|
|
25276
|
+
if (Object.keys(update).length === 0) {
|
|
25277
|
+
return {
|
|
25278
|
+
content: [
|
|
25279
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25280
|
+
],
|
|
25281
|
+
isError: true
|
|
25282
|
+
};
|
|
25283
|
+
}
|
|
25284
|
+
try {
|
|
25285
|
+
const res = await apiPut(
|
|
25286
|
+
`/tasks/${task_id}`,
|
|
25287
|
+
update
|
|
25288
|
+
);
|
|
25289
|
+
return {
|
|
25290
|
+
content: [
|
|
25291
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25292
|
+
]
|
|
25293
|
+
};
|
|
25294
|
+
} catch (err) {
|
|
25295
|
+
return {
|
|
25296
|
+
content: [
|
|
25297
|
+
{
|
|
25298
|
+
type: "text",
|
|
25299
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25300
|
+
}
|
|
25301
|
+
],
|
|
25302
|
+
isError: true
|
|
25303
|
+
};
|
|
25304
|
+
}
|
|
24261
25305
|
}
|
|
24262
|
-
|
|
24263
|
-
|
|
24264
|
-
|
|
24265
|
-
|
|
24266
|
-
|
|
25306
|
+
);
|
|
25307
|
+
server.registerTool(
|
|
25308
|
+
"complete_task",
|
|
25309
|
+
{
|
|
25310
|
+
description: "Mark a task as completed. Sets status to 'completed' and completed_at to now.",
|
|
25311
|
+
inputSchema: {
|
|
25312
|
+
task_id: external_exports.string().uuid().describe("The task UUID")
|
|
25313
|
+
}
|
|
25314
|
+
},
|
|
25315
|
+
async ({ task_id }) => {
|
|
25316
|
+
try {
|
|
25317
|
+
let hasUnapproved = false;
|
|
25318
|
+
let unapprovedCount = 0;
|
|
25319
|
+
let resolvedRepoId = null;
|
|
25320
|
+
try {
|
|
25321
|
+
const taskRes = await apiGet(
|
|
25322
|
+
`/tasks/${task_id}`
|
|
25323
|
+
);
|
|
25324
|
+
const checkpointRes = await apiGet(
|
|
25325
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25326
|
+
);
|
|
25327
|
+
resolvedRepoId = checkpointRes.data.repo_id;
|
|
25328
|
+
const fileChangesRes = await apiGet("/file-changes", {
|
|
25329
|
+
repo_id: resolvedRepoId,
|
|
25330
|
+
task_id
|
|
25331
|
+
});
|
|
25332
|
+
const unapproved = (fileChangesRes.data ?? []).filter(
|
|
25333
|
+
(f) => !f.user_approved
|
|
25334
|
+
);
|
|
25335
|
+
unapprovedCount = unapproved.length;
|
|
25336
|
+
hasUnapproved = unapprovedCount > 0;
|
|
25337
|
+
} catch {
|
|
25338
|
+
}
|
|
25339
|
+
if (hasUnapproved) {
|
|
25340
|
+
return {
|
|
25341
|
+
content: [
|
|
25342
|
+
{
|
|
25343
|
+
type: "text",
|
|
25344
|
+
text: `Error: Cannot complete task \u2014 ${unapprovedCount} file(s) are not approved. All files must be user_approved before task completion.`
|
|
25345
|
+
}
|
|
25346
|
+
],
|
|
25347
|
+
isError: true
|
|
25348
|
+
};
|
|
25349
|
+
}
|
|
25350
|
+
const res = await apiPut(`/tasks/${task_id}`, {
|
|
25351
|
+
status: "completed",
|
|
25352
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25353
|
+
});
|
|
25354
|
+
if (resolvedRepoId) {
|
|
25355
|
+
try {
|
|
25356
|
+
await apiPatch("/file-changes", {
|
|
25357
|
+
action: "lock_task",
|
|
25358
|
+
task_id,
|
|
25359
|
+
repo_id: resolvedRepoId
|
|
25360
|
+
});
|
|
25361
|
+
} catch {
|
|
25362
|
+
}
|
|
25363
|
+
}
|
|
25364
|
+
return {
|
|
25365
|
+
content: [
|
|
25366
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25367
|
+
]
|
|
25368
|
+
};
|
|
25369
|
+
} catch (err) {
|
|
25370
|
+
return {
|
|
25371
|
+
content: [
|
|
25372
|
+
{
|
|
25373
|
+
type: "text",
|
|
25374
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25375
|
+
}
|
|
25376
|
+
],
|
|
25377
|
+
isError: true
|
|
25378
|
+
};
|
|
25379
|
+
}
|
|
24267
25380
|
}
|
|
24268
|
-
|
|
24269
|
-
server.registerTool(
|
|
24270
|
-
|
|
24271
|
-
|
|
24272
|
-
|
|
25381
|
+
);
|
|
25382
|
+
server.registerTool(
|
|
25383
|
+
"add_round",
|
|
25384
|
+
{
|
|
25385
|
+
description: "Add a round to a task.",
|
|
25386
|
+
inputSchema: {
|
|
25387
|
+
task_id: external_exports.string().uuid().describe("The task UUID"),
|
|
25388
|
+
number: external_exports.number().int().describe("Round number"),
|
|
25389
|
+
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
25390
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25391
|
+
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
25392
|
+
triggered_by: external_exports.enum(["user", "claude"]).optional().describe("Who triggered the round (user or claude)"),
|
|
25393
|
+
context: external_exports.any().optional().describe("Context JSONB"),
|
|
25394
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25395
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25396
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25397
|
+
context_development: external_exports.any().optional().describe(
|
|
25398
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25399
|
+
),
|
|
25400
|
+
resources: external_exports.any().optional().describe(
|
|
25401
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25402
|
+
)
|
|
25403
|
+
}
|
|
25404
|
+
},
|
|
25405
|
+
async ({
|
|
25406
|
+
task_id,
|
|
25407
|
+
number: number3,
|
|
25408
|
+
requirements,
|
|
25409
|
+
status,
|
|
25410
|
+
started_at,
|
|
25411
|
+
triggered_by,
|
|
25412
|
+
context,
|
|
25413
|
+
qa,
|
|
25414
|
+
user_context,
|
|
25415
|
+
is_claude_written,
|
|
25416
|
+
context_development,
|
|
25417
|
+
resources
|
|
25418
|
+
}) => {
|
|
25419
|
+
try {
|
|
25420
|
+
const body = {
|
|
25421
|
+
task_id,
|
|
25422
|
+
number: number3,
|
|
25423
|
+
requirements: requirements ?? null,
|
|
25424
|
+
status: status ?? "pending",
|
|
25425
|
+
started_at: started_at ?? null
|
|
25426
|
+
};
|
|
25427
|
+
if (triggered_by !== void 0) body.triggered_by = triggered_by;
|
|
25428
|
+
if (context !== void 0) body.context = context;
|
|
25429
|
+
if (qa !== void 0) body.qa = qa;
|
|
25430
|
+
if (user_context !== void 0) body.user_context = user_context;
|
|
25431
|
+
if (is_claude_written !== void 0)
|
|
25432
|
+
body.is_claude_written = is_claude_written;
|
|
25433
|
+
if (context_development !== void 0)
|
|
25434
|
+
body.context_development = context_development;
|
|
25435
|
+
if (resources !== void 0) body.resources = resources;
|
|
25436
|
+
const res = await apiPost("/rounds", body);
|
|
25437
|
+
return {
|
|
25438
|
+
content: [
|
|
25439
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25440
|
+
]
|
|
25441
|
+
};
|
|
25442
|
+
} catch (err) {
|
|
25443
|
+
return {
|
|
25444
|
+
content: [
|
|
25445
|
+
{
|
|
25446
|
+
type: "text",
|
|
25447
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25448
|
+
}
|
|
25449
|
+
],
|
|
25450
|
+
isError: true
|
|
25451
|
+
};
|
|
25452
|
+
}
|
|
24273
25453
|
}
|
|
24274
|
-
|
|
24275
|
-
|
|
24276
|
-
|
|
24277
|
-
|
|
24278
|
-
|
|
24279
|
-
|
|
25454
|
+
);
|
|
25455
|
+
server.registerTool(
|
|
25456
|
+
"update_round",
|
|
25457
|
+
{
|
|
25458
|
+
description: "Update an existing round.",
|
|
25459
|
+
inputSchema: {
|
|
25460
|
+
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
25461
|
+
requirements: external_exports.string().optional().describe("Round requirements text"),
|
|
25462
|
+
status: external_exports.string().optional().describe("New status (pending, in_progress, completed)"),
|
|
25463
|
+
started_at: external_exports.string().optional().describe("Start timestamp (ISO format)"),
|
|
25464
|
+
completed_at: external_exports.string().optional().describe("Completion timestamp (ISO format)"),
|
|
25465
|
+
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes"),
|
|
25466
|
+
files_changed: external_exports.array(
|
|
25467
|
+
external_exports.object({
|
|
25468
|
+
path: external_exports.string().describe("File path relative to repo root"),
|
|
25469
|
+
action: external_exports.string().describe("File action (new, modified, deleted)"),
|
|
25470
|
+
status: external_exports.string().describe("Approval status (approved, not_approved)"),
|
|
25471
|
+
claude_approved: external_exports.boolean().optional().describe(
|
|
25472
|
+
"Whether Claude's automated checks passed for this file"
|
|
25473
|
+
),
|
|
25474
|
+
user_approved: external_exports.boolean().optional().describe(
|
|
25475
|
+
"Whether the user has approved this file (via git add or web UI)"
|
|
25476
|
+
),
|
|
25477
|
+
app_approved: external_exports.boolean().optional().describe(
|
|
25478
|
+
"Whether the app has approved this file (via automated checks)"
|
|
25479
|
+
)
|
|
25480
|
+
})
|
|
25481
|
+
).optional().describe("Files changed in this round with approval status"),
|
|
25482
|
+
context: external_exports.any().optional().describe("Context JSONB"),
|
|
25483
|
+
qa: external_exports.any().optional().describe("QA JSONB (checklist items with type, check, status)"),
|
|
25484
|
+
user_context: external_exports.string().optional().describe("Original user input text (never overwritten once set)"),
|
|
25485
|
+
is_claude_written: external_exports.boolean().optional().describe("Whether Claude wrote the user_context"),
|
|
25486
|
+
context_development: external_exports.any().optional().describe(
|
|
25487
|
+
"Context development JSONB (discussion entries, Q&A, assessment decisions)"
|
|
25488
|
+
),
|
|
25489
|
+
resources: external_exports.any().optional().describe(
|
|
25490
|
+
"Resources JSONB array [{url, description, type, added_by}]"
|
|
25491
|
+
)
|
|
25492
|
+
}
|
|
25493
|
+
},
|
|
25494
|
+
async ({
|
|
25495
|
+
round_id,
|
|
25496
|
+
requirements,
|
|
25497
|
+
status,
|
|
25498
|
+
started_at,
|
|
25499
|
+
completed_at,
|
|
25500
|
+
duration_minutes,
|
|
25501
|
+
files_changed,
|
|
25502
|
+
context,
|
|
25503
|
+
qa,
|
|
25504
|
+
user_context,
|
|
25505
|
+
is_claude_written,
|
|
25506
|
+
context_development,
|
|
25507
|
+
resources
|
|
25508
|
+
}) => {
|
|
25509
|
+
const update = {};
|
|
25510
|
+
if (requirements !== void 0) update.requirements = requirements;
|
|
25511
|
+
if (status !== void 0) update.status = status;
|
|
25512
|
+
if (started_at !== void 0) update.started_at = started_at;
|
|
25513
|
+
if (completed_at !== void 0) update.completed_at = completed_at;
|
|
25514
|
+
if (duration_minutes !== void 0)
|
|
25515
|
+
update.duration_minutes = duration_minutes;
|
|
25516
|
+
if (files_changed !== void 0) update.files_changed = files_changed;
|
|
25517
|
+
if (context !== void 0) update.context = context;
|
|
25518
|
+
if (qa !== void 0) update.qa = qa;
|
|
25519
|
+
if (user_context !== void 0) update.user_context = user_context;
|
|
25520
|
+
if (is_claude_written !== void 0)
|
|
25521
|
+
update.is_claude_written = is_claude_written;
|
|
25522
|
+
if (context_development !== void 0)
|
|
25523
|
+
update.context_development = context_development;
|
|
25524
|
+
if (resources !== void 0) update.resources = resources;
|
|
25525
|
+
if (Object.keys(update).length === 0) {
|
|
25526
|
+
return {
|
|
25527
|
+
content: [
|
|
25528
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25529
|
+
],
|
|
25530
|
+
isError: true
|
|
25531
|
+
};
|
|
25532
|
+
}
|
|
25533
|
+
try {
|
|
25534
|
+
const res = await apiPut(
|
|
25535
|
+
`/rounds/${round_id}`,
|
|
25536
|
+
update
|
|
25537
|
+
);
|
|
25538
|
+
if (files_changed && files_changed.length > 0) {
|
|
25539
|
+
try {
|
|
25540
|
+
const taskRes = await apiGet(
|
|
25541
|
+
`/tasks/${res.data.task_id}`
|
|
25542
|
+
);
|
|
25543
|
+
const checkpointRes = await apiGet(
|
|
25544
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25545
|
+
);
|
|
25546
|
+
await apiPatch("/file-changes", {
|
|
25547
|
+
action: "replace_round_files",
|
|
25548
|
+
round_id,
|
|
25549
|
+
repo_id: checkpointRes.data.repo_id,
|
|
25550
|
+
checkpoint_id: taskRes.data.checkpoint_id,
|
|
25551
|
+
task_id: res.data.task_id,
|
|
25552
|
+
source: "round",
|
|
25553
|
+
files: files_changed.map(
|
|
25554
|
+
(f) => ({
|
|
25555
|
+
file_path: f.path,
|
|
25556
|
+
action: f.action,
|
|
25557
|
+
claude_approved: f.claude_approved ?? false,
|
|
25558
|
+
user_approved: f.user_approved ?? false
|
|
25559
|
+
})
|
|
25560
|
+
)
|
|
25561
|
+
});
|
|
25562
|
+
} catch {
|
|
25563
|
+
}
|
|
25564
|
+
}
|
|
25565
|
+
return {
|
|
25566
|
+
content: [
|
|
25567
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25568
|
+
]
|
|
25569
|
+
};
|
|
25570
|
+
} catch (err) {
|
|
25571
|
+
return {
|
|
25572
|
+
content: [
|
|
25573
|
+
{
|
|
25574
|
+
type: "text",
|
|
25575
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25576
|
+
}
|
|
25577
|
+
],
|
|
25578
|
+
isError: true
|
|
25579
|
+
};
|
|
25580
|
+
}
|
|
24280
25581
|
}
|
|
24281
|
-
|
|
24282
|
-
server.registerTool(
|
|
24283
|
-
|
|
24284
|
-
|
|
24285
|
-
|
|
24286
|
-
|
|
24287
|
-
|
|
24288
|
-
|
|
24289
|
-
|
|
24290
|
-
|
|
25582
|
+
);
|
|
25583
|
+
server.registerTool(
|
|
25584
|
+
"complete_round",
|
|
25585
|
+
{
|
|
25586
|
+
description: "Mark a round as completed. Sets status to 'completed' and completed_at to now.",
|
|
25587
|
+
inputSchema: {
|
|
25588
|
+
round_id: external_exports.string().uuid().describe("The round UUID"),
|
|
25589
|
+
duration_minutes: external_exports.number().int().optional().describe("Duration in minutes")
|
|
25590
|
+
}
|
|
25591
|
+
},
|
|
25592
|
+
async ({ round_id, duration_minutes }) => {
|
|
25593
|
+
try {
|
|
25594
|
+
const update = {
|
|
25595
|
+
status: "completed",
|
|
25596
|
+
completed_at: (/* @__PURE__ */ new Date()).toISOString()
|
|
25597
|
+
};
|
|
25598
|
+
if (duration_minutes !== void 0)
|
|
25599
|
+
update.duration_minutes = duration_minutes;
|
|
25600
|
+
const res = await apiPut(
|
|
25601
|
+
`/rounds/${round_id}`,
|
|
25602
|
+
update
|
|
25603
|
+
);
|
|
25604
|
+
let unapprovedFiles = [];
|
|
25605
|
+
try {
|
|
25606
|
+
const taskRes = await apiGet(
|
|
25607
|
+
`/tasks/${res.data.task_id}`
|
|
25608
|
+
);
|
|
25609
|
+
const checkpointRes = await apiGet(
|
|
25610
|
+
`/checkpoints/${taskRes.data.checkpoint_id}`
|
|
25611
|
+
);
|
|
25612
|
+
const repoId = checkpointRes.data.repo_id;
|
|
25613
|
+
await apiPatch("/file-changes", {
|
|
25614
|
+
action: "lock_round",
|
|
25615
|
+
round_id,
|
|
25616
|
+
repo_id: repoId
|
|
25617
|
+
});
|
|
25618
|
+
const fileChangesRes = await apiGet("/file-changes", {
|
|
25619
|
+
repo_id: repoId,
|
|
25620
|
+
round_id
|
|
25621
|
+
});
|
|
25622
|
+
unapprovedFiles = (fileChangesRes.data ?? []).filter((f) => !f.user_approved).map((f) => ({ file_path: f.file_path, action: f.action }));
|
|
25623
|
+
} catch {
|
|
25624
|
+
}
|
|
25625
|
+
const result = {
|
|
25626
|
+
...res.data,
|
|
25627
|
+
unapproved_files: unapprovedFiles,
|
|
25628
|
+
unapproved_count: unapprovedFiles.length
|
|
25629
|
+
};
|
|
25630
|
+
return {
|
|
25631
|
+
content: [
|
|
25632
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
25633
|
+
]
|
|
25634
|
+
};
|
|
25635
|
+
} catch (err) {
|
|
25636
|
+
return {
|
|
25637
|
+
content: [
|
|
25638
|
+
{
|
|
25639
|
+
type: "text",
|
|
25640
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25641
|
+
}
|
|
25642
|
+
],
|
|
25643
|
+
isError: true
|
|
25644
|
+
};
|
|
25645
|
+
}
|
|
24291
25646
|
}
|
|
24292
|
-
|
|
24293
|
-
|
|
24294
|
-
|
|
24295
|
-
|
|
24296
|
-
|
|
24297
|
-
|
|
24298
|
-
|
|
24299
|
-
|
|
24300
|
-
|
|
24301
|
-
|
|
24302
|
-
|
|
24303
|
-
|
|
24304
|
-
|
|
24305
|
-
|
|
25647
|
+
);
|
|
25648
|
+
server.registerTool(
|
|
25649
|
+
"create_launch",
|
|
25650
|
+
{
|
|
25651
|
+
description: "Create a new launch for a repo.",
|
|
25652
|
+
inputSchema: {
|
|
25653
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
25654
|
+
title: external_exports.string().describe("Launch title"),
|
|
25655
|
+
type: external_exports.string().describe("Launch type"),
|
|
25656
|
+
status: external_exports.string().optional().describe("Initial status (default: pending)"),
|
|
25657
|
+
version: external_exports.string().optional().describe("Version string"),
|
|
25658
|
+
user_requirements: external_exports.any().optional().describe("User requirements (JSON)")
|
|
25659
|
+
}
|
|
25660
|
+
},
|
|
25661
|
+
async ({ repo_id, title, type, status, version: version2, user_requirements }) => {
|
|
25662
|
+
try {
|
|
25663
|
+
const res = await apiPost("/launches", {
|
|
25664
|
+
repo_id,
|
|
25665
|
+
title,
|
|
25666
|
+
type,
|
|
25667
|
+
status: status ?? "pending",
|
|
25668
|
+
version: version2 ?? null,
|
|
25669
|
+
user_requirements: user_requirements ?? null
|
|
25670
|
+
});
|
|
25671
|
+
return {
|
|
25672
|
+
content: [
|
|
25673
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25674
|
+
]
|
|
25675
|
+
};
|
|
25676
|
+
} catch (err) {
|
|
25677
|
+
return {
|
|
25678
|
+
content: [
|
|
25679
|
+
{
|
|
25680
|
+
type: "text",
|
|
25681
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25682
|
+
}
|
|
25683
|
+
],
|
|
25684
|
+
isError: true
|
|
25685
|
+
};
|
|
25686
|
+
}
|
|
24306
25687
|
}
|
|
24307
|
-
|
|
24308
|
-
server.registerTool(
|
|
24309
|
-
|
|
24310
|
-
|
|
24311
|
-
|
|
24312
|
-
|
|
24313
|
-
|
|
24314
|
-
|
|
24315
|
-
|
|
24316
|
-
|
|
24317
|
-
|
|
24318
|
-
|
|
24319
|
-
|
|
24320
|
-
|
|
24321
|
-
|
|
24322
|
-
|
|
24323
|
-
|
|
24324
|
-
|
|
25688
|
+
);
|
|
25689
|
+
server.registerTool(
|
|
25690
|
+
"update_launch",
|
|
25691
|
+
{
|
|
25692
|
+
description: "Update an existing launch.",
|
|
25693
|
+
inputSchema: {
|
|
25694
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID"),
|
|
25695
|
+
title: external_exports.string().optional().describe("New title"),
|
|
25696
|
+
type: external_exports.string().optional().describe("New type"),
|
|
25697
|
+
status: external_exports.string().optional().describe("New status"),
|
|
25698
|
+
version: external_exports.string().optional().describe("New version"),
|
|
25699
|
+
user_requirements: external_exports.any().optional().describe("New user requirements (JSON)")
|
|
25700
|
+
}
|
|
25701
|
+
},
|
|
25702
|
+
async ({ launch_id, title, type, status, version: version2, user_requirements }) => {
|
|
25703
|
+
const update = {};
|
|
25704
|
+
if (title !== void 0) update.title = title;
|
|
25705
|
+
if (type !== void 0) update.type = type;
|
|
25706
|
+
if (status !== void 0) update.status = status;
|
|
25707
|
+
if (version2 !== void 0) update.version = version2;
|
|
25708
|
+
if (user_requirements !== void 0)
|
|
25709
|
+
update.user_requirements = user_requirements;
|
|
25710
|
+
if (Object.keys(update).length === 0) {
|
|
25711
|
+
return {
|
|
25712
|
+
content: [
|
|
25713
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25714
|
+
],
|
|
25715
|
+
isError: true
|
|
25716
|
+
};
|
|
25717
|
+
}
|
|
25718
|
+
try {
|
|
25719
|
+
const res = await apiPut(
|
|
25720
|
+
`/launches/${launch_id}`,
|
|
25721
|
+
update
|
|
25722
|
+
);
|
|
25723
|
+
return {
|
|
25724
|
+
content: [
|
|
25725
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25726
|
+
]
|
|
25727
|
+
};
|
|
25728
|
+
} catch (err) {
|
|
25729
|
+
return {
|
|
25730
|
+
content: [
|
|
25731
|
+
{
|
|
25732
|
+
type: "text",
|
|
25733
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25734
|
+
}
|
|
25735
|
+
],
|
|
25736
|
+
isError: true
|
|
25737
|
+
};
|
|
25738
|
+
}
|
|
24325
25739
|
}
|
|
24326
|
-
|
|
24327
|
-
|
|
24328
|
-
|
|
24329
|
-
|
|
24330
|
-
|
|
25740
|
+
);
|
|
25741
|
+
server.registerTool(
|
|
25742
|
+
"delete_launch",
|
|
25743
|
+
{
|
|
25744
|
+
description: "Delete a launch by ID.",
|
|
25745
|
+
inputSchema: {
|
|
25746
|
+
launch_id: external_exports.string().uuid().describe("The launch UUID")
|
|
25747
|
+
}
|
|
25748
|
+
},
|
|
25749
|
+
async ({ launch_id }) => {
|
|
25750
|
+
try {
|
|
25751
|
+
await apiDelete(`/launches/${launch_id}`);
|
|
25752
|
+
return {
|
|
25753
|
+
content: [
|
|
25754
|
+
{ type: "text", text: "Launch deleted successfully" }
|
|
25755
|
+
]
|
|
25756
|
+
};
|
|
25757
|
+
} catch (err) {
|
|
25758
|
+
return {
|
|
25759
|
+
content: [
|
|
25760
|
+
{
|
|
25761
|
+
type: "text",
|
|
25762
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25763
|
+
}
|
|
25764
|
+
],
|
|
25765
|
+
isError: true
|
|
25766
|
+
};
|
|
25767
|
+
}
|
|
24331
25768
|
}
|
|
24332
|
-
|
|
24333
|
-
server.registerTool(
|
|
24334
|
-
|
|
24335
|
-
|
|
24336
|
-
|
|
25769
|
+
);
|
|
25770
|
+
server.registerTool(
|
|
25771
|
+
"create_session_log",
|
|
25772
|
+
{
|
|
25773
|
+
description: "Create a new session log for a repo.",
|
|
25774
|
+
inputSchema: {
|
|
25775
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
25776
|
+
session_date: external_exports.string().describe("Session date (YYYY-MM-DD)"),
|
|
25777
|
+
day_number: external_exports.number().int().describe("Day number (e.g. 41 for D-41)"),
|
|
25778
|
+
session_number: external_exports.number().int().optional().describe("Session number within the day (default: 1)"),
|
|
25779
|
+
content: external_exports.any().optional().describe("Session log content (JSON)"),
|
|
25780
|
+
worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID that created this session log")
|
|
25781
|
+
}
|
|
25782
|
+
},
|
|
25783
|
+
async ({
|
|
25784
|
+
repo_id,
|
|
25785
|
+
session_date,
|
|
25786
|
+
day_number,
|
|
25787
|
+
session_number,
|
|
25788
|
+
content,
|
|
25789
|
+
worktree_id
|
|
25790
|
+
}) => {
|
|
25791
|
+
try {
|
|
25792
|
+
const body = {
|
|
25793
|
+
repo_id,
|
|
25794
|
+
session_date,
|
|
25795
|
+
day_number,
|
|
25796
|
+
session_number: session_number ?? 1,
|
|
25797
|
+
content: content ?? null
|
|
25798
|
+
};
|
|
25799
|
+
if (worktree_id) body.worktree_id = worktree_id;
|
|
25800
|
+
const res = await apiPost(
|
|
25801
|
+
"/session-logs",
|
|
25802
|
+
body
|
|
25803
|
+
);
|
|
25804
|
+
return {
|
|
25805
|
+
content: [
|
|
25806
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25807
|
+
]
|
|
25808
|
+
};
|
|
25809
|
+
} catch (err) {
|
|
25810
|
+
return {
|
|
25811
|
+
content: [
|
|
25812
|
+
{
|
|
25813
|
+
type: "text",
|
|
25814
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25815
|
+
}
|
|
25816
|
+
],
|
|
25817
|
+
isError: true
|
|
25818
|
+
};
|
|
25819
|
+
}
|
|
24337
25820
|
}
|
|
24338
|
-
|
|
24339
|
-
|
|
24340
|
-
|
|
24341
|
-
|
|
24342
|
-
|
|
24343
|
-
|
|
25821
|
+
);
|
|
25822
|
+
server.registerTool(
|
|
25823
|
+
"update_session_log",
|
|
25824
|
+
{
|
|
25825
|
+
description: "Update an existing session log.",
|
|
25826
|
+
inputSchema: {
|
|
25827
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID"),
|
|
25828
|
+
session_date: external_exports.string().optional().describe("New session date"),
|
|
25829
|
+
day_number: external_exports.number().int().optional().describe("New day number"),
|
|
25830
|
+
session_number: external_exports.number().int().optional().describe("New session number"),
|
|
25831
|
+
content: external_exports.any().optional().describe("New content (JSON)")
|
|
25832
|
+
}
|
|
25833
|
+
},
|
|
25834
|
+
async ({
|
|
25835
|
+
session_log_id,
|
|
25836
|
+
session_date,
|
|
25837
|
+
day_number,
|
|
25838
|
+
session_number,
|
|
25839
|
+
content
|
|
25840
|
+
}) => {
|
|
25841
|
+
const update = {};
|
|
25842
|
+
if (session_date !== void 0) update.session_date = session_date;
|
|
25843
|
+
if (day_number !== void 0) update.day_number = day_number;
|
|
25844
|
+
if (session_number !== void 0) update.session_number = session_number;
|
|
25845
|
+
if (content !== void 0) update.content = content;
|
|
25846
|
+
if (Object.keys(update).length === 0) {
|
|
25847
|
+
return {
|
|
25848
|
+
content: [
|
|
25849
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
25850
|
+
],
|
|
25851
|
+
isError: true
|
|
25852
|
+
};
|
|
25853
|
+
}
|
|
25854
|
+
try {
|
|
25855
|
+
const res = await apiPut(
|
|
25856
|
+
`/session-logs/${session_log_id}`,
|
|
25857
|
+
update
|
|
25858
|
+
);
|
|
25859
|
+
return {
|
|
25860
|
+
content: [
|
|
25861
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
25862
|
+
]
|
|
25863
|
+
};
|
|
25864
|
+
} catch (err) {
|
|
25865
|
+
return {
|
|
25866
|
+
content: [
|
|
25867
|
+
{
|
|
25868
|
+
type: "text",
|
|
25869
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25870
|
+
}
|
|
25871
|
+
],
|
|
25872
|
+
isError: true
|
|
25873
|
+
};
|
|
25874
|
+
}
|
|
24344
25875
|
}
|
|
24345
|
-
|
|
24346
|
-
server.registerTool(
|
|
24347
|
-
|
|
24348
|
-
|
|
24349
|
-
|
|
24350
|
-
|
|
25876
|
+
);
|
|
25877
|
+
server.registerTool(
|
|
25878
|
+
"delete_session_log",
|
|
25879
|
+
{
|
|
25880
|
+
description: "Delete a session log by ID.",
|
|
25881
|
+
inputSchema: {
|
|
25882
|
+
session_log_id: external_exports.string().uuid().describe("The session log UUID")
|
|
25883
|
+
}
|
|
25884
|
+
},
|
|
25885
|
+
async ({ session_log_id }) => {
|
|
25886
|
+
try {
|
|
25887
|
+
await apiDelete(`/session-logs/${session_log_id}`);
|
|
25888
|
+
return {
|
|
25889
|
+
content: [
|
|
25890
|
+
{ type: "text", text: "Session log deleted successfully" }
|
|
25891
|
+
]
|
|
25892
|
+
};
|
|
25893
|
+
} catch (err) {
|
|
25894
|
+
return {
|
|
25895
|
+
content: [
|
|
25896
|
+
{
|
|
25897
|
+
type: "text",
|
|
25898
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25899
|
+
}
|
|
25900
|
+
],
|
|
25901
|
+
isError: true
|
|
25902
|
+
};
|
|
25903
|
+
}
|
|
24351
25904
|
}
|
|
24352
|
-
|
|
24353
|
-
|
|
24354
|
-
|
|
24355
|
-
|
|
24356
|
-
|
|
24357
|
-
|
|
24358
|
-
|
|
24359
|
-
|
|
24360
|
-
}
|
|
24361
|
-
|
|
24362
|
-
|
|
24363
|
-
|
|
25905
|
+
);
|
|
25906
|
+
server.registerTool(
|
|
25907
|
+
"sync_claude_files",
|
|
25908
|
+
{
|
|
25909
|
+
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.",
|
|
25910
|
+
inputSchema: {
|
|
25911
|
+
repo_id: external_exports.string().uuid().describe("Repository ID to sync files for"),
|
|
25912
|
+
project_path: external_exports.string().describe("Absolute path to the project root directory")
|
|
25913
|
+
}
|
|
25914
|
+
},
|
|
25915
|
+
async ({ repo_id, project_path }) => {
|
|
25916
|
+
try {
|
|
25917
|
+
const syncResult = await executeSyncToLocal({
|
|
25918
|
+
repoId: repo_id,
|
|
25919
|
+
projectPath: project_path
|
|
25920
|
+
});
|
|
25921
|
+
const { byType, totals, dbOnlyFiles } = syncResult;
|
|
25922
|
+
const summary = {
|
|
25923
|
+
...byType,
|
|
25924
|
+
totals: {
|
|
25925
|
+
created: totals.created,
|
|
25926
|
+
updated: totals.updated,
|
|
25927
|
+
deleted: totals.deleted
|
|
25928
|
+
},
|
|
25929
|
+
message: totals.created + totals.updated + totals.deleted === 0 ? "All files up to date" : `Synced: ${totals.created} created, ${totals.updated} updated, ${totals.deleted} deleted`
|
|
25930
|
+
};
|
|
25931
|
+
if (dbOnlyFiles.length > 0) {
|
|
25932
|
+
summary.db_only_files = dbOnlyFiles;
|
|
25933
|
+
summary.message += `
|
|
24364
25934
|
|
|
24365
25935
|
${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.`;
|
|
25936
|
+
}
|
|
25937
|
+
return {
|
|
25938
|
+
content: [
|
|
25939
|
+
{ type: "text", text: JSON.stringify(summary, null, 2) }
|
|
25940
|
+
]
|
|
25941
|
+
};
|
|
25942
|
+
} catch (err) {
|
|
25943
|
+
return {
|
|
25944
|
+
content: [
|
|
25945
|
+
{
|
|
25946
|
+
type: "text",
|
|
25947
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
25948
|
+
}
|
|
25949
|
+
],
|
|
25950
|
+
isError: true
|
|
25951
|
+
};
|
|
24366
25952
|
}
|
|
24367
|
-
return { content: [{ type: "text", text: JSON.stringify(summary, null, 2) }] };
|
|
24368
|
-
} catch (err) {
|
|
24369
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24370
|
-
}
|
|
24371
|
-
});
|
|
24372
|
-
server.registerTool("delete_claude_files", {
|
|
24373
|
-
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.",
|
|
24374
|
-
inputSchema: {
|
|
24375
|
-
repo_id: external_exports.string().uuid().describe("Repository ID"),
|
|
24376
|
-
files: external_exports.array(external_exports.object({
|
|
24377
|
-
type: external_exports.string().describe("File type: command, agent, skill, rule, hook, template, docs_stack"),
|
|
24378
|
-
name: external_exports.string().describe("File name"),
|
|
24379
|
-
category: external_exports.string().nullable().optional().describe("Category (for commands: e.g. 'development/checkpoint')")
|
|
24380
|
-
})).describe("Files to soft-delete from DB")
|
|
24381
|
-
}
|
|
24382
|
-
}, async ({ repo_id, files }) => {
|
|
24383
|
-
try {
|
|
24384
|
-
const res = await apiPost("/sync/files", {
|
|
24385
|
-
repo_id,
|
|
24386
|
-
delete_keys: files
|
|
24387
|
-
});
|
|
24388
|
-
return {
|
|
24389
|
-
content: [{
|
|
24390
|
-
type: "text",
|
|
24391
|
-
text: JSON.stringify({
|
|
24392
|
-
deleted: res.data.deleted,
|
|
24393
|
-
message: `Soft-deleted ${res.data.deleted} file(s) from DB. Run sync_claude_files to clean up local copies.`
|
|
24394
|
-
}, null, 2)
|
|
24395
|
-
}]
|
|
24396
|
-
};
|
|
24397
|
-
} catch (err) {
|
|
24398
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24399
25953
|
}
|
|
24400
|
-
|
|
24401
|
-
server.registerTool(
|
|
24402
|
-
|
|
24403
|
-
|
|
24404
|
-
|
|
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
|
-
|
|
25954
|
+
);
|
|
25955
|
+
server.registerTool(
|
|
25956
|
+
"delete_claude_files",
|
|
25957
|
+
{
|
|
25958
|
+
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.",
|
|
25959
|
+
inputSchema: {
|
|
25960
|
+
repo_id: external_exports.string().uuid().describe("Repository ID"),
|
|
25961
|
+
files: external_exports.array(
|
|
25962
|
+
external_exports.object({
|
|
25963
|
+
type: external_exports.string().describe(
|
|
25964
|
+
"File type: command, agent, skill, rule, hook, template, docs_stack, context"
|
|
25965
|
+
),
|
|
25966
|
+
name: external_exports.string().describe("File name"),
|
|
25967
|
+
category: external_exports.string().nullable().optional().describe(
|
|
25968
|
+
"Category (for commands: e.g. 'development/checkpoint')"
|
|
25969
|
+
)
|
|
25970
|
+
})
|
|
25971
|
+
).describe("Files to soft-delete from DB")
|
|
25972
|
+
}
|
|
25973
|
+
},
|
|
25974
|
+
async ({ repo_id, files }) => {
|
|
25975
|
+
try {
|
|
25976
|
+
const res = await apiPost("/sync/files", {
|
|
25977
|
+
repo_id,
|
|
25978
|
+
delete_keys: files
|
|
25979
|
+
});
|
|
25980
|
+
return {
|
|
25981
|
+
content: [
|
|
25982
|
+
{
|
|
25983
|
+
type: "text",
|
|
25984
|
+
text: JSON.stringify(
|
|
25985
|
+
{
|
|
25986
|
+
deleted: res.data.deleted,
|
|
25987
|
+
message: `Soft-deleted ${res.data.deleted} file(s) from DB. Run sync_claude_files to clean up local copies.`
|
|
25988
|
+
},
|
|
25989
|
+
null,
|
|
25990
|
+
2
|
|
25991
|
+
)
|
|
25992
|
+
}
|
|
25993
|
+
]
|
|
25994
|
+
};
|
|
25995
|
+
} catch (err) {
|
|
25996
|
+
return {
|
|
25997
|
+
content: [
|
|
25998
|
+
{
|
|
25999
|
+
type: "text",
|
|
26000
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26001
|
+
}
|
|
26002
|
+
],
|
|
26003
|
+
isError: true
|
|
26004
|
+
};
|
|
26005
|
+
}
|
|
24436
26006
|
}
|
|
24437
|
-
|
|
24438
|
-
server.registerTool(
|
|
24439
|
-
|
|
24440
|
-
|
|
24441
|
-
|
|
24442
|
-
|
|
24443
|
-
|
|
24444
|
-
|
|
26007
|
+
);
|
|
26008
|
+
server.registerTool(
|
|
26009
|
+
"update_session_state",
|
|
26010
|
+
{
|
|
26011
|
+
description: "Update session state for a repo. Actions: activate (deactivates other repos), deactivate, pause, refresh, clear_refresh.",
|
|
26012
|
+
inputSchema: {
|
|
26013
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26014
|
+
action: external_exports.enum(["activate", "deactivate", "pause", "refresh", "clear_refresh"]).describe("Session action to perform")
|
|
26015
|
+
}
|
|
26016
|
+
},
|
|
26017
|
+
async ({ repo_id, action }) => {
|
|
26018
|
+
try {
|
|
26019
|
+
const res = await apiPatch(
|
|
26020
|
+
`/repos/${repo_id}/session`,
|
|
26021
|
+
{ action }
|
|
26022
|
+
);
|
|
26023
|
+
return {
|
|
26024
|
+
content: [
|
|
26025
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
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
|
+
}
|
|
24445
26039
|
}
|
|
24446
|
-
|
|
24447
|
-
|
|
24448
|
-
|
|
24449
|
-
|
|
24450
|
-
|
|
24451
|
-
|
|
24452
|
-
|
|
24453
|
-
|
|
24454
|
-
|
|
24455
|
-
|
|
24456
|
-
|
|
26040
|
+
);
|
|
26041
|
+
server.registerTool(
|
|
26042
|
+
"update_server_config",
|
|
26043
|
+
{
|
|
26044
|
+
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.",
|
|
26045
|
+
inputSchema: {
|
|
26046
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26047
|
+
server_port: external_exports.number().int().optional().describe("Server port number"),
|
|
26048
|
+
server_type: external_exports.string().optional().describe("Server type"),
|
|
26049
|
+
active_servers: external_exports.any().optional().describe("Active servers (JSON array)")
|
|
26050
|
+
}
|
|
26051
|
+
},
|
|
26052
|
+
async ({ repo_id, server_port, server_type, active_servers }) => {
|
|
26053
|
+
const body = {};
|
|
26054
|
+
if (server_port !== void 0) body.server_port = server_port;
|
|
26055
|
+
if (server_type !== void 0) body.server_type = server_type;
|
|
26056
|
+
if (active_servers !== void 0) body.active_servers = active_servers;
|
|
26057
|
+
if (Object.keys(body).length === 0) {
|
|
26058
|
+
return {
|
|
26059
|
+
content: [
|
|
26060
|
+
{ type: "text", text: "Error: No fields to update" }
|
|
26061
|
+
],
|
|
26062
|
+
isError: true
|
|
26063
|
+
};
|
|
26064
|
+
}
|
|
26065
|
+
try {
|
|
26066
|
+
const res = await apiPatch(
|
|
26067
|
+
`/repos/${repo_id}/server`,
|
|
26068
|
+
body
|
|
26069
|
+
);
|
|
26070
|
+
return {
|
|
26071
|
+
content: [
|
|
26072
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26073
|
+
]
|
|
26074
|
+
};
|
|
26075
|
+
} catch (err) {
|
|
26076
|
+
return {
|
|
26077
|
+
content: [
|
|
26078
|
+
{
|
|
26079
|
+
type: "text",
|
|
26080
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26081
|
+
}
|
|
26082
|
+
],
|
|
26083
|
+
isError: true
|
|
26084
|
+
};
|
|
26085
|
+
}
|
|
24457
26086
|
}
|
|
24458
|
-
|
|
24459
|
-
server.registerTool(
|
|
24460
|
-
|
|
24461
|
-
|
|
24462
|
-
|
|
26087
|
+
);
|
|
26088
|
+
server.registerTool(
|
|
26089
|
+
"create_worktree",
|
|
26090
|
+
{
|
|
26091
|
+
description: "Create a new worktree for a repo.",
|
|
26092
|
+
inputSchema: {
|
|
26093
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26094
|
+
name: external_exports.string().describe("Worktree name (unique per repo)"),
|
|
26095
|
+
path: external_exports.string().optional().describe("Local filesystem path"),
|
|
26096
|
+
status: external_exports.string().optional().describe("Initial status (default: active)")
|
|
26097
|
+
}
|
|
26098
|
+
},
|
|
26099
|
+
async ({ repo_id, name, path, status }) => {
|
|
26100
|
+
try {
|
|
26101
|
+
const res = await apiPost("/worktrees", {
|
|
26102
|
+
repo_id,
|
|
26103
|
+
name,
|
|
26104
|
+
path: path ?? null,
|
|
26105
|
+
status: status ?? "active"
|
|
26106
|
+
});
|
|
26107
|
+
return {
|
|
26108
|
+
content: [
|
|
26109
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26110
|
+
]
|
|
26111
|
+
};
|
|
26112
|
+
} catch (err) {
|
|
26113
|
+
return {
|
|
26114
|
+
content: [
|
|
26115
|
+
{
|
|
26116
|
+
type: "text",
|
|
26117
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26118
|
+
}
|
|
26119
|
+
],
|
|
26120
|
+
isError: true
|
|
26121
|
+
};
|
|
26122
|
+
}
|
|
24463
26123
|
}
|
|
24464
|
-
|
|
24465
|
-
|
|
24466
|
-
|
|
24467
|
-
|
|
24468
|
-
|
|
24469
|
-
|
|
26124
|
+
);
|
|
26125
|
+
server.registerTool(
|
|
26126
|
+
"delete_worktree",
|
|
26127
|
+
{
|
|
26128
|
+
description: "Delete a worktree by ID.",
|
|
26129
|
+
inputSchema: {
|
|
26130
|
+
worktree_id: external_exports.string().uuid().describe("The worktree UUID")
|
|
26131
|
+
}
|
|
26132
|
+
},
|
|
26133
|
+
async ({ worktree_id }) => {
|
|
26134
|
+
try {
|
|
26135
|
+
await apiDelete(`/worktrees/${worktree_id}`);
|
|
26136
|
+
return {
|
|
26137
|
+
content: [
|
|
26138
|
+
{ type: "text", text: "Worktree deleted successfully" }
|
|
26139
|
+
]
|
|
26140
|
+
};
|
|
26141
|
+
} catch (err) {
|
|
26142
|
+
return {
|
|
26143
|
+
content: [
|
|
26144
|
+
{
|
|
26145
|
+
type: "text",
|
|
26146
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26147
|
+
}
|
|
26148
|
+
],
|
|
26149
|
+
isError: true
|
|
26150
|
+
};
|
|
26151
|
+
}
|
|
24470
26152
|
}
|
|
24471
|
-
|
|
24472
|
-
server.registerTool(
|
|
24473
|
-
|
|
24474
|
-
|
|
24475
|
-
|
|
24476
|
-
|
|
24477
|
-
|
|
24478
|
-
|
|
24479
|
-
|
|
26153
|
+
);
|
|
26154
|
+
server.registerTool(
|
|
26155
|
+
"create_pr",
|
|
26156
|
+
{
|
|
26157
|
+
description: "Create a GitHub PR for a repo. Uses gh CLI.",
|
|
26158
|
+
inputSchema: {
|
|
26159
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26160
|
+
head: external_exports.string().describe("Source branch name"),
|
|
26161
|
+
base: external_exports.string().describe("Target branch name"),
|
|
26162
|
+
title: external_exports.string().describe("PR title"),
|
|
26163
|
+
body: external_exports.string().optional().describe("PR description body")
|
|
26164
|
+
}
|
|
26165
|
+
},
|
|
26166
|
+
async ({ repo_id, head, base, title, body }) => {
|
|
26167
|
+
try {
|
|
26168
|
+
const repoRes = await apiGet(`/repos/${repo_id}`);
|
|
26169
|
+
const repo = repoRes.data;
|
|
26170
|
+
if (!repo.path) {
|
|
26171
|
+
return {
|
|
26172
|
+
content: [
|
|
26173
|
+
{
|
|
26174
|
+
type: "text",
|
|
26175
|
+
text: "Error: Repo path is not configured."
|
|
26176
|
+
}
|
|
26177
|
+
],
|
|
26178
|
+
isError: true
|
|
26179
|
+
};
|
|
26180
|
+
}
|
|
26181
|
+
const result = await createPR({
|
|
26182
|
+
repoPath: repo.path,
|
|
26183
|
+
head,
|
|
26184
|
+
base,
|
|
26185
|
+
title,
|
|
26186
|
+
body: body ?? ""
|
|
26187
|
+
});
|
|
26188
|
+
return {
|
|
26189
|
+
content: [
|
|
26190
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
26191
|
+
]
|
|
26192
|
+
};
|
|
26193
|
+
} catch (err) {
|
|
26194
|
+
return {
|
|
26195
|
+
content: [
|
|
26196
|
+
{
|
|
26197
|
+
type: "text",
|
|
26198
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26199
|
+
}
|
|
26200
|
+
],
|
|
26201
|
+
isError: true
|
|
26202
|
+
};
|
|
26203
|
+
}
|
|
24480
26204
|
}
|
|
24481
|
-
|
|
24482
|
-
|
|
24483
|
-
|
|
24484
|
-
|
|
24485
|
-
|
|
24486
|
-
|
|
26205
|
+
);
|
|
26206
|
+
server.registerTool(
|
|
26207
|
+
"get_pr_status",
|
|
26208
|
+
{
|
|
26209
|
+
description: "Get the status of a GitHub PR by number.",
|
|
26210
|
+
inputSchema: {
|
|
26211
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26212
|
+
pr_number: external_exports.number().int().describe("The PR number")
|
|
26213
|
+
}
|
|
26214
|
+
},
|
|
26215
|
+
async ({ repo_id, pr_number }) => {
|
|
26216
|
+
try {
|
|
26217
|
+
const repoRes = await apiGet(`/repos/${repo_id}`);
|
|
26218
|
+
const repo = repoRes.data;
|
|
26219
|
+
if (!repo.path) {
|
|
26220
|
+
return {
|
|
26221
|
+
content: [
|
|
26222
|
+
{
|
|
26223
|
+
type: "text",
|
|
26224
|
+
text: "Error: Repo path is not configured."
|
|
26225
|
+
}
|
|
26226
|
+
],
|
|
26227
|
+
isError: true
|
|
26228
|
+
};
|
|
26229
|
+
}
|
|
26230
|
+
const status = await getPRStatus(repo.path, pr_number);
|
|
26231
|
+
return {
|
|
26232
|
+
content: [
|
|
26233
|
+
{ type: "text", text: JSON.stringify(status, null, 2) }
|
|
26234
|
+
]
|
|
26235
|
+
};
|
|
26236
|
+
} catch (err) {
|
|
26237
|
+
return {
|
|
26238
|
+
content: [
|
|
26239
|
+
{
|
|
26240
|
+
type: "text",
|
|
26241
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26242
|
+
}
|
|
26243
|
+
],
|
|
26244
|
+
isError: true
|
|
26245
|
+
};
|
|
24487
26246
|
}
|
|
24488
|
-
const result = await createPR({
|
|
24489
|
-
repoPath: repo.path,
|
|
24490
|
-
head,
|
|
24491
|
-
base,
|
|
24492
|
-
title,
|
|
24493
|
-
body: body ?? ""
|
|
24494
|
-
});
|
|
24495
|
-
return { content: [{ type: "text", text: JSON.stringify(result, null, 2) }] };
|
|
24496
|
-
} catch (err) {
|
|
24497
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24498
26247
|
}
|
|
24499
|
-
|
|
24500
|
-
server.registerTool(
|
|
24501
|
-
|
|
24502
|
-
|
|
24503
|
-
|
|
24504
|
-
|
|
26248
|
+
);
|
|
26249
|
+
server.registerTool(
|
|
26250
|
+
"promote_checkpoint",
|
|
26251
|
+
{
|
|
26252
|
+
description: "Trigger full promotion flow for a checkpoint. Creates merge checklist from templates and GitHub PR (feat branch \u2192 development).",
|
|
26253
|
+
inputSchema: {
|
|
26254
|
+
checkpoint_id: external_exports.string().uuid().describe("The checkpoint UUID")
|
|
26255
|
+
}
|
|
26256
|
+
},
|
|
26257
|
+
async ({ checkpoint_id }) => {
|
|
26258
|
+
try {
|
|
26259
|
+
const result = await promoteCheckpoint(checkpoint_id);
|
|
26260
|
+
return {
|
|
26261
|
+
content: [
|
|
26262
|
+
{ type: "text", text: JSON.stringify(result, null, 2) }
|
|
26263
|
+
]
|
|
26264
|
+
};
|
|
26265
|
+
} catch (err) {
|
|
26266
|
+
return {
|
|
26267
|
+
content: [
|
|
26268
|
+
{
|
|
26269
|
+
type: "text",
|
|
26270
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26271
|
+
}
|
|
26272
|
+
],
|
|
26273
|
+
isError: true
|
|
26274
|
+
};
|
|
26275
|
+
}
|
|
24505
26276
|
}
|
|
24506
|
-
|
|
24507
|
-
|
|
24508
|
-
|
|
24509
|
-
|
|
24510
|
-
if
|
|
24511
|
-
|
|
26277
|
+
);
|
|
26278
|
+
server.registerTool(
|
|
26279
|
+
"acquire_sync_lock",
|
|
26280
|
+
{
|
|
26281
|
+
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.",
|
|
26282
|
+
inputSchema: {
|
|
26283
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID"),
|
|
26284
|
+
locked_by: external_exports.string().describe(
|
|
26285
|
+
"Identifier of the client acquiring the lock (e.g. repo name)"
|
|
26286
|
+
),
|
|
26287
|
+
reason: external_exports.string().optional().describe("Why the lock is being acquired"),
|
|
26288
|
+
ttl_minutes: external_exports.number().int().optional().describe("Lock TTL in minutes (default 10)"),
|
|
26289
|
+
worktree_id: external_exports.string().uuid().optional().describe("Worktree UUID if syncing from a worktree")
|
|
26290
|
+
}
|
|
26291
|
+
},
|
|
26292
|
+
async ({ repo_id, locked_by, reason, ttl_minutes, worktree_id }) => {
|
|
26293
|
+
try {
|
|
26294
|
+
const res = await apiPost("/sync/lock", {
|
|
26295
|
+
repo_id,
|
|
26296
|
+
locked_by,
|
|
26297
|
+
reason: reason ?? void 0,
|
|
26298
|
+
ttl_minutes: ttl_minutes ?? 10,
|
|
26299
|
+
worktree_id: worktree_id ?? void 0
|
|
26300
|
+
});
|
|
26301
|
+
return {
|
|
26302
|
+
content: [
|
|
26303
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26304
|
+
]
|
|
26305
|
+
};
|
|
26306
|
+
} catch (err) {
|
|
26307
|
+
return {
|
|
26308
|
+
content: [
|
|
26309
|
+
{
|
|
26310
|
+
type: "text",
|
|
26311
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26312
|
+
}
|
|
26313
|
+
],
|
|
26314
|
+
isError: true
|
|
26315
|
+
};
|
|
24512
26316
|
}
|
|
24513
|
-
const status = await getPRStatus(repo.path, pr_number);
|
|
24514
|
-
return { content: [{ type: "text", text: JSON.stringify(status, null, 2) }] };
|
|
24515
|
-
} catch (err) {
|
|
24516
|
-
return { content: [{ type: "text", text: `Error: ${err instanceof Error ? err.message : String(err)}` }], isError: true };
|
|
24517
26317
|
}
|
|
24518
|
-
|
|
24519
|
-
server.registerTool(
|
|
24520
|
-
|
|
24521
|
-
|
|
24522
|
-
|
|
26318
|
+
);
|
|
26319
|
+
server.registerTool(
|
|
26320
|
+
"release_sync_lock",
|
|
26321
|
+
{
|
|
26322
|
+
description: "Release a sync lock for a repo.",
|
|
26323
|
+
inputSchema: {
|
|
26324
|
+
repo_id: external_exports.string().uuid().describe("The repo UUID")
|
|
26325
|
+
}
|
|
26326
|
+
},
|
|
26327
|
+
async ({ repo_id }) => {
|
|
26328
|
+
try {
|
|
26329
|
+
await apiDelete("/sync/lock", { repo_id });
|
|
26330
|
+
return {
|
|
26331
|
+
content: [
|
|
26332
|
+
{
|
|
26333
|
+
type: "text",
|
|
26334
|
+
text: JSON.stringify({ released: true }, null, 2)
|
|
26335
|
+
}
|
|
26336
|
+
]
|
|
26337
|
+
};
|
|
26338
|
+
} catch (err) {
|
|
26339
|
+
return {
|
|
26340
|
+
content: [
|
|
26341
|
+
{
|
|
26342
|
+
type: "text",
|
|
26343
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26344
|
+
}
|
|
26345
|
+
],
|
|
26346
|
+
isError: true
|
|
26347
|
+
};
|
|
26348
|
+
}
|
|
24523
26349
|
}
|
|
24524
|
-
|
|
24525
|
-
|
|
24526
|
-
|
|
24527
|
-
|
|
24528
|
-
|
|
24529
|
-
|
|
26350
|
+
);
|
|
26351
|
+
server.registerTool(
|
|
26352
|
+
"resolve_sync_conflict",
|
|
26353
|
+
{
|
|
26354
|
+
description: "Resolve a sync conflict. Choose use_local, use_remote, merge (with resolved_content), or skip.",
|
|
26355
|
+
inputSchema: {
|
|
26356
|
+
conflict_id: external_exports.string().uuid().describe("The conflict UUID"),
|
|
26357
|
+
resolution_type: external_exports.enum(["use_local", "use_remote", "merge", "skip"]).describe("Resolution strategy"),
|
|
26358
|
+
resolved_content: external_exports.string().optional().describe("Merged content (required when resolution_type is merge)"),
|
|
26359
|
+
resolved_by_repo_id: external_exports.string().uuid().optional().describe("Repo UUID that resolved the conflict")
|
|
26360
|
+
}
|
|
26361
|
+
},
|
|
26362
|
+
async ({
|
|
26363
|
+
conflict_id,
|
|
26364
|
+
resolution_type,
|
|
26365
|
+
resolved_content,
|
|
26366
|
+
resolved_by_repo_id
|
|
26367
|
+
}) => {
|
|
26368
|
+
try {
|
|
26369
|
+
const res = await apiPatch("/sync/conflicts", {
|
|
26370
|
+
conflict_id,
|
|
26371
|
+
resolution_type,
|
|
26372
|
+
resolved_content: resolved_content ?? void 0,
|
|
26373
|
+
resolved_by_repo_id: resolved_by_repo_id ?? void 0
|
|
26374
|
+
});
|
|
26375
|
+
return {
|
|
26376
|
+
content: [
|
|
26377
|
+
{ type: "text", text: JSON.stringify(res.data, null, 2) }
|
|
26378
|
+
]
|
|
26379
|
+
};
|
|
26380
|
+
} catch (err) {
|
|
26381
|
+
return {
|
|
26382
|
+
content: [
|
|
26383
|
+
{
|
|
26384
|
+
type: "text",
|
|
26385
|
+
text: `Error: ${err instanceof Error ? err.message : String(err)}`
|
|
26386
|
+
}
|
|
26387
|
+
],
|
|
26388
|
+
isError: true
|
|
26389
|
+
};
|
|
26390
|
+
}
|
|
24530
26391
|
}
|
|
24531
|
-
|
|
26392
|
+
);
|
|
24532
26393
|
}
|
|
24533
26394
|
var init_write = __esm({
|
|
24534
26395
|
"src/tools/write.ts"() {
|
|
@@ -24712,7 +26573,16 @@ if (arg === "setup") {
|
|
|
24712
26573
|
}
|
|
24713
26574
|
if (arg === "sync") {
|
|
24714
26575
|
const { runSync: runSync2 } = await Promise.resolve().then(() => (init_sync(), sync_exports));
|
|
24715
|
-
await
|
|
26576
|
+
const { SyncCancelledError: SyncCancelledError2 } = await Promise.resolve().then(() => (init_confirm(), confirm_exports));
|
|
26577
|
+
try {
|
|
26578
|
+
await runSync2();
|
|
26579
|
+
} catch (err) {
|
|
26580
|
+
if (err instanceof SyncCancelledError2) {
|
|
26581
|
+
console.log("\n Sync cancelled.\n");
|
|
26582
|
+
process.exit(0);
|
|
26583
|
+
}
|
|
26584
|
+
throw err;
|
|
26585
|
+
}
|
|
24716
26586
|
process.exit(0);
|
|
24717
26587
|
}
|
|
24718
26588
|
if (arg === "help" || arg === "--help" || arg === "-h") {
|