@markmdev/pebble 0.1.6 → 0.1.8
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/index.js +271 -16
- package/dist/cli/index.js.map +1 -1
- package/dist/ui/assets/index-CAcD3W8c.css +1 -0
- package/dist/ui/assets/index-DzH8YyE_.js +322 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-B9ZwNZIm.js +0 -317
- package/dist/ui/assets/index-C8VlXm0M.css +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -12,7 +12,7 @@ import { Command } from "commander";
|
|
|
12
12
|
// src/shared/types.ts
|
|
13
13
|
var ISSUE_TYPES = ["task", "bug", "epic", "verification"];
|
|
14
14
|
var PRIORITIES = [0, 1, 2, 3, 4];
|
|
15
|
-
var STATUSES = ["open", "in_progress", "blocked", "closed"];
|
|
15
|
+
var STATUSES = ["open", "in_progress", "blocked", "pending_verification", "closed"];
|
|
16
16
|
var EVENT_TYPES = ["create", "update", "close", "reopen", "comment"];
|
|
17
17
|
var PRIORITY_LABELS = {
|
|
18
18
|
0: "critical",
|
|
@@ -25,6 +25,7 @@ var STATUS_LABELS = {
|
|
|
25
25
|
open: "Open",
|
|
26
26
|
in_progress: "In Progress",
|
|
27
27
|
blocked: "Blocked",
|
|
28
|
+
pending_verification: "Pending Verification",
|
|
28
29
|
closed: "Closed"
|
|
29
30
|
};
|
|
30
31
|
var TYPE_LABELS = {
|
|
@@ -183,6 +184,7 @@ function computeState(events) {
|
|
|
183
184
|
description: createEvent.data.description,
|
|
184
185
|
parent: createEvent.data.parent,
|
|
185
186
|
blockedBy: [],
|
|
187
|
+
relatedTo: [],
|
|
186
188
|
verifies: createEvent.data.verifies,
|
|
187
189
|
comments: [],
|
|
188
190
|
createdAt: event.timestamp,
|
|
@@ -216,6 +218,9 @@ function computeState(events) {
|
|
|
216
218
|
if (updateEvent.data.blockedBy !== void 0) {
|
|
217
219
|
issue.blockedBy = updateEvent.data.blockedBy;
|
|
218
220
|
}
|
|
221
|
+
if (updateEvent.data.relatedTo !== void 0) {
|
|
222
|
+
issue.relatedTo = updateEvent.data.relatedTo;
|
|
223
|
+
}
|
|
219
224
|
issue.updatedAt = event.timestamp;
|
|
220
225
|
}
|
|
221
226
|
break;
|
|
@@ -315,7 +320,7 @@ function getReady() {
|
|
|
315
320
|
const state = computeState(events);
|
|
316
321
|
const issues = Array.from(state.values());
|
|
317
322
|
return issues.filter((issue) => {
|
|
318
|
-
if (issue.status === "closed") {
|
|
323
|
+
if (issue.status === "closed" || issue.status === "pending_verification") {
|
|
319
324
|
return false;
|
|
320
325
|
}
|
|
321
326
|
for (const blockerId of issue.blockedBy) {
|
|
@@ -456,6 +461,36 @@ function getNewlyUnblocked(closedIssueId) {
|
|
|
456
461
|
}
|
|
457
462
|
return result;
|
|
458
463
|
}
|
|
464
|
+
function getRelated(issueId) {
|
|
465
|
+
const issue = getIssue(issueId);
|
|
466
|
+
if (!issue) {
|
|
467
|
+
return [];
|
|
468
|
+
}
|
|
469
|
+
const events = readEvents();
|
|
470
|
+
const state = computeState(events);
|
|
471
|
+
return issue.relatedTo.map((id) => state.get(id)).filter((i) => i !== void 0);
|
|
472
|
+
}
|
|
473
|
+
function hasOpenBlockersById(issueId) {
|
|
474
|
+
const issue = getIssue(issueId);
|
|
475
|
+
if (!issue) {
|
|
476
|
+
return false;
|
|
477
|
+
}
|
|
478
|
+
const events = readEvents();
|
|
479
|
+
const state = computeState(events);
|
|
480
|
+
return issue.blockedBy.some((blockerId) => {
|
|
481
|
+
const blocker = state.get(blockerId);
|
|
482
|
+
return blocker && blocker.status !== "closed";
|
|
483
|
+
});
|
|
484
|
+
}
|
|
485
|
+
function getOpenBlockers(issueId) {
|
|
486
|
+
const issue = getIssue(issueId);
|
|
487
|
+
if (!issue) {
|
|
488
|
+
return [];
|
|
489
|
+
}
|
|
490
|
+
const events = readEvents();
|
|
491
|
+
const state = computeState(events);
|
|
492
|
+
return issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed");
|
|
493
|
+
}
|
|
459
494
|
|
|
460
495
|
// src/cli/lib/output.ts
|
|
461
496
|
function formatJson(data) {
|
|
@@ -547,7 +582,7 @@ function formatIssueListPretty(issues) {
|
|
|
547
582
|
lines.push(`Total: ${issues.length} issue(s)`);
|
|
548
583
|
return lines.join("\n");
|
|
549
584
|
}
|
|
550
|
-
function formatDepsPretty(issueId, blockedBy, blocking) {
|
|
585
|
+
function formatDepsPretty(issueId, blockedBy, blocking, related = []) {
|
|
551
586
|
const lines = [];
|
|
552
587
|
lines.push(`Dependencies for ${issueId}`);
|
|
553
588
|
lines.push("\u2500".repeat(40));
|
|
@@ -570,6 +605,16 @@ function formatDepsPretty(issueId, blockedBy, blocking) {
|
|
|
570
605
|
lines.push(` \u25CB ${issue.id} - ${truncate(issue.title, 30)}`);
|
|
571
606
|
}
|
|
572
607
|
}
|
|
608
|
+
lines.push("");
|
|
609
|
+
lines.push("Related:");
|
|
610
|
+
if (related.length === 0) {
|
|
611
|
+
lines.push(" (none)");
|
|
612
|
+
} else {
|
|
613
|
+
for (const issue of related) {
|
|
614
|
+
const status = issue.status === "closed" ? "\u2713" : "\u25CB";
|
|
615
|
+
lines.push(` ${status} ${issue.id} - ${truncate(issue.title, 30)}`);
|
|
616
|
+
}
|
|
617
|
+
}
|
|
573
618
|
return lines.join("\n");
|
|
574
619
|
}
|
|
575
620
|
function formatError(error) {
|
|
@@ -832,6 +877,12 @@ function updateCommand(program2) {
|
|
|
832
877
|
results.push({ id, success: false, error: `Issue not found: ${id}` });
|
|
833
878
|
continue;
|
|
834
879
|
}
|
|
880
|
+
if (data.status === "in_progress" && hasOpenBlockersById(resolvedId)) {
|
|
881
|
+
const blockers = getOpenBlockers(resolvedId);
|
|
882
|
+
const blockerIds = blockers.map((b) => b.id).join(", ");
|
|
883
|
+
results.push({ id: resolvedId, success: false, error: `Cannot set to in_progress - blocked by: ${blockerIds}` });
|
|
884
|
+
continue;
|
|
885
|
+
}
|
|
835
886
|
const event = {
|
|
836
887
|
type: "update",
|
|
837
888
|
issueId: resolvedId,
|
|
@@ -914,6 +965,25 @@ function closeCommand(program2) {
|
|
|
914
965
|
};
|
|
915
966
|
appendEvent(commentEvent, pebbleDir);
|
|
916
967
|
}
|
|
968
|
+
const pendingVerifications = getVerifications(resolvedId).filter((v) => v.status !== "closed");
|
|
969
|
+
if (pendingVerifications.length > 0) {
|
|
970
|
+
const updateEvent = {
|
|
971
|
+
type: "update",
|
|
972
|
+
issueId: resolvedId,
|
|
973
|
+
timestamp,
|
|
974
|
+
data: {
|
|
975
|
+
status: "pending_verification"
|
|
976
|
+
}
|
|
977
|
+
};
|
|
978
|
+
appendEvent(updateEvent, pebbleDir);
|
|
979
|
+
results.push({
|
|
980
|
+
id: resolvedId,
|
|
981
|
+
success: true,
|
|
982
|
+
status: "pending_verification",
|
|
983
|
+
pendingVerifications: pendingVerifications.map((v) => ({ id: v.id, title: v.title }))
|
|
984
|
+
});
|
|
985
|
+
continue;
|
|
986
|
+
}
|
|
917
987
|
const closeEvent = {
|
|
918
988
|
type: "close",
|
|
919
989
|
issueId: resolvedId,
|
|
@@ -927,6 +997,7 @@ function closeCommand(program2) {
|
|
|
927
997
|
results.push({
|
|
928
998
|
id: resolvedId,
|
|
929
999
|
success: true,
|
|
1000
|
+
status: "closed",
|
|
930
1001
|
unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0
|
|
931
1002
|
});
|
|
932
1003
|
} catch (error) {
|
|
@@ -937,18 +1008,29 @@ function closeCommand(program2) {
|
|
|
937
1008
|
const result = results[0];
|
|
938
1009
|
if (result.success) {
|
|
939
1010
|
if (pretty) {
|
|
940
|
-
|
|
941
|
-
|
|
1011
|
+
if (result.status === "pending_verification") {
|
|
1012
|
+
console.log(`\u23F3 ${result.id} \u2192 pending_verification`);
|
|
942
1013
|
console.log(`
|
|
1014
|
+
Pending verifications:`);
|
|
1015
|
+
for (const v of result.pendingVerifications || []) {
|
|
1016
|
+
console.log(` \u2022 ${v.id} - ${v.title}`);
|
|
1017
|
+
}
|
|
1018
|
+
} else {
|
|
1019
|
+
console.log(`\u2713 ${result.id}`);
|
|
1020
|
+
if (result.unblocked && result.unblocked.length > 0) {
|
|
1021
|
+
console.log(`
|
|
943
1022
|
Unblocked:`);
|
|
944
|
-
|
|
945
|
-
|
|
1023
|
+
for (const u of result.unblocked) {
|
|
1024
|
+
console.log(` \u2192 ${u.id} - ${u.title}`);
|
|
1025
|
+
}
|
|
946
1026
|
}
|
|
947
1027
|
}
|
|
948
1028
|
} else {
|
|
949
1029
|
console.log(formatJson({
|
|
950
1030
|
id: result.id,
|
|
951
1031
|
success: true,
|
|
1032
|
+
status: result.status,
|
|
1033
|
+
...result.pendingVerifications && { pendingVerifications: result.pendingVerifications },
|
|
952
1034
|
...result.unblocked && { unblocked: result.unblocked }
|
|
953
1035
|
}));
|
|
954
1036
|
}
|
|
@@ -959,10 +1041,17 @@ Unblocked:`);
|
|
|
959
1041
|
if (pretty) {
|
|
960
1042
|
for (const result of results) {
|
|
961
1043
|
if (result.success) {
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
for (const
|
|
965
|
-
console.log(` \
|
|
1044
|
+
if (result.status === "pending_verification") {
|
|
1045
|
+
console.log(`\u23F3 ${result.id} \u2192 pending_verification`);
|
|
1046
|
+
for (const v of result.pendingVerifications || []) {
|
|
1047
|
+
console.log(` \u2022 ${v.id} - ${v.title}`);
|
|
1048
|
+
}
|
|
1049
|
+
} else {
|
|
1050
|
+
console.log(`\u2713 ${result.id}`);
|
|
1051
|
+
if (result.unblocked && result.unblocked.length > 0) {
|
|
1052
|
+
for (const u of result.unblocked) {
|
|
1053
|
+
console.log(` \u2192 ${u.id} - ${u.title}`);
|
|
1054
|
+
}
|
|
966
1055
|
}
|
|
967
1056
|
}
|
|
968
1057
|
} else {
|
|
@@ -973,7 +1062,9 @@ Unblocked:`);
|
|
|
973
1062
|
console.log(formatJson(results.map((r) => ({
|
|
974
1063
|
id: r.id,
|
|
975
1064
|
success: r.success,
|
|
1065
|
+
status: r.status,
|
|
976
1066
|
...r.error && { error: r.error },
|
|
1067
|
+
...r.pendingVerifications && { pendingVerifications: r.pendingVerifications },
|
|
977
1068
|
...r.unblocked && { unblocked: r.unblocked }
|
|
978
1069
|
}))));
|
|
979
1070
|
}
|
|
@@ -1041,6 +1132,12 @@ function claimCommand(program2) {
|
|
|
1041
1132
|
results.push({ id: resolvedId, success: false, error: `Cannot claim closed issue: ${resolvedId}` });
|
|
1042
1133
|
continue;
|
|
1043
1134
|
}
|
|
1135
|
+
if (hasOpenBlockersById(resolvedId)) {
|
|
1136
|
+
const blockers = getOpenBlockers(resolvedId);
|
|
1137
|
+
const blockerIds = blockers.map((b) => b.id).join(", ");
|
|
1138
|
+
results.push({ id: resolvedId, success: false, error: `Cannot claim blocked issue. Blocked by: ${blockerIds}` });
|
|
1139
|
+
continue;
|
|
1140
|
+
}
|
|
1044
1141
|
const event = {
|
|
1045
1142
|
type: "update",
|
|
1046
1143
|
issueId: resolvedId,
|
|
@@ -1264,6 +1361,99 @@ function depCommand(program2) {
|
|
|
1264
1361
|
outputError(error, pretty);
|
|
1265
1362
|
}
|
|
1266
1363
|
});
|
|
1364
|
+
dep.command("relate <id1> <id2>").description("Add a bidirectional related link between two issues").action(async (id1, id2) => {
|
|
1365
|
+
const pretty = program2.opts().pretty ?? false;
|
|
1366
|
+
try {
|
|
1367
|
+
const pebbleDir = getOrCreatePebbleDir();
|
|
1368
|
+
const resolvedId1 = resolveId(id1);
|
|
1369
|
+
const resolvedId2 = resolveId(id2);
|
|
1370
|
+
const issue1 = getIssue(resolvedId1);
|
|
1371
|
+
if (!issue1) {
|
|
1372
|
+
throw new Error(`Issue not found: ${id1}`);
|
|
1373
|
+
}
|
|
1374
|
+
const issue2 = getIssue(resolvedId2);
|
|
1375
|
+
if (!issue2) {
|
|
1376
|
+
throw new Error(`Issue not found: ${id2}`);
|
|
1377
|
+
}
|
|
1378
|
+
if (resolvedId1 === resolvedId2) {
|
|
1379
|
+
throw new Error("Cannot relate issue to itself");
|
|
1380
|
+
}
|
|
1381
|
+
if (issue1.relatedTo.includes(resolvedId2)) {
|
|
1382
|
+
throw new Error(`Issues are already related: ${resolvedId1} \u2194 ${resolvedId2}`);
|
|
1383
|
+
}
|
|
1384
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1385
|
+
const event1 = {
|
|
1386
|
+
type: "update",
|
|
1387
|
+
issueId: resolvedId1,
|
|
1388
|
+
timestamp,
|
|
1389
|
+
data: {
|
|
1390
|
+
relatedTo: [...issue1.relatedTo, resolvedId2]
|
|
1391
|
+
}
|
|
1392
|
+
};
|
|
1393
|
+
const event2 = {
|
|
1394
|
+
type: "update",
|
|
1395
|
+
issueId: resolvedId2,
|
|
1396
|
+
timestamp,
|
|
1397
|
+
data: {
|
|
1398
|
+
relatedTo: [...issue2.relatedTo, resolvedId1]
|
|
1399
|
+
}
|
|
1400
|
+
};
|
|
1401
|
+
appendEvent(event1, pebbleDir);
|
|
1402
|
+
appendEvent(event2, pebbleDir);
|
|
1403
|
+
if (pretty) {
|
|
1404
|
+
console.log(`\u2713 ${resolvedId1} \u2194 ${resolvedId2}`);
|
|
1405
|
+
} else {
|
|
1406
|
+
console.log(formatJson({ id1: resolvedId1, id2: resolvedId2, related: true }));
|
|
1407
|
+
}
|
|
1408
|
+
} catch (error) {
|
|
1409
|
+
outputError(error, pretty);
|
|
1410
|
+
}
|
|
1411
|
+
});
|
|
1412
|
+
dep.command("unrelate <id1> <id2>").description("Remove a bidirectional related link between two issues").action(async (id1, id2) => {
|
|
1413
|
+
const pretty = program2.opts().pretty ?? false;
|
|
1414
|
+
try {
|
|
1415
|
+
const pebbleDir = getOrCreatePebbleDir();
|
|
1416
|
+
const resolvedId1 = resolveId(id1);
|
|
1417
|
+
const resolvedId2 = resolveId(id2);
|
|
1418
|
+
const issue1 = getIssue(resolvedId1);
|
|
1419
|
+
if (!issue1) {
|
|
1420
|
+
throw new Error(`Issue not found: ${id1}`);
|
|
1421
|
+
}
|
|
1422
|
+
const issue2 = getIssue(resolvedId2);
|
|
1423
|
+
if (!issue2) {
|
|
1424
|
+
throw new Error(`Issue not found: ${id2}`);
|
|
1425
|
+
}
|
|
1426
|
+
if (!issue1.relatedTo.includes(resolvedId2)) {
|
|
1427
|
+
throw new Error(`Issues are not related: ${resolvedId1} \u2194 ${resolvedId2}`);
|
|
1428
|
+
}
|
|
1429
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
1430
|
+
const event1 = {
|
|
1431
|
+
type: "update",
|
|
1432
|
+
issueId: resolvedId1,
|
|
1433
|
+
timestamp,
|
|
1434
|
+
data: {
|
|
1435
|
+
relatedTo: issue1.relatedTo.filter((id) => id !== resolvedId2)
|
|
1436
|
+
}
|
|
1437
|
+
};
|
|
1438
|
+
const event2 = {
|
|
1439
|
+
type: "update",
|
|
1440
|
+
issueId: resolvedId2,
|
|
1441
|
+
timestamp,
|
|
1442
|
+
data: {
|
|
1443
|
+
relatedTo: issue2.relatedTo.filter((id) => id !== resolvedId1)
|
|
1444
|
+
}
|
|
1445
|
+
};
|
|
1446
|
+
appendEvent(event1, pebbleDir);
|
|
1447
|
+
appendEvent(event2, pebbleDir);
|
|
1448
|
+
if (pretty) {
|
|
1449
|
+
console.log(`\u2713 ${resolvedId1} \u21AE ${resolvedId2}`);
|
|
1450
|
+
} else {
|
|
1451
|
+
console.log(formatJson({ id1: resolvedId1, id2: resolvedId2, related: false }));
|
|
1452
|
+
}
|
|
1453
|
+
} catch (error) {
|
|
1454
|
+
outputError(error, pretty);
|
|
1455
|
+
}
|
|
1456
|
+
});
|
|
1267
1457
|
dep.command("list <id>").description("List dependencies for an issue").action(async (id) => {
|
|
1268
1458
|
const pretty = program2.opts().pretty ?? false;
|
|
1269
1459
|
try {
|
|
@@ -1274,13 +1464,15 @@ function depCommand(program2) {
|
|
|
1274
1464
|
}
|
|
1275
1465
|
const blockedBy = getBlockers(resolvedId);
|
|
1276
1466
|
const blocking = getBlocking(resolvedId);
|
|
1467
|
+
const related = getRelated(resolvedId);
|
|
1277
1468
|
if (pretty) {
|
|
1278
|
-
console.log(formatDepsPretty(resolvedId, blockedBy, blocking));
|
|
1469
|
+
console.log(formatDepsPretty(resolvedId, blockedBy, blocking, related));
|
|
1279
1470
|
} else {
|
|
1280
1471
|
console.log(formatJson({
|
|
1281
1472
|
issueId: resolvedId,
|
|
1282
1473
|
blockedBy: blockedBy.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
1283
|
-
blocking: blocking.map((i) => ({ id: i.id, title: i.title, status: i.status }))
|
|
1474
|
+
blocking: blocking.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
1475
|
+
related: related.map((i) => ({ id: i.id, title: i.title, status: i.status }))
|
|
1284
1476
|
}));
|
|
1285
1477
|
}
|
|
1286
1478
|
} catch (error) {
|
|
@@ -1880,9 +2072,26 @@ data: ${message}
|
|
|
1880
2072
|
results.push({ id: issueId, success: false, error: "Cannot close epic with open children" });
|
|
1881
2073
|
continue;
|
|
1882
2074
|
}
|
|
2075
|
+
const pendingVerifications = getVerifications(issueId).filter((v) => v.status !== "closed");
|
|
2076
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2077
|
+
if (pendingVerifications.length > 0) {
|
|
2078
|
+
const updateEvent = {
|
|
2079
|
+
type: "update",
|
|
2080
|
+
issueId,
|
|
2081
|
+
timestamp,
|
|
2082
|
+
data: { status: "pending_verification" }
|
|
2083
|
+
};
|
|
2084
|
+
appendEvent(updateEvent, pebbleDir);
|
|
2085
|
+
results.push({
|
|
2086
|
+
id: issueId,
|
|
2087
|
+
success: true,
|
|
2088
|
+
error: `Moved to pending_verification (${pendingVerifications.length} verification(s) pending)`
|
|
2089
|
+
});
|
|
2090
|
+
continue;
|
|
2091
|
+
}
|
|
1883
2092
|
const event = {
|
|
1884
2093
|
issueId,
|
|
1885
|
-
timestamp
|
|
2094
|
+
timestamp,
|
|
1886
2095
|
type: "close",
|
|
1887
2096
|
data: { reason: "Bulk close" }
|
|
1888
2097
|
};
|
|
@@ -1910,7 +2119,7 @@ data: ${message}
|
|
|
1910
2119
|
return;
|
|
1911
2120
|
}
|
|
1912
2121
|
if (updates.status) {
|
|
1913
|
-
const validStatuses = ["open", "in_progress", "blocked"];
|
|
2122
|
+
const validStatuses = ["open", "in_progress", "blocked", "pending_verification"];
|
|
1914
2123
|
if (!validStatuses.includes(updates.status)) {
|
|
1915
2124
|
res.status(400).json({
|
|
1916
2125
|
error: `Invalid status: ${updates.status}. Use close endpoint to close issues.`
|
|
@@ -1978,7 +2187,7 @@ data: ${message}
|
|
|
1978
2187
|
issue = localIssue;
|
|
1979
2188
|
targetFile = path2.join(pebbleDir, "issues.jsonl");
|
|
1980
2189
|
}
|
|
1981
|
-
const { title, type, priority, status, description, parent } = req.body;
|
|
2190
|
+
const { title, type, priority, status, description, parent, relatedTo } = req.body;
|
|
1982
2191
|
const updates = {};
|
|
1983
2192
|
if (title !== void 0) {
|
|
1984
2193
|
if (typeof title !== "string" || title.trim() === "") {
|
|
@@ -2037,6 +2246,28 @@ data: ${message}
|
|
|
2037
2246
|
}
|
|
2038
2247
|
updates.parent = parent;
|
|
2039
2248
|
}
|
|
2249
|
+
if (relatedTo !== void 0) {
|
|
2250
|
+
if (!Array.isArray(relatedTo)) {
|
|
2251
|
+
res.status(400).json({ error: "relatedTo must be an array" });
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
for (const relatedId of relatedTo) {
|
|
2255
|
+
if (isMultiWorktree()) {
|
|
2256
|
+
const found = findIssueInSources(relatedId, issueFiles);
|
|
2257
|
+
if (!found) {
|
|
2258
|
+
res.status(400).json({ error: `Related issue not found: ${relatedId}` });
|
|
2259
|
+
return;
|
|
2260
|
+
}
|
|
2261
|
+
} else {
|
|
2262
|
+
const relatedIssue = getIssue(relatedId);
|
|
2263
|
+
if (!relatedIssue) {
|
|
2264
|
+
res.status(400).json({ error: `Related issue not found: ${relatedId}` });
|
|
2265
|
+
return;
|
|
2266
|
+
}
|
|
2267
|
+
}
|
|
2268
|
+
}
|
|
2269
|
+
updates.relatedTo = relatedTo;
|
|
2270
|
+
}
|
|
2040
2271
|
if (Object.keys(updates).length === 0) {
|
|
2041
2272
|
res.status(400).json({ error: "No valid updates provided" });
|
|
2042
2273
|
return;
|
|
@@ -2094,6 +2325,30 @@ data: ${message}
|
|
|
2094
2325
|
}
|
|
2095
2326
|
const { reason } = req.body;
|
|
2096
2327
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2328
|
+
let pendingVerifications = [];
|
|
2329
|
+
if (isMultiWorktree()) {
|
|
2330
|
+
const allIssues = mergeIssuesFromFiles(issueFiles);
|
|
2331
|
+
pendingVerifications = allIssues.filter(
|
|
2332
|
+
(i) => i.verifies === issueId && i.status !== "closed"
|
|
2333
|
+
);
|
|
2334
|
+
} else {
|
|
2335
|
+
pendingVerifications = getVerifications(issueId).filter((v) => v.status !== "closed");
|
|
2336
|
+
}
|
|
2337
|
+
if (pendingVerifications.length > 0) {
|
|
2338
|
+
const updateEvent = {
|
|
2339
|
+
type: "update",
|
|
2340
|
+
issueId,
|
|
2341
|
+
timestamp,
|
|
2342
|
+
data: { status: "pending_verification" }
|
|
2343
|
+
};
|
|
2344
|
+
appendEventToFile(updateEvent, targetFile);
|
|
2345
|
+
const updatedIssue = { ...issue, status: "pending_verification", updatedAt: timestamp };
|
|
2346
|
+
res.json({
|
|
2347
|
+
...updatedIssue,
|
|
2348
|
+
_pendingVerifications: pendingVerifications.map((v) => ({ id: v.id, title: v.title }))
|
|
2349
|
+
});
|
|
2350
|
+
return;
|
|
2351
|
+
}
|
|
2097
2352
|
const event = {
|
|
2098
2353
|
type: "close",
|
|
2099
2354
|
issueId,
|