@markmdev/pebble 0.1.10 → 0.1.12

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
@@ -1137,11 +1137,31 @@ function closeCommand(program2) {
1137
1137
  };
1138
1138
  appendEvent(closeEvent, pebbleDir);
1139
1139
  const unblocked = getNewlyUnblocked(resolvedId);
1140
+ let autoClosed;
1141
+ if (issue.verifies) {
1142
+ const targetIssue = getIssue(issue.verifies);
1143
+ if (targetIssue && targetIssue.status === "pending_verification") {
1144
+ const remainingVerifications = getVerifications(issue.verifies).filter((v) => v.status !== "closed");
1145
+ if (remainingVerifications.length === 0) {
1146
+ const autoCloseEvent = {
1147
+ type: "close",
1148
+ issueId: issue.verifies,
1149
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
1150
+ data: {
1151
+ reason: "All verifications completed"
1152
+ }
1153
+ };
1154
+ appendEvent(autoCloseEvent, pebbleDir);
1155
+ autoClosed = { id: targetIssue.id, title: targetIssue.title };
1156
+ }
1157
+ }
1158
+ }
1140
1159
  results.push({
1141
1160
  id: resolvedId,
1142
1161
  success: true,
1143
1162
  status: "closed",
1144
- unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0
1163
+ unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0,
1164
+ autoClosed
1145
1165
  });
1146
1166
  } catch (error) {
1147
1167
  results.push({ id, success: false, error: error.message });
@@ -1169,6 +1189,10 @@ Unblocked:`);
1169
1189
  console.log(` \u2192 ${u.id} - ${u.title}`);
1170
1190
  }
1171
1191
  }
1192
+ if (result.autoClosed) {
1193
+ console.log(`
1194
+ \u2713 ${result.autoClosed.id} auto-closed (all verifications complete)`);
1195
+ }
1172
1196
  }
1173
1197
  } else {
1174
1198
  console.log(formatJson({
@@ -1177,7 +1201,8 @@ Unblocked:`);
1177
1201
  status: result.status,
1178
1202
  ...result.pendingVerifications && { pendingVerifications: result.pendingVerifications },
1179
1203
  ...result.pendingVerifications && { hint: `pb verifications ${result.id}` },
1180
- ...result.unblocked && { unblocked: result.unblocked }
1204
+ ...result.unblocked && { unblocked: result.unblocked },
1205
+ ...result.autoClosed && { autoClosed: result.autoClosed }
1181
1206
  }));
1182
1207
  }
1183
1208
  } else {
@@ -1200,6 +1225,9 @@ Unblocked:`);
1200
1225
  console.log(` \u2192 ${u.id} - ${u.title}`);
1201
1226
  }
1202
1227
  }
1228
+ if (result.autoClosed) {
1229
+ console.log(` \u2713 ${result.autoClosed.id} auto-closed (all verifications complete)`);
1230
+ }
1203
1231
  }
1204
1232
  } else {
1205
1233
  console.log(`\u2717 ${result.id}: ${result.error}`);
@@ -1213,7 +1241,8 @@ Unblocked:`);
1213
1241
  ...r.error && { error: r.error },
1214
1242
  ...r.pendingVerifications && { pendingVerifications: r.pendingVerifications },
1215
1243
  ...r.pendingVerifications && { hint: `pb verifications ${r.id}` },
1216
- ...r.unblocked && { unblocked: r.unblocked }
1244
+ ...r.unblocked && { unblocked: r.unblocked },
1245
+ ...r.autoClosed && { autoClosed: r.autoClosed }
1217
1246
  }))));
1218
1247
  }
1219
1248
  }
@@ -2651,11 +2680,49 @@ data: ${message}
2651
2680
  data: { reason }
2652
2681
  };
2653
2682
  appendEventToFile(event, targetFile);
