@markmdev/pebble 0.1.21 → 0.1.23
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 +161 -33
- package/dist/cli/index.js.map +1 -1
- package/dist/ui/assets/index-BLTrHFDr.js +343 -0
- package/dist/ui/assets/index-CSfeY9Yu.css +1 -0
- package/dist/ui/index.html +5 -2
- package/package.json +4 -3
- package/dist/ui/assets/index-BCOLl-f4.css +0 -1
- package/dist/ui/assets/index-CZtoCJT9.js +0 -332
package/dist/cli/index.js
CHANGED
|
@@ -658,6 +658,73 @@ function getAncestryChain(issueId, state) {
|
|
|
658
658
|
}
|
|
659
659
|
return chain;
|
|
660
660
|
}
|
|
661
|
+
function getAncestryBlocker(issueId, state) {
|
|
662
|
+
let current = state.get(issueId);
|
|
663
|
+
while (current?.parent) {
|
|
664
|
+
const parent = state.get(current.parent);
|
|
665
|
+
if (!parent) break;
|
|
666
|
+
const openBlockers = parent.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed" && !i.deleted);
|
|
667
|
+
if (openBlockers.length > 0) {
|
|
668
|
+
return { blockedAncestor: parent, blockers: openBlockers };
|
|
669
|
+
}
|
|
670
|
+
current = parent;
|
|
671
|
+
}
|
|
672
|
+
return null;
|
|
673
|
+
}
|
|
674
|
+
function claimWithCascade(issueId, pebbleDir) {
|
|
675
|
+
const events = readEvents();
|
|
676
|
+
const state = computeState(events);
|
|
677
|
+
const issue = state.get(issueId);
|
|
678
|
+
if (!issue) {
|
|
679
|
+
return { success: false, error: `Issue not found: ${issueId}` };
|
|
680
|
+
}
|
|
681
|
+
if (issue.deleted) {
|
|
682
|
+
return { success: false, error: `Cannot claim deleted issue: ${issueId}` };
|
|
683
|
+
}
|
|
684
|
+
if (issue.status === "closed") {
|
|
685
|
+
return { success: false, error: `Cannot claim closed issue: ${issueId}` };
|
|
686
|
+
}
|
|
687
|
+
const openBlockers = issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed" && !i.deleted);
|
|
688
|
+
if (openBlockers.length > 0) {
|
|
689
|
+
const blockerIds = openBlockers.map((b) => b.id).join(", ");
|
|
690
|
+
return {
|
|
691
|
+
success: false,
|
|
692
|
+
error: `Cannot claim blocked issue. Blocked by: ${blockerIds}`
|
|
693
|
+
};
|
|
694
|
+
}
|
|
695
|
+
const ancestryBlocker = getAncestryBlocker(issueId, state);
|
|
696
|
+
if (ancestryBlocker) {
|
|
697
|
+
const blockerIds = ancestryBlocker.blockers.map((b) => b.id).join(", ");
|
|
698
|
+
return {
|
|
699
|
+
success: false,
|
|
700
|
+
error: `Parent ${ancestryBlocker.blockedAncestor.id} is blocked by: ${blockerIds}`
|
|
701
|
+
};
|
|
702
|
+
}
|
|
703
|
+
const toClaim = [];
|
|
704
|
+
if (issue.status !== "in_progress") {
|
|
705
|
+
toClaim.push(issueId);
|
|
706
|
+
}
|
|
707
|
+
let current = issue;
|
|
708
|
+
while (current.parent) {
|
|
709
|
+
const parent = state.get(current.parent);
|
|
710
|
+
if (!parent) break;
|
|
711
|
+
if (parent.status === "open") {
|
|
712
|
+
toClaim.push(parent.id);
|
|
713
|
+
}
|
|
714
|
+
current = parent;
|
|
715
|
+
}
|
|
716
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
717
|
+
for (const id of toClaim) {
|
|
718
|
+
const event = {
|
|
719
|
+
type: "update",
|
|
720
|
+
issueId: id,
|
|
721
|
+
timestamp,
|
|
722
|
+
data: { status: "in_progress" }
|
|
723
|
+
};
|
|
724
|
+
appendEvent(event, pebbleDir);
|
|
725
|
+
}
|
|
726
|
+
return { success: true, claimedIds: toClaim };
|
|
727
|
+
}
|
|
661
728
|
function getDescendants(issueId, state) {
|
|
662
729
|
const issueState = state ?? getComputedState();
|
|
663
730
|
const descendants = [];
|
|
@@ -1740,7 +1807,7 @@ function restoreCommand(program2) {
|
|
|
1740
1807
|
|
|
1741
1808
|
// src/cli/commands/claim.ts
|
|
1742
1809
|
function claimCommand(program2) {
|
|
1743
|
-
program2.command("claim <ids...>").description("Claim issues (set status to in_progress).
|
|
1810
|
+
program2.command("claim <ids...>").description("Claim issues (set status to in_progress). Cascades to open parent issues.").action(async (ids) => {
|
|
1744
1811
|
const pretty = program2.opts().pretty ?? false;
|
|
1745
1812
|
try {
|
|
1746
1813
|
const pebbleDir = getOrCreatePebbleDir();
|
|
@@ -1752,35 +1819,20 @@ function claimCommand(program2) {
|
|
|
1752
1819
|
for (const id of allIds) {
|
|
1753
1820
|
try {
|
|
1754
1821
|
const resolvedId = resolveId(id);
|
|
1755
|
-
const
|
|
1756
|
-
if (
|
|
1757
|
-
results.push({
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1762
|
-
|
|
1763
|
-
|
|
1764
|
-
|
|
1765
|
-
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
if (hasOpenBlockersById(resolvedId)) {
|
|
1769
|
-
const blockers = getOpenBlockers(resolvedId);
|
|
1770
|
-
const blockerIds = blockers.map((b) => b.id).join(", ");
|
|
1771
|
-
results.push({ id: resolvedId, success: false, error: `Cannot claim blocked issue. Blocked by: ${blockerIds}` });
|
|
1772
|
-
continue;
|
|
1822
|
+
const result = claimWithCascade(resolvedId, pebbleDir);
|
|
1823
|
+
if (result.success) {
|
|
1824
|
+
results.push({
|
|
1825
|
+
id: resolvedId,
|
|
1826
|
+
success: true,
|
|
1827
|
+
claimedIds: result.claimedIds
|
|
1828
|
+
});
|
|
1829
|
+
} else {
|
|
1830
|
+
results.push({
|
|
1831
|
+
id: resolvedId,
|
|
1832
|
+
success: false,
|
|
1833
|
+
error: result.error
|
|
1834
|
+
});
|
|
1773
1835
|
}
|
|
1774
|
-
const event = {
|
|
1775
|
-
type: "update",
|
|
1776
|
-
issueId: resolvedId,
|
|
1777
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
1778
|
-
data: {
|
|
1779
|
-
status: "in_progress"
|
|
1780
|
-
}
|
|
1781
|
-
};
|
|
1782
|
-
appendEvent(event, pebbleDir);
|
|
1783
|
-
results.push({ id: resolvedId, success: true });
|
|
1784
1836
|
} catch (error) {
|
|
1785
1837
|
results.push({ id, success: false, error: error.message });
|
|
1786
1838
|
}
|
|
@@ -1788,7 +1840,20 @@ function claimCommand(program2) {
|
|
|
1788
1840
|
if (allIds.length === 1) {
|
|
1789
1841
|
const result = results[0];
|
|
1790
1842
|
if (result.success) {
|
|
1791
|
-
|
|
1843
|
+
if (pretty) {
|
|
1844
|
+
const cascaded = result.claimedIds?.filter((cid) => cid !== result.id) ?? [];
|
|
1845
|
+
if (cascaded.length > 0) {
|
|
1846
|
+
console.log(`\u2713 ${result.id} (also claimed: ${cascaded.join(", ")})`);
|
|
1847
|
+
} else {
|
|
1848
|
+
console.log(`\u2713 ${result.id}`);
|
|
1849
|
+
}
|
|
1850
|
+
} else {
|
|
1851
|
+
console.log(formatJson({
|
|
1852
|
+
id: result.id,
|
|
1853
|
+
success: true,
|
|
1854
|
+
claimedIds: result.claimedIds
|
|
1855
|
+
}));
|
|
1856
|
+
}
|
|
1792
1857
|
} else {
|
|
1793
1858
|
throw new Error(result.error || "Unknown error");
|
|
1794
1859
|
}
|
|
@@ -1796,7 +1861,12 @@ function claimCommand(program2) {
|
|
|
1796
1861
|
if (pretty) {
|
|
1797
1862
|
for (const result of results) {
|
|
1798
1863
|
if (result.success) {
|
|
1799
|
-
|
|
1864
|
+
const cascaded = result.claimedIds?.filter((cid) => cid !== result.id) ?? [];
|
|
1865
|
+
if (cascaded.length > 0) {
|
|
1866
|
+
console.log(`\u2713 ${result.id} (also claimed: ${cascaded.join(", ")})`);
|
|
1867
|
+
} else {
|
|
1868
|
+
console.log(`\u2713 ${result.id}`);
|
|
1869
|
+
}
|
|
1800
1870
|
} else {
|
|
1801
1871
|
console.log(`\u2717 ${result.id}: ${result.error}`);
|
|
1802
1872
|
}
|
|
@@ -1805,6 +1875,7 @@ function claimCommand(program2) {
|
|
|
1805
1875
|
console.log(formatJson(results.map((r) => ({
|
|
1806
1876
|
id: r.id,
|
|
1807
1877
|
success: r.success,
|
|
1878
|
+
...r.claimedIds && { claimedIds: r.claimedIds },
|
|
1808
1879
|
...r.error && { error: r.error }
|
|
1809
1880
|
}))));
|
|
1810
1881
|
}
|
|
@@ -2995,11 +3066,47 @@ data: ${message}
|
|
|
2995
3066
|
}
|
|
2996
3067
|
updates.priority = priority;
|
|
2997
3068
|
}
|
|
3069
|
+
const cascadeClaim = [];
|
|
2998
3070
|
if (status !== void 0) {
|
|
2999
3071
|
if (!STATUSES.includes(status)) {
|
|
3000
3072
|
res.status(400).json({ error: `Invalid status. Must be one of: ${STATUSES.join(", ")}` });
|
|
3001
3073
|
return;
|
|
3002
3074
|
}
|
|
3075
|
+
if (status === "in_progress" && issue.status !== "in_progress") {
|
|
3076
|
+
const state = isMultiWorktree() ? computeState(issueFiles.flatMap((f) => readEventsFromFile(f))) : getComputedState();
|
|
3077
|
+
const openBlockers = issue.blockedBy.map((id) => state.get(id)).filter((i) => i !== void 0 && i.status !== "closed" && !i.deleted);
|
|
3078
|
+
if (openBlockers.length > 0) {
|
|
3079
|
+
const blockerIds = openBlockers.map((b) => b.id).join(", ");
|
|
3080
|
+
res.status(400).json({
|
|
3081
|
+
error: `Cannot claim blocked issue. Blocked by: ${blockerIds}`
|
|
3082
|
+
});
|
|
3083
|
+
return;
|
|
3084
|
+
}
|
|
3085
|
+
const ancestryBlocker = getAncestryBlocker(issueId, state);
|
|
3086
|
+
if (ancestryBlocker) {
|
|
3087
|
+
const blockerIds = ancestryBlocker.blockers.map((b) => b.id).join(", ");
|
|
3088
|
+
res.status(400).json({
|
|
3089
|
+
error: `Parent ${ancestryBlocker.blockedAncestor.id} is blocked by: ${blockerIds}`
|
|
3090
|
+
});
|
|
3091
|
+
return;
|
|
3092
|
+
}
|
|
3093
|
+
let current = issue;
|
|
3094
|
+
while (current.parent) {
|
|
3095
|
+
const parent2 = state.get(current.parent);
|
|
3096
|
+
if (!parent2) break;
|
|
3097
|
+
if (parent2.status === "open") {
|
|
3098
|
+
let parentTargetFile = targetFile;
|
|
3099
|
+
if (isMultiWorktree()) {
|
|
3100
|
+
const parentFound = findIssueInSources(parent2.id, issueFiles);
|
|
3101
|
+
if (parentFound) {
|
|
3102
|
+
parentTargetFile = parentFound.targetFile;
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
cascadeClaim.push({ id: parent2.id, targetFile: parentTargetFile });
|
|
3106
|
+
}
|
|
3107
|
+
current = parent2;
|
|
3108
|
+
}
|
|
3109
|
+
}
|
|
3003
3110
|
updates.status = status;
|
|
3004
3111
|
}
|
|
3005
3112
|
if (description !== void 0) {
|
|
@@ -3065,11 +3172,32 @@ data: ${message}
|
|
|
3065
3172
|
data: updates
|
|
3066
3173
|
};
|
|
3067
3174
|
appendEventToFile(event, targetFile);
|
|
3175
|
+
const cascadeClaimedIds = [];
|
|
3176
|
+
for (const { id, targetFile: parentTargetFile } of cascadeClaim) {
|
|
3177
|
+
const cascadeEvent = {
|
|
3178
|
+
type: "update",
|
|
3179
|
+
issueId: id,
|
|
3180
|
+
timestamp,
|
|
3181
|
+
data: { status: "in_progress" }
|
|
3182
|
+
};
|
|
3183
|
+
appendEventToFile(cascadeEvent, parentTargetFile);
|
|
3184
|
+
cascadeClaimedIds.push(id);
|
|
3185
|
+
}
|
|
3068
3186
|
if (isMultiWorktree()) {
|
|
3069
3187
|
const updated = findIssueInSources(issueId, issueFiles);
|
|
3070
|
-
|
|
3188
|
+
const baseIssue = updated?.issue || { ...issue, ...updates, updatedAt: timestamp };
|
|
3189
|
+
if (cascadeClaimedIds.length > 0) {
|
|
3190
|
+
res.json({ ...baseIssue, _cascadeClaimed: cascadeClaimedIds });
|
|
3191
|
+
} else {
|
|
3192
|
+
res.json(baseIssue);
|
|
3193
|
+
}
|
|
3071
3194
|
} else {
|
|
3072
|
-
|
|
3195
|
+
const updatedIssue = getIssue(issueId);
|
|
3196
|
+
if (cascadeClaimedIds.length > 0) {
|
|
3197
|
+
res.json({ ...updatedIssue, _cascadeClaimed: cascadeClaimedIds });
|
|
3198
|
+
} else {
|
|
3199
|
+
res.json(updatedIssue);
|
|
3200
|
+
}
|
|
3073
3201
|
}
|
|
3074
3202
|
} catch (error) {
|
|
3075
3203
|
res.status(500).json({ error: error.message });
|