@markmdev/pebble 0.1.2 → 0.1.4

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
@@ -4,7 +4,7 @@
4
4
  import { Command } from "commander";
5
5
 
6
6
  // src/shared/types.ts
7
- var ISSUE_TYPES = ["task", "bug", "epic"];
7
+ var ISSUE_TYPES = ["task", "bug", "epic", "verification"];
8
8
  var PRIORITIES = [0, 1, 2, 3, 4];
9
9
  var STATUSES = ["open", "in_progress", "blocked", "closed"];
10
10
  var EVENT_TYPES = ["create", "update", "close", "reopen", "comment"];
@@ -24,7 +24,8 @@ var STATUS_LABELS = {
24
24
  var TYPE_LABELS = {
25
25
  task: "Task",
26
26
  bug: "Bug",
27
- epic: "Epic"
27
+ epic: "Epic",
28
+ verification: "Verification"
28
29
  };
29
30
 
30
31
  // src/cli/lib/storage.ts
@@ -176,6 +177,7 @@ function computeState(events) {
176
177
  description: createEvent.data.description,
177
178
  parent: createEvent.data.parent,
178
179
  blockedBy: [],
180
+ verifies: createEvent.data.verifies,
179
181
  comments: [],
180
182
  createdAt: event.timestamp,
181
183
  updatedAt: event.timestamp
@@ -316,6 +318,12 @@ function getReady() {
316
318
  return false;
317
319
  }
318
320
  }
321
+ if (issue.type === "verification" && issue.verifies) {
322
+ const target = state.get(issue.verifies);
323
+ if (!target || target.status !== "closed") {
324
+ return false;
325
+ }
326
+ }
319
327
  return true;
320
328
  });
321
329
  }
@@ -402,10 +410,46 @@ function getChildren(epicId) {
402
410
  const state = computeState(events);
403
411
  return Array.from(state.values()).filter((issue) => issue.parent === epicId);
404
412
  }
413
+ function getVerifications(issueId) {
414
+ const events = readEvents();
415
+ const state = computeState(events);
416
+ return Array.from(state.values()).filter((issue) => issue.verifies === issueId);
417
+ }
405
418
  function hasOpenChildren(epicId) {
406
419
  const children = getChildren(epicId);
407
420
  return children.some((child) => child.status !== "closed");
408
421
  }
422
+ function getNewlyUnblocked(closedIssueId) {
423
+ const events = readEvents();
424
+ const state = computeState(events);
425
+ const result = [];
426
+ for (const issue of state.values()) {
427
+ if (issue.status === "closed") continue;
428
+ let isUnblockedByThis = false;
429
+ if (issue.blockedBy.includes(closedIssueId)) {
430
+ const allBlockersClosed = issue.blockedBy.every((blockerId) => {
431
+ const blocker = state.get(blockerId);
432
+ return blocker?.status === "closed";
433
+ });
434
+ if (allBlockersClosed) {
435
+ isUnblockedByThis = true;
436
+ }
437
+ }
438
+ if (issue.type === "verification" && issue.verifies === closedIssueId) {
439
+ const allBlockersClosed = issue.blockedBy.every((blockerId) => {
440
+ const blocker = state.get(blockerId);
441
+ return blocker?.status === "closed";
442
+ });
443
+ if (allBlockersClosed) {
444
+ isUnblockedByThis = true;
445
+ }
446
+ }
447
+ if (isUnblockedByThis) {
448
+ result.push(issue);
449
+ }
450
+ }
451
+ return result;
452
+ }
409
453
 
410
454
  // src/cli/lib/output.ts