2683
+ let autoClosed;
2684
+ if (issue.verifies) {
2685
+ let targetIssue;
2686
+ let targetVerifications = [];
2687
+ if (isMultiWorktree()) {
2688
+ const found = findIssueInSources(issue.verifies, issueFiles);
2689
+ if (found) {
2690
+ targetIssue = found.issue;
2691
+ const allIssues = mergeIssuesFromFiles(issueFiles);
2692
+ targetVerifications = allIssues.filter(
2693
+ (i) => i.verifies === issue.verifies && i.status !== "closed"
2694
+ );
2695
+ }
2696
+ } else {
2697
+ targetIssue = getIssue(issue.verifies);
2698
+ targetVerifications = getVerifications(issue.verifies).filter((v) => v.status !== "closed");
2699
+ }
2700
+ if (targetIssue && targetIssue.status === "pending_verification" && targetVerifications.length === 0) {
2701
+ const autoCloseEvent = {
2702
+ type: "close",
2703
+ issueId: issue.verifies,
2704
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
2705
+ data: { reason: "All verifications completed" }
2706
+ };
2707
+ if (isMultiWorktree()) {
2708
+ const targetFound = findIssueInSources(issue.verifies, issueFiles);
2709
+ if (targetFound) {
2710
+ appendEventToFile(autoCloseEvent, targetFound.targetFile);
2711
+ }
2712
+ } else {
2713
+ const pebbleDir = getOrCreatePebbleDir();
2714
+ appendEventToFile(autoCloseEvent, path2.join(pebbleDir, "issues.jsonl"));
2715
+ }
2716
+ autoClosed = { id: targetIssue.id, title: targetIssue.title };
2717
+ }
2718
+ }
2654
2719
  if (isMultiWorktree()) {
2655
2720
  const updated = findIssueInSources(issueId, issueFiles);
2656
- res.json(updated?.issue || { ...issue, status: "closed", updatedAt: timestamp });
2721
+ const result = updated?.issue || { ...issue, status: "closed", updatedAt: timestamp };
2722
+ res.json(autoClosed ? { ...result, _autoClosed: autoClosed } : result);
2657
2723
  } else {
2658
- res.json(getIssue(issueId));
2724
+ const result = getIssue(issueId);
2725
+ res.json(autoClosed ? { ...result, _autoClosed: autoClosed } : result);
2659
2726
  }
