@markmdev/pebble 0.1.23 → 0.2.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/index.js +104 -397
- package/dist/cli/index.js.map +1 -1
- package/dist/ui/assets/index-D_QGntQB.js +333 -0
- package/dist/ui/assets/index-PT9nHzvM.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BLTrHFDr.js +0 -343
- package/dist/ui/assets/index-CSfeY9Yu.css +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -13,9 +13,9 @@ import { fileURLToPath as fileURLToPath2 } from "url";
|
|
|
13
13
|
import { dirname as dirname2, join as join3 } from "path";
|
|
14
14
|
|
|
15
15
|
// src/shared/types.ts
|
|
16
|
-
var ISSUE_TYPES = ["task", "bug", "epic"
|
|
16
|
+
var ISSUE_TYPES = ["task", "bug", "epic"];
|
|
17
17
|
var PRIORITIES = [0, 1, 2, 3, 4];
|
|
18
|
-
var STATUSES = ["open", "in_progress", "blocked", "
|
|
18
|
+
var STATUSES = ["open", "in_progress", "blocked", "closed"];
|
|
19
19
|
var EVENT_TYPES = ["create", "update", "close", "reopen", "comment", "delete", "restore"];
|
|
20
20
|
var PRIORITY_LABELS = {
|
|
21
21
|
0: "critical",
|
|
@@ -28,14 +28,12 @@ var STATUS_LABELS = {
|
|
|
28
28
|
open: "Open",
|
|
29
29
|
in_progress: "In Progress",
|
|
30
30
|
blocked: "Blocked",
|
|
31
|
-
pending_verification: "Pending Verification",
|
|
32
31
|
closed: "Closed"
|
|
33
32
|
};
|
|
34
33
|
var TYPE_LABELS = {
|
|
35
34
|
task: "Task",
|
|
36
35
|
bug: "Bug",
|
|
37
|
-
epic: "Epic"
|
|
38
|
-
verification: "Verification"
|
|
36
|
+
epic: "Epic"
|
|
39
37
|
};
|
|
40
38
|
|
|
41
39
|
// src/cli/lib/storage.ts
|
|
@@ -290,7 +288,6 @@ function computeState(events) {
|
|
|
290
288
|
parent: createEvent.data.parent,
|
|
291
289
|
blockedBy: [],
|
|
292
290
|
relatedTo: [],
|
|
293
|
-
verifies: createEvent.data.verifies,
|
|
294
291
|
comments: [],
|
|
295
292
|
createdAt: event.timestamp,
|
|
296
293
|
updatedAt: event.timestamp,
|
|
@@ -467,7 +464,7 @@ function getReady() {
|
|
|
467
464
|
if (issue.deleted) {
|
|
468
465
|
return false;
|
|
469
466
|
}
|
|
470
|
-
if (issue.status === "closed"
|
|
467
|
+
if (issue.status === "closed") {
|
|
471
468
|
return false;
|
|
472
469
|
}
|
|
473
470
|
for (const blockerId of issue.blockedBy) {
|
|
@@ -476,12 +473,6 @@ function getReady() {
|
|
|
476
473
|
return false;
|
|
477
474
|
}
|
|
478
475
|
}
|
|
479
|
-
if (issue.type === "verification" && issue.verifies) {
|
|
480
|
-
const target = state.get(issue.verifies);
|
|
481
|
-
if (!target || target.status !== "closed") {
|
|
482
|
-
return false;
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
476
|
return true;
|
|
486
477
|
});
|
|
487
478
|
}
|
|
@@ -573,11 +564,6 @@ function getChildren(epicId, includeDeleted = false) {
|
|
|
573
564
|
(issue) => issue.parent === epicId && (includeDeleted || !issue.deleted)
|
|
574
565
|
);
|
|
575
566
|
}
|
|
576
|
-
function getVerifications(issueId) {
|
|
577
|
-
const events = readEvents();
|
|
578
|
-
const state = computeState(events);
|
|
579
|
-
return Array.from(state.values()).filter((issue) => issue.verifies === issueId);
|
|
580
|
-
}
|
|
581
567
|
function hasOpenChildren(epicId) {
|
|
582
568
|
const children = getChildren(epicId);
|
|
583
569
|
return children.some((child) => child.status !== "closed");
|
|
@@ -588,28 +574,15 @@ function getNewlyUnblocked(closedIssueId) {
|
|
|
588
574
|
const result = [];
|
|
589
575
|
for (const issue of state.values()) {
|
|
590
576
|
if (issue.status === "closed") continue;
|
|
591
|
-
let isUnblockedByThis = false;
|
|
592
577
|
if (issue.blockedBy.includes(closedIssueId)) {
|
|
593
578
|
const allBlockersClosed = issue.blockedBy.every((blockerId) => {
|
|
594
579
|
const blocker = state.get(blockerId);
|
|
595
580
|
return blocker?.status === "closed";
|
|
596
581
|
});
|
|
597
582
|
if (allBlockersClosed) {
|
|
598
|
-
|
|
583
|
+
result.push(issue);
|
|
599
584
|
}
|
|
600
585
|
}
|
|
601
|
-
if (issue.type === "verification" && issue.verifies === closedIssueId) {
|
|
602
|
-
const allBlockersClosed = issue.blockedBy.every((blockerId) => {
|
|
603
|
-
const blocker = state.get(blockerId);
|
|
604
|
-
return blocker?.status === "closed";
|
|
605
|
-
});
|
|
606
|
-
if (allBlockersClosed) {
|
|
607
|
-
isUnblockedByThis = true;
|
|
608
|
-
}
|
|
609
|
-
}
|
|
610
|
-
if (isUnblockedByThis) {
|
|
611
|
-
result.push(issue);
|
|
612
|
-
}
|
|
613
586
|
}
|
|
614
587
|
return result;
|
|
615
588
|
}
|
|
@@ -798,10 +771,8 @@ function formatIssueDetailPretty(issue, ctx) {
|
|
|
798
771
|
}
|
|
799
772
|
if (ctx.children.length > 0) {
|
|
800
773
|
const closedChildren = ctx.children.filter((c) => c.status === "closed");
|
|
801
|
-
const pendingChildren = ctx.children.filter((c) => c.status === "pending_verification");
|
|
802
|
-
const pendingStr = pendingChildren.length > 0 ? ` (${pendingChildren.length} pending verification)` : "";
|
|
803
774
|
lines.push("");
|
|
804
|
-
lines.push(`Children (${closedChildren.length}/${ctx.children.length} done
|
|
775
|
+
lines.push(`Children (${closedChildren.length}/${ctx.children.length} done):`);
|
|
805
776
|
for (const child of ctx.children) {
|
|
806
777
|
const statusIcon = child.status === "closed" ? "\u2713" : child.status === "in_progress" ? "\u25B6" : "\u25CB";
|
|
807
778
|
lines.push(` ${statusIcon} ${child.id} - ${child.title} [${child.status}]`);
|
|
@@ -815,18 +786,6 @@ function formatIssueDetailPretty(issue, ctx) {
|
|
|
815
786
|
lines.push("");
|
|
816
787
|
lines.push(`Blocking: ${ctx.blocking.map((i) => i.id).join(", ")}`);
|
|
817
788
|
}
|
|
818
|
-
if (ctx.verifications.length > 0) {
|
|
819
|
-
const closedVerifications = ctx.verifications.filter((v) => v.status === "closed");
|
|
820
|
-
lines.push("");
|
|
821
|
-
lines.push(`Verifications (${closedVerifications.length}/${ctx.verifications.length} done):`);
|
|
822
|
-
for (const v of ctx.verifications) {
|
|
823
|
-
const statusIcon = v.status === "closed" ? "\u2713" : "\u25CB";
|
|
824
|
-
lines.push(` ${statusIcon} ${v.id} - ${v.title} [${v.status}]`);
|
|
825
|
-
}
|
|
826
|
-
} else if (issue.status === "pending_verification") {
|
|
827
|
-
lines.push("");
|
|
828
|
-
lines.push("Verifications: None found (status may be stale)");
|
|
829
|
-
}
|
|
830
789
|
if (ctx.related.length > 0) {
|
|
831
790
|
lines.push("");
|
|
832
791
|
lines.push(`Related: ${ctx.related.map((r) => r.id).join(", ")}`);
|
|
@@ -853,7 +812,6 @@ function outputIssueDetail(issue, ctx, pretty) {
|
|
|
853
812
|
...issue,
|
|
854
813
|
blocking: ctx.blocking.map((i) => i.id),
|
|
855
814
|
children: ctx.children.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
856
|
-
verifications: ctx.verifications.map((i) => ({ id: i.id, title: i.title, status: i.status })),
|
|
857
815
|
related: ctx.related.map((i) => i.id),
|
|
858
816
|
...ctx.ancestry && ctx.ancestry.length > 0 && { ancestry: ctx.ancestry }
|
|
859
817
|
};
|
|
@@ -939,8 +897,9 @@ function formatErrorPretty(error) {
|
|
|
939
897
|
function outputMutationSuccess(id, pretty, extra) {
|
|
940
898
|
if (pretty) {
|
|
941
899
|
const notes = [];
|
|
942
|
-
if (extra?.
|
|
943
|
-
|
|
900
|
+
if (extra?.parentsReopened?.length) {
|
|
901
|
+
const ids = extra.parentsReopened.map((p) => p.id).join(", ");
|
|
902
|
+
notes.push(`reopened: ${ids}`);
|
|
944
903
|
}
|
|
945
904
|
if (extra?.blockersReopened?.length) {
|
|
946
905
|
const ids = extra.blockersReopened.map((b) => b.id).join(", ");
|
|
@@ -953,8 +912,8 @@ function outputMutationSuccess(id, pretty, extra) {
|
|
|
953
912
|
}
|
|
954
913
|
} else {
|
|
955
914
|
const result = { id, success: true };
|
|
956
|
-
if (extra?.
|
|
957
|
-
result.
|
|
915
|
+
if (extra?.parentsReopened?.length) {
|
|
916
|
+
result._parentsReopened = extra.parentsReopened;
|
|
958
917
|
}
|
|
959
918
|
if (extra?.blockersReopened?.length) {
|
|
960
919
|
result._blockersReopened = extra.blockersReopened;
|
|
@@ -1020,7 +979,7 @@ function formatIssueTreePretty(nodes, sectionHeader) {
|
|
|
1020
979
|
const totalCount = nodes.reduce((sum, node) => sum + countAll(node), 0);
|
|
1021
980
|
const formatNode = (node, prefix, isLast, isRoot) => {
|
|
1022
981
|
const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
1023
|
-
const statusIcon = node.status === "closed" ? "\u2713" : node.status === "in_progress" ? "\u25B6" :
|
|
982
|
+
const statusIcon = node.status === "closed" ? "\u2713" : node.status === "in_progress" ? "\u25B6" : "\u25CB";
|
|
1024
983
|
const statusText = STATUS_LABELS[node.status].toLowerCase();
|
|
1025
984
|
const relativeTime = node.statusChangedAt ? formatRelativeTime(node.statusChangedAt) : formatRelativeTime(node.createdAt);
|
|
1026
985
|
lines.push(`${prefix}${connector}${statusIcon} ${node.id}: ${node.title} [${node.type}] P${node.priority} ${statusText} ${relativeTime}`);
|
|
@@ -1071,7 +1030,7 @@ function formatIssueListVerbose(issues, sectionHeader) {
|
|
|
1071
1030
|
lines.push("");
|
|
1072
1031
|
}
|
|
1073
1032
|
for (const info of issues) {
|
|
1074
|
-
const { issue, blocking, children,
|
|
1033
|
+
const { issue, blocking, children, blockers, ancestry } = info;
|
|
1075
1034
|
const statusTime = issue.statusChangedAt ? formatRelativeTime(issue.statusChangedAt) : formatRelativeTime(issue.createdAt);
|
|
1076
1035
|
lines.push(`${issue.id}: ${issue.title}`);
|
|
1077
1036
|
lines.push(` Type: ${formatType(issue.type)} | Priority: P${issue.priority} | Status: ${formatStatus(issue.status)} (${statusTime})`);
|
|
@@ -1086,7 +1045,7 @@ function formatIssueListVerbose(issues, sectionHeader) {
|
|
|
1086
1045
|
lines.push(` Blocked by: ${blockers.join(", ")}`);
|
|
1087
1046
|
}
|
|
1088
1047
|
if (issue.type === "epic" && children > 0) {
|
|
1089
|
-
lines.push(` Children: ${children}
|
|
1048
|
+
lines.push(` Children: ${children}`);
|
|
1090
1049
|
}
|
|
1091
1050
|
lines.push("");
|
|
1092
1051
|
}
|
|
@@ -1099,11 +1058,10 @@ function outputIssueListVerbose(issues, pretty, sectionHeader, limitInfo) {
|
|
|
1099
1058
|
console.log(formatLimitMessage(limitInfo));
|
|
1100
1059
|
}
|
|
1101
1060
|
} else {
|
|
1102
|
-
const output = issues.map(({ issue, blocking, children,
|
|
1061
|
+
const output = issues.map(({ issue, blocking, children, blockers, ancestry }) => ({
|
|
1103
1062
|
...issue,
|
|
1104
1063
|
blocking,
|
|
1105
1064
|
childrenCount: issue.type === "epic" ? children : void 0,
|
|
1106
|
-
verificationsCount: verifications,
|
|
1107
1065
|
...blockers && { openBlockers: blockers },
|
|
1108
1066
|
...ancestry.length > 0 && { ancestry }
|
|
1109
1067
|
}));
|
|
@@ -1117,19 +1075,17 @@ function outputIssueListVerbose(issues, pretty, sectionHeader, limitInfo) {
|
|
|
1117
1075
|
|
|
1118
1076
|
// src/cli/commands/create.ts
|
|
1119
1077
|
function createCommand(program2) {
|
|
1120
|
-
program2.command("create <title>").description("Create a new issue").option("-t, --type <type>", "Issue type (task, bug, epic
|
|
1078
|
+
program2.command("create <title>").description("Create a new issue").option("-t, --type <type>", "Issue type (task, bug, epic)", "task").option("-p, --priority <priority>", "Priority (0-4)", "2").option("-d, --description <desc>", "Description").option("--parent <id>", "Parent issue ID").option("--blocked-by <ids>", "Comma-separated IDs of issues that block this one").option("--blocks <ids>", "Comma-separated IDs of issues this one will block").addHelpText("after", `
|
|
1079
|
+
Notes:
|
|
1080
|
+
IDs support partial matching (e.g., "abc" matches "PROJ-abc123")
|
|
1081
|
+
Creating a child under a closed parent auto-reopens the parent chain
|
|
1082
|
+
`).action(async (title, options) => {
|
|
1121
1083
|
const pretty = program2.opts().pretty ?? false;
|
|
1122
1084
|
try {
|
|
1123
|
-
|
|
1124
|
-
if (options.verifies && type !== "verification") {
|
|
1125
|
-
type = "verification";
|
|
1126
|
-
}
|
|
1085
|
+
const type = options.type;
|
|
1127
1086
|
if (!ISSUE_TYPES.includes(type)) {
|
|
1128
1087
|
throw new Error(`Invalid type: ${type}. Must be one of: ${ISSUE_TYPES.join(", ")}`);
|
|
1129
1088
|
}
|
|
1130
|
-
if (type === "verification" && !options.verifies) {
|
|
1131
|
-
throw new Error("Verification issues require --verifies <id> to specify the issue being verified");
|
|
1132
|
-
}
|
|
1133
1089
|
const priority = parseInt(options.priority, 10);
|
|
1134
1090
|
if (!PRIORITIES.includes(priority)) {
|
|
1135
1091
|
throw new Error(`Invalid priority: ${options.priority}. Must be 0-4`);
|
|
@@ -1137,33 +1093,27 @@ function createCommand(program2) {
|
|
|
1137
1093
|
const pebbleDir = getOrCreatePebbleDir();
|
|
1138
1094
|
const config = getConfig(pebbleDir);
|
|
1139
1095
|
let parentId;
|
|
1140
|
-
|
|
1096
|
+
const parentsReopened = [];
|
|
1141
1097
|
if (options.parent) {
|
|
1142
1098
|
parentId = resolveId(options.parent);
|
|
1143
1099
|
const parent = getIssue(parentId);
|
|
1144
1100
|
if (!parent) {
|
|
1145
1101
|
throw new Error(`Parent issue not found: ${options.parent}`);
|
|
1146
1102
|
}
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
let verifiesId;
|
|
1162
|
-
if (options.verifies) {
|
|
1163
|
-
verifiesId = resolveId(options.verifies);
|
|
1164
|
-
const target = getIssue(verifiesId);
|
|
1165
|
-
if (!target) {
|
|
1166
|
-
throw new Error(`Target issue not found: ${options.verifies}`);
|
|
1103
|
+
const state = getComputedState();
|
|
1104
|
+
let current = state.get(parentId);
|
|
1105
|
+
while (current) {
|
|
1106
|
+
if (current.status === "closed") {
|
|
1107
|
+
const reopenEvent = {
|
|
1108
|
+
type: "reopen",
|
|
1109
|
+
issueId: current.id,
|
|
1110
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1111
|
+
data: { reason: "Reopened to add descendant" }
|
|
1112
|
+
};
|
|
1113
|
+
appendEvent(reopenEvent, pebbleDir);
|
|
1114
|
+
parentsReopened.push({ id: current.id, title: current.title });
|
|
1115
|
+
}
|
|
1116
|
+
current = current.parent ? state.get(current.parent) : void 0;
|
|
1167
1117
|
}
|
|
1168
1118
|
}
|
|
1169
1119
|
const blockedByIds = [];
|
|
@@ -1212,8 +1162,7 @@ function createCommand(program2) {
|
|
|
1212
1162
|
type,
|
|
1213
1163
|
priority,
|
|
1214
1164
|
description: options.description,
|
|
1215
|
-
parent: parentId
|
|
1216
|
-
verifies: verifiesId
|
|
1165
|
+
parent: parentId
|
|
1217
1166
|
}
|
|
1218
1167
|
};
|
|
1219
1168
|
appendEvent(event, pebbleDir);
|
|
@@ -1246,7 +1195,7 @@ function createCommand(program2) {
|
|
|
1246
1195
|
};
|
|
1247
1196
|
appendEvent(depEvent, pebbleDir);
|
|
1248
1197
|
}
|
|
1249
|
-
const extra =
|
|
1198
|
+
const extra = parentsReopened.length > 0 || blockersReopened.length > 0 ? { parentsReopened: parentsReopened.length > 0 ? parentsReopened : void 0, blockersReopened: blockersReopened.length > 0 ? blockersReopened : void 0 } : void 0;
|
|
1250
1199
|
outputMutationSuccess(id, pretty, extra);
|
|
1251
1200
|
} catch (error) {
|
|
1252
1201
|
outputError(error, pretty);
|
|
@@ -1256,7 +1205,7 @@ function createCommand(program2) {
|
|
|
1256
1205
|
|
|
1257
1206
|
// src/cli/commands/update.ts
|
|
1258
1207
|
function updateCommand(program2) {
|
|
1259
|
-
program2.command("update <ids...>").description("Update issues. Supports multiple IDs.").option("--status <status>", "Status (open, in_progress, blocked
|
|
1208
|
+
program2.command("update <ids...>").description("Update issues. Supports multiple IDs.").option("--status <status>", "Status (open, in_progress, blocked)").option("--priority <priority>", "Priority (0-4)").option("--title <title>", "Title").option("--description <desc>", "Description").option("--parent <id>", 'Parent issue ID (use "null" to remove parent)').action(async (ids, options) => {
|
|
1260
1209
|
const pretty = program2.opts().pretty ?? false;
|
|
1261
1210
|
try {
|
|
1262
1211
|
const pebbleDir = getOrCreatePebbleDir();
|
|
@@ -1290,7 +1239,7 @@ function updateCommand(program2) {
|
|
|
1290
1239
|
data.description = options.description;
|
|
1291
1240
|
hasChanges = true;
|
|
1292
1241
|
}
|
|
1293
|
-
|
|
1242
|
+
const parentsReopened = [];
|
|
1294
1243
|
if (options.parent !== void 0) {
|
|
1295
1244
|
if (options.parent.toLowerCase() === "null") {
|
|
1296
1245
|
data.parent = "";
|
|
@@ -1300,18 +1249,20 @@ function updateCommand(program2) {
|
|
|
1300
1249
|
if (!parentIssue) {
|
|
1301
1250
|
throw new Error(`Parent issue not found: ${options.parent}`);
|
|
1302
1251
|
}
|
|
1303
|
-
|
|
1304
|
-
|
|
1305
|
-
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1252
|
+
const state = getComputedState();
|
|
1253
|
+
let current = state.get(parentId);
|
|
1254
|
+
while (current) {
|
|
1255
|
+
if (current.status === "closed") {
|
|
1256
|
+
const reopenEvent = {
|
|
1257
|
+
type: "reopen",
|
|
1258
|
+
issueId: current.id,
|
|
1259
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1260
|
+
data: { reason: "Reopened to add descendant" }
|
|
1261
|
+
};
|
|
1262
|
+
appendEvent(reopenEvent, pebbleDir);
|
|
1263
|
+
parentsReopened.push({ id: current.id, title: current.title });
|
|
1264
|
+
}
|
|
1265
|
+
current = current.parent ? state.get(current.parent) : void 0;
|
|
1315
1266
|
}
|
|
1316
1267
|
data.parent = parentId;
|
|
1317
1268
|
}
|
|
@@ -1350,7 +1301,7 @@ function updateCommand(program2) {
|
|
|
1350
1301
|
if (allIds.length === 1) {
|
|
1351
1302
|
const result = results[0];
|
|
1352
1303
|
if (result.success) {
|
|
1353
|
-
outputMutationSuccess(result.id, pretty,
|
|
1304
|
+
outputMutationSuccess(result.id, pretty, parentsReopened.length > 0 ? { parentsReopened } : void 0);
|
|
1354
1305
|
} else {
|
|
1355
1306
|
throw new Error(result.error || "Unknown error");
|
|
1356
1307
|
}
|
|
@@ -1417,25 +1368,6 @@ function closeCommand(program2) {
|
|
|
1417
1368
|
};
|
|
1418
1369
|
appendEvent(commentEvent, pebbleDir);
|
|
1419
1370
|
}
|
|
1420
|
-
const pendingVerifications = getVerifications(resolvedId).filter((v) => v.status !== "closed");
|
|
1421
|
-
if (pendingVerifications.length > 0) {
|
|
1422
|
-
const updateEvent = {
|
|
1423
|
-
type: "update",
|
|
1424
|
-
issueId: resolvedId,
|
|
1425
|
-
timestamp,
|
|
1426
|
-
data: {
|
|
1427
|
-
status: "pending_verification"
|
|
1428
|
-
}
|
|
1429
|
-
};
|
|
1430
|
-
appendEvent(updateEvent, pebbleDir);
|
|
1431
|
-
results.push({
|
|
1432
|
-
id: resolvedId,
|
|
1433
|
-
success: true,
|
|
1434
|
-
status: "pending_verification",
|
|
1435
|
-
pendingVerifications: pendingVerifications.map((v) => ({ id: v.id, title: v.title }))
|
|
1436
|
-
});
|
|
1437
|
-
continue;
|
|
1438
|
-
}
|
|
1439
1371
|
const closeEvent = {
|
|
1440
1372
|
type: "close",
|
|
1441
1373
|
issueId: resolvedId,
|
|
@@ -1446,31 +1378,10 @@ function closeCommand(program2) {
|
|
|
1446
1378
|
};
|
|
1447
1379
|
appendEvent(closeEvent, pebbleDir);
|
|
1448
1380
|
const unblocked = getNewlyUnblocked(resolvedId);
|
|
1449
|
-
let autoClosed;
|
|
1450
|
-
if (issue.verifies) {
|
|
1451
|
-
const targetIssue = getIssue(issue.verifies);
|
|
1452
|
-
if (targetIssue && targetIssue.status === "pending_verification") {
|
|
1453
|
-
const remainingVerifications = getVerifications(issue.verifies).filter((v) => v.status !== "closed");
|
|
1454
|
-
if (remainingVerifications.length === 0) {
|
|
1455
|
-
const autoCloseEvent = {
|
|
1456
|
-
type: "close",
|
|
1457
|
-
issueId: issue.verifies,
|
|
1458
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1459
|
-
data: {
|
|
1460
|
-
reason: "All verifications completed"
|
|
1461
|
-
}
|
|
1462
|
-
};
|
|
1463
|
-
appendEvent(autoCloseEvent, pebbleDir);
|
|
1464
|
-
autoClosed = { id: targetIssue.id, title: targetIssue.title };
|
|
1465
|
-
}
|
|
1466
|
-
}
|
|
1467
|
-
}
|
|
1468
1381
|
results.push({
|
|
1469
1382
|
id: resolvedId,
|
|
1470
1383
|
success: true,
|
|
1471
|
-
|
|
1472
|
-
unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0,
|
|
1473
|
-
autoClosed
|
|
1384
|
+
unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0
|
|
1474
1385
|
});
|
|
1475
1386
|
} catch (error) {
|
|
1476
1387
|
results.push({ id, success: false, error: error.message });
|
|
@@ -1480,38 +1391,19 @@ function closeCommand(program2) {
|
|
|
1480
1391
|
const result = results[0];
|
|
1481
1392
|
if (result.success) {
|
|
1482
1393
|
if (pretty) {
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
console.log(`
|
|
1486
|
-
Pending verifications:`);
|
|
1487
|
-
for (const v of result.pendingVerifications || []) {
|
|
1488
|
-
console.log(` \u2022 ${v.id} - ${v.title}`);
|
|
1489
|
-
}
|
|
1394
|
+
console.log(`\u2713 ${result.id}`);
|
|
1395
|
+
if (result.unblocked && result.unblocked.length > 0) {
|
|
1490
1396
|
console.log(`
|
|
1491
|
-
Run: pb verifications ${result.id}`);
|
|
1492
|
-
} else {
|
|
1493
|
-
console.log(`\u2713 ${result.id}`);
|
|
1494
|
-
if (result.unblocked && result.unblocked.length > 0) {
|
|
1495
|
-
console.log(`
|
|
1496
1397
|
Unblocked:`);
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
}
|
|
1500
|
-
}
|
|
1501
|
-
if (result.autoClosed) {
|
|
1502
|
-
console.log(`
|
|
1503
|
-
\u2713 ${result.autoClosed.id} auto-closed (all verifications complete)`);
|
|
1398
|
+
for (const u of result.unblocked) {
|
|
1399
|
+
console.log(` \u2192 ${u.id} - ${u.title}`);
|
|
1504
1400
|
}
|
|
1505
1401
|
}
|
|
1506
1402
|
} else {
|
|
1507
1403
|
console.log(formatJson({
|
|
1508
1404
|
id: result.id,
|
|
1509
1405
|
success: true,
|
|
1510
|
-
|
|
1511
|
-
...result.pendingVerifications && { pendingVerifications: result.pendingVerifications },
|
|
1512
|
-
...result.pendingVerifications && { hint: `pb verifications ${result.id}` },
|
|
1513
|
-
...result.unblocked && { unblocked: result.unblocked },
|
|
1514
|
-
...result.autoClosed && { autoClosed: result.autoClosed }
|
|
1406
|
+
...result.unblocked && { unblocked: result.unblocked }
|
|
1515
1407
|
}));
|
|
1516
1408
|
}
|
|
1517
1409
|
} else {
|
|
@@ -1521,21 +1413,10 @@ Unblocked:`);
|
|
|
1521
1413
|
if (pretty) {
|
|
1522
1414
|
for (const result of results) {
|
|
1523
1415
|
if (result.success) {
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
for (const
|
|
1527
|
-
console.log(` \
|
|
1528
|
-
}
|
|
1529
|
-
console.log(` Run: pb verifications ${result.id}`);
|
|
1530
|
-
} else {
|
|
1531
|
-
console.log(`\u2713 ${result.id}`);
|
|
1532
|
-
if (result.unblocked && result.unblocked.length > 0) {
|
|
1533
|
-
for (const u of result.unblocked) {
|
|
1534
|
-
console.log(` \u2192 ${u.id} - ${u.title}`);
|
|
1535
|
-
}
|
|
1536
|
-
}
|
|
1537
|
-
if (result.autoClosed) {
|
|
1538
|
-
console.log(` \u2713 ${result.autoClosed.id} auto-closed (all verifications complete)`);
|
|
1416
|
+
console.log(`\u2713 ${result.id}`);
|
|
1417
|
+
if (result.unblocked && result.unblocked.length > 0) {
|
|
1418
|
+
for (const u of result.unblocked) {
|
|
1419
|
+
console.log(` \u2192 ${u.id} - ${u.title}`);
|
|
1539
1420
|
}
|
|
1540
1421
|
}
|
|
1541
1422
|
} else {
|
|
@@ -1546,12 +1427,8 @@ Unblocked:`);
|
|
|
1546
1427
|
console.log(formatJson(results.map((r) => ({
|
|
1547
1428
|
id: r.id,
|
|
1548
1429
|
success: r.success,
|
|
1549
|
-
status: r.status,
|
|
1550
1430
|
...r.error && { error: r.error },
|
|
1551
|
-
...r.
|
|
1552
|
-
...r.pendingVerifications && { hint: `pb verifications ${r.id}` },
|
|
1553
|
-
...r.unblocked && { unblocked: r.unblocked },
|
|
1554
|
-
...r.autoClosed && { autoClosed: r.autoClosed }
|
|
1431
|
+
...r.unblocked && { unblocked: r.unblocked }
|
|
1555
1432
|
}))));
|
|
1556
1433
|
}
|
|
1557
1434
|
}
|
|
@@ -1671,13 +1548,6 @@ function deleteCommand(program2) {
|
|
|
1671
1548
|
alreadyQueued.add(desc.id);
|
|
1672
1549
|
}
|
|
1673
1550
|
}
|
|
1674
|
-
const verifications = getVerifications(resolvedId);
|
|
1675
|
-
for (const v of verifications) {
|
|
1676
|
-
if (!alreadyQueued.has(v.id) && !v.deleted) {
|
|
1677
|
-
toDelete.push({ id: v.id, cascade: true });
|
|
1678
|
-
alreadyQueued.add(v.id);
|
|
1679
|
-
}
|
|
1680
|
-
}
|
|
1681
1551
|
} catch (error) {
|
|
1682
1552
|
results.push({ id, success: false, error: error.message });
|
|
1683
1553
|
}
|
|
@@ -1888,7 +1758,7 @@ function claimCommand(program2) {
|
|
|
1888
1758
|
|
|
1889
1759
|
// src/cli/commands/list.ts
|
|
1890
1760
|
function listCommand(program2) {
|
|
1891
|
-
program2.command("list").description("List issues").option("--status <status>", "Filter by status").option("-t, --type <type>", "Filter by type").option("--priority <priority>", "Filter by priority").option("--parent <id>", "Filter by parent epic").option("-v, --verbose", "Show expanded details (parent, children, blocking
|
|
1761
|
+
program2.command("list").description("List issues").option("--status <status>", "Filter by status").option("-t, --type <type>", "Filter by type").option("--priority <priority>", "Filter by priority").option("--parent <id>", "Filter by parent epic").option("-v, --verbose", "Show expanded details (parent, children, blocking)").option("--flat", "Show flat list instead of hierarchical tree").option("--limit <n>", "Max issues to return (default: 30)").option("--all", "Show all issues (no limit)").action(async (options) => {
|
|
1892
1762
|
const pretty = program2.opts().pretty ?? false;
|
|
1893
1763
|
try {
|
|
1894
1764
|
getOrCreatePebbleDir();
|
|
@@ -1936,7 +1806,6 @@ function listCommand(program2) {
|
|
|
1936
1806
|
issue,
|
|
1937
1807
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1938
1808
|
children: getChildren(issue.id).length,
|
|
1939
|
-
verifications: getVerifications(issue.id).length,
|
|
1940
1809
|
ancestry: getAncestryChain(issue.id, state)
|
|
1941
1810
|
};
|
|
1942
1811
|
});
|
|
@@ -1973,11 +1842,10 @@ function showCommand(program2) {
|
|
|
1973
1842
|
}
|
|
1974
1843
|
const blocking = getBlocking(resolvedId);
|
|
1975
1844
|
const children = issue.type === "epic" ? getChildren(resolvedId) : [];
|
|
1976
|
-
const verifications = getVerifications(resolvedId);
|
|
1977
1845
|
const related = getRelated(resolvedId);
|
|
1978
1846
|
const state = getComputedState();
|
|
1979
1847
|
const ancestry = getAncestryChain(resolvedId, state);
|
|
1980
|
-
outputIssueDetail(issue, { blocking, children,
|
|
1848
|
+
outputIssueDetail(issue, { blocking, children, related, ancestry }, pretty);
|
|
1981
1849
|
} catch (error) {
|
|
1982
1850
|
outputError(error, pretty);
|
|
1983
1851
|
}
|
|
@@ -1986,7 +1854,7 @@ function showCommand(program2) {
|
|
|
1986
1854
|
|
|
1987
1855
|
// src/cli/commands/ready.ts
|
|
1988
1856
|
function readyCommand(program2) {
|
|
1989
|
-
program2.command("ready").description("Show issues ready for work (no open blockers)").option("-v, --verbose", "Show expanded details (parent, children, blocking
|
|
1857
|
+
program2.command("ready").description("Show issues ready for work (no open blockers)").option("-v, --verbose", "Show expanded details (parent, children, blocking)").option("-t, --type <type>", "Filter by type: task, bug, epic").option("--limit <n>", "Max issues to return (default: 30)").option("--all", "Show all issues (no limit)").action(async (options) => {
|
|
1990
1858
|
const pretty = program2.opts().pretty ?? false;
|
|
1991
1859
|
try {
|
|
1992
1860
|
getOrCreatePebbleDir();
|
|
@@ -2016,7 +1884,6 @@ function readyCommand(program2) {
|
|
|
2016
1884
|
issue,
|
|
2017
1885
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
2018
1886
|
children: getChildren(issue.id).length,
|
|
2019
|
-
verifications: getVerifications(issue.id).length,
|
|
2020
1887
|
ancestry: getAncestryChain(issue.id, state)
|
|
2021
1888
|
};
|
|
2022
1889
|
});
|
|
@@ -2057,7 +1924,6 @@ function blockedCommand(program2) {
|
|
|
2057
1924
|
issue,
|
|
2058
1925
|
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
2059
1926
|
children: getChildren(issue.id).length,
|
|
2060
|
-
verifications: getVerifications(issue.id).length,
|
|
2061
1927
|
blockers: openBlockers,
|
|
2062
1928
|
ancestry: getAncestryChain(issue.id, state)
|
|
2063
1929
|
};
|
|
@@ -2075,7 +1941,14 @@ function blockedCommand(program2) {
|
|
|
2075
1941
|
// src/cli/commands/dep.ts
|
|
2076
1942
|
function depCommand(program2) {
|
|
2077
1943
|
const dep = program2.command("dep").description("Manage dependencies");
|
|
2078
|
-
dep.command("add <id> [blockerId]").description("Add a blocking dependency
|
|
1944
|
+
dep.command("add <id> [blockerId]").description("Add a blocking dependency: <id> is blocked by [blockerId]").option("--needs <id>", "Issue that must be completed first (first arg needs this)").option("--blocks <id>", "Issue that this blocks (first arg blocks this)").addHelpText("after", `
|
|
1945
|
+
Examples:
|
|
1946
|
+
pb dep add B A B is blocked by A (A must close before B is ready)
|
|
1947
|
+
pb dep add B --needs A Same as above, self-documenting
|
|
1948
|
+
pb dep add A --blocks B Same effect: B is blocked by A
|
|
1949
|
+
|
|
1950
|
+
Think: "B needs A" \u2192 pb dep add B A
|
|
1951
|
+
`).action(async (id, blockerId, options) => {
|
|
2079
1952
|
const pretty = program2.opts().pretty ?? false;
|
|
2080
1953
|
try {
|
|
2081
1954
|
if (options.needs && options.blocks) {
|
|
@@ -2274,7 +2147,7 @@ function depCommand(program2) {
|
|
|
2274
2147
|
outputError(error, pretty);
|
|
2275
2148
|
}
|
|
2276
2149
|
});
|
|
2277
|
-
dep.command("tree <id>").description("Show issue tree (children
|
|
2150
|
+
dep.command("tree <id>").description("Show issue tree (children and full hierarchy)").action(async (id) => {
|
|
2278
2151
|
const pretty = program2.opts().pretty ?? false;
|
|
2279
2152
|
try {
|
|
2280
2153
|
const resolvedId = resolveId(id);
|
|
@@ -2303,7 +2176,7 @@ function buildIssueTree2(issueId, state) {
|
|
|
2303
2176
|
const buildChildren = (id, visited2) => {
|
|
2304
2177
|
const children = [];
|
|
2305
2178
|
for (const [, i] of state) {
|
|
2306
|
-
if (
|
|
2179
|
+
if (i.parent === id && !visited2.has(i.id)) {
|
|
2307
2180
|
visited2.add(i.id);
|
|
2308
2181
|
const nodeChildren = buildChildren(i.id, visited2);
|
|
2309
2182
|
children.push({
|
|
@@ -2339,7 +2212,7 @@ function buildIssueTree2(issueId, state) {
|
|
|
2339
2212
|
if (!parentIssue) break;
|
|
2340
2213
|
const siblings = [];
|
|
2341
2214
|
for (const [, i] of state) {
|
|
2342
|
-
if (
|
|
2215
|
+
if (i.parent === parentIssue.id && i.id !== currentIssue.id) {
|
|
2343
2216
|
siblings.push({
|
|
2344
2217
|
id: i.id,
|
|
2345
2218
|
title: i.title,
|
|
@@ -2372,7 +2245,7 @@ function formatIssueTreePretty2(node) {
|
|
|
2372
2245
|
const lines = [];
|
|
2373
2246
|
const formatNode = (n, prefix, isLast, isRoot) => {
|
|
2374
2247
|
const connector = isRoot ? "" : isLast ? "\u2514\u2500 " : "\u251C\u2500 ";
|
|
2375
|
-
const statusIcon = n.status === "closed" ? "\u2713" : n.status === "in_progress" ? "\u25B6" :
|
|
2248
|
+
const statusIcon = n.status === "closed" ? "\u2713" : n.status === "in_progress" ? "\u25B6" : "\u25CB";
|
|
2376
2249
|
const marker = n.isTarget ? " \u25C0" : "";
|
|
2377
2250
|
lines.push(`${prefix}${connector}${statusIcon} ${n.id}: ${n.title} [${n.type}] P${n.priority}${marker}`);
|
|
2378
2251
|
const children = n.children ?? [];
|
|
@@ -2850,26 +2723,27 @@ data: ${message}
|
|
|
2850
2723
|
res.status(400).json({ error: "Priority must be 0-4" });
|
|
2851
2724
|
return;
|
|
2852
2725
|
}
|
|
2853
|
-
|
|
2726
|
+
const parentsReopened = [];
|
|
2854
2727
|
if (parent) {
|
|
2855
2728
|
const parentIssue = getIssue(parent);
|
|
2856
2729
|
if (!parentIssue) {
|
|
2857
2730
|
res.status(400).json({ error: `Parent issue not found: ${parent}` });
|
|
2858
2731
|
return;
|
|
2859
2732
|
}
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2733
|
+
const state = getComputedState();
|
|
2734
|
+
let current = state.get(parent);
|
|
2735
|
+
while (current) {
|
|
2736
|
+
if (current.status === "closed") {
|
|
2737
|
+
const reopenEvent = {
|
|
2738
|
+
type: "reopen",
|
|
2739
|
+
issueId: current.id,
|
|
2740
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
2741
|
+
data: { reason: "Reopened to add descendant" }
|
|
2742
|
+
};
|
|
2743
|
+
appendEvent(reopenEvent, pebbleDir);
|
|
2744
|
+
parentsReopened.push({ id: current.id, title: current.title });
|
|
2745
|
+
}
|
|
2746
|
+
current = current.parent ? state.get(current.parent) : void 0;
|
|
2873
2747
|
}
|
|
2874
2748
|
}
|
|
2875
2749
|
const issueId = generateId(config.prefix);
|
|
@@ -2897,7 +2771,7 @@ data: ${message}
|
|
|
2897
2771
|
appendEvent(parentUpdateEvent, pebbleDir);
|
|
2898
2772
|
}
|
|
2899
2773
|
const issue = getIssue(issueId);
|
|
2900
|
-
const result =
|
|
2774
|
+
const result = parentsReopened.length > 0 ? { ...issue, _parentsReopened: parentsReopened } : issue;
|
|
2901
2775
|
res.status(201).json(result);
|
|
2902
2776
|
} catch (error) {
|
|
2903
2777
|
res.status(500).json({ error: error.message });
|
|
@@ -2928,23 +2802,7 @@ data: ${message}
|
|
|
2928
2802
|
results.push({ id: issueId, success: false, error: "Cannot close issue with open children" });
|
|
2929
2803
|
continue;
|
|
2930
2804
|
}
|
|
2931
|
-
const pendingVerifications = getVerifications(issueId).filter((v) => v.status !== "closed");
|
|
2932
2805
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
2933
|
-
if (pendingVerifications.length > 0) {
|
|
2934
|
-
const updateEvent = {
|
|
2935
|
-
type: "update",
|
|
2936
|
-
issueId,
|
|
2937
|
-
timestamp,
|
|
2938
|
-
data: { status: "pending_verification" }
|
|
2939
|
-
};
|
|
2940
|
-
appendEvent(updateEvent, pebbleDir);
|
|
2941
|
-
results.push({
|
|
2942
|
-
id: issueId,
|
|
2943
|
-
success: true,
|
|
2944
|
-
error: `Moved to pending_verification (${pendingVerifications.length} verification(s) pending)`
|
|
2945
|
-
});
|
|
2946
|
-
continue;
|
|
2947
|
-
}
|
|
2948
2806
|
const event = {
|
|
2949
2807
|
issueId,
|
|
2950
2808
|
timestamp,
|
|
@@ -2975,7 +2833,7 @@ data: ${message}
|
|
|
2975
2833
|
return;
|
|
2976
2834
|
}
|
|
2977
2835
|
if (updates.status) {
|
|
2978
|
-
const validStatuses = ["open", "in_progress", "blocked"
|
|
2836
|
+
const validStatuses = ["open", "in_progress", "blocked"];
|
|
2979
2837
|
if (!validStatuses.includes(updates.status)) {
|
|
2980
2838
|
res.status(400).json({
|
|
2981
2839
|
error: `Invalid status: ${updates.status}. Use close endpoint to close issues.`
|
|
@@ -3120,20 +2978,12 @@ data: ${message}
|
|
|
3120
2978
|
res.status(400).json({ error: `Parent issue not found: ${parent}` });
|
|
3121
2979
|
return;
|
|
3122
2980
|
}
|
|
3123
|
-
if (parentFound.issue.type === "verification") {
|
|
3124
|
-
res.status(400).json({ error: "Verification issues cannot be parents" });
|
|
3125
|
-
return;
|
|
3126
|
-
}
|
|
3127
2981
|
} else {
|
|
3128
2982
|
const parentIssue = getIssue(parent);
|
|
3129
2983
|
if (!parentIssue) {
|
|
3130
2984
|
res.status(400).json({ error: `Parent issue not found: ${parent}` });
|
|
3131
2985
|
return;
|
|
3132
2986
|
}
|
|
3133
|
-
if (parentIssue.type === "verification") {
|
|
3134
|
-
res.status(400).json({ error: "Verification issues cannot be parents" });
|
|
3135
|
-
return;
|
|
3136
|
-
}
|
|
3137
2987
|
}
|
|
3138
2988
|
}
|
|
3139
2989
|
updates.parent = parent;
|
|
@@ -3238,30 +3088,6 @@ data: ${message}
|
|
|
3238
3088
|
}
|
|
3239
3089
|
const { reason } = req.body;
|
|
3240
3090
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
3241
|
-
let pendingVerifications = [];
|
|
3242
|
-
if (isMultiWorktree()) {
|
|
3243
|
-
const allIssues = mergeIssuesFromFiles(issueFiles);
|
|
3244
|
-
pendingVerifications = allIssues.filter(
|
|
3245
|
-
(i) => i.verifies === issueId && i.status !== "closed"
|
|
3246
|
-
);
|
|
3247
|
-
} else {
|
|
3248
|
-
pendingVerifications = getVerifications(issueId).filter((v) => v.status !== "closed");
|
|
3249
|
-
}
|
|
3250
|
-
if (pendingVerifications.length > 0) {
|
|
3251
|
-
const updateEvent = {
|
|
3252
|
-
type: "update",
|
|
3253
|
-
issueId,
|
|
3254
|
-
timestamp,
|
|
3255
|
-
data: { status: "pending_verification" }
|
|
3256
|
-
};
|
|
3257
|
-
appendEventToFile(updateEvent, targetFile);
|
|
3258
|
-
const updatedIssue = { ...issue, status: "pending_verification", updatedAt: timestamp };
|
|
3259
|
-
res.json({
|
|
3260
|
-
...updatedIssue,
|
|
3261
|
-
_pendingVerifications: pendingVerifications.map((v) => ({ id: v.id, title: v.title }))
|
|
3262
|
-
});
|
|
3263
|
-
return;
|
|
3264
|
-
}
|
|
3265
3091
|
const event = {
|
|
3266
3092
|
type: "close",
|
|
3267
3093
|
issueId,
|
|
@@ -3269,49 +3095,11 @@ data: ${message}
|
|
|
3269
3095
|
data: { reason }
|
|
3270
3096
|
};
|
|
3271
3097
|
appendEventToFile(event, targetFile);
|
|
3272
|
-
let autoClosed;
|
|
3273
|
-
if (issue.verifies) {
|
|
3274
|
-
let targetIssue;
|
|
3275
|
-
let targetVerifications = [];
|
|
3276
|
-
if (isMultiWorktree()) {
|
|
3277
|
-
const found = findIssueInSources(issue.verifies, issueFiles);
|
|
3278
|
-
if (found) {
|
|
3279
|
-
targetIssue = found.issue;
|
|
3280
|
-
const allIssues = mergeIssuesFromFiles(issueFiles);
|
|
3281
|
-
targetVerifications = allIssues.filter(
|
|
3282
|
-
(i) => i.verifies === issue.verifies && i.status !== "closed"
|
|
3283
|
-
);
|
|
3284
|
-
}
|
|
3285
|
-
} else {
|
|
3286
|
-
targetIssue = getIssue(issue.verifies);
|
|
3287
|
-
targetVerifications = getVerifications(issue.verifies).filter((v) => v.status !== "closed");
|
|
3288
|
-
}
|
|
3289
|
-
if (targetIssue && targetIssue.status === "pending_verification" && targetVerifications.length === 0) {
|
|
3290
|
-
const autoCloseEvent = {
|
|
3291
|
-
type: "close",
|
|
3292
|
-
issueId: issue.verifies,
|
|
3293
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
3294
|
-
data: { reason: "All verifications completed" }
|
|
3295
|
-
};
|
|
3296
|
-
if (isMultiWorktree()) {
|
|
3297
|
-
const targetFound = findIssueInSources(issue.verifies, issueFiles);
|
|
3298
|
-
if (targetFound) {
|
|
3299
|
-
appendEventToFile(autoCloseEvent, targetFound.targetFile);
|
|
3300
|
-
}
|
|
3301
|
-
} else {
|
|
3302
|
-
const pebbleDir = getOrCreatePebbleDir();
|
|
3303
|
-
appendEventToFile(autoCloseEvent, path3.join(pebbleDir, "issues.jsonl"));
|
|
3304
|
-
}
|
|
3305
|
-
autoClosed = { id: targetIssue.id, title: targetIssue.title };
|
|
3306
|
-
}
|
|
3307
|
-
}
|
|
3308
3098
|
if (isMultiWorktree()) {
|
|
3309
3099
|
const updated = findIssueInSources(issueId, issueFiles);
|
|
3310
|
-
|
|
3311
|
-
res.json(autoClosed ? { ...result, _autoClosed: autoClosed } : result);
|
|
3100
|
+
res.json(updated?.issue || { ...issue, status: "closed", updatedAt: timestamp });
|
|
3312
3101
|
} else {
|
|
3313
|
-
|
|
3314
|
-
res.json(autoClosed ? { ...result, _autoClosed: autoClosed } : result);
|
|
3102
|
+
res.json(getIssue(issueId));
|
|
3315
3103
|
}
|
|
3316
3104
|
} catch (error) {
|
|
3317
3105
|
res.status(500).json({ error: error.message });
|
|
@@ -3408,13 +3196,6 @@ data: ${message}
|
|
|
3408
3196
|
alreadyQueued.add(desc.id);
|
|
3409
3197
|
}
|
|
3410
3198
|
}
|
|
3411
|
-
const verifications = getVerifications(issueId);
|
|
3412
|
-
for (const v of verifications) {
|
|
3413
|
-
if (!alreadyQueued.has(v.id) && !v.deleted) {
|
|
3414
|
-
toDelete.push({ id: v.id, cascade: true });
|
|
3415
|
-
alreadyQueued.add(v.id);
|
|
3416
|
-
}
|
|
3417
|
-
}
|
|
3418
3199
|
const cleanupReferences = (deletedId) => {
|
|
3419
3200
|
for (const [id, iss] of state) {
|
|
3420
3201
|
if (id === deletedId || iss.deleted) continue;
|
|
@@ -4000,23 +3781,11 @@ function countChildren(epicId) {
|
|
|
4000
3781
|
return {
|
|
4001
3782
|
total: children.length,
|
|
4002
3783
|
done: children.filter((c) => c.status === "closed").length,
|
|
4003
|
-
pending_verification: children.filter((c) => c.status === "pending_verification").length,
|
|
4004
3784
|
in_progress: children.filter((c) => c.status === "in_progress").length,
|
|
4005
3785
|
open: children.filter((c) => c.status === "open").length,
|
|
4006
3786
|
blocked: children.filter((c) => c.status === "blocked").length
|
|
4007
3787
|
};
|
|
4008
3788
|
}
|
|
4009
|
-
function countVerifications(epicId) {
|
|
4010
|
-
const children = getChildren(epicId);
|
|
4011
|
-
let total = 0;
|
|
4012
|
-
let done = 0;
|
|
4013
|
-
for (const child of children) {
|
|
4014
|
-
const verifications = getVerifications(child.id);
|
|
4015
|
-
total += verifications.length;
|
|
4016
|
-
done += verifications.filter((v) => v.status === "closed").length;
|
|
4017
|
-
}
|
|
4018
|
-
return { total, done };
|
|
4019
|
-
}
|
|
4020
3789
|
function getIssueComments(issueId) {
|
|
4021
3790
|
const events = readEvents();
|
|
4022
3791
|
return events.filter((e) => e.type === "comment" && e.issueId === issueId).sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()).map((e) => ({ text: e.data.text, timestamp: e.timestamp, source: e.source }));
|
|
@@ -4069,13 +3838,10 @@ function formatSummaryPretty(summaries, sectionHeader) {
|
|
|
4069
3838
|
lines.push(`## ${sectionHeader} (${summaries.length})`);
|
|
4070
3839
|
lines.push("");
|
|
4071
3840
|
for (const summary of summaries) {
|
|
4072
|
-
const { children
|
|
3841
|
+
const { children } = summary;
|
|
4073
3842
|
lines.push(`${summary.id}: ${summary.title}`);
|
|
4074
3843
|
lines.push(` Created: ${formatRelativeTime(summary.createdAt)} | Updated: ${formatRelativeTime(summary.updatedAt)}`);
|
|
4075
|
-
|
|
4076
|
-
const issueCount = `Issues: ${children.done}/${children.total} done${pendingStr}`;
|
|
4077
|
-
const verifCount = `Verifications: ${verifications.done}/${verifications.total} done`;
|
|
4078
|
-
lines.push(` ${issueCount} | ${verifCount}`);
|
|
3844
|
+
lines.push(` Issues: ${children.done}/${children.total} done`);
|
|
4079
3845
|
if (summary.parent) {
|
|
4080
3846
|
lines.push(` Parent: ${summary.parent.id} (${summary.parent.title})`);
|
|
4081
3847
|
}
|
|
@@ -4103,8 +3869,7 @@ function summaryCommand(program2) {
|
|
|
4103
3869
|
status: epic.status,
|
|
4104
3870
|
createdAt: epic.createdAt,
|
|
4105
3871
|
updatedAt: epic.updatedAt,
|
|
4106
|
-
children: countChildren(epic.id)
|
|
4107
|
-
verifications: countVerifications(epic.id)
|
|
3872
|
+
children: countChildren(epic.id)
|
|
4108
3873
|
};
|
|
4109
3874
|
if (epic.parent) {
|
|
4110
3875
|
const parentIssue = getIssue(epic.parent);
|
|
@@ -4400,63 +4165,6 @@ function searchCommand(program2) {
|
|
|
4400
4165
|
});
|
|
4401
4166
|
}
|
|
4402
4167
|
|
|
4403
|
-
// src/cli/commands/verifications.ts
|
|
4404
|
-
function verificationsCommand(program2) {
|
|
4405
|
-
program2.command("verifications <id>").description("List verification issues for a given issue").option("--limit <n>", "Max verifications to return (default: 30)").option("--all", "Show all verifications (no limit)").action(async (id, options) => {
|
|
4406
|
-
const pretty = program2.opts().pretty ?? false;
|
|
4407
|
-
try {
|
|
4408
|
-
getOrCreatePebbleDir();
|
|
4409
|
-
const resolvedId = resolveId(id);
|
|
4410
|
-
const issue = getIssue(resolvedId);
|
|
4411
|
-
if (!issue) {
|
|
4412
|
-
throw new Error(`Issue not found: ${id}`);
|
|
4413
|
-
}
|
|
4414
|
-
let verifications = getVerifications(resolvedId);
|
|
4415
|
-
verifications.sort((a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime());
|
|
4416
|
-
const total = verifications.length;
|
|
4417
|
-
const limit = options.all ? 0 : options.limit ? parseInt(options.limit, 10) : 30;
|
|
4418
|
-
if (limit > 0 && verifications.length > limit) {
|
|
4419
|
-
verifications = verifications.slice(0, limit);
|
|
4420
|
-
}
|
|
4421
|
-
const limitInfo = {
|
|
4422
|
-
total,
|
|
4423
|
-
shown: verifications.length,
|
|
4424
|
-
limited: limit > 0 && total > limit
|
|
4425
|
-
};
|
|
4426
|
-
if (pretty) {
|
|
4427
|
-
if (verifications.length === 0) {
|
|
4428
|
-
console.log(`No verifications for ${resolvedId}`);
|
|
4429
|
-
} else {
|
|
4430
|
-
console.log(`Verifications for ${resolvedId} "${issue.title}"`);
|
|
4431
|
-
console.log("\u2500".repeat(50));
|
|
4432
|
-
for (const v of verifications) {
|
|
4433
|
-
const status = v.status === "closed" ? "\u2713" : "\u25CB";
|
|
4434
|
-
console.log(` ${status} ${v.id} - ${v.title}`);
|
|
4435
|
-
}
|
|
4436
|
-
console.log("");
|
|
4437
|
-
console.log(`Total: ${verifications.length} verification(s)`);
|
|
4438
|
-
if (limitInfo.limited) {
|
|
4439
|
-
console.log(formatLimitMessage(limitInfo));
|
|
4440
|
-
}
|
|
4441
|
-
}
|
|
4442
|
-
} else {
|
|
4443
|
-
const output = {
|
|
4444
|
-
issueId: resolvedId,
|
|
4445
|
-
verifications: verifications.map((v) => ({
|
|
4446
|
-
id: v.id,
|
|
4447
|
-
title: v.title,
|
|
4448
|
-
status: v.status
|
|
4449
|
-
})),
|
|
4450
|
-
...limitInfo.limited && { _meta: limitInfo }
|
|
4451
|
-
};
|
|
4452
|
-
console.log(formatJson(output));
|
|
4453
|
-
}
|
|
4454
|
-
} catch (error) {
|
|
4455
|
-
outputError(error, pretty);
|
|
4456
|
-
}
|
|
4457
|
-
});
|
|
4458
|
-
}
|
|
4459
|
-
|
|
4460
4168
|
// src/cli/commands/init.ts
|
|
4461
4169
|
import * as path6 from "path";
|
|
4462
4170
|
function initCommand(program2) {
|
|
@@ -4485,7 +4193,7 @@ var __filename2 = fileURLToPath2(import.meta.url);
|
|
|
4485
4193
|
var __dirname2 = dirname2(__filename2);
|
|
4486
4194
|
var packageJson = JSON.parse(readFileSync2(join3(__dirname2, "../../package.json"), "utf-8"));
|
|
4487
4195
|
var program = new Command();
|
|
4488
|
-
program.name("pebble").description("A lightweight JSONL-based issue tracker").version(packageJson.version);
|
|
4196
|
+
program.name("pebble").description("A lightweight JSONL-based issue tracker").version(packageJson.version).addHelpText("after", '\nAll commands accept partial IDs (e.g., "abc" matches "PROJ-abc123")');
|
|
4489
4197
|
program.option("-P, --pretty", "Human-readable output (default: JSON)");
|
|
4490
4198
|
program.option("--json", "JSON output (this is the default, flag not needed)");
|
|
4491
4199
|
program.option("--local", "Use local .pebble directory even in a git worktree");
|
|
@@ -4514,7 +4222,6 @@ mergeCommand(program);
|
|
|
4514
4222
|
summaryCommand(program);
|
|
4515
4223
|
historyCommand(program);
|
|
4516
4224
|
searchCommand(program);
|
|
4517
|
-
verificationsCommand(program);
|
|
4518
4225
|
initCommand(program);
|
|
4519
4226
|
program.parse();
|
|
4520
4227
|
//# sourceMappingURL=index.js.map
|