411
455
  function formatJson(data) {
@@ -427,7 +471,7 @@ function truncate(str, maxLength) {
427
471
  function pad(str, width) {
428
472
  return str.padEnd(width);
429
473
  }
430
- function formatIssuePretty(issue) {
474
+ function formatIssuePrettyWithBlocking(issue, blocking) {
431
475
  const lines = [];
432
476
  lines.push(`${issue.id} - ${issue.title}`);
433
477
  lines.push("\u2500".repeat(60));
@@ -446,6 +490,10 @@ function formatIssuePretty(issue) {
446
490
  lines.push("");
447
491
  lines.push(`Blocked by: ${issue.blockedBy.join(", ")}`);
448
492
  }
493
+ if (blocking.length > 0) {
494
+ lines.push("");
495
+ lines.push(`Blocking: ${blocking.map((i) => i.id).join(", ")}`);
496
+ }
449
497
  if (issue.comments.length > 0) {
450
498
  lines.push("");
451
499
  lines.push("Comments:");
@@ -526,11 +574,15 @@ function formatErrorPretty(error) {
526
574
  const message = error instanceof Error ? error.message : error;
527
575
  return `Error: ${message}`;
528
576
  }
529
- function outputIssue(issue, pretty) {
577
+ function outputIssueWithBlocking(issue, blocking, pretty) {
530
578
  if (pretty) {
531
- console.log(formatIssuePretty(issue));
579
+ console.log(formatIssuePrettyWithBlocking(issue, blocking));
532
580
  } else {
533
- console.log(formatJson(issue));
581
+ const output = {
582
+ ...issue,
583
+ blocking: blocking.map((i) => i.id)
584
+ };
585
+ console.log(formatJson(output));
534
586
  }
535
587
  }
536
588
  function outputMutationSuccess(id, pretty) {
@@ -558,13 +610,19 @@ function outputError(error, pretty) {
558
610
 
559
611
  // src/cli/commands/create.ts
560
612
  function createCommand(program2) {
561
- 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 epic ID").action(async (title, options) => {
613
+ program2.command("create <title>").description("Create a new issue").option("-t, --type <type>", "Issue type (task, bug, epic, verification)", "task").option("-p, --priority <priority>", "Priority (0-4)", "2").option("-d, --description <desc>", "Description").option("--parent <id>", "Parent epic ID").option("--verifies <id>", "ID of issue this verifies (sets type to verification)").action(async (title, options) => {
562
614
  const pretty = program2.opts().pretty ?? false;
563
615
  try {
564
- const type = options.type;
616
+ let type = options.type;
617
+ if (options.verifies && type !== "verification") {
618
+ type = "verification";
619
+ }
565
620
  if (!ISSUE_TYPES.includes(type)) {
566
621
  throw new Error(`Invalid type: ${type}. Must be one of: ${ISSUE_TYPES.join(", ")}`);
567
622
  }
623
+ if (type === "verification" && !options.verifies) {
624
+ throw new Error("Verification issues require --verifies <id> to specify the issue being verified");
625
+ }
568
626
  const priority = parseInt(options.priority, 10);
569
627
  if (!PRIORITIES.includes(priority)) {
570
628
  throw new Error(`Invalid priority: ${options.priority}. Must be 0-4`);
@@ -585,6 +643,14 @@ function createCommand(program2) {
585
643
  throw new Error(`Cannot add children to closed epic: ${parentId}`);
586
644
  }
587
645
  }
646
+ let verifiesId;
647
+ if (options.verifies) {
648
+ verifiesId = resolveId(options.verifies);
649
+ const target = getIssue(verifiesId);
650
+ if (!target) {
651
+ throw new Error(`Target issue not found: ${options.verifies}`);
652
+ }
653
+ }
588
654
  const id = generateId(config.prefix);
589
655
  const timestamp = (/* @__PURE__ */ new Date()).toISOString();
590
656
  const event = {
@@ -596,7 +662,8 @@ function createCommand(program2) {
596
662
  type,
597
663
  priority,
598
664
  description: options.description,
599
- parent: parentId
665
+ parent: parentId,
666
+ verifies: verifiesId
600
667
  }
601
668
  };
602
669
  appendEvent(event, pebbleDir);
@@ -746,7 +813,12 @@ function closeCommand(program2) {
746
813
  }
747
814
  };
748
815
  appendEvent(closeEvent, pebbleDir);
749
- results.push({ id: resolvedId, success: true });
816
+ const unblocked = getNewlyUnblocked(resolvedId);
817
+ results.push({
818
+ id: resolvedId,
819
+ success: true,
820
+ unblocked: unblocked.length > 0 ? unblocked.map((i) => ({ id: i.id, title: i.title })) : void 0
821
+ });
750
822
  } catch (error) {
751
823
  results.push({ id, success: false, error: error.message });
752
824
  }
@@ -754,7 +826,22 @@ function closeCommand(program2) {
754
826
  if (allIds.length === 1) {
755
827
  const result = results[0];
756
828
  if (result.success) {
757
- outputMutationSuccess(result.id, pretty);
829
+ if (pretty) {
830
+ console.log(`\u2713 ${result.id}`);
831
+ if (result.unblocked && result.unblocked.length > 0) {
832
+ console.log(`
833
+ Unblocked:`);
834
+ for (const u of result.unblocked) {
835
+ console.log(` \u2192 ${u.id} - ${u.title}`);
836
+ }
837
+ }
838
+ } else {
839
+ console.log(formatJson({
840
+ id: result.id,
841
+ success: true,
842
+ ...result.unblocked && { unblocked: result.unblocked }
843
+ }));
844
+ }
758
845
  } else {
759
846
  throw new Error(result.error || "Unknown error");
760
847
  }
@@ -763,6 +850,11 @@ function closeCommand(program2) {
763
850
  for (const result of results) {
764
851
  if (result.success) {
765
852
  console.log(`\u2713 ${result.id}`);
853
+ if (result.unblocked && result.unblocked.length > 0) {
854
+ for (const u of result.unblocked) {
855
+ console.log(` \u2192 ${u.id} - ${u.title}`);
856
+ }
857
+ }
766
858
  } else {
767
859
  console.log(`\u2717 ${result.id}: ${result.error}`);
768
860
  }
@@ -771,7 +863,8 @@ function closeCommand(program2) {
771
863
  console.log(formatJson(results.map((r) => ({
772
864
  id: r.id,
773
865
  success: r.success,
774
- ...r.error && { error: r.error }
866
+ ...r.error && { error: r.error },
867
+ ...r.unblocked && { unblocked: r.unblocked }
775
868
  }))));
776
869
  }
777
870
  }
@@ -932,7 +1025,8 @@ function showCommand(program2) {
932
1025
  if (!issue) {
933
1026
  throw new Error(`Issue not found: ${id}`);
934
1027
  }
935
- outputIssue(issue, pretty);
1028
+ const blocking = getBlocking(resolvedId);
1029
+ outputIssueWithBlocking(issue, blocking, pretty);
936
1030
  } catch (error) {
937
1031
  outputError(error, pretty);
938
1032
  }
@@ -2374,6 +2468,9 @@ function formatSummaryPretty(summaries) {
2374
2468
  const { children } = summary;
2375
2469
  const progress = children.total > 0 ? `(${children.done}/${children.total} done)` : "(no children)";
2376
2470
  lines.push(`${summary.id} ${summary.title} ${progress}`);
2471
+ if (summary.parent) {
2472
+ lines.push(` Parent: ${summary.parent.id} "${summary.parent.title}"`);
2473
+ }
2377
2474
  if (summary.description) {
2378
2475
  const desc = summary.description.split("\n")[0];
2379
2476
  const truncated = desc.length > 60 ? desc.slice(0, 57) + "..." : desc;
@@ -2405,13 +2502,25 @@ function summaryCommand(program2) {
2405
2502
  if (limit > 0) {
2406
2503
  epics = epics.slice(0, limit);
2407
2504
  }
2408
- const summaries = epics.map((epic) => ({
2409
- id: epic.id,
2410
- title: epic.title,
2411
- description: epic.description,
2412
- status: epic.status,
2413
- children: countChildren(epic.id)
2414
- }));
2505
+ const summaries = epics.map((epic) => {
2506
+ const summary = {
2507
+ id: epic.id,
2508
+ title: epic.title,
2509
+ description: epic.description,
2510
+ status: epic.status,
2511
+ children: countChildren(epic.id)
2512
+ };
2513
+ if (epic.parent) {
2514
+ const parentIssue = getIssue(epic.parent);
2515
+ if (parentIssue) {
2516
+ summary.parent = {
2517
+ id: parentIssue.id,
2518
+ title: parentIssue.title
2519
+ };
2520
+ }
2521
+ }
2522
+ return summary;
2523
+ });
2415
2524
  if (pretty) {
2416
2525
  console.log(formatSummaryPretty(summaries));
2417
2526
  } else {
@@ -2469,7 +2578,7 @@ function formatHistoryPretty(entries) {
2469
2578
  return lines.join("\n");
2470
2579
  }
2471
2580
  function historyCommand(program2) {
2472
- program2.command("history").description("Show recent activity log").option("--limit <n>", "Max events to return", "20").option("--type <type>", "Filter by event type (create/close/reopen/update/comment)").option("--since <duration>", 'Only show events since (e.g., "7d", "24h")').action(async (options) => {
2581
+ program2.command("history").description("Show recent activity log").option("--limit <n>", "Max events to return", "20").option("--type <types>", "Filter by event type(s), comma-separated (create,close,reopen,update,comment)").option("--since <duration>", 'Only show events since (e.g., "7d", "24h")').action(async (options) => {
2473
2582
  const pretty = program2.opts().pretty ?? false;
2474
2583
  try {
2475
2584
  getOrCreatePebbleDir();
@@ -2477,11 +2586,13 @@ function historyCommand(program2) {
2477
2586
  const state = computeState(events);
2478
2587
  let filteredEvents = events;
2479
2588
  if (options.type !== void 0) {
2480
- const eventType = options.type;
2481
- if (!EVENT_TYPES.includes(eventType)) {
2482
- throw new Error(`Invalid event type: ${eventType}. Must be one of: ${EVENT_TYPES.join(", ")}`);
2589
+ const types = options.type.split(",").map((t) => t.trim());
2590
+ for (const t of types) {
2591
+ if (!EVENT_TYPES.includes(t)) {
2592
+ throw new Error(`Invalid event type: ${t}. Must be one of: ${EVENT_TYPES.join(", ")}`);
2593
+ }
2483
2594
  }
2484
- filteredEvents = filteredEvents.filter((e) => e.type === eventType);
2595
+ filteredEvents = filteredEvents.filter((e) => types.includes(e.type));
2485
2596
  }
2486
2597
  if (options.since !== void 0) {
2487
2598
  const sinceMs = parseDuration(options.since);
@@ -2594,10 +2705,51 @@ function searchCommand(program2) {
2594
2705
  });
2595
2706
  }
2596
2707
 
2708
+ // src/cli/commands/verifications.ts
2709
+ function verificationsCommand(program2) {
2710
+ program2.command("verifications <id>").description("List verification issues for a given issue").action(async (id) => {
2711
+ const pretty = program2.opts().pretty ?? false;
2712
+ try {
2713
+ getOrCreatePebbleDir();
2714
+ const resolvedId = resolveId(id);
2715
+ const issue = getIssue(resolvedId);
2716
+ if (!issue) {
2717
+ throw new Error(`Issue not found: ${id}`);
2718
+ }
2719
+ const verifications = getVerifications(resolvedId);
2720
+ if (pretty) {
2721
+ if (verifications.length === 0) {
2722
+ console.log(`No verifications for ${resolvedId}`);
2723
+ } else {
2724
+ console.log(`Verifications for ${resolvedId} "${issue.title}"`);
2725
+ console.log("\u2500".repeat(50));
2726
+ for (const v of verifications) {
2727
+ const status = v.status === "closed" ? "\u2713" : "\u25CB";
2728
+ console.log(` ${status} ${v.id} - ${v.title}`);
2729
+ }
2730
+ console.log("");
2731
+ console.log(`Total: ${verifications.length} verification(s)`);
2732
+ }
2733
+ } else {
2734
+ console.log(formatJson({
2735
+ issueId: resolvedId,
2736
+ verifications: verifications.map((v) => ({
2737
+ id: v.id,
2738
+ title: v.title,
2739
+ status: v.status
2740
+ }))
2741
+ }));
2742
+ }
2743
+ } catch (error) {
2744
+ outputError(error, pretty);
2745
+ }
2746
+ });
2747
+ }
2748
+
2597
2749
  // src/cli/index.ts
2598
2750
  var program = new Command();
2599
2751
  program.name("pebble").description("A lightweight JSONL-based issue tracker").version("0.1.0");
2600
- program.option("--pretty", "Human-readable output (default: JSON)");
2752
+ program.option("-P, --pretty", "Human-readable output (default: JSON)");
2601
2753
  createCommand(program);
2602
2754
  updateCommand(program);
2603
2755
  closeCommand(program);
@@ -2616,5 +2768,6 @@ mergeCommand(program);
2616
2768
  summaryCommand(program);
2617
2769
  historyCommand(program);
2618
2770
  searchCommand(program);
2771
+ verificationsCommand(program);
2619
2772
  program.parse();
2620
2773
  //# sourceMappingURL=index.js.map