@markmdev/pebble 0.1.5 → 0.1.6
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 +169 -12
- package/dist/cli/index.js.map +1 -1
- package/dist/ui/assets/index-B9ZwNZIm.js +317 -0
- package/dist/ui/assets/index-C8VlXm0M.css +1 -0
- package/dist/ui/index.html +2 -2
- package/package.json +1 -1
- package/dist/ui/assets/index-BwKh2bk7.js +0 -343
- package/dist/ui/assets/index-R8Oj47ay.css +0 -1
package/dist/cli/index.js
CHANGED
|
@@ -149,7 +149,7 @@ function getConfigPath(pebbleDir) {
|
|
|
149
149
|
function getConfig(pebbleDir) {
|
|
150
150
|
const configPath = getConfigPath(pebbleDir);
|
|
151
151
|
if (!fs.existsSync(configPath)) {
|
|
152
|
-
throw new Error("
|
|
152
|
+
throw new Error("No .pebble directory found. Run 'pb init' to initialize.");
|
|
153
153
|
}
|
|
154
154
|
const content = fs.readFileSync(configPath, "utf-8");
|
|
155
155
|
return JSON.parse(content);
|
|
@@ -211,7 +211,7 @@ function computeState(events) {
|
|
|
211
211
|
issue.description = updateEvent.data.description;
|
|
212
212
|
}
|
|
213
213
|
if (updateEvent.data.parent !== void 0) {
|
|
214
|
-
issue.parent = updateEvent.data.parent;
|
|
214
|
+
issue.parent = updateEvent.data.parent || void 0;
|
|
215
215
|
}
|
|
216
216
|
if (updateEvent.data.blockedBy !== void 0) {
|
|
217
217
|
issue.blockedBy = updateEvent.data.blockedBy;
|
|
@@ -613,10 +613,48 @@ function outputError(error, pretty) {
|
|
|
613
613
|
}
|
|
614
614
|
process.exit(1);
|
|
615
615
|
}
|
|
616
|
+
function formatIssueListVerbose(issues) {
|
|
617
|
+
if (issues.length === 0) {
|
|
618
|
+
return "No issues found.";
|
|
619
|
+
}
|
|
620
|
+
const lines = [];
|
|
621
|
+
for (const info of issues) {
|
|
622
|
+
const { issue, blocking, children, verifications, blockers } = info;
|
|
623
|
+
lines.push(`${issue.id} - ${issue.title}`);
|
|
624
|
+
lines.push("\u2500".repeat(60));
|
|
625
|
+
lines.push(` Type: ${formatType(issue.type)}`);
|
|
626
|
+
lines.push(` Priority: P${issue.priority}`);
|
|
627
|
+
lines.push(` Status: ${issue.status}`);
|
|
628
|
+
lines.push(` Parent: ${issue.parent || "-"}`);
|
|
629
|
+
lines.push(` Children: ${issue.type === "epic" ? children : "-"}`);
|
|
630
|
+
lines.push(` Blocking: ${blocking.length > 0 ? blocking.join(", ") : "[]"}`);
|
|
631
|
+
lines.push(` Verifications: ${verifications}`);
|
|
632
|
+
if (blockers && blockers.length > 0) {
|
|
633
|
+
lines.push(` Blocked by: ${blockers.join(", ")}`);
|
|
634
|
+
}
|
|
635
|
+
lines.push("");
|
|
636
|
+
}
|
|
637
|
+
lines.push(`Total: ${issues.length} issue(s)`);
|
|
638
|
+
return lines.join("\n");
|
|
639
|
+
}
|
|
640
|
+
function outputIssueListVerbose(issues, pretty) {
|
|
641
|
+
if (pretty) {
|
|
642
|
+
console.log(formatIssueListVerbose(issues));
|
|
643
|
+
} else {
|
|
644
|
+
const output = issues.map(({ issue, blocking, children, verifications, blockers }) => ({
|
|
645
|
+
...issue,
|
|
646
|
+
blocking,
|
|
647
|
+
childrenCount: issue.type === "epic" ? children : void 0,
|
|
648
|
+
verificationsCount: verifications,
|
|
649
|
+
...blockers && { openBlockers: blockers }
|
|
650
|
+
}));
|
|
651
|
+
console.log(formatJson(output));
|
|
652
|
+
}
|
|
653
|
+
}
|
|
616
654
|
|
|
617
655
|
// src/cli/commands/create.ts
|
|
618
656
|
function createCommand(program2) {
|
|
619
|
-
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) => {
|
|
657
|
+
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)").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").action(async (title, options) => {
|
|
620
658
|
const pretty = program2.opts().pretty ?? false;
|
|
621
659
|
try {
|
|
622
660
|
let type = options.type;
|
|
@@ -657,6 +695,33 @@ function createCommand(program2) {
|
|
|
657
695
|
throw new Error(`Target issue not found: ${options.verifies}`);
|
|
658
696
|
}
|
|
659
697
|
}
|
|
698
|
+
const blockedByIds = [];
|
|
699
|
+
if (options.blockedBy) {
|
|
700
|
+
const ids = options.blockedBy.split(",").map((s) => s.trim()).filter(Boolean);
|
|
701
|
+
for (const rawId of ids) {
|
|
702
|
+
const resolvedId = resolveId(rawId);
|
|
703
|
+
const blocker = getIssue(resolvedId);
|
|
704
|
+
if (!blocker) {
|
|
705
|
+
throw new Error(`Blocker issue not found: ${rawId}`);
|
|
706
|
+
}
|
|
707
|
+
if (blocker.status === "closed") {
|
|
708
|
+
throw new Error(`Cannot be blocked by closed issue: ${resolvedId}`);
|
|
709
|
+
}
|
|
710
|
+
blockedByIds.push(resolvedId);
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
const blocksIds = [];
|
|
714
|
+
if (options.blocks) {
|
|
715
|
+
const ids = options.blocks.split(",").map((s) => s.trim()).filter(Boolean);
|
|
716
|
+
for (const rawId of ids) {
|
|
717
|
+
const resolvedId = resolveId(rawId);
|
|
718
|
+
const blocked = getIssue(resolvedId);
|
|
719
|
+
if (!blocked) {
|
|
720
|
+
throw new Error(`Issue to block not found: ${rawId}`);
|
|
721
|
+
}
|
|
722
|
+
blocksIds.push(resolvedId);
|
|
723
|
+
}
|
|
724
|
+
}
|
|
660
725
|
const id = generateId(config.prefix);
|
|
661
726
|
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
662
727
|
const event = {
|
|
@@ -673,6 +738,26 @@ function createCommand(program2) {
|
|
|
673
738
|
}
|
|
674
739
|
};
|
|
675
740
|
appendEvent(event, pebbleDir);
|
|
741
|
+
if (blockedByIds.length > 0) {
|
|
742
|
+
const depEvent = {
|
|
743
|
+
type: "update",
|
|
744
|
+
issueId: id,
|
|
745
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
746
|
+
data: { blockedBy: blockedByIds }
|
|
747
|
+
};
|
|
748
|
+
appendEvent(depEvent, pebbleDir);
|
|
749
|
+
}
|
|
750
|
+
for (const targetId of blocksIds) {
|
|
751
|
+
const target = getIssue(targetId);
|
|
752
|
+
const existingBlockers = target?.blockedBy || [];
|
|
753
|
+
const depEvent = {
|
|
754
|
+
type: "update",
|
|
755
|
+
issueId: targetId,
|
|
756
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
757
|
+
data: { blockedBy: [...existingBlockers, id] }
|
|
758
|
+
};
|
|
759
|
+
appendEvent(depEvent, pebbleDir);
|
|
760
|
+
}
|
|
676
761
|
outputMutationSuccess(id, pretty);
|
|
677
762
|
} catch (error) {
|
|
678
763
|
outputError(error, pretty);
|
|
@@ -682,7 +767,7 @@ function createCommand(program2) {
|
|
|
682
767
|
|
|
683
768
|
// src/cli/commands/update.ts
|
|
684
769
|
function updateCommand(program2) {
|
|
685
|
-
program2.command("update <ids...>").description("Update issues. Supports multiple IDs.").option("--status <status>", "Status (open, in_progress, blocked, closed)").option("--priority <priority>", "Priority (0-4)").option("--title <title>", "Title").option("--description <desc>", "Description").action(async (ids, options) => {
|
|
770
|
+
program2.command("update <ids...>").description("Update issues. Supports multiple IDs.").option("--status <status>", "Status (open, in_progress, blocked, closed)").option("--priority <priority>", "Priority (0-4)").option("--title <title>", "Title").option("--description <desc>", "Description").option("--parent <id>", 'Parent epic ID (use "null" to remove parent)').action(async (ids, options) => {
|
|
686
771
|
const pretty = program2.opts().pretty ?? false;
|
|
687
772
|
try {
|
|
688
773
|
const pebbleDir = getOrCreatePebbleDir();
|
|
@@ -716,8 +801,27 @@ function updateCommand(program2) {
|
|
|
716
801
|
data.description = options.description;
|
|
717
802
|
hasChanges = true;
|
|
718
803
|
}
|
|
804
|
+
if (options.parent !== void 0) {
|
|
805
|
+
if (options.parent.toLowerCase() === "null") {
|
|
806
|
+
data.parent = "";
|
|
807
|
+
} else {
|
|
808
|
+
const parentId = resolveId(options.parent);
|
|
809
|
+
const parentIssue = getIssue(parentId);
|
|
810
|
+
if (!parentIssue) {
|
|
811
|
+
throw new Error(`Parent issue not found: ${options.parent}`);
|
|
812
|
+
}
|
|
813
|
+
if (parentIssue.type !== "epic") {
|
|
814
|
+
throw new Error(`Parent must be an epic. ${parentId} is a ${parentIssue.type}`);
|
|
815
|
+
}
|
|
816
|
+
if (parentIssue.status === "closed") {
|
|
817
|
+
throw new Error(`Cannot set parent to closed epic: ${parentId}`);
|
|
818
|
+
}
|
|
819
|
+
data.parent = parentId;
|
|
820
|
+
}
|
|
821
|
+
hasChanges = true;
|
|
822
|
+
}
|
|
719
823
|
if (!hasChanges) {
|
|
720
|
-
throw new Error("No changes specified. Use --status, --priority, --title, or --
|
|
824
|
+
throw new Error("No changes specified. Use --status, --priority, --title, --description, or --parent");
|
|
721
825
|
}
|
|
722
826
|
const results = [];
|
|
723
827
|
for (const id of allIds) {
|
|
@@ -1041,12 +1145,22 @@ function showCommand(program2) {
|
|
|
1041
1145
|
|
|
1042
1146
|
// src/cli/commands/ready.ts
|
|
1043
1147
|
function readyCommand(program2) {
|
|
1044
|
-
program2.command("ready").description("Show issues ready for work (no open blockers)").action(async () => {
|
|
1148
|
+
program2.command("ready").description("Show issues ready for work (no open blockers)").option("-v, --verbose", "Show expanded details (parent, children, blocking, verifications)").action(async (options) => {
|
|
1045
1149
|
const pretty = program2.opts().pretty ?? false;
|
|
1046
1150
|
try {
|
|
1047
1151
|
getOrCreatePebbleDir();
|
|
1048
1152
|
const issues = getReady();
|
|
1049
|
-
|
|
1153
|
+
if (options.verbose) {
|
|
1154
|
+
const verboseIssues = issues.map((issue) => ({
|
|
1155
|
+
issue,
|
|
1156
|
+
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1157
|
+
children: getChildren(issue.id).length,
|
|
1158
|
+
verifications: getVerifications(issue.id).length
|
|
1159
|
+
}));
|
|
1160
|
+
outputIssueListVerbose(verboseIssues, pretty);
|
|
1161
|
+
} else {
|
|
1162
|
+
outputIssueList(issues, pretty);
|
|
1163
|
+
}
|
|
1050
1164
|
} catch (error) {
|
|
1051
1165
|
outputError(error, pretty);
|
|
1052
1166
|
}
|
|
@@ -1055,12 +1169,27 @@ function readyCommand(program2) {
|
|
|
1055
1169
|
|
|
1056
1170
|
// src/cli/commands/blocked.ts
|
|
1057
1171
|
function blockedCommand(program2) {
|
|
1058
|
-
program2.command("blocked").description("Show blocked issues (have open blockers)").action(async () => {
|
|
1172
|
+
program2.command("blocked").description("Show blocked issues (have open blockers)").option("-v, --verbose", "Show expanded details including WHY each issue is blocked").action(async (options) => {
|
|
1059
1173
|
const pretty = program2.opts().pretty ?? false;
|
|
1060
1174
|
try {
|
|
1061
1175
|
getOrCreatePebbleDir();
|
|
1062
1176
|
const issues = getBlocked();
|
|
1063
|
-
|
|
1177
|
+
if (options.verbose) {
|
|
1178
|
+
const verboseIssues = issues.map((issue) => {
|
|
1179
|
+
const allBlockers = getBlockers(issue.id);
|
|
1180
|
+
const openBlockers = allBlockers.filter((b) => b.status !== "closed").map((b) => b.id);
|
|
1181
|
+
return {
|
|
1182
|
+
issue,
|
|
1183
|
+
blocking: getBlocking(issue.id).map((i) => i.id),
|
|
1184
|
+
children: getChildren(issue.id).length,
|
|
1185
|
+
verifications: getVerifications(issue.id).length,
|
|
1186
|
+
blockers: openBlockers
|
|
1187
|
+
};
|
|
1188
|
+
});
|
|
1189
|
+
outputIssueListVerbose(verboseIssues, pretty);
|
|
1190
|
+
} else {
|
|
1191
|
+
outputIssueList(issues, pretty);
|
|
1192
|
+
}
|
|
1064
1193
|
} catch (error) {
|
|
1065
1194
|
outputError(error, pretty);
|
|
1066
1195
|
}
|
|
@@ -2439,15 +2568,19 @@ function generateSuffix() {
|
|
|
2439
2568
|
import * as fs4 from "fs";
|
|
2440
2569
|
import * as path4 from "path";
|
|
2441
2570
|
function mergeEvents(filePaths) {
|
|
2442
|
-
const
|
|
2571
|
+
const seen = /* @__PURE__ */ new Map();
|
|
2443
2572
|
for (const filePath of filePaths) {
|
|
2444
2573
|
const events = readEventsFromFile(filePath);
|
|
2445
2574
|
for (const event of events) {
|
|
2446
|
-
|
|
2575
|
+
const key = `${event.issueId}-${event.timestamp}-${event.type}`;
|
|
2576
|
+
if (!seen.has(key)) {
|
|
2577
|
+
seen.set(key, event);
|
|
2578
|
+
}
|
|
2447
2579
|
}
|
|
2448
2580
|
}
|
|
2581
|
+
const allEvents = Array.from(seen.values());
|
|
2449
2582
|
allEvents.sort((a, b) => new Date(a.timestamp).getTime() - new Date(b.timestamp).getTime());
|
|
2450
|
-
return allEvents
|
|
2583
|
+
return allEvents;
|
|
2451
2584
|
}
|
|
2452
2585
|
function mergeIssues(filePaths) {
|
|
2453
2586
|
const merged = /* @__PURE__ */ new Map();
|
|
@@ -2808,6 +2941,29 @@ function verificationsCommand(program2) {
|
|
|
2808
2941
|
});
|
|
2809
2942
|
}
|
|
2810
2943
|
|
|
2944
|
+
// src/cli/commands/init.ts
|
|
2945
|
+
import * as path5 from "path";
|
|
2946
|
+
function initCommand(program2) {
|
|
2947
|
+
program2.command("init").description("Initialize a new .pebble directory in the current directory").option("--force", "Re-initialize even if .pebble already exists").action((options) => {
|
|
2948
|
+
const existing = discoverPebbleDir();
|
|
2949
|
+
if (existing && !options.force) {
|
|
2950
|
+
console.error(JSON.stringify({
|
|
2951
|
+
error: "Already initialized",
|
|
2952
|
+
path: existing,
|
|
2953
|
+
hint: "Use --force to re-initialize"
|
|
2954
|
+
}));
|
|
2955
|
+
process.exit(1);
|
|
2956
|
+
}
|
|
2957
|
+
const pebbleDir = ensurePebbleDir(process.cwd());
|
|
2958
|
+
console.log(JSON.stringify({
|
|
2959
|
+
initialized: true,
|
|
2960
|
+
path: pebbleDir,
|
|
2961
|
+
configPath: path5.join(pebbleDir, "config.json"),
|
|
2962
|
+
issuesPath: path5.join(pebbleDir, "issues.jsonl")
|
|
2963
|
+
}));
|
|
2964
|
+
});
|
|
2965
|
+
}
|
|
2966
|
+
|
|
2811
2967
|
// src/cli/index.ts
|
|
2812
2968
|
var program = new Command();
|
|
2813
2969
|
program.name("pebble").description("A lightweight JSONL-based issue tracker").version("0.1.0");
|
|
@@ -2831,5 +2987,6 @@ summaryCommand(program);
|
|
|
2831
2987
|
historyCommand(program);
|
|
2832
2988
|
searchCommand(program);
|
|
2833
2989
|
verificationsCommand(program);
|
|
2990
|
+
initCommand(program);
|
|
2834
2991
|
program.parse();
|
|
2835
2992
|
//# sourceMappingURL=index.js.map
|