@markmdev/pebble 0.1.15 → 0.1.17

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 CHANGED
@@ -16,7 +16,7 @@ import { dirname as dirname2, join as join3 } from "path";
16
16
  var ISSUE_TYPES = ["task", "bug", "epic", "verification"];
17
17
  var PRIORITIES = [0, 1, 2, 3, 4];
18
18
  var STATUSES = ["open", "in_progress", "blocked", "pending_verification", "closed"];
19
- var EVENT_TYPES = ["create", "update", "close", "reopen", "comment"];
19
+ var EVENT_TYPES = ["create", "update", "close", "reopen", "comment", "delete", "restore"];
20
20
  var PRIORITY_LABELS = {
21
21
  0: "critical",
22
22
  1: "high",
@@ -86,6 +86,17 @@ function getMainWorktreeRoot() {
86
86
  return null;
87
87
  }
88
88
  }
89
+ function getCurrentWorktreeName() {
90
+ try {
91
+ const toplevel = execSync("git rev-parse --show-toplevel", {
92
+ encoding: "utf-8",
93
+ stdio: ["pipe", "pipe", "pipe"]
94
+ }).trim();
95
+ return path.basename(toplevel);
96
+ } catch {
97
+ return null;
98
+ }
99
+ }
89
100
 
90
101
  // src/cli/lib/storage.ts
91
102
  var PEBBLE_DIR = ".pebble";
@@ -187,9 +198,21 @@ function getIssuesPath(pebbleDir) {
187
198
  const dir = pebbleDir ?? getPebbleDir();
188
199
  return path2.join(dir, ISSUES_FILE);
189
200
  }
201
+ function getEventSource() {
202
+ const worktreeName = getCurrentWorktreeName();
203
+ if (worktreeName) {
204
+ return worktreeName;
205
+ }
206
+ const pebbleDir = discoverPebbleDir();
207
+ if (pebbleDir) {
208
+ return path2.basename(path2.dirname(pebbleDir));
209
+ }
210
+ return "unknown";
211
+ }
190
212
  function appendEvent(event, pebbleDir) {
191
213
  const issuesPath = getIssuesPath(pebbleDir);
192
- const line = JSON.stringify(event) + "\n";
214
+ const eventWithSource = event.source ? event : { ...event, source: getEventSource() };
215
+ const line = JSON.stringify(eventWithSource) + "\n";
193
216
  fs.appendFileSync(issuesPath, line, "utf-8");
194
217
  }
195
218
  function readEventsFromFile(filePath) {
@@ -270,7 +293,8 @@ function computeState(events) {
270
293
  verifies: createEvent.data.verifies,
271
294
  comments: [],
272
295
  createdAt: event.timestamp,
273
- updatedAt: event.timestamp
296
+ updatedAt: event.timestamp,
297
+ lastSource: event.source
274
298
  };
275
299
  issues.set(event.issueId, issue);
276
300
  break;
@@ -304,6 +328,7 @@ function computeState(events) {
304
328
  issue.relatedTo = updateEvent.data.relatedTo;
305
329
  }
306
330
  issue.updatedAt = event.timestamp;
331
+ if (event.source) issue.lastSource = event.source;
307
332
  }
308
333
  break;
309
334
  }
@@ -312,6 +337,7 @@ function computeState(events) {
312
337
  if (issue) {
313
338
  issue.status = "closed";
314
339
  issue.updatedAt = event.timestamp;
340
+ if (event.source) issue.lastSource = event.source;
315
341
  }
316
342
  break;
317
343
  }
@@ -320,6 +346,7 @@ function computeState(events) {
320
346
  if (issue) {
321
347
  issue.status = "open";
322
348
  issue.updatedAt = event.timestamp;
349
+ if (event.source) issue.lastSource = event.source;
323
350
  }
324
351
  break;
325
352
  }