2660
2727
  } catch (error) {
2661
2728
  res.status(500).json({ error: error.message });
@@ -3245,7 +3312,7 @@ function formatSummaryPretty(summaries, sectionHeader) {
3245
3312
  return lines.join("\n");
3246
3313
  }
3247
3314
  function summaryCommand(program2) {
3248
- program2.command("summary").description("Show epic summary with child completion status").option("--status <status>", "Filter epics by status (default: open)").option("--limit <n>", "Max epics to return", "10").option("--include-closed", "Include closed epics").action(async (options) => {
3315
+ program2.command("summary").description("Show epic summary with child completion status").option("--status <status>", "Filter epics by specific status").option("--limit <n>", "Max epics to return per section", "10").action(async (options) => {
3249
3316
  const pretty = program2.opts().pretty ?? false;
3250
3317
  try {
3251
3318
  getOrCreatePebbleDir();
@@ -3273,60 +3340,56 @@ function summaryCommand(program2) {
3273
3340
  return summary;
3274
3341
  };
3275
3342
  const limit = parseInt(options.limit, 10);
3276
- if (options.includeClosed) {
3277
- const openEpics = allEpics.filter((e) => e.status !== "closed");
3278
- const seventyTwoHoursAgo = Date.now() - 72 * 60 * 60 * 1e3;
3279
- const closedEpics = allEpics.filter(
3280
- (e) => e.status === "closed" && new Date(e.updatedAt).getTime() > seventyTwoHoursAgo
3281
- );
3282
- openEpics.sort(
3283
- (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
3284
- );
3285
- closedEpics.sort(
3343
+ if (options.status !== void 0) {
3344
+ const status = options.status;
3345
+ if (!STATUSES.includes(status)) {
3346
+ throw new Error(`Invalid status: ${status}. Must be one of: ${STATUSES.join(", ")}`);
3347
+ }
3348
+ let epics = allEpics.filter((e) => e.status === status);
3349
+ epics.sort(
3286
3350
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
3287
3351
  );
3288
- const limitedOpen = limit > 0 ? openEpics.slice(0, limit) : openEpics;
3289
- const limitedClosed = limit > 0 ? closedEpics.slice(0, limit) : closedEpics;
3290
- const openSummaries = limitedOpen.map(buildSummary);
3291
- const closedSummaries = limitedClosed.map(buildSummary);
3352
+ if (limit > 0) {
3353
+ epics = epics.slice(0, limit);
3354
+ }
3355
+ const summaries = epics.map(buildSummary);
3292
3356
  if (pretty) {
3293
- const output = [];
3294
- if (openSummaries.length > 0) {
3295
- output.push(formatSummaryPretty(openSummaries, "Open Epics"));
3296
- }
3297
- if (closedSummaries.length > 0) {
3298
- if (output.length > 0) output.push("");
3299
- output.push(formatSummaryPretty(closedSummaries, "Recently Closed Epics (last 72h)"));
3300
- }
3301
- console.log(output.join("\n"));
3357
+ console.log(formatSummaryPretty(summaries, `${STATUS_LABELS[status]} Epics`));
3302
3358
  } else {
3303
- console.log(formatJson({ open: openSummaries, closed: closedSummaries }));
3359
+ console.log(formatJson(summaries));
3304
3360
  }
3305
3361
  return;
3306
3362
  }
3307
- let epics = allEpics;
3308
- let sectionHeader = "Open Epics";
3309
- if (options.status !== void 0) {
3310
- const status = options.status;
3311
- if (!STATUSES.includes(status)) {
3312
- throw new Error(`Invalid status: ${status}. Must be one of: ${STATUSES.join(", ")}`);
3313
- }
3314
- epics = epics.filter((e) => e.status === status);
3315
- sectionHeader = `${STATUS_LABELS[status]} Epics`;
3316
- } else {
3317
- epics = epics.filter((e) => e.status !== "closed");
3318
- }
3319
- epics.sort(
3363
+ const openEpics = allEpics.filter((e) => e.status !== "closed");
3364
+ const seventyTwoHoursAgo = Date.now() - 72 * 60 * 60 * 1e3;
3365
+ const closedEpics = allEpics.filter(
3366
+ (e) => e.status === "closed" && new Date(e.updatedAt).getTime() > seventyTwoHoursAgo
3367
+ );
3368
+ openEpics.sort(
3320
3369
  (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
3321
3370
  );
3322
- if (limit > 0) {
3323
- epics = epics.slice(0, limit);
3324
- }
3325
- const summaries = epics.map(buildSummary);
3371
+ closedEpics.sort(
3372
+ (a, b) => new Date(b.createdAt).getTime() - new Date(a.createdAt).getTime()
3373
+ );
3374
+ const limitedOpen = limit > 0 ? openEpics.slice(0, limit) : openEpics;
3375
+ const limitedClosed = limit > 0 ? closedEpics.slice(0, limit) : closedEpics;
3376
+ const openSummaries = limitedOpen.map(buildSummary);
3377
+ const closedSummaries = limitedClosed.map(buildSummary);
3326
3378
  if (pretty) {
3327
- console.log(formatSummaryPretty(summaries, sectionHeader));
3379
+ const output = [];
3380
+ if (openSummaries.length > 0) {
3381
+ output.push(formatSummaryPretty(openSummaries, "Open Epics"));
3382
+ }
3383
+ if (closedSummaries.length > 0) {
3384
+ if (output.length > 0) output.push("");
3385
+ output.push(formatSummaryPretty(closedSummaries, "Recently Closed Epics (last 72h)"));
3386
+ }
3387
+ if (output.length === 0) {
3388
+ output.push("No epics found.");
3389
+ }
3390
+ console.log(output.join("\n"));
3328
3391
  } else {
3329
- console.log(formatJson(summaries));
3392
+ console.log(formatJson({ open: openSummaries, closed: closedSummaries }));
3330
3393
  }
3331
3394
  } catch (error) {
3332
3395
  outputError(error, pretty);