@@ -329,6 +356,27 @@ function computeState(events) {
329
356
  if (issue) {
330
357
  issue.comments.push(commentEvent.data);
331
358
  issue.updatedAt = event.timestamp;
359
+ if (event.source) issue.lastSource = event.source;
360
+ }
361
+ break;
362
+ }
363
+ case "delete": {
364
+ const issue = issues.get(event.issueId);
365
+ if (issue) {
366
+ issue.deleted = true;
367
+ issue.deletedAt = event.timestamp;
368
+ issue.updatedAt = event.timestamp;
369
+ if (event.source) issue.lastSource = event.source;
370
+ }
371
+ break;
372
+ }
373
+ case "restore": {
374
+ const issue = issues.get(event.issueId);
375
+ if (issue) {
376
+ issue.deleted = false;
377
+ issue.deletedAt = void 0;
378
+ issue.updatedAt = event.timestamp;
379
+ if (event.source) issue.lastSource = event.source;
332
380
  }
333
381
  break;
334
382
  }
@@ -336,10 +384,13 @@ function computeState(events) {
336
384
  }
337
385
  return issues;
338
386
  }
339
- function getIssues(filters) {
387
+ function getIssues(filters, includeDeleted = false) {
340
388
  const events = readEvents();
341
389
  const state = computeState(events);
342
390
  let issues = Array.from(state.values());
391
+ if (!includeDeleted) {
392
+ issues = issues.filter((i) => !i.deleted);
393
+ }
343
394
  if (filters) {
344
395
  if (filters.status !== void 0) {
345
396
  issues = issues.filter((i) => i.status === filters.status);
@@ -356,10 +407,14 @@ function getIssues(filters) {
356
407
  }
357
408
  return issues;
358
409
  }
359
- function getIssue(id) {
410
+ function getIssue(id, includeDeleted = false) {
360
411
  const events = readEvents();
361
412
  const state = computeState(events);
362
- return state.get(id);
413
+ const issue = state.get(id);
414
+ if (issue && issue.deleted && !includeDeleted) {
415
+ return void 0;
416
+ }
417
+ return issue;
363
418
  }
364
419
  function resolveId(partial) {
365
420
  const events = readEvents();
@@ -402,6 +457,9 @@ function getReady() {
402
457
  const state = computeState(events);
403
458
  const issues = Array.from(state.values());
404
459
  return issues.filter((issue) => {
460
+ if (issue.deleted) {
461
+ return false;
462
+ }
405
463
  if (issue.status === "closed" || issue.status === "pending_verification") {
406
464
  return false;
407
465
  }
@@ -425,6 +483,9 @@ function getBlocked() {
425
483
  const state = computeState(events);
426
484
  const issues = Array.from(state.values());
427
485
  return issues.filter((issue) => {
486
+ if (issue.deleted) {
487
+ return false;
488
+ }
428
489
  if (issue.status === "closed") {
429
490
  return false;
430
491
  }
@@ -588,6 +649,20 @@ function getAncestryChain(issueId, state) {
588
649
  }
589
650
  return chain;
590
651
  }
652
+ function getDescendants(issueId, state) {
653
+ const issueState = state ?? getComputedState();
654
+ const descendants = [];
655
+ function collectDescendants(parentId) {
656
+ for (const issue of issueState.values()) {
657
+ if (issue.parent === parentId && !issue.deleted) {
658
+ descendants.push(issue);
659
+ collectDescendants(issue.id);
660
+ }
661
+ }
662
+ }
663
+ collectDescendants(issueId);
664
+ return descendants;
665
+ }
591
666
 
592
667
  // src/shared/time.ts
593
668
  import { formatDistanceToNow, parseISO } from "date-fns";
@@ -903,7 +978,7 @@ function formatIssueListVerbose(issues, sectionHeader) {
903
978
  lines.push(`${issue.id}: ${issue.title}`);
904
979
  lines.push(` Type: ${formatType(issue.type)} | Priority: P${issue.priority} | Created: ${formatRelativeTime(issue.createdAt)}`);
905
980
  if (ancestry.length > 0) {
906
- const chain = [...ancestry].reverse().map((a) => a.id).join(" > ");
981
+ const chain = [...ancestry].reverse().map((a) => a.title).join(" \u2192 ");
907
982
  lines.push(` Ancestry: ${chain}`);
908
983
  }
909
984
  if (blocking.length > 0) {
@@ -1384,6 +1459,220 @@ function reopenCommand(program2) {
1384
1459
  });
1385
1460
  }
1386
1461
 
1462
+ // src/cli/commands/delete.ts
1463
+ function collectReferenceCleanup(deletedId, deletedIds, state, updates) {
1464
+ for (const [id, issue] of state) {
1465
+ if (deletedIds.has(id)) continue;
1466
+ if (issue.deleted) continue;
1467
+ let entry = updates.get(id);
1468
+ const currentBlockedBy = entry?.blockedBy ?? issue.blockedBy;
1469
+ if (currentBlockedBy.includes(deletedId)) {
1470
+ if (!entry) {
1471
+ entry = {};
1472
+ updates.set(id, entry);
1473
+ }
1474
+ entry.blockedBy = currentBlockedBy.filter((bid) => bid !== deletedId);
1475
+ }
1476
+ const currentRelatedTo = entry?.relatedTo ?? issue.relatedTo;
1477
+ if (currentRelatedTo.includes(deletedId)) {
1478
+ if (!entry) {
1479
+ entry = {};
1480
+ updates.set(id, entry);
1481
+ }
1482
+ entry.relatedTo = currentRelatedTo.filter((rid) => rid !== deletedId);
1483
+ }
1484
+ if (issue.parent === deletedId) {
1485
+ if (!entry) {
1486
+ entry = {};
1487
+ updates.set(id, entry);
1488
+ }
1489
+ entry.parent = "";
1490
+ }
1491
+ }
1492
+ }
1493
+ function emitReferenceCleanup(updates, pebbleDir, timestamp) {
1494
+ for (const [id, data] of updates) {
1495
+ if (Object.keys(data).length > 0) {
1496
+ const updateEvent = {
1497
+ type: "update",
1498
+ issueId: id,
1499
+ timestamp,
1500
+ data
1501
+ };
1502
+ appendEvent(updateEvent, pebbleDir);
1503
+ }
1504
+ }
1505
+ }
1506
+ function deleteCommand(program2) {
1507
+ program2.command("delete <ids...>").description("Delete issues (soft delete). Epics cascade-delete their children.").option("-r, --reason <reason>", "Reason for deleting").action(async (ids, options) => {
1508
+ const pretty = program2.opts().pretty ?? false;
1509
+ try {
1510
+ const pebbleDir = getOrCreatePebbleDir();
1511
+ const state = getComputedState();
1512
+ const allIds = ids.flatMap((id) => id.split(",").map((s) => s.trim()).filter(Boolean));
1513
+ if (allIds.length === 0) {
1514
+ throw new Error("No issue IDs provided");
1515
+ }
1516
+ const results = [];
1517
+ const toDelete = [];
1518
+ const alreadyQueued = /* @__PURE__ */ new Set();
1519
+ for (const id of allIds) {
1520
+ try {
1521
+ const resolvedId = resolveId(id);
1522
+ const issue = state.get(resolvedId);
1523
+ if (!issue) {
1524
+ results.push({ id, success: false, error: `Issue not found: ${id}` });
1525
+ continue;
1526
+ }
1527
+ if (issue.deleted) {
1528
+ results.push({ id: resolvedId, success: false, error: `Issue is already deleted: ${resolvedId}` });
1529
+ continue;
1530
+ }
1531
+ if (!alreadyQueued.has(resolvedId)) {
1532
+ toDelete.push({ id: resolvedId, cascade: false });
1533
+ alreadyQueued.add(resolvedId);
1534
+ }
1535
+ const descendants = getDescendants(resolvedId, state);
1536
+ for (const desc of descendants) {
1537
+ if (!alreadyQueued.has(desc.id) && !desc.deleted) {
1538
+ toDelete.push({ id: desc.id, cascade: true });
1539
+ alreadyQueued.add(desc.id);
1540
+ }
1541
+ }
1542
+ const verifications = getVerifications(resolvedId);
1543
+ for (const v of verifications) {
1544
+ if (!alreadyQueued.has(v.id) && !v.deleted) {
1545
+ toDelete.push({ id: v.id, cascade: true });
1546
+ alreadyQueued.add(v.id);
1547
+ }
1548
+ }
1549
+ } catch (error) {
1550
+ results.push({ id, success: false, error: error.message });
1551
+ }
1552
+ }
1553
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1554
+ const deletedIds = new Set(toDelete.map((d) => d.id));
1555
+ const referenceUpdates = /* @__PURE__ */ new Map();
1556
+ for (const { id } of toDelete) {
1557
+ collectReferenceCleanup(id, deletedIds, state, referenceUpdates);
1558
+ }
1559
+ emitReferenceCleanup(referenceUpdates, pebbleDir, timestamp);
1560
+ for (const { id, cascade } of toDelete) {
1561
+ const issue = state.get(id);
1562
+ if (!issue) continue;
1563
+ const deleteEvent = {
1564
+ type: "delete",
1565
+ issueId: id,
1566
+ timestamp,
1567
+ data: {
1568
+ reason: options.reason,
1569
+ cascade: cascade || void 0,
1570
+ previousStatus: issue.status
1571
+ }
1572
+ };
1573
+ appendEvent(deleteEvent, pebbleDir);
1574
+ results.push({ id, success: true, cascade: cascade || void 0 });
1575
+ }
1576
+ if (pretty) {
1577
+ const primary = results.filter((r) => r.success && !r.cascade);
1578
+ const cascaded = results.filter((r) => r.success && r.cascade);
1579
+ const failed = results.filter((r) => !r.success);
1580
+ for (const result of primary) {
1581
+ console.log(`\u{1F5D1}\uFE0F ${result.id} deleted`);
1582
+ }
1583
+ for (const result of cascaded) {
1584
+ console.log(` \u2514\u2500 ${result.id} deleted (cascade)`);
1585
+ }
1586
+ for (const result of failed) {
1587
+ console.log(`\u2717 ${result.id}: ${result.error}`);
1588
+ }
1589
+ } else {
1590
+ console.log(formatJson({
1591
+ deleted: results.filter((r) => r.success).map((r) => ({ id: r.id, cascade: r.cascade ?? false })),
1592
+ ...results.some((r) => !r.success) && {
1593
+ errors: results.filter((r) => !r.success).map((r) => ({ id: r.id, error: r.error }))
1594
+ }
1595
+ }));
1596
+ }
1597
+ } catch (error) {
1598
+ outputError(error, pretty);
1599
+ }
1600
+ });
1601
+ }
1602
+
1603
+ // src/cli/commands/restore.ts
1604
+ function restoreCommand(program2) {
1605
+ program2.command("restore <ids...>").description("Restore deleted issues").option("-r, --reason <reason>", "Reason for restoring").action(async (ids, options) => {
1606
+ const pretty = program2.opts().pretty ?? false;
1607
+ try {
1608
+ const pebbleDir = getOrCreatePebbleDir();
1609
+ const allIds = ids.flatMap((id) => id.split(",").map((s) => s.trim()).filter(Boolean));
1610
+ if (allIds.length === 0) {
1611
+ throw new Error("No issue IDs provided");
1612
+ }
1613
+ const results = [];
1614
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
1615
+ for (const id of allIds) {
1616
+ try {
1617
+ const resolvedId = resolveId(id);
1618
+ const issue = getIssue(resolvedId, true);
1619
+ if (!issue) {
1620
+ results.push({ id, success: false, error: `Issue not found: ${id}` });
1621
+ continue;
1622
+ }
1623
+ if (!issue.deleted) {
1624
+ results.push({ id: resolvedId, success: false, error: `Issue is not deleted: ${resolvedId}` });
1625
+ continue;
1626
+ }
1627
+ const restoreEvent = {
1628
+ type: "restore",
1629
+ issueId: resolvedId,
1630
+ timestamp,
1631
+ data: {
1632
+ reason: options.reason
1633
+ }
1634
+ };
1635
+ appendEvent(restoreEvent, pebbleDir);
1636
+ results.push({ id: resolvedId, success: true });
1637
+ } catch (error) {
1638
+ results.push({ id, success: false, error: error.message });
1639
+ }
1640
+ }
1641
+ if (allIds.length === 1) {
1642
+ const result = results[0];
1643
+ if (result.success) {
1644
+ if (pretty) {
1645
+ console.log(`\u21A9\uFE0F ${result.id} restored`);
1646
+ } else {
1647
+ console.log(formatJson({ id: result.id, success: true }));
1648
+ }
1649
+ } else {
1650
+ throw new Error(result.error || "Unknown error");
1651
+ }
1652
+ } else {
1653
+ if (pretty) {
1654
+ for (const result of results) {
1655
+ if (result.success) {
1656
+ console.log(`\u21A9\uFE0F ${result.id} restored`);
1657
+ } else {
1658
+ console.log(`\u2717 ${result.id}: ${result.error}`);
1659
+ }
1660
+ }
1661
+ } else {
1662
+ console.log(formatJson({
1663
+ restored: results.filter((r) => r.success).map((r) => r.id),
1664
+ ...results.some((r) => !r.success) && {
1665
+ errors: results.filter((r) => !r.success).map((r) => ({ id: r.id, error: r.error }))
1666
+ }
1667
+ }));
1668
+ }
1669
+ }
1670
+ } catch (error) {
1671
+ outputError(error, pretty);
1672
+ }
1673
+ });
1674
+ }
1675
+
1387
1676
  // src/cli/commands/claim.ts
1388
1677
  function claimCommand(program2) {
1389
1678
  program2.command("claim <ids...>").description("Claim issues (set status to in_progress). Supports multiple IDs.").action(async (ids) => {
@@ -2205,7 +2494,8 @@ function findIssueInSources(issueId, filePaths) {
2205
2494
  return { issue: found, targetFile };
2206
2495
  }
2207
2496
  function appendEventToFile(event, filePath) {
2208
- const line = JSON.stringify(event) + "\n";
2497
+ const eventWithSource = event.source ? event : { ...event, source: getEventSource() };
2498
+ const line = JSON.stringify(eventWithSource) + "\n";
2209
2499
  fs2.appendFileSync(filePath, line, "utf-8");
2210
2500
  }
2211
2501
  function uiCommand(program2) {
@@ -2865,6 +3155,151 @@ data: ${message}
2865
3155
  res.status(500).json({ error: error.message });
2866
3156
  }
2867
3157
  });
3158
+ app.post("/api/issues/:id/delete", (req, res) => {
3159
+ try {
3160
+ let issue;
3161
+ let issueId;
3162
+ let targetFile;
3163
+ if (isMultiWorktree()) {
3164
+ const found = findIssueInSources(req.params.id, issueFiles);
3165
+ if (!found) {
3166
+ res.status(404).json({ error: `Issue not found: ${req.params.id}` });
3167
+ return;
3168
+ }
3169
+ issue = found.issue;
3170
+ issueId = issue.id;
3171
+ targetFile = found.targetFile;
3172
+ } else {
3173
+ const pebbleDir = getOrCreatePebbleDir();
3174
+ issueId = resolveId(req.params.id);
3175
+ const localIssue = getIssue(issueId);
3176
+ if (!localIssue) {
3177
+ res.status(404).json({ error: `Issue not found: ${req.params.id}` });
3178
+ return;
3179
+ }
3180
+ issue = localIssue;
3181
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
3182
+ }
3183
+ if (issue.deleted) {
3184
+ res.status(400).json({ error: "Issue is already deleted" });
3185
+ return;
3186
+ }
3187
+ const { reason } = req.body;
3188
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3189
+ const state = getComputedState();
3190
+ const toDelete = [];
3191
+ const alreadyQueued = /* @__PURE__ */ new Set();
3192
+ toDelete.push({ id: issueId, cascade: false });
3193
+ alreadyQueued.add(issueId);
3194
+ const descendants = getDescendants(issueId, state);
3195
+ for (const desc of descendants) {
3196
+ if (!alreadyQueued.has(desc.id) && !desc.deleted) {
3197
+ toDelete.push({ id: desc.id, cascade: true });
3198
+ alreadyQueued.add(desc.id);
3199
+ }
3200
+ }
3201
+ const verifications = getVerifications(issueId);
3202
+ for (const v of verifications) {
3203
+ if (!alreadyQueued.has(v.id) && !v.deleted) {
3204
+ toDelete.push({ id: v.id, cascade: true });
3205
+ alreadyQueued.add(v.id);
3206
+ }
3207
+ }
3208
+ const cleanupReferences = (deletedId) => {
3209
+ for (const [id, iss] of state) {
3210
+ if (id === deletedId || iss.deleted) continue;
3211
+ const updates = {};
3212
+ if (iss.blockedBy.includes(deletedId)) {
3213
+ updates.blockedBy = iss.blockedBy.filter((bid) => bid !== deletedId);
3214
+ }
3215
+ if (iss.relatedTo.includes(deletedId)) {
3216
+ updates.relatedTo = iss.relatedTo.filter((rid) => rid !== deletedId);
3217
+ }
3218
+ if (iss.parent === deletedId) {
3219
+ updates.parent = "";
3220
+ }
3221
+ if (Object.keys(updates).length > 0) {
3222
+ const updateEvent = {
3223
+ type: "update",
3224
+ issueId: id,
3225
+ timestamp,
3226
+ data: updates
3227
+ };
3228
+ appendEventToFile(updateEvent, targetFile);
3229
+ }
3230
+ }
3231
+ };
3232
+ for (const { id, cascade } of toDelete) {
3233
+ const iss = state.get(id);
3234
+ if (!iss) continue;
3235
+ cleanupReferences(id);
3236
+ const deleteEvent = {
3237
+ type: "delete",
3238
+ issueId: id,
3239
+ timestamp,
3240
+ data: {
3241
+ reason,
3242
+ cascade: cascade || void 0,
3243
+ previousStatus: iss.status
3244
+ }
3245
+ };
3246
+ appendEventToFile(deleteEvent, targetFile);
3247
+ }
3248
+ res.json({
3249
+ deleted: toDelete
3250
+ });
3251
+ } catch (error) {
3252
+ res.status(500).json({ error: error.message });
3253
+ }
3254
+ });
3255
+ app.post("/api/issues/:id/restore", (req, res) => {
3256
+ try {
3257
+ let issue;
3258
+ let issueId;
3259
+ let targetFile;
3260
+ if (isMultiWorktree()) {
3261
+ const found = findIssueInSources(req.params.id, issueFiles);
3262
+ if (!found) {
3263
+ res.status(404).json({ error: `Issue not found: ${req.params.id}` });
3264
+ return;
3265
+ }
3266
+ issue = found.issue;
3267
+ issueId = issue.id;
3268
+ targetFile = found.targetFile;
3269
+ } else {
3270
+ const pebbleDir = getOrCreatePebbleDir();
3271
+ issueId = resolveId(req.params.id);
3272
+ const localIssue = getIssue(issueId);
3273
+ if (!localIssue) {
3274
+ res.status(404).json({ error: `Issue not found: ${req.params.id}` });
3275
+ return;
3276
+ }
3277
+ issue = localIssue;
3278
+ targetFile = path3.join(pebbleDir, "issues.jsonl");
3279
+ }
3280
+ if (!issue.deleted) {
3281
+ res.status(400).json({ error: "Issue is not deleted" });
3282
+ return;
3283
+ }
3284
+ const { reason } = req.body;
3285
+ const timestamp = (/* @__PURE__ */ new Date()).toISOString();
3286
+ const event = {
3287
+ type: "restore",
3288
+ issueId,
3289
+ timestamp,
3290
+ data: { reason }
3291
+ };
3292
+ appendEventToFile(event, targetFile);
3293
+ if (isMultiWorktree()) {
3294
+ const updated = findIssueInSources(issueId, issueFiles);
3295
+ res.json(updated?.issue || { ...issue, deleted: false, deletedAt: void 0, updatedAt: timestamp });
3296
+ } else {
3297
+ res.json(getIssue(issueId));
3298
+ }
3299
+ } catch (error) {
3300
+ res.status(500).json({ error: error.message });
3301
+ }
3302
+ });
2868
3303
  app.post("/api/issues/:id/comments", (req, res) => {
2869
3304
  try {
2870
3305
  let issue;
@@ -3372,6 +3807,35 @@ function countVerifications(epicId) {
3372
3807
  }
3373
3808
  return { total, done };
3374
3809
  }
3810
+ function getIssueComments(issueId) {
3811
+ const events = readEvents();
3812
+ 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 }));
3813
+ }
3814
+ function formatInProgressPretty(issues) {
3815
+ if (issues.length === 0) return "";
3816
+ const lines = [];
3817
+ lines.push(`## In Progress (${issues.length})`);
3818
+ lines.push("");
3819
+ for (const issue of issues) {
3820
+ lines.push(`\u25B6 ${issue.id}: ${issue.title} [${issue.type}]`);
3821
+ lines.push(` Updated: ${formatRelativeTime(issue.updatedAt)}${issue.lastSource ? ` | Source: ${issue.lastSource}` : ""}`);
3822
+ if (issue.parent) {
3823
+ lines.push(` Parent: ${issue.parent.title}`);
3824
+ }
3825
+ if (issue.comments.length > 0) {
3826
+ const recentComments = issue.comments.slice(0, 3);
3827
+ for (const comment of recentComments) {
3828
+ const truncated = comment.text.length > 100 ? comment.text.substring(0, 100) + "..." : comment.text;
3829
+ lines.push(` \u2022 ${formatRelativeTime(comment.timestamp)}: ${truncated.replace(/\n/g, " ")}`);
3830
+ }
3831
+ if (issue.comments.length > 3) {
3832
+ lines.push(` (${issue.comments.length - 3} more comment${issue.comments.length - 3 === 1 ? "" : "s"})`);
3833
+ }
3834
+ }
3835
+ lines.push("");
3836
+ }
3837
+ return lines.join("\n");
3838
+ }
3375
3839
  function formatSummaryPretty(summaries, sectionHeader) {
3376
3840
  if (summaries.length === 0) {
3377
3841
  return "No epics found.";
@@ -3449,6 +3913,28 @@ function summaryCommand(program2) {
3449
3913
  }
3450
3914
  return;
3451
3915
  }
3916
+ const inProgressIssues = getIssues({ status: "in_progress" });
3917
+ inProgressIssues.sort(
3918
+ (a, b) => new Date(b.updatedAt).getTime() - new Date(a.updatedAt).getTime()
3919
+ );
3920
+ const inProgressSummaries = inProgressIssues.map((issue) => {
3921
+ const summary = {
3922
+ id: issue.id,
3923
+ title: issue.title,
3924
+ type: issue.type,
3925
+ createdAt: issue.createdAt,
3926
+ updatedAt: issue.updatedAt,
3927
+ lastSource: issue.lastSource,
3928
+ comments: getIssueComments(issue.id)
3929
+ };
3930
+ if (issue.parent) {
3931
+ const parentIssue = getIssue(issue.parent, true);
3932
+ if (parentIssue) {
3933
+ summary.parent = { id: parentIssue.id, title: parentIssue.title };
3934
+ }
3935
+ }
3936
+ return summary;
3937
+ });
3452
3938
  const openEpics = allEpics.filter((e) => e.status !== "closed");
3453
3939
  const seventyTwoHoursAgo = Date.now() - 72 * 60 * 60 * 1e3;
3454
3940
  const closedEpics = allEpics.filter(
@@ -3466,7 +3952,12 @@ function summaryCommand(program2) {
3466
3952
  const closedSummaries = limitedClosed.map(buildSummary);
3467
3953
  if (pretty) {
3468
3954
  const output = [];
3955
+ const inProgressOutput = formatInProgressPretty(inProgressSummaries);
3956
+ if (inProgressOutput) {
3957
+ output.push(inProgressOutput);
3958
+ }
3469
3959
  if (openSummaries.length > 0) {
3960
+ if (output.length > 0) output.push("");
3470
3961
  output.push(formatSummaryPretty(openSummaries, "Open Epics"));
3471
3962
  }
3472
3963
  if (closedSummaries.length > 0) {
@@ -3474,11 +3965,11 @@ function summaryCommand(program2) {
3474
3965
  output.push(formatSummaryPretty(closedSummaries, "Recently Closed Epics (last 72h)"));
3475
3966
  }
3476
3967
  if (output.length === 0) {
3477
- output.push("No epics found.");
3968
+ output.push("No issues in progress and no epics found.");
3478
3969
  }
3479
3970
  console.log(output.join("\n"));
3480
3971
  } else {
3481
- console.log(formatJson({ open: openSummaries, closed: closedSummaries }));
3972
+ console.log(formatJson({ inProgress: inProgressSummaries, open: openSummaries, closed: closedSummaries }));
3482
3973
  }
3483
3974
  } catch (error) {
3484
3975
  outputError(error, pretty);
@@ -3757,6 +4248,8 @@ createCommand(program);
3757
4248
  updateCommand(program);
3758
4249
  closeCommand(program);
3759
4250
  reopenCommand(program);
4251
+ deleteCommand(program);
4252
+ restoreCommand(program);
3760
4253
  claimCommand(program);
3761
4254
  listCommand(program);
3762
4255
  showCommand(program);